简介:这份PDF技术文档面向工业机器人集成与视觉应用工程师,详细讲解FANUC机器人与康耐视智能相机基于TCP/IP的Socket标准通信协议,覆盖2D视觉抓取、Pass/Fail检测、OCR字符识别/读码及测量结果输出等典型场景,帮助读者建立从命令接口到数据交互的完整认知。资源共1个PDF文件,大小约695KB,内容精简、结构清晰,适合现场调试时快速查阅。文档从软硬件准备入手,说明FANUC侧User Socket Msg(R648)的启用方法,并指出FRA/FAC北美标准机型需额外安装KAREL(R632),同时给出康耐视In-Sight软件与网线连接要求。随后介绍DATA_SEND与DATA_RECV调用方式,以及位置坐标、Pass/Fail信号、OCR/读码结果、测量结果四种数据格式的传输规则——数据以逗号分隔、CR为结束符,并说明各类数据在机器人寄存器中的存放逻辑。该资料目前已获7383人学习下载,对进行机器人与视觉系统联调的技术人员有直接参考价值。
1. 工业机器人与视觉相机的Socket标准通信协议:为什么值得亲自做一遍
工业机器人和视觉相机之间的Socket标准通信协议,聊的不是“能不能连上”,而是连上之后怎么保证每一次拍照请求都有结果。很多项目卡在最后联调阶段,机器人把拍照请求发出去了,相机也拍了,但结果要么没收到,要么收到的是拆散的半包数据。Socket本身不是黑匣子,问题几乎都出在报文格式和超时约定上。这篇内容写给两类人:一是准备做视觉引导定位、需要把相机结果回传机器人控制器的工程师,二是已经调通了现场总线、但想在跨品牌设备之间快速建立点对点通信的开发者。看完能照着搭出一套可复现、可排查的通信链路。
2. Socket标准通信协议设计:先定帧格式和交互模型,再谈实现
2.1 Socket不是黑匣子:TCP长连接为何比串口和UDP更适合相机
很多老产线用串口做机器人和相机的通信,串口的优势是简单,PLC侧和机器人侧都支持,但速率和线缆长度是硬伤。相机拍照结果通常是一小段JSON或ASCII文本,几百个字节,串口也能传,可一旦现场有变频器或电机干扰,串口误码率会明显上升,排查起来非常费劲。工业现场最常见的替代方案是Socket通信,而且默认走TCP,不是UDP。
我一般会把Socket通信理解为“两个程序之间通过IP和端口建立一条双向管道”。机器人和相机都带以太网口,插一根网线、配好IP就能通信,不需要额外的串口服务器或总线网关。TCP协议本身负责分段传输、重传和顺序保证,比UDP在可靠性上强很多。视觉相机的典型处理流程是:收到拍照指令、触发曝光、算法处理、返回结果,这个周期通常在几十毫秒到一两秒,网络传输只占其中很小一部分。TCP的协议开销完全不影响节拍,没必要为了省几毫秒去选UDP然后自己处理丢包重传。
选型的另一个理由是跨品牌兼容。现场总线协议往往绑定设备厂商,而这个品牌机器人和那个品牌相机之间想直接走总线,通常需要额外买通信模块或者对方开放私有协议。Socket通信是通用的TCP/IP,只要双方都按约定解析字节流,就能通信。这也是为什么“Socket标准通信协议”这个说法在跨品牌项目里高频出现:标准不在协议栈,而在你定义的报文格式能否被对方程序正确解析。
2.2 帧格式:长度头加JSON,粘包半包一次说清
Socket通信比串口容易掉进去的第一个坑,是把数据当成“消息”来理解。TCP是字节流协议,send一次数据,对端recv不一定一次读完;同样,连续两次send,对端可能一次recv就把两段数据拼在一起收到。这就是粘包和半包。解决方法是通信双方在业务层约定一个帧格式。
我常用的帧格式是“4字节长度头 + JSON消息体”。前4字节用大端序表示整个JSON消息体的字节长度,接收方先攒够4字节,解析出长度,再等够对应字节数,才认为收到一条完整消息。这样可以同时处理半包和粘包:半包时数据不足就继续等,粘包时按长度切出第一条,剩下的留在缓冲区里处理下一条。
| 字段 | 字节数 | 说明 |
|---|---|---|
| Length | 4 | 消息体字节长度,大端序,不含长度头自身 |
| Payload | Length | JSON格式的业务消息,UTF-8编码 |
JSON消息体内部必须固定字段。相机返回结果至少要包含三个字段:状态码、消息文本、业务数值。以视觉定位为例,相机返回的可能是坐标X、Y和角度,那消息体就应该设计成{"status":0, "message":"OK", "x":12.5, "y":20.3, "angle":1.2}。状态码0表示成功,非0表示失败,message写给人看,x/y/angle交给机器人解析。
import socket import struct import json def recv_frame(sock): # 先读满4字节长度头 header = recv_all(sock, 4) if header is None: return None length = struct.unpack(">I", header)[0] if length > 1024 * 1024: raise ValueError("frame too large") body = recv_all(sock, length) return json.loads(body.decode("utf-8")) def recv_all(sock, n): data = b"" while len(data) < n: chunk = sock.recv(n - len(data)) if not chunk: return None data += chunk return data这段代码的核心是recv_all,它会循环接收直到凑够指定字节数,避免半包问题。recv_frame先读4字节长度头,再按长度读消息体,最后用JSON解析。>I表示大端序的无符号整数,这在网络字节序里是标准写法。解析函数里加一个最大长度限制,防止异常数据把内存撑爆,这个限制值要大于实际最大报文,我一般设成1MB。
2.3 交互时序:相机做服务端,机器人做客户端,谁先发谁负责超时
协议设计里比帧格式更容易漏掉的部分是交互时序。我的习惯是固定“相机做TCP服务端,机器人做TCP客户端”,原因是相机IP一般是固定的,机器人主动连过去,方便后续换相机或者切换工位时只改机器人的连接参数。如果反过来让机器人做服务端,相机就要主动连机器人,一旦机器人重启,相机的连接逻辑就要额外处理重连,多一层麻烦。
连接建立之后,交互流程固定成三步。第一步,机器人发送拍照请求,消息体用{"cmd":"snap"}这类简单指令。第二步,相机收到请求后执行拍照和算法处理,完成后把结果写回Socket连接。第三步,机器人调用接收函数等待结果,收到后校验状态码再决定下一步动作。
时序里最容易踩雷的地方是超时职责。发送请求之后,双方都要设超时,但这个超时值不一样。机器人端接收结果的超时一定要大于相机的最大处理时间,否则相机还在跑算法,机器人已经判定超时,连接状态就乱了。相机端接收请求的超时通常设短一些,比如5秒内没收到完整帧就断开当前连接,等机器人重连,防止客户端半途掉线后服务端傻等。还有一个细节:机器人每拍完一帧就关闭连接,还是保持长连接复用?我建议项目前期用“短连接”,连一次拍一次,出问题容易定位,节拍允许的话没必要一开始就上长连接。长连接适合拍照节拍快、连续请求多的场景,但必须额外处理断线重连和心跳。
3. 机器人侧配置:用示教器打通Socket客户端的最小流程
3.1 前置检查:IP、端口、同一网段一个不能少
机器人侧动手写Socket程序之前,先把三个前置条件确认清楚:机器人控制器IP、相机IP、通信端口。这三个参数只要有一个不对,后续所有程序都白写。我见过不少项目,程序逻辑完全正确,最后发现机器人和相机一个在192.168.1.xxx网段,另一个在192.168.0.xxx网段,跨网段导致连接失败。
同一网段是Socket通信的第一条规矩,两端只用IP区分设备,不在同一个二层网络里即使路由能通,工业现场也经常因为交换机配置或防火墙策略把包丢掉。所以调试第一步不是写程序,而是用PC分别ping两个设备的IP,确认网络层通了再继续。机器人侧如果实在找不到能执行ping的入口,就先把机器人网口接一台PC,PC和相机互相ping通之后,再接回机器人网口排查。
端口选择也有门道。工业相机常见的默认端口是80、5000、8000之类,具体以设备手册为准。自己定义端口时避开常用端口和动态端口范围,我一般选5000到9000之间的一个固定值,并且在整个项目里保持唯一。端口和IP都确认好了,把这两个参数写到机器人程序里时要注意,示教器上通常要求字符串格式,IP中间的点不能漏,端口是整数,不要写成字符串。
3.2 示教器上的连接、发送、接收程序怎么写
机器人示教器上做Socket通信,不同品牌、不同控制器提供的指令集略有差异,但思路完全一致。我不会贴某一家的私有指令,因为版本差别太大,照着写容易误导,但可以给一个通用流程骨架,你在自己控制器上用对应指令替换就行。
! 机器人Socket通信最小流程 ! 参数定义区 IP = "192.168.0.10" PORT = 5000 REQ = '{"cmd":"snap"}' TIMEOUT = 5000 ! 第一步:建立TCP连接 SOCKET_CONNECT(IP, PORT, TIMEOUT) ! 第二步:发送拍照请求 SOCKET_SEND(REQ) ! 第三步:接收完整响应帧,存入BUFFER SOCKET_RECEIVE(BUFFER, TIMEOUT) ! 第四步:解析状态码 IF 解析成功 THEN REG[1] = X坐标 REG[2] = Y坐标 ELSE REG[9] = 错误码 ENDIF ! 第五步:关闭连接 SOCKET_CLOSE这段流程里 SOCKET_CONNECT、SOCKET_SEND、SOCKET_RECEIVE、SOCKET_CLOSE 是四个核心动作,和操作系统里的 socket API 对应。SOCKET_CONNECT 的 TIMEOUT 是建立连接阶段的等待时间,写3秒足够;SOCKET_RECEIVE 的 TIMEOUT 要从相机发出结果开始算,必须大于相机处理时间,我习惯给到5秒以上。很多控制器没有自带JSON解析能力,这时候需要把收到的响应字符串按固定分隔符拆开,所以上一章说的帧格式里长度头只负责传输完整性,机器人程序能不能轻松解析才是设计Payload时最需要迁就的。
3.3 点位坐标与拍照结果怎么回填到寄存器
机器人拿到相机的JSON响应,最终目的是把里面的坐标数值变成机器人能用的位置信息。多数控制器的寄存器分布尔、整数和实数类型,坐标值通常是带小数的浮点数,所以要存到实数寄存器里。我在项目里一般约定:X坐标存寄存器1,Y坐标存寄存器2,角度存寄存器3,状态码存寄存器10。这样梯形图也好、后续运动指令也好,引用起来一目了然。
解析逻辑别放在机器人程序里绕太深。控制器处理字符串的能力普遍偏弱,如果相机响应里除了坐标还带乱七八糟的调试信息,机器人侧写解析会非常痛苦。我的做法是相机端把Payload精简成固定顺序的数值拼接,比如字符串"12.50,20.30,1.20",机器人只需按逗号切三次,再转换成浮点数填入寄存器。这种格式牺牲了一点点可读性,但换来了机器人侧解析的确定性。JSON适合人看和跨平台,纯逗号分隔适合机器人控制器处理,两者可以兼容:消息体用JSON,里面只放一个data字段,机器人取到data的字符串后再切分。
寄存器回填后,运动指令里直接用寄存器作为目标位置。要让这批坐标有实际意义,还必须考虑坐标系变换:相机安装在固定位置时,像素坐标到机器人基座坐标需要一个标定矩阵;相机装在机器人末端时,还要额外补偿法兰到工具的中心偏移。Socket协议只负责把数值摆到寄存器里,后续要不要做旋转平移,是视觉标定的事,但你在联调前就得想清楚,否则会出现“通信正常但机器人位置完全偏”的诡异现象。
4. 相机侧模拟服务端:PC上先跑通再上真机
4.1 用Python启动一个TCP服务端最小样例
现场联调最怕两头都是黑匣子,机器人侧报错,相机侧也报错,不知道问题出在谁身上。我的习惯是先用PC模拟相机端,把机器人侧的逻辑单独调通,再接入真相机。PC上最快的工具是Python,几十行就能实现一个符合上一章协议的TCP服务端。这个服务端不需要真拍照,收到请求后固定返回一组模拟坐标,足以验证机器人的连接、发送、接收、解析全链路。
import socket import json import struct 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"mock camera listening on {PORT}") conn, addr = server.accept() print(f"robot connected: {addr}") buffer = b"" while True: data = conn.recv(1024) if not data: break buffer += data while len(buffer) >= 4: length = struct.unpack(">I", buffer[:4])[0] if len(buffer) < length + 4: break payload = json.loads(buffer[4:4 + length].decode("utf-8")) print("request:", payload) if payload.get("cmd") == "snap": result = {"status": 0, "message": "OK", "data": "12.50,20.30,1.20"} else: result = {"status": 1, "message": "unknown cmd", "data": ""} body = json.dumps(result).encode("utf-8") frame = struct.pack(">I", len(body)) + body conn.sendall(frame) buffer = buffer[4 + length:]端口的监听地址写成0.0.0.0,表示接受任意网卡的连接请求,这是为了让机器人连的是PC的局域网IP也能进来。SO_REUSEADDR解决的是服务端重启后端口被占用的问题,不加这个选项,程序退出后端口会进入TIME_WAIT状态,立刻重启会报地址已被占用。recv(1024)是每次最多读取的字节数,这个值不影响帧完整性,因为后续有buffer和长度判断兜底。收到请求后按命令字分支,模拟拍照结果,再用struct.pack(">I", len(body))把长度头和数据一起发回。
4.2 解析请求与返回结果:把协议约定落到代码
上面这段服务端代码里,接收侧的逻辑和机器人接收端应该用同一套拆帧思路。buffer += data把新收到的数据追加到缓冲区,然后循环检查len(buffer) >= 4,也就是长度头是否攒够。长度头解析出来后,再看缓冲区里的数据是否达到length + 4,不够就跳出循环继续等,够就切出完整一帧。这个过程就是处理TCP粘包和半包的标准姿势。
请求解析我用的是JSON,因为模拟服务端在PC上,JSON处理很方便。但前面提过,真机相机可能输出纯字符串,所以服务端返回的data字段特意做成"12.50,20.30,1.20"这样的逗号分隔字符串,让机器人端好切分。这里有一个容易想当然的点:坐标数值要不要保留引号。我的建议是全部按字符串返回,由机器人侧转浮点数,因为字符串在任何控制器的字符串处理指令里都更可控,浮点转出来再存寄存器,精度损失很小。
如果你在PC端测试时发现机器人收到的数据是乱的,优先怀疑长度头解析不一致。>I是大端序,有些设备默认用小端序,同样的字节会被解析成完全不同的长度。排查方法很简单:在Python服务端把收到的第一个字节打出来,和机器人的发送代码对比。长度头只要多一字节、少一字节,后续所有数据都会错位,而且错位是持续性的,不会自己恢复。
4.3 用命令行工具做连通性验证
Python模拟服务端跑起来之后,先用PC上的命令行工具验证网络层和端口是否正常,别急着让机器人来连。最简单的命令是ping,确认两个设备IP通不通。接着用NC测试端口是否在监听,Linux和macOS自带nc,Windows上可以用PowerShell的Test-NetConnection命令。
ping 192.168.0.10 nc -zv 192.168.0.10 5000nc -zv的z表示只扫描不发送数据,v是显示详细输出。这条命令能确认端口通不通,但测不出协议是否正确。想验证协议,可以直接用Python写一个临时客户端,发一帧带长度头的JSON请求,看服务端有没有正确回帧。这一步的意义在于把通信问题拆成两层:端口连通是网络层问题,报文解析是协议层问题。层与层分开验证,联调时才能快速缩小故障范围。
真相机接入后,模拟服务端这套代码也别删,留作回归测试工具。换相机型号、改IP、改端口时,先用模拟服务端跑一遍机器人程序,确认机器人侧没被改坏,再切回真相机。这样能避免“新旧程序混在一起不知道哪边出错”的尴尬。
5. 联调避坑:Socket通信最容易翻车的4个现场
5.1 现象:机器人连接超时,相机侧却显示有连接进来
项目现场最常见的情况是机器人报连接失败,但相机端日志里明明有来自机器人IP的连接记录,两边对不上,各说各话。原因通常是三条:一是机器人连接的是相机IP,但相机实际监听在另一个端口;二是中间隔着防火墙或交换机ACL策略;三是相机的Socket服务只监听在固定网段,比如监听在127.0.0.1,机器人当然连不上。
解决的顺序是先在相机端确认监听地址和端口,用网络命令查看监听列表,确认监听的是0.0.0.0而不是回环地址,再确认端口号完全一致。相机的网络配置页如果让你填本机IP,同时设一个监听端口,那就把这两个值并列检查。机器人侧也要检查目标IP写没写错,我在现场见过把192.168.0.10写成192.168.0.1的,少打一位数字,排查了半天。最快定位方法是把PC接到同一网络,用nc -zv主动连一次,PC能连上说明相机端正常,问题在机器人侧;PC也连不上,说明相机端监听有问题。
5.2 现象:报文粘包/半包,JSON解析一会儿成功一会儿失败
表现是联调时第一次通信正常,第二次偶发超时,相机端报JSON解析失败,机器人端报接收字节数不对。盯着代码看了半天,逻辑没问题,最后发现是TCP流没按帧拆。相机端第三次开机后连续发了两帧数据,机器人一次读完,把两帧当一帧解析,肯定失败。半包则恰好相反,一帧没发完,机器人就急着解析,同样失败。
解决的唯一正确方向是严格按“长度头 + 固定长度消息体”拆帧。我在第4章代码里的buffer循环就是标准写法,真机联调时把这个逻辑同样实现在机器人侧。不要用固定睡眠时间等数据,网络延迟不是定值,等多久都可能出错。也不要依赖尾分隔符,比如用换行符结尾的ASCII文本,一旦数据内容里出现换行,立刻产生新问题。长度头不怕内容里有特殊字符,因为长度是数值,不受Payload内容影响。
5.3 现象:相机拍照要2秒,机器人却只等了1秒
这个坑特别容易出现在前期用模拟服务端调通的场景里。模拟服务端收到请求立刻返回,机器人侧接收超时设成1秒完全够用。换了真相机,拍照加算法处理可能跑2秒,机器人1秒就判定超时,把连接断掉,相机结果发送时发现管道已经关了,只能报错。解决方法是把接收超时设为相机标称最大处理时间的两倍,或者至少加1秒余量。
正规做法是查看相机的触发到输出延迟指标,那个值乘以1.5再加1秒作为机器人侧接收超时。如果相机支持异步发送结果,还可以改成“拍照请求发出后不等待结果,轮询查询结果”的交互模式。但异步模式对机器人编程要求更高,我不建议项目前期用。前期把超时放宽,先跑通全链路,稳定后再根据实际节拍收紧超时值。
5.4 现象:断开重连失败,端口一直被占用
机器人每次拍完都关闭连接,第二次拍照时重新连接,结果第二次连接失败,日志显示端口被占用。原因是TCP四次挥手里,主动关闭方会进入TIME_WAIT状态,持续约2分钟,期间该端口不能立即重新绑定。机器人作为主动关闭方,关闭后立刻重连,就可能撞上这个状态。
解决分两端处理。服务端是相机或模拟服务端,启动时设置SO_REUSEADDR,这在第4章代码里已经做了。机器人作为客户端,主动关闭后TIME_WAIT一般影响的是服务端,如果机器人每次用同一端口去连,也会受影响,这时候要让机器人每次重连时使用新的临时端口,绝大多数控制器的Socket指令默认就是这么做的。如果真机上频繁重连还是出问题,可以把短连接改成长连接,连接建立后持续复用,不关闭,从根上避免TIME_WAIT。
6. 把方案做扎实:验证方法、心跳保活和一条保命技巧
6.1 先跑回环测试,再碰真机
我最后养成的习惯是每套通信程序都保留一个回环测试入口。所谓回环,就是机器人把数据发出来,经过一个调试服务端原样转发回去,机器人再收回来解析。这个测试可以在不接相机的情况下完成,专门验证机器人侧程序本身的收发和解析逻辑有没有问题。回环通过后,再把服务端换成相机,这样问题一旦出现,就能明确是相机端的问题而不是机器人端的问题。做法很简单,把第4章的Python服务端代码里的result改为接收什么就回什么,就得到一个回环服务端。
6.2 长连接心跳:解决相机掉线后机器人不自知的问题
如果你决定用长连接,必须加心跳。机器人和相机之间的TCP连接,物理链路断了之后,操作系统可能很久才发现,机器人端会一直认为连接还活着,直到下次发送数据时才报错。心跳的做法是空闲时每2到5秒发一个空帧或专用心跳帧,相机收到后立即返回。机器人连续三次心跳没收到响应,就判定断线,主动关闭旧连接并重连。心跳报文不要用业务拍照请求,定义单独的{"cmd":"ping"}字段,和业务逻辑完全分开。
6.3 我最后的习惯:所有超时参数写进注释
Socket联调做到最后,我吃过最大的亏是超时参数乱调之后忘了原始值。连接超时、发送超时、接收超时、心跳间隔,这些参数散落在程序各处,时间一长没人记得当时为什么设成这个数值。现在的习惯是在每个超时参数旁边写上三行注释:这个值是多少、为什么设成这个值、如果改大了有什么后果。拍照超时写“相机标称最大处理时间2秒,余量0.5秒,建议不要小于2.5秒,否则会误杀正常拍照”,这种注释在三个月后的维护期比任何文档都管用。Socket协议本身不难,难的是让协议的每个参数都能被后来的人快速理解。希望帮到你。
本文还有配套的精品资源,点击获取