资讯动态

TwinCAT ADS通讯路由器状态检测实战:从Sample08到稳定上位机

发布时间:2026/10/13 4:16:26 来源:尧图企业网站定制
做工业上位机开发的多多少少都遇到过这种场景设备跑得好好的突然某个气缸不动作了触摸屏上弹出一个ADS超时PLC那边却明明还在运行。查了一圈最后发现是TwinCAT系统里路由器状态变了所有ADS会话全部失效。我刚接手倍福这套东西的时候就在这上面栽过一次跟头后来把Beckhoff官方示例Sample08翻出来重新啃了一遍才算把路由器状态检测这件事彻底弄明白。这篇文章就围绕这个示例把我当时的理解、实测过程和踩过的坑完整记录下来给同样在做.NET Framework环境下倍福ADS通讯的朋友做个参考。1. TwinCAT路由器在通讯链路里的真实角色不止是转发消息1.1 路由器的职责边界很多人其实没搞清楚TwinCAT路由器这个名字听起来抽象你可以把它理解成所有ADS设备之间的调度中心。不管是本机的PLC Runtime、NC轴、I/O映射还是远程的另一个IPC上的PLC它们之间传递的任何ADS消息都要先经过路由器做路由判断。我把路由器的职责归纳成三件事消息寻址根据目标AMSNetId和端口号判断这个消息该发到本机哪个进程还是转发到网卡对面那台设备。连接管理维护当前的ADS连接表包括哪个客户端订阅了哪个设备端口、通知句柄是否有效。状态广播把自身的运行状态、设备列表变化推送给所有注册了监听的客户端。所以当你发现某个PLC的变量怎么也读不上来时不要急着怀疑网线或者代码先看一眼路由器状态。它一旦处于停止或者切换中的状态下面所有ADS服务都是废的。1.2 不做状态检测上位机迟早要吃大亏我见过不少项目上位机代码写得挺规范连接、读写、异常处理都齐全就是漏了路由器状态检测。结果就是控制器从Config模式切到Run模式或者反过来路由器会在切换瞬间重置ADS会话订阅的通知全部失效。如果上位机没有监听RouterState变化就永远不会知道订阅已经丢了。TwinCAT系统偶尔会自动重启比如装了驱动更新或者修改了网卡配置。重启期间路由器的AMSNetId会短暂消失再回来。这时上位机如果还在按原Handle读写数据收到的全是错误码。双机热备场景里主备切换必然伴随路由器状态变化。不监听状态主备之间的动作协调就只能靠循环轮询慢不说还容易漏掉瞬间的状态窗口。更关键的是路由器状态变化本身不产生ADS错误码。它只是让后续所有ADS调用都变成无效操作你排查半天只看到莫名其妙的各种超时根本想不到根因在路由器上。所以状态检测不只是锦上添花而是必须做的一步基础能力。2. Sample08依赖的ADS基础AdsCommunication与端口选项2.1 官方示例里最核心的两个命名空间Sample08全称是Sample08 - Get/Set the TwinCAT router state它做两件事读取当前路由器状态以及监听路由器状态变化。它用到的核心类大多来自Beckhoff.Automation.TcAds命名空间在.NET Framework环境下引用方式很简单NuGet包名是Beckhoff.Automation.TcAds或者直接引用安装ADS后的DLL。整个示例的入口是AdsCommunication类构造参数里带了一个port这个port不是TCP端口号而是ADS设备端口号。在倍福体系里每个服务进程都有一个固定端口服务端口号TwinCAT 2 PLC Runtime801TwinCAT 3 PLC Runtime851NC轴501I/O设备300路由器自身状态监听10000Sample08里用的就是10000这个端口因为只有端口10000是专门用来暴露路由器状态信息的。用其他端口也能连上但收到的是各个Runtime自己的状态不是路由器全局的状态。2.2 AMSNetId和路由表决定了你监听的是谁的状态AMSNetId结构上类似IP地址但含义完全不同。IP地址定位的是网卡AMSNetId定位的是一个TwinCAT节点的路由标识。本机的AMSNetId一般可以在TwinCAT System Manager或XAE开发环境中看到默认通常是127.0.0.1.1.1。需要说明的是本机模板NetId在不同环境下可能不同实际使用时以开发环境里显示为准。Sample08如果调试本机状态用本机NetId即可。如果要从这台工控机去监听另一台设备的TwinCAT路由器状态就需要在静态路由表里把远程AMSNetId和它的IP地址绑定好然后在AdsCommunication构造时传入远程NetId。我当时刚开始搞反了以为只要把AMSNetId改成远程的就行结果连静态路由都没配消息根本发不出去。这里提醒一句远程连接时先确认目标机器的静态路由表里有没有你的工控机记录否则AMSNetId填得再正规也是白搭。2.3 .NET Framework下用同步API注意别跑UI线程Sample08的完整代码里读取状态用的是AdsStream配合AdsState监听用AdsNotification。在.NET Framework 4.x环境下这些API大多是同步的。如果你在WinForm或者WPF的按钮事件里直接调用界面会卡住直到ADS响应超时。我在实测时把连接和监听都放到了任务线程里UI线程只负责收回调结果。如果你不想引入线程复杂度可以把AdsCommunication的运行逻辑包在一个后台任务里回调里再用Dispatcher回到UI线程更新界面。这个习惯在这个示例里不是必须的但日后扩展到复杂项目时能省下很多头疼事。3. Sample08核心代码拆解端口属性、回调注册与状态字段3.1 状态字段里的关键信息AdsState和RouterStateADS状态体系里有两层状态AdsStateADS设备本身的状态比如运行、停止、配置。数值定义比较直观0表示ADS状态未知5表示运行6表示停止。RouterState路由器状态包括配置、运行、切换中、停止。这个状态才真正反映TwinCAT系统级的情况。Sample08在读取时会把这两个状态一起返回代码里通常是用一个AdsState结构体接收。这个结构体里有一个AdsState枚举成员和一个RouterState枚举成员有时候还会带有一个DeviceState之类的扩展值用来描述具体设备子状态。枚举数值含义AdsState.Stop6设备停止AdsState.Run5设备运行AdsState.Config4设备配置中RouterState.Stop6路由器停止RouterState.Run5路由器运行RouterState.Config4路由器配置中RouterState.Toggle3路由器状态切换中3.2 订阅通知的方式AdsNotification而不是轮询大多数人可能下意识会用一个定时器每秒去读一次路由器状态这种方法虽然能实现但问题很多状态切换可能只持续几百毫秒轮询间隔稍微大一点就捕捉不到频繁轮询还会增加路由器负担在项目现场尤其明显。Sample08采用的是AdsNotification机制。客户端向路由器注册一个通知回调路由器在状态变化时主动推送消息过来。这样不仅实时性好而且没有轮询开销。这里的订阅参数里有一个cycleTime也就是发送周期一般设成100ms或者200ms就够了。如果设得太短路由器在高频切换时会频繁推送消息原本是帮你监听状态的机制反而成了消息风暴。设置通知的代码大致是这样的var settings new NotificationSettings(AdsTransMode.OnChange, 100, 0); var notificationHandle adsCommunication.CreateNotification(TcAdsDataType.AdsState, 1, settings, callback);AdsTransMode.OnChange表示状态值发生变化时才通知100是周期毫秒0是最大延迟。如果你需要记录完整的状态变化轨迹也可以把这个模式改成Cycle让它每个周期都上报一次代价是消息量变大。3.3 用一个字段记录当前路由器状态回调里只做轻量操作Sample08里有一个典型的做法在类里维护一个字段保存当前路由器状态回调线程里只更新这个字段或者把状态变化推送进一个队列再由上层业务逻辑处理。这个设计看着简单但非常关键。ADS通知回调是跑在路由器通讯线程里的。如果你在回调里直接做耗时操作比如写日志、弹窗口、调数据库轻则回调阻塞重则导致ADS通讯线程卡死整个连接出现假死。我最初在回调里直接加日志压测时发现只要状态变化密集一会儿通讯就异常了。改成只更新字段之后问题立刻消失。private AdsState _currentState; private void OnRouterStateChanged(object sender, AdsNotificationEventArgs e) { _currentState (AdsState)e.ValueList[0]; // 这里只记录不做耗时操作 }读取时直接查看这个字段即可不需要每次去请求ADS。如果你对实时性有更高要求可以采用双缓冲思路一个字段由回调线程写另一个字段由业务线程读中间用时间戳判断数据是否新鲜。3.4 初始化路由表的细节搞错了连本机都连不上Sample08里AdsCommunication构造有一个Init或者Connect的步骤背后做的事情就是建立到路由器端口10000的ADS连接。这里有几处细节很容易踩第一AMSNetId字符串一定要规范格式。它长得像192.168.0.5.1.1其中最后两位是子网和节点号不要学IP地址只写四段。少了后面两个数字ADS连接会一直停留在初始状态。第二如果连接失败报的错误是ADS Device Notification Exception那基本就是路由器没有运行。TwinCAT系统如果处于停止状态路由器也可能没有完全就绪先生成激活配置再测试。第三用TcpClient还是AdsCommunication取决于你的.NET版本。在.NET Framework 4.x下直接用AdsCommunication比较稳妥它的底层封装了ADS的完整握手流程。如果你在.NET Core 3.1以上环境建议改用AdsClientAPI风格类似但底层实现针对新平台做了调整。3.5 关闭连接时的顺序先释放通知再断开端口示例代码结束时会有一个清理步骤。别小看这段它的顺序直接影响下一次启动是否正常。正确的顺序是删除已经创建的通知Handle调用DeleteNotification或者等价方法。断开ADS连接释放端口。可选把路由器状态字段重置为初始值。如果顺序反了比如先断开连接再删除通知路由器那边可能残留一个无效通知关联。下次连接时再注册同名通知可能会收到之前残留的垃圾回调。多端口多客户端的环境下这种残留还会导致内存句柄缓慢增长。4. 实测中的三种状态切换场景启动、停止、重启4.1 场景一从停止到运行状态触发流程我搭建了最简单的测试环境一台工控机TwinCAT 3运行时以XAR模式跑着PLC程序处以停止状态。上位机启动后连接到端口10000并注册通知初始状态显示为AdsState.Stop / RouterState.Stop。这时在开发环境里点击激活运行观察到的变化序列很有意思RouterState: Config - Stop RouterState: Stop - Toggle RouterState: Toggle - Run AdsState: Stop - Run路由器先进入Config配置短暂状态然后经过Toggle切换最终稳定在Run。整个切换耗时大概在几百毫秒到一两秒之间取决于项目中任务配置的复杂度。如果项目的启动任务里含有大量初始化逻辑这个窗口期还会更长。在这个窗口期里任何ADS读写都会出现不确定行为有时超时有时返回错误码有时甚至可能正常访问到旧状态区的数据。上位机如果正处于这个阶段最好的策略是缓存写入请求等RouterState稳定为Run后再统一下发。4.2 场景二运行中停止状态回滚顺序反向操作同样值得关注。我在PLC运行过程中直接点了停止状态变化序列是RouterState: Run - Toggle RouterState: Toggle - Config AdsState: Run - Stop这个序列里有意思的地方在于AdsState的变化发生时RouterState已经离开了Run状态。如果你是在独立线程里分别读取这两个状态会发现它们之间存在短暂的时间差。所以在判断系统是否可安全通讯时建议以RouterState为准而不是AdsState。我做测试时连续切换了十几次每次都能稳定捕捉到Toggle中间态说明OnChange模式在这个场景下是可靠的。只要不在回调里做重活理论上不会漏事件。4.3 场景三立即重置和系统重启断开的连接谁来恢复第三个场景更接近真实故障我把整个TwinCAT服务重启。这时候的上位机看到的现象是状态通知回调一直没有触发——因为路由器自己都没了自然发不出通知消息。也就是说监听路由器状态这个机制本身在路由器完全宕机时是不可用的。不能只靠回调来做故障检测还必须配合基础的心跳机制。比如上位机每隔几秒主动调用一次ADS状态读取如果连续几次都超时就判定路由器失联。这点值得单独拎出来说。很多人在做状态检测时以为注册了通知就万事大吉路由器被杀死时直接傻了眼。其实Sample08只是给你提供了状态变化感知能力它假设的前提是路由器还活着。真正的冗余方案必须把主动探测和被动通知结合起来。我在实际项目中是这么组合的正常运行时依赖AdsNotification的OnChange回调感知状态变化。每隔5秒主动读一次路由器端口做心跳校验。心跳连续失败3次以上判定路由器完全不可用触发上位机重连流程。两条通道互补才能既保证实时性又保证故障无死角。5. 容易翻车的细节从状态回调到重连设计的完整坑位5.1 回调里的线程安全和对象生命周期前面说了回调里不能做耗时操作这只是第一层。第二层是线程安全。ADS通知回调默认运行在路由器的通讯线程上。这意味着如果你在回调里更新UI控件的Text属性在WinForm里会直接抛线程间操作异常在WPF里可能表现为偶发崩溃。我当时的处理方式是回调里只把一个结构体放进并发队列UI线程用定时器每隔100ms取一次队列内容刷新界面。本质上就是经典的生产者消费者模式。同样如果上位机业务里有多个模块都关心路由器状态不要在回调里逐个通知而是用一个事件聚合器在业务线程统一触发。这里有一个容易忽视的坑事件重复订阅。如果AdsCommunication对象的RouterStateChanged事件绑定了多个处理器而某个处理器内部又创建了新的事件订阅稍不留意就会形成递归式增长。当状态切换频繁时这个事件链会越来越大最后内存占用居高不下。排查时可以先打印事件调用次数确认每个订阅者只被注册了一次。5.2 手动置位的状态和真实状态不一致还有一个不太常见的坑有时路由器状态会保持在一个看起来正常的值但底层通信实际已经断裂。这种情况通常发生在网络故障恢复后路由器自动重建了连接表但某些老会话没有被完全清理。这时候不能盲目相信回调数据。我建议在业务逻辑里加一个二次确认机制收到RouterState.Run后不要立刻开始大批量读写而是先尝试读取某个PLC的简单变量比如系统时间或者版本号确认ADS链路确实通畅后再继续后面的逻辑。这个二次确认的开销极小但能挡住很多隐性故障。5.3 多连接环境下删除通知时的Handle可能已经失效如果你的上位机同时连了多个ADS设备每个设备都各自注册了状态通知。在某个设备的通知回调里如果你想清理其他设备的Handle要特别注意目标Handle是否还处于有效状态。比如路由器状态切到Stop时部分设备连接可能已经断开对应的Handle已经自动失效此时再调用删除接口反而会抛出异常。稳妥的做法是统一在一个状态机里维护所有设备的连接和通知生命周期。收到RouterState进入Stop的广播后先遍历设备连接表逐个标记为待重连但不立即强制断开等到RouterState回到Run再统一执行重连和重新注册通知。这样既避免了操作已失效对象也让整体流程更可控。我实际项目里一开始没做状态机各个设备各自管各自的结果路由器一重启设备A重连了设备B还卡在等待状态两边不同步排障起来非常混乱。后来统一改成状态机驱动才把这类问题理顺。5.4 日志记录要分等级别把状态切换刷成流水账Sample08跑起来后路由器每切换一次状态回调就会触发一次。如果你在这个回调里直接写日志现场跑一天能刷出好几万条。不仅日志文件暴增还可能影响工控机SSD寿命。我最后把所有状态变化事件分成了两级Info级别记录路由器状态的每一次正常变化例如Run到Toggle。Warning级别记录进入Config或者异常Stop的情况。Error级别记录心跳探测失败、连接重置等异常。最关键的是只在状态切换的那一瞬间写一次日志不要在OnChange的每个周期都写。如果你用AdsTransMode.Cycle模式那就更需要谨慎这种模式下回调频率等于通讯周期随便打个日志都足以拖垮吞吐。5.5 启动过程中就注册通知要处理通知在连接建立前触发的情况有一种比较隐蔽的时序问题上位机刚启动TwinCAT路由器可能正处于Configure模式。这时你在端口10000上注册状态通知虽然连接能建立但通知的初始值可能还没准备好回调第一次触发时拿到的AdsState可能是不完整的默认值。我的做法是在连接建立后先主动调用一次ReadState获取当前状态把这个值当作初始快照写入状态字段然后再去创建通知订阅。这样即使第一次通知来得很晚或者丢了上位机也能有一个真实的起点状态而不是默认值。如果需要更严谨可以在初始化阶段多读几次状态做比对。这个处理在代码里只是多几行但能避免很多初始状态误判。比如系统刚启动时路由器实际上已经快完成切换了上位机却以为它一直处于Config导致业务功能迟迟不打开。6. 从Sample08延伸开去状态检测在上位机里的完整应用闭环6.1 状态检测只是发现后面还要接处置跑通Sample08只是第一步离一个可用的项目还差得很远。它解决的是发现问题而现场更关心的是发现问题之后怎么办。我在实际工程里把路由器状态检测接到了一个简单的状态机里RouterState为Run时所有业务读写正常执行。RouterState为Toggle或Config时进入过渡模式业务读写进入缓存队列同时暂停新的任务下发。RouterState为Stop或者心跳连续失败时进入重连模式释放旧连接等待路由器恢复后重新初始化。从重连模式恢复到Run后先做二次确认再恢复业务功能。这套逻辑听起来不复杂但写出来很考验对ADS通讯细节的理解。尤其是重连过程不只要重新建立ADS连接还要同步重新注册路由器状态通知、重新订阅PLC变量、重新读取初始化数据。任何一步漏掉系统看起来连上了实际上脑子里还是空的。6.2 分享一个降低调试难度的经验最后分享一个实际调试中的小技巧在做路由器状态检测时不要只盯着代码调试把开发者工具里的TwinCAT System View和上位机日志同时打开。在System View里手动切换系统状态同时观察上位机日志里的RouterState变化时间戳就能快速判断通知链路是否通了。我用的方法是手动点击停止时在日志里打印当前时间和RouterState随后点击运行再打印切换后的状态和时间。如果日志里的变化顺序和System View里的实际顺序对不上说明通知有延迟或者丢失需要对通讯周期和NotificationSettings重新调整。这招能省下大量盲调时间。对于刚接触倍福ADS通讯的人来说Sample08是一个很好的起点。它能让你看到路由器状态这个平时躲在背后、出事时却要命的关键环节。把我上面提到的那些坑全部排掉之后这个状态检测机制可以在很多复杂场景里稳定运转。做工业上位机开发稳定性永远比花哨更重要而一个可靠的路由器状态检测恰恰是稳定性的基石之一。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑