
简介面向工业自动化领域的C#开发人员讲解如何基于interface接口规范地实现上位机与FANUC机器人的通信。资源包含项目完整源码、工程文件以及Robot_interfaceV3.0手册提供通信协议、指令集等关键信息并配有interfaceConnect连接测试工具可快速验证链路是否正常。源码演示了从定义IFanucRobot接口、实现FanucRobotController控制器到使用串口或以太网TCP/IP完成命令发送与反馈接收的完整过程同时兼顾Windows Forms/WPF界面设计的接入方式适合需要对接FANUC机器人做数据交互或二次开发的工程师参考。压缩包共73个文件约207MB以dll运行库、xml文档、cs源码、exe工具、config配置及PDF手册等类型为主附带HslCommunication、Newtonsoft.Json等依赖库便于直接还原工程。资料已有5554人学习亲测可用的通信示例可帮助读者减少摸索时间提升工业通信项目开发效率。 调试 FANUC 机器人跟电脑之间的通信做上位机的朋友应该都有体会简单场景下用 DI/DO 信号也能凑合但一旦要读关节坐标、下发速度参数、抓取当前报警信息就必须要走网络通信了。早些年很多解决方案是用串口或者专用总线卡维护成本和接线成本都不低。现在以太网口基本是 FANUC 机器人的标配通过 TCP/IP 协议和机器人端的 KAREL 程序做数据交换是性价比最高的方式。我这次的项目是给一台 FANUC R-2000iC 做产线监控上位机要求实时显示机器人当前位置、末端执行器状态并能远程启停程序。PC 端我选了 C# 的 WinForms没选 WPF 是因为产线调试机上 .NET 环境比较老旧WinForms 兼容性最省心。整套通信层用 interface 做了抽象同时维护了 TCP 真机实现和一个模拟器实现机器人不在手边的时候照样能开发界面。这个方案实测稳定运行在生产线上通信周期设成 100ms没有出现丢数据和断连卡死的问题。1. 为什么用 C# 加 interface 搭这套通信1.1 这个项目到底在解决什么问题先交代项目背景。产线上这台 FANUC 机器人负责上下料原来一直是人工在示教器上操作现场需要把机器人的运行状态、报警信息和当前作业数量汇总到中控室。需求拆开就三块一是读数据二是发指令三是状态监控。读数据包括关节位置、报警代码、循环计数发指令包括启动、暂停、复位状态监控要求前端界面能实时看到机器人在哪个工位、当前处于自动还是手动模式。如果只是读几个点位其实可以走 FANUC 的专用协议但专用协议需要购买授权而且对 C# 开发者来说调试流程不透明。自己用 KAREL 写 socket 服务端上位机用 TCP 客户端去连协议完全掌握在自己手里排错也直观。这套方案的适用范围很广除了 FANUC其他支持网络通信的机器人也能用同样的思路只是机器人端脚本语言不同而已。1.2 interface 到底帮了什么忙很多第一次写 C# 上位机的朋友不太理解为什么一个简单的 Socket 通信要套 interface。我的体会是当项目只有“连上机器人读个坐标”这种功能时interface 确实是多余的但只要是真实的产线项目一定会遇到下面这些情况现场机器人还没到位开发要提前做机器人版本和手册对不上联调时间未知调试机上不想一直跑真机怕出危险。有了 interface我可以在不改动任何 UI 代码的前提下把FanucTcpRobotClient换成RobotSimulator模拟器界面照常显示数据。这就把“机器人侧开发”和“上位机侧开发”彻底解耦了。换一个角度如果后续要把通信方式改成串口或 Modbus TCP也只需要新增一个实现类业务逻辑一行不用改。这比把所有代码写在一个大TcpClient类里要省心得多。2. FANUC 机器人端先让机器人开口说话2.1 网络配置和 KAREL 程序准备机器人端是整个链路的前提这部分不准备好C# 这边写得再漂亮也白搭。首先在示教器上进入MENU - 设置 - 网络/Host Comm配置以太网口的 IP 地址。我这边给机器人分配的固定 IP 是192.168.0.10子网掩码255.255.255.0电脑用网线直连或者通过交换机都行关键是两个设备必须在一个网段里。配好之后在示教器上执行一下 PING 测试能 ping 通 PC网络这层就过了。接下来是机器人端程序。FANUC 机器人上的自定义通信程序用 KAREL 语言编写KAREL 是一种类 Pascal 的语言FANUC 的机器人软件里自带编辑器但 KAREL 选项需要订购机器人时选配。程序的核心流程不复杂打开 socket 监听进入循环等待 PC 连接收到连接后接收数据、解析指令、返回响应。KAREL 里针对 socket 通信有一组对应指令不同系统软件版本名称略有差异比如有的版本用SOCKET_ACCEPT配合FILE变量有的则用SOCKET变量直接收发写之前一定先对照自己机器人版本的 KAREL 参考手册。这里我想专门提醒一句机器人端 KAREL 程序只是一个“壳子”真正让机器人执行动作还是要靠 KAREL 调用后台任务里的$SBR这类系统状态数据。网上讨论比较多的$SBR[n].$PARAM[47]就属于这种参数它在机器人系统里专门用来做后台程序之间的数据传递。通信程序收到 PC 指令后把指令写到对应的 SBR 参数里后台任务读到变化就执行动作后台任务把当前位置写到 SBR通信程序取出来回传给 PC。这其实就是“共享内存”式的交换方式。不过要注意不要照搬别人博客里的参数编号不同系统软件版本对这些内部参数的编号和含义有差异必须以你手上机器人系统的实际变量表为准。2.2 定义一套两边都认的报文协议通信双方都要遵守的报文格式是整套系统里最容易返工的地方。我踩过教训之后强烈建议不管多简单的需求报文协议都必须固定下来至少包含帧头、指令类型、数据长度、数据体和帧尾/校验信息。这里我用的协议是一行 ASCII 文本帧头;;结束符\n指令与参数分隔符|格式;;指令|参数1|参数2...\n举两个例子。上位机请求当前关节坐标发送;;GET_POS|JOINT\n机器人端回;;POS|90.0|0.0|-45.0|0.0|0.0|0.0\n。上位机下发启动指令发送;;START|1\n机器人端回;;RESPONSE|OK\n。协议设计为什么非要做这么死因为 TCP 是流式传输它不保证一次Send对应一次Receive如果只是简单地根据缓冲区一次收到的字节数来当完整报文后续加需求时百分之百会出问题。固定分隔符加帧头就是为了在 C# 侧做“组帧”收到一段字节流后按;;找消息头按\n找消息尾中间再按|拆字段。这样即使 TCP 一包只到了半个消息下一包到了剩下半个也能正确拼出来。2.3 机器人侧数据交换的暗坑机器人端有几个隐藏很深的坑必须提前说。第一个是缓冲区大小KAREL 的 socket 接收缓冲区通常只有几百字节一次不要发送超过 100 字节的数据否则可能被丢弃。第二个是字符集KAREL 的字符串处理默认是 ASCII如果你在 C# 里用 UTF-8 发中文机器人端收到的基本是乱码这在做报警信息回传时特别容易出现。我的处理是通信消息全部走 ASCII中文报警文本留在机器人内部PC 端只收报警代码再由上位机根据代码表翻译成中文。第三个是残留连接。如果 PC 端异常退出机器人端的 socket 不会立刻感知必须依靠心跳超时来清理。我在 KAREL 程序里维护一个计数器超过 5 秒没收到 PC 的心跳就关闭当前连接回到监听状态。3. C# 通信层的 interface 设计与实现3.1 接口先行的设计思路现在进入正题。C# 这边设计通信层我的原则是“接口先行”。接口定义只关心“能和机器人做什么”不关心“具体怎么通信”。下面是我定义的接口public interface IRobotClient : IDisposable { bool IsConnected { get; } Taskbool ConnectAsync(string host, int port, int timeoutMs); void Disconnect(); Task SendAsync(string command); event EventHandlerstring DataReceived; event EventHandlerstring Log; }这里有几个细节值得解释。SendAsync返回Task是因为上位机 UI 里不能同步阻塞等网络返回否则界面会假死。DataReceived事件用于支持机器人主动上报的场景比如机器人报警或者到达某个点位时主动通知上位机不需要上位机一直轮询。Log事件把所有收发原始报文暴露给调试窗口联调时只看这个日志就能定位大半问题。注意接口里没有任何TcpClient、Socket之类的字段因为这些是实现细节不应该漏到接口层。调用方看到的只有“连接、断开、发命令、收事件”至于底层是 TCP 还是串口还是模拟器调用方一概不知。3.2 TCP 客户端实现连接、收发、断线重连FanucTcpRobotClient类实现这个接口核心是封装System.Net.Sockets.TcpClient。连接部分这样写public async Taskbool ConnectAsync(string host, int port, int timeoutMs) { _tcp new TcpClient(); var connectTask _tcp.ConnectAsync(host, port); var completed await Task.WhenAny(connectTask, Task.Delay(timeoutMs)); if (completed ! connectTask) { _tcp.Close(); return false; } await connectTask; _stream _tcp.GetStream(); _cts new CancellationTokenSource(); _ Task.Run(async () await ReceiveLoopAsync(_cts.Token)); return true; }重点在ReceiveLoopAsync。这个循环负责从NetworkStream里持续读数据按协议组帧private async Task ReceiveLoopAsync(CancellationToken token) { var buffer new byte[1024]; var frameBuilder new StringBuilder(); while (!token.IsCancellationRequested) { int n await _stream.ReadAsync(buffer, 0, buffer.Length, token); if (n 0) break; // 连接关闭 frameBuilder.Append(Encoding.ASCII.GetString(buffer, 0, n)); while (true) { int start frameBuilder.ToString().IndexOf(;;); int end frameBuilder.ToString().IndexOf(\n, start 2); if (start 0 || end 0) break; string frame frameBuilder.ToString() .Substring(start 2, end - start - 2); frameBuilder.Remove(start, end - start 1); DataReceived?.Invoke(this, frame); } } }这段代码里最关键的逻辑是“循环组帧”。因为 TCP 分包可能一次读入的内容包含多条完整指令也可能只有半条指令。我的做法是把所有读到的字节先塞进frameBuilder然后反复查找帧头和帧尾把能解析出来的完整消息一条条剥出去剩下的残包留在缓冲区里等下一轮数据。这也是上一节为什么坚持协议必须带帧头和结束符的原因。断线重连不要写在连接方法里死等。我单独开一个定时器每 3 秒检查一次连接状态发现断开就自动重连重连间隔用指数退避2 秒、4 秒、8 秒……最多 30 秒避免机器人端还在重启时 PC 端疯狂重连把端口打满。3.3 模拟器实现没有真机也能调界面RobotSimulator类同样实现IRobotClient但内部不做任何网络操作。连接方法直接返回 trueSendAsync根据收到的指令返回模拟数据。比如收到;;GET_POS|JOINT就随机生成六个关节角度格式跟真机一模一样收到;;PING就触发一个包含;;PONG的DataReceived事件。UI 在模拟器模式下看到的是一套完整可交互的界面数据每秒刷新一次滚动日志照常输出。这个模拟器帮了我大忙因为机器人在产线上一个月才有一两次维护窗口而我的大部分开发工作都靠模拟器完成。到了真机联调那天只需要把创建客户端对象的工厂方法从new RobotSimulator()改成new FanucTcpRobotClient()其余代码一行都不用改。这就是 interface 带来的最直观的好处。4. 完整实操流程从握手到业务数据4.1 分步操作清单如果你想把整套流程走一遍我整理了一份经过验证的清单PC 和机器人直连设置好 IP在示教器上 PING 测试网络连通性。在机器人端编写 KAREL socket 服务端程序先实现一个最小功能收到PING就回PONG。这一步先不接任何业务只验证链路通不通。编译加载 KAREL 程序在示教器上启动该后台任务。用任意 TCP 测试工具连接机器人 IP 和端口手动发送PING\n如果能收到PONG说明机器人端服务正常。PC 端创建 C# WinForms 项目定义IRobotClient接口和两个实现类。写 UI把按钮、状态栏、日志窗口绑定到通信层的事件上。关键点所有按钮点击都走async void内部调用await SendAsync绝对不要用.Result或.Wait()拿异步结果。先用RobotSimulator跑通整个界面流程再切到FanucTcpRobotClient做真机联调。联调通过后把机器人端 KAREL 任务设为随系统启动PC 端上位机设为开机自动登录并启动整套系统就能无人值守运行。4.2 核心代码串讲在实际项目里我不会在MainForm里直接 new 实现类而是写一个静态工厂方法public static class RobotClientFactory { public static IRobotClient Create() { if (AppConfig.UseSimulator) return new RobotSimulator(); return new FanucTcpRobotClient(); } }然后在MainForm_Load里调用工厂方法拿到实例订阅事件调用ConnectAsync。这样整个业务层和 UI 层只依赖IRobotClient接口。写界面时有个细节WinForms 的控件只能在 UI 线程更新而DataReceived是从后台线程抛出来的事件所以事件处理函数里一定要用BeginInvoke把更新 UI 的动作切回 UI 线程。忘了这一点程序运行到某个瞬间就会抛“线程间操作无效”的异常。4.3 把通信层注入到业务逻辑业务逻辑也不应该直接散落在按钮事件里。我会单独建一个RobotService类它内部持有IRobotClient向上暴露的是业务方法比如GetCurrentPosition()、StartProgram()、ResetAlarm()。按钮事件只调RobotService的方法不直接碰IRobotClient的SendAsync。这样做的好处是如果以后协议变化了只需改RobotService内部UI 完全不用动。接口在这里起的是“契约”作用让通信层和业务层的边界变得很清晰。5. 调试实录与常见问题排查5.1 五类高频问题列一下我实际遇到过的典型问题做成表格方便直接对照现象可能原因解决办法PC 连不上机器人端口IP 不在同一网段机器人端 KAREL 任务没启动端口被占用先 PING 通再确认 KAREL 任务正在运行最后换个端口试连上了但收不到任何回复报文格式不对机器人端解析失败KAREL 等待指令超时用 TCP 测试工具手动发一帧确认机器人端响应正常再检查 C# 侧发送格式收到数据乱码编码不一致机器人端缓冲区溢出统一使用 ASCII消息长度控制在 100 字节以内UI 界面卡死在 UI 线程里同步等待网络数据所有网络调用改成 async/await用BeginInvoke更新控件PC 重连一直失败机器人端还保留着旧连接socket 占着没释放机器人端加心跳超时机制超时后关闭旧连接再回到监听状态5.2 排查技巧和个人心得调试这种跨设备的通信最大的教训就是把顺序反过来。千万别一上来就写满整个通信层然后一次性去联调。我的习惯是“链路最简化”先用 TCP 工具实测机器人端确认工具能通信再用 C# 去连这样每一步变量都很少。如果 C# 连不上优先用 Wireshark 抓包看 TCP 三次握手到底通没通SYN 有没有回 ACK网络层的问题一眼就能看出来。还有一个技巧特别实用在FanucTcpRobotClient里把发送和接收的原始报文都打到Log事件里日志带上毫秒时间戳输出到调试窗口和文件双份。真机联调遇到问题时让现场操作人员把日志文件发回来远程就能定位。这种做法在很多工控项目里比远程桌面还管用。这次项目做完之后我现在做任何上位机项目都会先想清楚通信层要支持哪些实现再去写界面。特别是跟工厂设备对接的时候真实设备永远不是最方便拿来调试的“模拟器加接口”这套组合能省下大把等设备的时间。如果后续要扩展功能比如同时接两台 FANUC 机器人或者把通信改成冗余双网其实都是在这套接口里再增加实现类而已。本文还有配套的精品资源点击获取