ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C#上位机与FANUC机器人通信:interface接口设计与TCP实现

C#上位机与FANUC机器人通信:interface接口设计与TCP实现 简介基于C#接口实现上位机与FANUC机器人通信的完整示例包面向工业自动化开发者、PLC工程师及.NET程序员解决串口或以太网通信协议对接、指令收发与参数配置等实际问题。包体共73个文件大小约207.57MB包含可编译的C#源码工程、项目配置与第三方通信库、调试运行程序、说明文档以及厂家提供的通信接口手册目录结构完整支持在Visual Studio中直接打开、还原依赖并运行验证。已有5554人学习下载具备一定实操验证基础。资料内除了接口定义与控制器实现示例还提供连接测试工具和参考压缩包覆盖从接口抽象、类实现到上位机界面调用的完整链路接口中定义了发送指令、接收反馈、设置参数等方法具体控制类封装底层通信逻辑界面层通过按钮触发调用并显示机器人反馈信息并配有操作说明图片、运行截图与排错思路便于对照练习和二次开发。适合需要快速搭建工业通信原型、理解C#接口特性的中高级开发者也可作为课程设计或项目起步参考。 做工业上位机开发的兄弟应该都有过这种经历现场调试FANUC机器人通信代码看着逻辑没问题TCP连接也建立了但发过去的指令就跟石沉大海一样机器人一点反应都没有。我在这个项目里用C#配合interface设计了一套与FANUC机器人的通信层从通信协议约定到TCP收发实现全部走了一遍跑了两条产线目前状态稳定。今天把整个设计思路、核心代码和踩过的坑整理出来给后面要接FANUC或者其他品牌机器人的朋友一个参考。1. 先别急着写代码为什么通信层要用interface来收口1.1 上位机项目的通病业务逻辑被通信代码绑架大多数上位机项目写着写着就变成这样窗体按钮的事件里直接new TcpClient()连接、发送、解析全部揉在一个方法里。今天只接一台FANUC还好明天要加一台ABB后天客户说我们也有一台老款的KUKA你顺便接一下情况就失控了。更难受的是调试。通信代码和业务代码耦合在一起机器人没动的时候你根本分不清是通信层发错了还是机器人逻辑没对上。我在这个项目里踩过几次这种坑之后决定把通信层彻底抽出来用C#的interface给上位机和机器人之间画一条清晰的边界线。1.2 interface背后那个依赖倒置的思路interface在这里不是花架子它解决的是上位机开发里最本质的问题上位机的业务逻辑不应该依赖某个具体机器人的通信协议。业务层只关心发一个取位置指令拿到坐标结果不关心这个指令是TCP发出去的还是串口发出去的通信层只关心把字节流发出去把响应收回来不关心业务层拿这个数据去干嘛两者通过interface约定一个通信契约互不越界。接口定义清楚之后实现类可以随时替换。测试的时候我可以挂一个虚拟机器人Simulator开发的时候连真机客户现场出问题了我还能挂一个抓包模式。这些动作不需要改动业务层的任何代码这就是interface带来的直接好处。public interface IRobotCommunicator : IDisposable { bool IsConnected { get; } bool Connect(string ipAddress, int port, int timeoutMs 3000); void Disconnect(); TaskRobotResponse SendCommandAsync(RobotCommand command); }这个接口很精简就四个成员。但要注意connect和send必须分离不能把连接埋进构造函数里否则换模拟器的时候没法绕开网络连接Mock也就失去了意义。2. FANUC机器人端要做的准备工作2.1 网络配置和Socket功能选项FANUC机器人和上位机通信最常用的是TCP/IP Socket方式。前提是机器人控制柜上开通了Socket Messaging功能选项这个在购机时可以选配也可以通过FANUC售后开通。没有这个选项机器人端的TP程序里是找不到网络通信相关指令的。网络配置上FANUC控制柜一般会在示教器上通过MENU - Setup - Host Comm或者TCP/IP菜单设置控制柜的IP地址需要一个固定的IP和上位机放在同一个网段。别小看这一步我遇到过一个现场的机器人IP和摄像头网关冲突搞得通信时断时续排查了半天。建议先把机器人端的网络参数确认清楚记下这几个值配置项典型值备注机器人控制器IP192.168.1.50需固定不能在DHCP池里子网掩码255.255.255.0与上位机一致通信端口8000自定义避开常用端口上位机IP192.168.1.100建议也固定2.2 机器人端Socket程序逻辑服务端模式FANUC机器人通常作为Socket Server等待上位机连接。示教器上的TP程序逻辑大致是这样的! 伪代码示意具体指令以机器人安装的选项版本为准 ! 1. 创建服务端Socket监听8000端口 TCPCREATE(SERVER, 8000, STATUS, SOCKET) ! 2. 等待上位机连接 TCPACCEPT(SOCKET, STATUS, NEWSOCKET) ! 3. 接收上位机发来的数据 TCPRECV(NEWSOCKET, BUFFER, LENGTH, STATUS) ! 4. 解析指令并执行动作 IF BUFFER GETPOS THEN TP_POS GetCurrentPos() TCPSEND(NEWSOCKET, 0POS TP_POS, STATUS) ENDIF这一步的难点在于FANUC的Socket Messaging指令在不同软件版本上名称和参数有差异建议先在TP程序里写一个最简单接收指令、原样返回的回环逻辑把链路打通之后再扩展业务指令。先用Tera Term或者Windows自带的Telnet客户端手动连接机器人端口能收发数据再写正式逻辑这是最快的方式。2.3 通信协议约定谁先说话很多上位机通信出问题一半以上是协议没约定清楚。FANUC机器人作为Server上位机作为Client这个清晰但指令格式、响应格式、字符编码、结束符这四件事必须在动代码之前定死。我在这个项目里定义的协议很简单全部基于ASCII文本行请求帧: 命令名参数\r\n 示例: GETPOS\r\n SETDO_1:ON\r\n 响应帧: 状态码命令名数据\r\n 示例: 0POS124.50,-23.10,655.20\r\n 1ERRUNKNOW_CMD\r\n状态码0表示成功1表示业务错误2表示参数格式错误每帧以\r\n结束机器人端解析的时候按行切割全链路使用ASCII编码发送中文注释在机器人端很容易乱码能免则免。协议简单的好处是机器人端TP程序解析容易上位机这边也不容易出错。很多工程师喜欢用XML或者JSON但机器人端没有现成的解析库用KAREL硬写JSON解析纯属给自己挖坑。3. C#接口设计与核心实现代码3.1 命令和响应的数据模型接口有了接下来定义命令和响应的载体。这段代码看起来简单但它是整个通信层的通用语言。public class RobotCommand { public string Command { get; set; } // 命令名如 GET/SET public string Payload { get; set; } // 参数如 POR/DO_1:ON public override string ToString() ${Command}{Payload}\r\n; } public class RobotResponse { public bool Success { get; set; } public string Message { get; set; } public string RawData { get; set; } }ToString()把命令对象转换成协议规定的文本帧这样后面实现类里就不会到处拼字符串。3.2 FanucTcpCommunicator实现类的关键细节下面是核心的TCP通信实现类我在里面做了几件比较重要的事逐一说明。public class FanucTcpCommunicator : IRobotCommunicator { private TcpClient _tcpClient; private NetworkStream _stream; private readonly SemaphoreSlim _sendLock new SemaphoreSlim(1, 1); private readonly int _defaultTimeout 2000; public bool IsConnected _tcpClient ! null _tcpClient.Connected; public bool Connect(string ipAddress, int port, int timeoutMs 3000) { Disconnect(); _tcpClient new TcpClient(); var connectTask _tcpClient.ConnectAsync(ipAddress, port); if (!connectTask.Wait(timeoutMs)) { _tcpClient.Close(); throw new TimeoutException($连接FANUC机器人超时{ipAddress}:{port}); } _stream _tcpClient.GetStream(); _stream.ReadTimeout _defaultTimeout; _stream.WriteTimeout _defaultTimeout; return true; } public void Disconnect() { _stream?.Close(); _stream null; _tcpClient?.Close(); _tcpClient null; } public async TaskRobotResponse SendCommandAsync(RobotCommand command) { await _sendLock.WaitAsync(); try { if (!IsConnected || _stream null) return new RobotResponse { Success false, Message 未连接 }; byte[] sendBuffer Encoding.ASCII.GetBytes(command.ToString()); await _stream.WriteAsync(sendBuffer, 0, sendBuffer.Length); await _stream.FlushAsync(); byte[] receiveBuffer new byte[1024]; using (var cts new CancellationTokenSource(_defaultTimeout)) { int bytesRead await _stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, cts.Token); if (bytesRead 0) return new RobotResponse { Success false, Message 连接已被机器人端关闭 }; string raw Encoding.ASCII.GetString(receiveBuffer, 0, bytesRead); return ParseResponse(raw); } } catch (OperationCanceledException) { return new RobotResponse { Success false, Message 读取响应超时 }; } catch (IOException ex) { return new RobotResponse { Success false, Message $通信异常{ex.Message} }; } finally { _sendLock.Release(); } } private RobotResponse ParseResponse(string raw) { string[] lines raw.TrimEnd(\r, \n).Split(\n); string firstLine lines[0].Trim(); if (string.IsNullOrEmpty(firstLine)) return new RobotResponse { Success false, Message 空响应 }; if (firstLine.StartsWith(0)) return new RobotResponse { Success true, Message OK, RawData firstLine }; return new RobotResponse { Success false, Message firstLine, RawData raw }; } }这里有几个关键点需要注意SemaphoreSlim线程锁上位机界面上的自动扫描线程和手动操作线程可能同时调用发送方法TcpClient的NetworkStream不是线程安全的不锁的话会出现流被占用的异常。锁的范围必须覆盖发送和接收单锁发送会把两个指令交叉写入流机器人端解析必崩异步方法内部用CancellationTokenSource做超时ReadTimeout在某些底层网络异常时并不能完全兜底CancellationToken是更可靠的方式byte[]长度1024FANUC机器人指令返回的位置数据一般不会超过这个长度但如果你的项目会传大块数据需要提高或改动态读取。3.3 业务层怎么用interface实现类写完之后业务层只依赖接口不依赖具体类。public class RobotService { private readonly IRobotCommunicator _communicator; public RobotService(IRobotCommunicator communicator) { _communicator communicator; } public async Taskdouble[] GetCurrentPositionAsync() { var response await _communicator.SendCommandAsync( new RobotCommand { Command GET, Payload POS }); if (!response.Success) throw new Exception($获取位置失败{response.Message}); string[] parts response.RawData.Split()[1].Split(,); return Array.ConvertAll(parts, double.Parse); } }这样业务层完全不需要知道对面是FANUC还是ABB。后续哪怕换一个机器人只要实现同一个IRobotCommunicator接口RobotService一行代码都不用动。4. 实战中容易翻车的几个通信细节4.1 超时设置不能全靠默认值有经验的工程师都知道工业现场的网络环境远比办公室复杂。交换机拥塞、机器人控制柜CPU繁忙、网线接触不良任何一个因素都可能导致响应延迟。读超时如果设太短机器人那边TP程序多执行两条逻辑上位机就报超时了设太长操作工按了急停上位机还要傻等好几秒。我最终把读超时放在2000ms写超时1000ms收发分离。connect超时单独3000ms。三者不能共用一个值否则调试的时候很难定位是哪一步慢。4.2 断线重连的时机和幂等性机器人重启、网线被现场物料刮断都是常见事。断线之后怎么处理最好在设计阶段就想好发送指令时发现IsConnected false直接触发自动重连不要返回错误让操作员手动处理重连要加间隔比如失败后延迟2秒再试防止机器人还没启动完就疯狂重连重连成功后主动发送一次PINGPING探活指令确认机器人端收发链路恢复。public async Task EnsureConnectedAsync(string ip, int port) { if (IsConnected) return; for (int i 0; i 5; i) { try { Connect(ip, port, 3000); var ping await SendCommandAsync(new RobotCommand { Command PING, Payload PING }); if (ping.Success) return; } catch { await Task.Delay(2000); } } throw new Exception(机器人通信重连失败); }4.3 数据解析不要只读一次Windows的TCP接收有个特性一次Read不一定能收到完整的一帧数据。机器人端的TCPSEND可能把一帧数据拆成两个TCP包发出来或者反过来两个响应帧粘在一个包里。如果只调一次Read就解析极大概率会碰到半包或者粘包。更稳的做法是循环读取直到拿到的数据以\r\n结尾private string ReadLine(NetworkStream stream, CancellationToken ct) { var buffer new byte[1]; var lineBuilder new StringBuilder(); while (true) { ct.ThrowIfCancellationRequested(); int read stream.Read(buffer, 0, 1); if (read 0) break; char ch (char)buffer[0]; if (ch \n) break; if (ch ! \r) lineBuilder.Append(ch); } return lineBuilder.ToString(); }这个方案是按字节读性能不算最优但对于机器人通信这种低频交互场景完全够用而且逻辑简单不容易出错。如果数据量大可以改成ReadAsync到缓冲区后缓存起来再按行切道理一样。5. 亲测排障记录三个真实问题的完整排查链路5.1 问题一机器人收到指令但不动现象上位机发送SETDO_1:ON返回正常但机器人侧的DO_1没有动作。初步猜测指令格式错了机器人端解析有问题还是DO编号对不上排查过程先用Tera Term手动连接机器人端口逐条发送指令发现SETDO_1:ON确实返回成功但回到示教器看DO_1依然是OFF说明机器人端程序逻辑有问题单步运行TP程序发现TCPRECV之后多了一条IF判断判断的字符串是DO_1ON而不是DO_1:ON上位机和机器人端协议里参数分隔符一个用的冒号一个用的等号两边根本没对上。根因协议约定只在纸上写了没有在机器人端做同义对照。这个锅不在C#代码在沟通环节。解决统一把参数分隔符定为冒号上位机和机器人端程序都改掉用一条自动化测试脚本把常用指令全部过一遍。5.2 问题二返回数据的第一个字节神秘消失现象上位机收到的响应始终少一个字符比如0POS...变成了POS...状态码0丢了。初步猜测解码问题TCP分包问题排查过程抓包对比发现上位机发出的请求和机器人返回的TCP包都完整但Wireshark里能看到返回包的前面有几个特殊的字节序列FF FA之类查了一下发现FANUC Socket Messaging默认开启了TELNET协议模式TELNET的IAC命令在传输过程中会被处理掉而第一个可显示的字符被当成了TELNET协商内容那个消失的0实际上是被TELNET层解释为子选项协商的一部分。根因机器人端Socket打开了TELNET模式应该使用Raw模式原始TCP通信。解决在机器人端Socket Messaging配置里关闭TELNET改为RAW模式重连后响应数据完整。这个坑极具迷惑性因为连接正常、请求正常、大部分响应正常只有首字节消失或者偶尔多出几个奇怪字符不抓包基本发现不了。5.3 问题三长时间空闲后连接自动断开现象产线中午停线一小时下午恢复生产上位机发指令全部超时重连之后恢复正常。初步猜测机器人端TCP超时交换机端口老化排查过程查看机器人端TP程序日志发现控制柜显示连接早已断开检查上位机程序日志发现上一次成功通信是停线前之后一直没有发送任何数据检查交换机配置发现启用了闲置连接回收策略空闲超过1800秒会发RST包断开连接机器人端作为Server在没有数据收发时不会主动维持连接交换机RST一到两边都不知道处于假连接状态。根因网络设备空闲回收机制 没有心跳。解决上位机加一个独立的心跳线程每10秒发送一次PINGPING机器人端收到后回复链路保持活跃。同时在上位机做好断线检测万一心跳失败立刻触发自动重连。这里要注意心跳指令必须是轻量的不能让机器人执行实际动作否则每10秒机器人就动一下生产线就乱了。心跳的响应状态码和处理逻辑要单独定义。6. interface抽象带来的额外收益模拟器与多品牌适配最后说一下interface设计给我省下的两个大麻烦。第一个是模拟器。开发阶段没有机器人可以联调我写了一个SimulatorCommunicator : IRobotCommunicator随机生成位置数据返回。业务逻辑的开发、界面调试、异常处理全都可以在没有硬件的情况下完成。到了现场只需要把依赖注入的实现类从Simulator换成FanucTcpCommunicator其他代码一行不用改。public class SimulatorCommunicator : IRobotCommunicator { public bool IsConnected true; public bool Connect(string ipAddress, int port, int timeoutMs 3000) true; public void Disconnect() { } public TaskRobotResponse SendCommandAsync(RobotCommand command) { var random new Random(); string pos $0POS{random.Next(100, 500)},{random.Next(-100, 100)},{random.Next(300, 800)}; return Task.FromResult(new RobotResponse { Success true, RawData pos }); } }第二个是品牌适配。后来客户那边真的加了一台其他品牌的机器人通信协议不一样但同样是TCP加文本指令。我只需要多写一个实现类在工厂方法里按配置启用不同的实现完全不需要动RobotService和界面层。这就是开头说的面向接口编程最实在的回报它能让你在一个项目里轻松应对未来还会变的部分。项目收尾的时候我复盘了一下interface这套做法本身不复杂复杂的是提前看清楚哪部分会变。通信协议会变机器人品牌会变网络环境会变但发指令、收响应这个交互模式不会变。把不会变的东西抽象成接口把会变的东西塞进实现类上位机项目就能越做越顺手。本文还有配套的精品资源点击获取
返回列表