简介:面向C#开发者和工业自动化工程师,这套资料展示了通过TCP协议与库卡(KUKA)机器人建立通信的上位机实现思路,覆盖实时位置返回与运动控制两大核心环节,适用于工业现场数据采集、远程调试及自动化产线集成等场景。压缩包共39个文件、18.59MB,含PC端C#工程(cs、resx、sln等)、KUKA端KRL源文件(src、dat、sub)、XML通信配置,以及KSS系统软件手册和Ethernet KRL中文PDF,目录按PC端和KUKA端分开,便于对照联调。已有299人学习下载。资源内提供可运行示例与readme步骤说明,同时深入涉及TCP客户端搭建、数据包格式设计、校验与解析等细节,可帮助读者快速搭建C#与KUKA机器人控制器的双向通信链路,理解运动控制指令的发送与状态反馈流程。PC端与KUKA端分离的工程组织方式也方便二次开发与功能扩展,适合用作工业自动化上位机开发的入门参考和工程模板。
1. C# 上位机 + TCP + KUKA:这套方案解决什么问题
当一台 KUKA 机器人停在工位中间,控制器坐标明明在实时变化,上位机却只能等 PLC 传来“到位”信号才放行下一步,这种盲等状态在产线联调里太常见了。标题里的“C# 上位机通过 TCP 通讯实现 KUKA 机器人实时位置返回及运动控制”,核心就是一根网线、一个 TCP 端口,让 .NET 程序直接读回机器人的实际位置,再把目标点下发给机器人侧执行。它能省掉专用总线模块和 OPC 网关的一部分折腾,适合集成商调试工具、视觉引导上下料、坐标记录进 MES 这类场景。
对刚接触机器人通讯的 C# 工程师来说,先掌握 TCP 这条链路,比一上来研究现场总线协议更划算;对老手而言,更值得关注的是通讯稳定性、限位安全这些实际坑。这篇笔记从机器人侧 KRL 准备、EKI 配置,到 C# 端收发实现和典型故障排查,按落地顺序一次讲清楚。
2. 机器人侧先动刀:EKI 的 XML 配置与 KRL 服务程序骨架
想从 KUKA 机器人读位置、发运动指令,第一步不是在 C# 里写 TcpClient,而是把机器人控制器当成一个 TCP 服务端“伺候好”。KUKA 控制器里跑的是 Windows Embedded 加 KRL 解释器,位置数据不会主动吐给上位机,必须由机器人侧程序主动往外发。常见做法是用 KUKA 的 Ethernet KRL Interface(EKI)选件,或者用 KRL 自带 Socket 能力自己封装协议。
2.1 先认清方案形态:TCP Server 和 C# 客户端谁主谁从
很多第一次做机器人通讯的工程师会下意识认为“上位机是服务端,机器人连过来发数据”,这个方向在 KUKA 场景下一般反了。EKI 最常见的形态是机器人侧做 TCP Server,监听一个固定端口;C# 上位机作为 TCP Client 主动连接,连上之后机器人侧持续往外发位置数据,同时接收上位机下发的命令。这样设计的好处是机器人侧程序一旦启动就稳定监听,上位机可以随时重连,不用在机器人那边做复杂的客户端重连逻辑。
方案选型上,我见过三种:
| 通讯方案 | 机器人侧要求 | C# 侧工作量 | 适用场景 |
|---|---|---|---|
| EKI(Ethernet KRL Interface) | 需要配置 XML,KRL 调用封装函数 | 低,标准 TcpClient 即可 | 项目首选,实时位置回传加目标点下发 |
| KRL 原生 Socket | 自己封装握手、拆包、字节序 | 中 | 私有协议、EKI 选件不可用的老版本 |
| 官方机器人 API | 需要额外授权选件 | 低,封装完善 | 预算充足且不想碰 KRL 的团队 |
EKI 胜在机器人侧的收发被封装成了库函数,XML 文件里把数据项定义清楚,KRL 程序调用函数就能把变量值发出去,也能把收到的值写进 KRL 变量。自写 Socket 的好处是不依赖选件,代价是 KRL 侧字符串拆分和协议解析全要自己来,调试周期明显变长。我一般建议项目首选 EKI,除非现场控制器版本确实不支持。
2.2 EKI 配置:XML 里定义端口和工作模式
EKI 的配置由两个 XML 构成:一个定义 TCP 连接参数,另一个定义具体收发数据模板。连接参数文件大致长这样:
<ETHERNETKRL> <CONFIGURATION> <EXTERNAL> <SOCKET> <TYPE>Server</TYPE> <PORT>8000</PORT> <TIMEOUT>1000</TIMEOUT> </SOCKET> </EXTERNAL> </CONFIGURATION> </ETHERNETKRL>这段配置的意思是:机器人侧作为 TCP Server 监听 8000 端口,单次通讯超时 1000 毫秒。这里三个参数最关键:TYPE 决定了机器人侧是 Server 还是 Client,本项目必须写成 Server;PORT 要避开现场 PLC、HMI 等设备占用的端口,我习惯用 8000 到 9000 之间的私有端口;TIMEOUT 设置得太大会让断线后的恢复变慢,太短又会误杀慢速大报文,1000 毫秒是相对稳妥的起点。
数据模板文件定义每一帧报文里传哪些字段,比如位置回传模板:
<SENSOR> <FRAME> <X>0.0</X> <Y>0.0</Y> <Z>0.0</Z> <A>0.0</A> <B>0.0</B> <C>0.0</C> <PNO>0</PNO> </FRAME> </SENSOR>这个模板对应 KUKA 世界坐标系下的 X、Y、Z 和 A、B、C 姿态角,PNO 是程序号,用来标识当前运行的程序。EKI 收到这个 XML 后会把外层标签剥离,把数值对应写入 KRL 变量。注意 XML 里的小数点、字段顺序必须固定,上位机解析时也要按同一套规则,否则容易出现“读到了但解释错”的隐患。
2.3 KRL 服务程序骨架:实时位置回发与命令接收
机器人侧程序是整个链路的地基。KRL 代码核心逻辑是:初始化 EKI、打开通道、循环发送当前位置、接收命令并执行。骨架如下:
DEF EKI_POS_SERVER() DECL EKI_STATUS RET DECL REAL X_POS, Y_POS, Z_POS, A_ANG, B_ANG, C_ANG DECL INT PNO DECL CHAR CMD_MSG[100] ; 初始化 EKI 服务端 EKI_Init("config/EthernetKRL.xml", RET) ; 打开通讯通道 EKI_Open("CHANNEL_1", RET) WHILE TRUE ; 读取当前实际位置 X_POS = $POS_ACT.X Y_POS = $POS_ACT.Y Z_POS = $POS_ACT.Z A_ANG = $POS_ACT.A B_ANG = $POS_ACT.B C_ANG = $POS_ACT.C ; 把位置数据通过模板发送给上位机 EKI_Send("CHANNEL_1", "pos_out.xml", RET) ; 等待上位机命令,命令写入 CMD_MSG EKI_Receive("CHANNEL_1", "cmd_in.xml", CMD_MSG, RET) ; 根据命令内容执行运动,简化示意 IF CMD_MSG == "MOVE" THEN PTP {X X_POS, Y Y_POS, Z Z_POS, A A_ANG, B B_ANG, C C_ANG} ENDIF ENDWHILE EKI_Close("CHANNEL_1", RET) EKI_End(RET) END这段代码的关键点在于:循环里先发位置再收命令,发送频率就是循环周期,如果想要更稳定的 50Hz 回传,应该在循环里加一个小延时,让发送节奏和 KRL 的轨迹规划不互相干扰。$POS_ACT是 KUKA 的系统变量,读取的是机器人当前实际笛卡尔坐标,不是目标坐标,这也是“实时位置返回”的数据来源。注意不同 KSS 版本下 EKI 函数签名可能有细微差别,上面是示意结构,实际写的时候以你安装的 EKI 版本头文件为准。
还有一个容易踩的坑:如果主程序在 PTP 运动过程中一直阻塞,EKI 的 Send 和 Receive 就会停摆,上位机那边位置刷新立刻断掉。常见做法是把通讯循环放在后台任务里,主程序只负责运动,位置回传由副任务持续执行。KUKA 的后台任务机制可以做到位置 20ms 到 50ms 一帧,满足绝大多数视觉引导场景。
2.4 部署到控制器:加载配置、激活程序、验证连通
KRL 写好后,要在 WorkVisual 里把 EKI 配置和数据模板导入到机器人项目对应目录。导入完成后用 WorkVisual 连接控制器部署,激活项目,再把 KRL 程序传到控制器上运行。启动后先在示教器上确认程序没有报警,再用电脑上的 TCP 调试工具连接机器人 IP 的 8000 端口,如果能看到持续返回的坐标文本,说明机器人侧已经通了。这一步是“黑匣子”阶段的结束,后续 C# 端怎么改都不会是链路问题。
提示:验证连通时不要只测端口通不通,要确认能看到位置报文内容。端口通但没数据,通常意味着 KRL 程序没有真正跑起来。
3. 用 C# 建立 TCP 客户端:实时位置读取与坐标解析
机器人侧准备好之后,C# 上位机的任务就清晰了:作为 TCP 客户端,连接机器人,持续读取位置数据,解析成坐标对象。很多人在这一步把代码写得过于复杂,引入大量第三方组件,其实标准库的 TcpClient 加异步循环就够了。
3.1 连接参数:IP、端口、超时和 NoDelay
C# 端连接需要明确几个参数:机器人控制器 IP、EKI 配置的端口、连接超时、以及是否禁用 Nagle 算法。Nagle 算法默认开启,会把小报文合并发送,对于位置回传这种高频小帧数据来说,会平白增加几十毫秒延迟。我一般连接成功后立即设置NoDelay = true。
public async Task<bool> ConnectAsync(string ip, int port, int timeoutMs) { _client = new TcpClient(); var connectTask = _client.ConnectAsync(IPAddress.Parse(ip), port); var timeoutTask = Task.Delay(timeoutMs); if (await Task.WhenAny(connectTask, timeoutTask) != connectTask) { _client.Close(); return false; } await connectTask; _stream = _client.GetStream(); _stream.ReadTimeout = 2000; _client.NoDelay = true; return _client.Connected; }这里用Task.WhenAny做连接超时,比直接调Connect更安全,因为机器人控制器如果 IP 不通,默认连接可能卡很久。ReadTimeout是单次读取超时,设为 2000 毫秒是为了配合后面的断线检测,如果超过两秒读不到任何数据,说明链路已经异常。连接成功后NoDelay = true,这是实时位置通讯里非常重要的一行。
3.2 异步接收循环:按行拆帧、处理粘包
机器人侧发上来的数据通常是一行一条位置记录,但 TCP 本身是字节流,不能保证一次 Read 刚好读到一个完整行。如果一次 Read 收到三行半,或者半行加两行,解析就会出错。所以接收循环必须做“缓冲 + 按换行符拆帧”的处理:
private async Task ReceiveLoopAsync(CancellationToken ct) { byte[] buffer = new byte[4096]; string pending = ""; while (!ct.IsCancellationRequested) { int bytesRead; try { bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, ct); } catch (Exception ex) { RaiseDisconnected(ex); break; } if (bytesRead == 0) { RaiseDisconnected(new IOException("对端关闭连接")); break; } pending += Encoding.ASCII.GetString(buffer, 0, bytesRead); int newlineIndex; while ((newlineIndex = pending.IndexOf('\n')) >= 0) { string line = pending.Substring(0, newlineIndex).Trim(); pending = pending.Substring(newlineIndex + 1); if (line.Length > 0) { ProcessLine(line); } } } }这是粘包拆包最朴素也最可靠的写法。pending保存不完整的半行数据,每次 Read 后先追加,再循环按换行符切出完整行。注意Trim()会把\r也去掉,兼容机器人侧发\r\n的情况。缓冲区 4096 字节对位置报文绰绰有余,没必要开更大,省内存也减少单次 Read 等待时间。
3.3 坐标解析:从字节流到 KukaPosition
拿到的行文本需要转成结构体。约定机器人侧发过来的格式是:
POS,X=123.45,Y=234.56,Z=345.67,A=0.00,B=45.12,C=-30.00C# 侧解析:
public class KukaPosition { public double X, Y, Z, A, B, C; public DateTime Timestamp; } private void ParsePositionLine(string line) { if (!line.StartsWith("POS,")) return; var parts = line.Split(','); if (parts.Length < 7) return; var pos = new KukaPosition { Timestamp = DateTime.UtcNow }; foreach (var part in parts.Skip(1)) { var kv = part.Split('='); if (kv.Length != 2) continue; switch (kv[0]) { case "X": pos.X = double.Parse(kv[1], CultureInfo.InvariantCulture); break; case "Y": pos.Y = double.Parse(kv[1], CultureInfo.InvariantCulture); break; case "Z": pos.Z = double.Parse(kv[1], CultureInfo.InvariantCulture); break; case "A": pos.A = double.Parse(kv[1], CultureInfo.InvariantCulture); break; case "B": pos.B = double.Parse(kv[1], CultureInfo.InvariantCulture); break; case "C": pos.C = double.Parse(kv[1], CultureInfo.InvariantCulture); break; } } LastPosition = pos; PositionUpdated?.Invoke(pos); }这段代码里最容易忽略的是CultureInfo.InvariantCulture。如果上位机运行环境是德语、法语区域设置,默认double.Parse会把小数点当成千分位分隔符,解析直接异常或得到完全错误的值。机器人侧永远用英文小数点发送,上位机解析也必须强制不变区域。坐标解析后记录一个DateTime.UtcNow时间戳,后续看门狗和位置缓存都靠它判断数据是否新鲜。
3.4 推送到 UI 线程:避免卡住接收循环
接收循环跑在后台任务里,解析出坐标后要显示到 WPF 或 WinForms 界面。但如果直接在接收循环里同步刷新 UI,一旦界面卡顿,接收线程就停了,位置更新立刻中断。正确做法是解析完只触发事件,UI 侧通过 Dispatcher 或BeginInvoke更新界面。
public event Action<KukaPosition> PositionUpdated; // UI 侧订阅 _tcpClient.PositionUpdated += pos => { Dispatcher.BeginInvoke(new Action(() => { TxtX.Text = pos.X.ToString("F3"); TxtY.Text = pos.Y.ToString("F3"); })); };这里用到 Dispatcher 是因为 WPF 的 UI 控件只能在 UI 线程修改。BeginInvoke是异步投递,不会阻塞接收线程。如果 WinForms,就把Dispatcher.BeginInvoke换成Control.BeginInvoke。位置刷新频率再高,界面最多一秒刷 30 次就够了,不需要每帧都刷新,这也是为什么第 6 章会提到“位置缓存”的用途。
4. 运动控制指令下发:目标点位、速度倍率与回执校验
位置读回来了,下一步就是下发运动控制指令。这里要先把预期摆正:TCP 发命令给 KUKA,不等于上位机直接控制伺服轴,而是把目标点、运动模式、速度倍率发给 KRL 程序,由 KRL 后续调用 PTP 或 LIN 指令完成轨迹规划。这种模式适合定点搬运、视觉引导抓取,不适合要求毫秒级响应的插补联动。
4.1 命令协议设计:字段、序列号和校验码
C# 和 KRL 之间约定一套文本协议,我常用的命令帧格式是:
REQ|SEQ:123|CMD:MOVE_PTP|X:100.0|Y:200.0|Z:300.0|A:0.0|B:45.0|C:0.0|VEL:0.3|CRCF:3A19| 字段 | 含义 | 说明 |
|---|---|---|
| SEQ | 序列号 | 每次自增,用于回执匹配和命令去重 |
| CMD | 命令类型 | MOVE_PTP 绝对定位,MOVE_LIN 直线运动,STOP 急停 |
| X/Y/Z/A/B/C | 目标位姿 | 世界坐标系,单位毫米和度 |
| VEL | 速度倍率 | 0 到 1 之间的浮点数,映射 KRL 的 $OVERRIDE |
| CRCF | CRC 校验 | 对整条命令做 CRC16,防止物理层乱码导致位置突变 |
序列号很重要,因为 TCP 可能重传、上位机可能重发,KRL 如果连续执行同一条命令,机器人可能重复运动。有了 SEQ,机器人侧可以记录上一个序列号,相同序列号直接丢弃。CRC16 不用复杂的加密,能挡住偶发的线路干扰就行。
4.2 C# 发送命令并等待回执
发送命令后必须等 KRL 回执,不能发了就以为执行完。回执格式:
ACK|SEQ:123|STATUS:OKC# 侧维护一个等待回执的字典,收到 ACK 后用序列号匹配:
private readonly ConcurrentDictionary<int, TaskCompletionSource<bool>> _ackWaiters = new(); private void ProcessLine(string line) { if (line.StartsWith("ACK|")) { int seq = ExtractSeq(line); if (_ackWaiters.TryRemove(seq, out var tcs)) { tcs.TrySetResult(true); } return; } } public async Task<bool> SendMoveAsync( KukaPosition target, string cmdType, double velocity, int timeoutMs, CancellationToken ct) { int seq = Interlocked.Increment(ref _seq); string payload = $"REQ|SEQ:{seq}|CMD:{cmdType}" + $"|X:{target.X:F3}|Y:{target.Y:F3}|Z:{target.Z:F3}" + $"|A:{target.A:F3}|B:{target.B:F3}|C:{target.C:F3}" + $"|VEL:{velocity:F2}"; byte[] bodyBytes = Encoding.ASCII.GetBytes(payload); string frame = payload + $"|CRCF:{Crc16(bodyBytes):X4}\n"; byte[] frameBytes = Encoding.ASCII.GetBytes(frame); var tcs = new TaskCompletionSource<bool>( TaskCreationOptions.RunContinuationsAsynchronously); _ackWaiters[seq] = tcs; await _stream.WriteAsync(frameBytes, 0, frameBytes.Length, ct); if (await tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(timeoutMs))) return true; _ackWaiters.TryRemove(seq, out _); return false; }这段代码里,ConcurrentDictionary处理多个命令同时等待回执的情况,比如上位机同时给两台机器人发命令。RunContinuationsAsynchronously是为了防止回执回调卡住接收线程。如果用的是 .NET Framework 4.7.2,没有WaitAsync,就改成Task.WhenAny(tcs.Task, Task.Delay(timeoutMs))。回执超时后一定要从字典里移除序列号,否则字典会越积越大。
4.3 机器人侧限位和安全边界
运动指令不能无条件执行。KRL 侧收到目标点后,第一件事不是 PTP,而是做边界检查。比如某工位 X 轴绝对不允许超过 800mm,A 角度不允许超过正负 90 度,任何越界直接回执ACK|SEQ:xx|STATUS:REJECT。我在 KRL 程序里习惯写一段独立的限位判断,而不是散落在主逻辑里:
IF X_TAR < LIMIT_X_MIN OR X_TAR > LIMIT_X_MAX THEN EKI_Send("CHANNEL_1", "ack_reject.xml", RET) ELSE $OVERRIDE = VEL_TARGET * 100.0 PTP {X X_TAR, Y Y_TAR, Z Z_TAR, A A_TAR, B B_TAR, C C_TAR} EKI_Send("CHANNEL_1", "ack_ok.xml", RET) ENDIF这里$OVERRIDE是 KUKA 总速度倍率,0 到 100。上位机传的 VEL 是 0 到 1,KRL 侧乘以 100 再赋值,保持一致。限位值从示教器现场实测得来,不要拍脑袋。即使上位机做了同样检查,机器人侧也必须双保险,因为 TCP 链路一旦异常,最坏情况下上位机的软限位就和没写一样。
注意:不要把 TCP 运动控制当成安全回路。机器人侧要接硬急停、安全 PLC 和现场护栏,上位机限位只是辅助手段。
5. 避坑排查:KUKA TCP 上位机通讯的 5 个典型翻车现场
这一章是血泪经验汇总。位置读不回来、命令发出去没反应、坐标突然跳变,这些问题是每个做机器人上位机的人都会撞上的。
5.1 现象:坐标全为 0 或一直卡在第一次的值
原因大概率不在 C#,而是机器人侧 KRL 程序没有真正循环发送,或者$POS_ACT在程序启动时读了一次就退出循环了。KRL 程序如果只在初始化时读位置,那上位机拿到的当然是同一个值。另一个常见原因是 EKI 的数据模板变量没有和 KRL 变量绑定,模板里写死了 0.0。
解决:先到示教器看 KRL 程序是否在运行、循环是否真的在走。然后在模板里把初始值改成明显的大数,比如 999.0,如果上位机收到 999,说明数据链路通但变量映射不对。如果收到的是正常值但不动,检查是不是 PTP 运动过程阻塞了通讯副任务。
5.2 现象:命令发了机器人不动,KRL 也像没收到
最常见的原因有三个。第一是字符串拆包问题,上位机发的命令里目标点带了很多小数位,KRL 侧按固定长度截取,把数字截断了,解析失败。第二是命令帧末尾没有换行符,EKI_Receive 等不到完整帧。第三是 KRL 的接收缓冲里积压了多条未处理命令,当前的刚执行完又开始执行旧命令,看起来就像“反应迟钝”。
解决:统一用换行符作为帧结束符,KRL 侧每次处理完一条就丢弃缓冲;上位机发送序号必须严格递增,KRL 侧记录LAST_SEQ,同样的序号直接忽略。调试阶段我习惯把所有命令和解析结果都写到机器人侧的日志文件里,不要靠猜。
5.3 现象:位置偶尔跳变几十毫米,然后又恢复正常
这类问题最玄学,因为定位到后面你会发现坐标数据本身没错,而是上位机解析时序出问题了。如果接收线程在解析时正好 UI 线程也在读同一个LastPosition,而字段是自动属性,可能出现读到一半的脏数据。另外,如果机器人侧发送频率快于上位机解析速度,pending缓冲里积压的数据会越来越多,解析出越来越旧的位置,视觉上就表现为“卡顿后突然跳变”。
解决:把所有坐标字段封装成带日期时间戳的不可变对象,接收循环解析时单独存一份,UI 和运动逻辑只读GetLastPosition()方法返回的快照。接收和处理彻底解耦,而不是共享可变字段。跳变超过 5mm 就报警,大多数情况下你已经知道去哪查。
5.4 现象:姿态反了、关节转错方向
原因基本可以锁定为欧拉角约定不一致。KUKA 的 A、B、C 是固定在世界坐标系下的 ZYX 欧拉角,机器人运动学里叫 RPY 也没错,但有些上位机工程师习惯用 XYZ 欧拉角或者四元数去理解,照搬到命令里就废了。另一种情况是坐标轴方向理解反了,把机器人的基坐标系 X 轴当成产线坐标系的 Y 轴。
解决:姿态角不做任何转换,原样下发原样解析。如果现场必须做坐标系变换,变换矩阵写在独立模块里,用已知点位验证后再接入产线。千万不要在命令字符串里顺手把 A 和 B 对调,这种坑查起来最浪费时间。
5.5 现象:跑几小时自动断开,重连后又正常
这是长连接项目的典型故障。核心原因是中间网络设备对空闲 TCP 连接有回收机制,如果机器人和上位机之间隔了交换机或防火墙,一段时间没有数据就会切断连接。位置回传正常时不容易触发,但到夜里程序暂停或者机器人回原点不运动时,连接就悄悄断了。
解决:上位机侧做两件事。第一,设置 TCP KeepAlive:
_client.Client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);第二,每 500 毫秒发一个心跳PING\n,机器人侧收到后回PONG\n。心跳和位置回传互不干扰,即使机器人不运动,链路也是活跃的。断线后不能靠用户手动重连,必须做成自动重连,这就是下一章要讲的重点。
6. 收尾拔高:断线重连、位置缓存与看门狗让通讯跑得更稳
做了这么多项目,我发现最容易让上位机“上线翻车”的不是功能缺失,而是稳定性。把断线重连做进去,是让 TCP 通讯能交给产线的基础。
6.1 断线重连
接收循环退出后,自动等几秒重连,重连间隔逐步增加,避免频繁失败时疯狂打端口。
public async Task RunWithReconnectAsync(CancellationToken ct) { int retryCount = 0; while (!ct.IsCancellationRequested) { try { bool connected = await ConnectAsync(_ip, _port, 2000); if (!connected) { Log.Warn("连接失败,等待重试"); } else { retryCount = 0; await ReceiveLoopAsync(ct); } } catch (Exception ex) { Log.Error(ex, "TCP 链路异常"); } int delaySeconds = Math.Min(5 + retryCount * 3, 30); retryCount++; await Task.Delay(TimeSpan.FromSeconds(delaySeconds), ct); } }这个循环把连接、读取、重连融在一起,任何异常都会回到重连逻辑。延迟从 5 秒逐步涨到最多 30 秒,防止机器人控制器还没恢复时,上位机一直在高频重试。
6.2 位置缓存与看门狗
UI 刷新不需要每帧都从接收线程拿数据,我习惯让接收线程只更新时间戳,UI 用一个定时器每 200 毫秒去读一次最新位置。同时加一个看门狗:如果超过 2 秒时间戳没有更新,就认为链路已经假死,主动断开连接触发重连。这两行代码能避免很多“界面显示正常但实际已经断线”的假象。
我的个人习惯是:先把机器人侧 EKI 跑通,再用 TCP 调试工具确认报文格式,最后才写 C# 解析代码。而每一位后来接手的人都会感谢你为每一帧报文写下的注释——尤其是那个CultureInfo.InvariantCulture。
这行的东西,调试时看不出区别,产线上一换德语环境就露馅。希望这篇笔记帮你在做 KUKA 上位机通讯时少走一段弯路。
本文还有配套的精品资源,点击获取