☰
FANUC机器人KAREL SOCKET通信编程实战:从零实现与外部设备数据交互
2026/10/5 14:58:47 网站建设 项目流程

做机器人集成,绕不开的一件事就是“通信”。我之前带一个项目,现场视觉系统坚持要FANUC机器人直接从视觉服务器读取检测结果,不能再走老式的IO硬接线。IO当然能传信号,但你想传一个不断变化的坐标值、一串产品条码,靠那几十个点位的开关量,能把人逼疯。这时候就得让机器人走以太网,用SOCKET把数据包拿进来。FANUC机器人要自己写网络通信程序,最实用的路子就是KAREL——它底层支持一套SOCKET函数,配合.KL源文件可以编译出可运行的程序。这篇文章我就把手写的KAREL SOCKET通信程序拆开,一步一步讲清楚,新手照着做也能连上。

这篇东西适合谁呢?如果你在搞视觉引导、MES数据上报、机器人远程运维,或者只是想让机器人和你们自己的上位机/数据库之间做点轻量级的数据交换,KAREL SOCKET都是一条相当靠谱的路。文章里我不写废话,大部分内容是拿现场踩过的坑换来的,你可以直接照着参考。

1. 项目背景与SOCKET通信方案的设计逻辑

1.1 为什么偏偏是KAREL,而不是TP指令

很多第一次接触FANUC的朋友会问:示教器上那个TP编程界面,不也能写程序吗?确实能写,但TP程序不是用来处理网络报文的。TP指令里你可以设置IP、可以配置远程IO,也可以做简单的AR寄存器运算,但你要拿它去拼接字符串、解析一帧带校验的TCP数据,或者维护一个需要重连的通信状态机,那成本就太高了,而且可读性非常差。

KAREL是FANUC机器人上的高级编程语言,语法类似Pascal,有变量、数组、字符串、循环、条件分支,还允许调用系统提供的各种功能函数。关键是它里面有一套完整的SOCKET API——从建立连接、收发数据到关闭连接,全部可以直接在KAREL程序里调用。所以在需要灵活通信的场景下,KAREL几乎是唯一直接可行的控制器端方案。

有人可能会说,那我不用KAREL,外挂一个网关或协议转换模块行不行?当然行,比如视觉服务器发Modbus TCP给PLC,PLC再发数字量/模拟量给机器人,这种链路在很多老项目里都有。但这个方案有两个问题:一是中间环节越多,调试越麻烦,出了问题很难定位是视觉没发、PLC没转发还是机器人没收到;二是硬成本摆在那,多一个网关就要多花一笔钱,还得给它配电源、配网络、配安装位置。KAREL SOCKET把所有逻辑都收在机器人控制器内部,程序、报文、异常处理全在一处,维护起来省事多了。

1.2 网络架构和通信协议怎么设计

先说网络架构。最推荐的方式是让机器人作为TCP客户端,由机器人主动去连接外部设备(比如视觉主机、工控机、MES服务器)上开放的TCP服务端口。为什么推荐机器人当客户端?因为机器人端的KAREL程序是跑在控制器里的,它的IP和端口不像上位机那样灵活可变;让上位机先监听端口,机器人这边程序启动后主动去连,连接成功后再进行数据交互,这种方式最容易在示教器上手工触发调试,也最容易做自动重连。

反过来,如果你非要机器人当服务器,也不是不行,KAREL里也有SOCKET_BIND可用于监听。但实际调试的时候你就会发现,机器人当服务器意味着你得先保证机器人程序已经跑起来并且绑定了端口,外部设备才能连进来,这对现场的启动顺序要求更高,而大多数自动化产线的惯例是“机器人主动往外发消息”,所以下面我的示例都按机器人当客户端来写。

再说数据格式。工业现场我强烈建议别用太花哨的协议,能用固定长度的ASCII报文就不用变长解析。比如我习惯定义一个固定格式:四个ASCII字符的帧头 + 数据区 + 两个ASCII字符的校验/结束符。为什么?因为变长报文在TCP里会涉及粘包和拆包,需要你自己去维护缓冲区;而固定长度报文收到一定字节数就能判定为一帧,处理逻辑非常简单,写起来也不容易出错。等跑通了,再考虑要不要升级成JSON或者自描述格式。

通信内容上,至少建议包含握手帧、心跳帧、数据帧和断开帧四类:

  • 握手帧:机器人连上后先发一段约定好的标识,确认双方都在线。
  • 心跳帧:周期性发送,比如每2秒发一次,用来告诉对方“我还活着”。
  • 数据帧:真正要交互的业务数据,比如视觉坐标、检测结果条码、设备状态。
  • 断开帧:程序退出前主动发一段通知,让对方尽快释放资源。

很多现场故障都是因为“没握手就发数据”“断了连接还在那干等”造成的,协议设计上多花十分钟,后面调试能省一天。

2. KAREL SOCKET编程核心API与程序骨架

2.1 TCP/IP协议在KAREL里是怎么暴露成API的

KAREL里面把TCP/IP网络接口封装成一组和Unix socket风格类似的函数。我用过的核心函数大概有这么几个:SOCKET_BIND、SOCKET_CONNECT、SOCKET_SEND、SOCKET_RECV、SOCKET_CLOSE、SOCKET_FLUSH。它们的分工非常明确,和你在Linux C或者Python socket里理解的那套东西是一样的。

函数作用典型参数返回
SOCKET_BIND绑定端口并进入监听状态端口号socket句柄
SOCKET_CONNECT主动连接远端主机的指定端口IP地址、端口号socket句柄
SOCKET_SEND发送一段数据句柄、数据缓冲区、长度状态码
SOCKET_RECV接收一段数据,可设置超时句柄、接收缓冲区、超时时间状态码
SOCKET_CLOSE关闭一个socket连接句柄状态码
SOCKET_FLUSH丢弃socket接收缓冲区里的遗留数据句柄状态码

后五个函数返回的状态码里,0通常代表成功;非零一般是各类错误码,具体含义需要查你那个控制器操作系统版本对应的手册。SOCKET_CONNECT返回的socket句柄如果是一个负数,基本可以断定连接没建立成功;如果是一个大于等于0的值,说明连接已经建立,后续SEND和RECV都要拿这个句柄去做参数。

这里要特别提醒一句:FANUC在V8.30、V9.x这些不同控制器软件版本里,KAREL SOCKET函数的具体参数个数、有没有VAR参数、返回值是句柄还是状态码,可能会有一点差别。我下面贴的代码是参照我常用版本来写的,你在自己设备上编译之前,最好先看看你所用的KAREL参考手册里这几个函数的签名,避免因为参数不匹配浪费时间。这不是客套话,是真有人卡在这里一整个下午。

2.2 一个KAREL程序的整体骨架长什么样

写任何KAREL程序之前,先理清楚它的四段式结构:PROGRAM头、VAR变量声明区、BEGIN到END之间的可执行区、最后的END PROGRAM名。

PROGRAM SOCKET_COMM VAR sock_id : INTEGER status : INTEGER ... BEGIN -- 这里是程序逻辑 END SOCKET_COMM

变量声明区里能声明整型、浮点、布尔、字符串、数组,甚至结构体。字符串在KAREL里是定长字符串,声明时必须给它一个最大长度,比如STRING[128]就是最多128个字符。这一点和C语言里的char数组更像,而不是Python那种随意伸缩的字符串。使用的时候一定注意长度别超出,否则轻则截断、重则程序报错。

执行区里可以写顺序语句、IF/ELSE分支、FOR/WHILE循环,也可以调用系统过程。一个典型的SOCKET程序执行顺序是:初始化参数 -> 尝试连接 -> 发送握手数据 -> 进入收发数据的循环 -> 循环结束关闭socket。所有可能出错的地方,我都会要求现场工程师加状态判断和异常处理,后面你就能看到我为什么反复强调这件事。

3. 手把手实战:完整.KL文件连接外部设备

3.1 动手前先做这四件事

别急着敲代码,先把基础环境确认好,不然程序写得再对,在控制器里也跑不起来。

第一,确认控制器有KAREL运行许可。KAREL不是所有FANUC机器人标配的功能,它是需要开授权的功能选项。控制器的选项界面里能看到有没有KAREL相关的许可;如果没有,程序编译都过不了,加载也会直接报错,这一步先排除掉。

第二,设置好机器人控制柜的以太网IP。示教器上一般通过“MENU -> 系统设置 -> 网络”这类路径进入,找到控制器对应的有线网口,给它配一个和外部设备同一网段的IP。比如上位机是192.168.1.100,机器人就配192.168.1.50,子网掩码255.255.255.0。配完之后最好在外部设备那边ping一下机器人的IP,能ping通再继续。

第三,准备一根好网线,直连或者过交换机都行。直连相对简单,但有些老控制柜的网口协商速度比较慢,插上之后等十几秒再ping。

第四,在Roboguide或者实际控制器里先建一个空的KAREL工程,确认编译、加载、运行的链路是通的。Roboguide里新建KAREL程序文件,输入一个最简单程序(比如只写一个WRITE),编译生成.PC文件,再加载到控制器里跑一下。这一遍跑通了,后面调试真正的SOCKET程序时就不会把问题错怪到环境上。

3.2 完整.KL文件源码(参考版)

下面这个源码是我摘出来的简化版,但结构完整,能实现“连接外部设备 -> 发送一段数据 -> 接收一段数据 -> 关闭连接”的完整流程。你可以拿它当骨架去改。

PROGRAM SOCKET_COMM VAR sock_id : INTEGER status : INTEGER ip_addr : STRING[32] port_num : INTEGER send_data : STRING[128] recv_data : STRING[128] timeout_ms : INTEGER conn_ok : BOOLEAN loop_count : INTEGER max_retry : INTEGER BEGIN -- 参数初始化 ip_addr = '192.168.1.100' port_num = 5000 timeout_ms = 3000 max_retry = 5 conn_ok = FALSE loop_count = 0 sock_id = -1 -- 主程序:先建立连接 WRITE '开始连接外部设备...' WHILE (conn_ok = FALSE) AND (loop_count < max_retry) DO sock_id = SOCKET_CONNECT(ip_addr, port_num) IF sock_id >= 0 THEN conn_ok = TRUE WRITE '连接成功,socket句柄=', sock_id ELSE WRITE '第', loop_count + 1, '次连接失败,稍后重试...' loop_count = loop_count + 1 DELAY 1000 ENDIF ENDWHILE IF conn_ok = FALSE THEN WRITE '重试', max_retry, '次后仍未连接成功,程序退出' RETURN ENDIF -- 发送握手帧 send_data = 'HELLO_ROBOT' status = SOCKET_SEND(sock_id, send_data, LEN(send_data)) IF status <> 0 THEN WRITE '发送握手帧失败,错误码=', status ELSE WRITE '已发送:', send_data ENDIF -- 接收响应 recv_data = '' -- 清空缓冲区 status = SOCKET_RECV(sock_id, recv_data, timeout_ms) IF status = 0 THEN WRITE '收到响应:', recv_data ELSE WRITE '接收响应超时或失败,错误码=', status ENDIF -- 模拟业务数据交互 send_data = 'GET_POSITION' status = SOCKET_SEND(sock_id, send_data, LEN(send_data)) IF status = 0 THEN WRITE '请求已发送' ENDIF timeout_ms = 2000 recv_data = '' status = SOCKET_RECV(sock_id, recv_data, timeout_ms) IF status = 0 THEN WRITE '业务数据:', recv_data ELSE WRITE '业务数据接收失败,错误码=', status ENDIF -- 主动通知断开 send_data = 'BYE' status = SOCKET_SEND(sock_id, send_data, LEN(send_data)) -- 关闭连接 status = SOCKET_CLOSE(sock_id) WRITE '连接已关闭' END SOCKET_COMM

提醒一句,这份代码里的SOCKET_CONNECT、SOCKET_SEND、SOCKET_RECV、SOCKET_CLOSE的调用写法是我常用版本里的风格,你实际编译以你自己手册的函数签名为准。如果编译报参数不匹配,多半就是这里出了出入,把调用方式改成手册里的写法即可。

3.3 关键代码段逐行解读

我把上面代码里几个关键节点单独拎出来讲。

连接重试逻辑。网络通信和IO不一样,IO信号在正常接线情况下几乎不会瞬间丢失,但网络连接可能因为交换机重启、上位机程序重启、网线松动等原因出现瞬断。所以现场程序里如果有连接操作,我基本都会套一个重试循环。代码里我用WHILE循环和max_retry变量限制总次数,每次失败后DELAY 1000,让人眼能看到状态,也给了网络栈一点恢复时间。这套逻辑不复杂,但对现场稳定性帮助非常大。

SOCKET_SEND的第三个参数。在KAREL里字符串是定长变量,直接传整个变量会给发送端带去很多我们不要的空字符(因为声明了STRING[128],但实际内容可能只有十几个字符)。所以我用LEN(send_data)获取实际有效长度再去发送,目的就是精确地只发有效数据。如果你不这么做,对方收到的报文里会拖一串空格,协议解析时就容易出现不匹配。

接收超时。SOCKET_RECV里有个超时参数,意思是等待数据到达的最长时间。为什么要有超时?因为TCP接收是阻塞型操作,如果没有超时限制,对方一直不发数据,机器人程序就会一直卡在RECV那行,后面的运动指令、循环逻辑全都停摆。现场生产是等不得这种死等的,所以一定要设置一个合理的超时值。根据业务不同,几百毫秒到几秒都有,我的习惯是业务数据接收给2到3秒,心跳检查给更长一点,但不能无限等。

断开通知。程序最后我会主动发送一个BYE或者类似的自定义断开帧,然后再调用SOCKET_CLOSE。这么做的意义是让外部设备能立即知道“机器人要退出了”,而不是等到TCP层最终检测到断开才处理。对方收到BYE后可以直接清理会话,现场日志也能记录得干干净净。

如果你想改成机器人做服务器,核心逻辑其实就是把“建立连接”这一段换成SOCKET_BIND。SOCKET_BIND会在指定端口上进入监听状态,等待外部设备的TCP连接进来。实际写法类似:先调用SOCKET_BIND获得一个监听句柄,外部设备连接后,再拿这个句柄去SEND、RECV。但就如前面说的,调试时启动顺序会比较麻烦,所以我个人只在特殊场合才这么干。

3.4 上位机端参考代码(拿Python快速调通)

有了参考代码,再配一个上位机脚本。就算你眼前没有真机器人,也可以在电脑本机先模拟一遍通信逻辑,用Python写一个TCP服务器即可。

import socket HOST = '0.0.0.0' PORT = 5000 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((HOST, PORT)) server.listen(1) print(f'监听端口 {PORT} ...') conn, addr = server.accept() print('机器人已连接:', addr) conn.settimeout(5) while True: data = conn.recv(1024).decode('ascii', errors='ignore').strip('\x00').strip() if not data: print('连接已关闭') break print('收到:', data) if data == 'HELLO_ROBOT': conn.sendall(b'HELLO_OK') elif data == 'GET_POSITION': # 模拟返回一个坐标字符串 conn.sendall(b'X=100,Y=200,Z=300') elif data == 'BYE': conn.sendall(b'BYE_OK') break else: # 未知帧直接原样回显,方便调试 conn.sendall(('ECHO:' + data).encode('ascii')) conn.close() server.close()

这个脚本就是让你在电脑上先验证“机器人发什么、上位机回什么”的约定。实际项目中,上位机里会换成C#、C++、Java或者你自己的MES接口,但TCP这段原理完全一样。

4. 常见问题与排查技巧实录

4.1 连接失败,先按这个顺序检查

如果机器人程序跑起来后一直是“连接失败”,我建议按下面的顺序排查:

  1. 先ping。在外部设备上ping机器人的IP,在机器人那边(如果显示里面有CMD或通过网络工具)也可以反向ping外部设备。ping不通,说明二层网络有问题,查网线、交换机端口、IP段。
  2. 再确认端口有没有在监听。在外部设备上,用netstat -an | grep 端口号看看有没有LISTEN。上位机程序如果没起来,端口肯定没人听,机器人当然连不上。
  3. 再看防火墙。外部设备的系统防火墙或者杀毒软件完全可能拦住TCP连接,先把防火墙临时关掉试一次,通了再按需放行。
  4. 最后看KAREL程序里的IP、端口拼写。我在现场看到过把端口写成字符串、把IP多写了一个点的低级错误,这种错误编译不一定报错,但连接必定失败。

4.2 端口冲突和“bind: only one usage of each socket address”

如果你不是让机器人当客户端,而是用SOCKET_BIND让机器人当服务器,或者你之前调试时程序退出不干净,很可能遇到一个经典错误:“bind: only one usage of each socket address (protocol/network address/port)”。这个报错信息在Unix类系统里很常见,意思就是你要绑定的端口已经被占用了。

它发生的情形一般有两种。一种是你多次启动了同一个服务器程序,上一个进程还没释放端口;另一种是TCP连接处于TIME_WAIT状态,频繁重启服务器时,端口暂时还留在系统内核里没释放完。

解决办法也直接:第一,换一个没有被占用的端口;第二,程序中确保每次退出都调用SOCKET_CLOSE,避免僵尸连接占着端口;第三,如果上位机服务器是自己写的Linux程序,可以在bind之前加SO_REUSEADDR选项,允许端口重用,但不能无脑开着,得知道自己在干什么。KAREL端也类似,每次程序启动时不要重复绑定同一个端口而不关闭旧句柄,程序退出逻辑一定要走到SOCKET_CLOSE。

4.3 字符串长度和接收缓冲区问题

KAREL的定长字符串是个大坑。声明STRING[128]之后,这个变量的存储空间就是固定128字节。你往里放短字符串没问题,但如果收到超过128字节的数据,轻则接收不完整,重则程序报字符串越界错误。经常有现场工程师问我“为什么收到的数据只有一半”,查到最后就是接收缓冲区开小了。

解决办法有两个方向:一是按报文最大长度预留够大的缓冲区,比如业务数据可能长达200字节,就声明STRING[256];二是在协议设计时就约定好报文长度上限,超过上限的包按错误帧处理。我一般两个都做,程序健壮性会好很多。

4.4 KAREL程序运行的权限与TP调用

还有一个非常常见的问题:程序写好了、也加载进控制器了,但就是运行不了。这时候优先检查两样东西:KAREL授权和调用方式。

KAREL授权的问题,只能看控制器本身的选项表,没有授权谁也救不了,只能找系统集成商或机器人供应商确认是否能增加选项。调用方式上,KAREL程序通常有两种运行途径:一种是直接在程序选择列表里选中运行,另一种是在TP程序里用CALL指令去调用它。如果是后者,注意TP程序里写CALL SOCKET_COMM时,KAREL程序必须已经加载并可见,否则TP执行到CALL那行就会报错。还有,KAREL程序如果被设为周期自动运行(比如后台任务),一定要确认它的循环体和通信逻辑不会互相卡死,我一般不会让通信程序在一个后台任务里无限跑,而是做成“启动连接、收发一轮、断开重连”的有限状态机,需要时再触发。

最后再分享一个实际的场景:现场经常有上位机比机器人后启动的情况。如果机器人程序启动时就去做SOCKET_CONNECT,对面服务没起来,就会一直失败。我的习惯是把整个通信逻辑包在一个状态机里,程序启动后先做正常业务,通信线程或通信子程序周期去尝试连接,这样上位机什么时候准备好,机器人就能什么时候自动连上,不需要人工去按启动键。这样处理下来,现场电气调试配合起来会顺畅很多。

说实话,KAREL SOCKET这里面最考验人的不是写出连接那几行,而是把异常处理想周全。我接手过好几个KAREL工程,一看程序发现连断开重连都没有,一旦网络闪断,程序就卡死在那,只能人工去复位。所以我把上面这些重试逻辑写成了常规操作,宁可代码啰嗦一点,也不让现场出幺蛾子。这个习惯帮我少加了很多夜班。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询