ARTICLE DETAIL

资讯详情

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

ADS协议深度剖析:从AMS路由到Python实操,一次读懂倍福PLC通信

ADS协议深度剖析:从AMS路由到Python实操,一次读懂倍福PLC通信 有一次在客户现场排查“偶发连不上PLC”的问题我习惯性地打开Wireshark抓TCP端口48898的包。结果发现上位机和PLC之间的TCP三次握手已经完成但随后发出去的ADS请求包就像石沉大海一样一个响应都没有。顺着这个线索往下查最后定位到的问题居然是路由表里的AMSNetId少写了一段。当时客户和我说了一句很有意思的话“你们天天说ADS这玩意儿到底是怎么把一个数据包从电脑送到PLC里的”这句话让我意识到很多人嘴上挂着ADS但真正理解ADS从握手到数据包完整旅程的人并不多。所以这篇博文我想把倍福ADS通讯协议彻底扒开来讲一次它依赖的AMS路由体系是什么AMSNetId和Port到底在说什么一次读写请求的数据包长什么样以及在Windows上用Python和它通信需要注意哪些坑。内容不绕弯子适合刚接触倍福上位机开发的工程师也适合那些被ADS报错折磨过的现场调试人员。1. ADS到底是什么它解决了什么问题1.1 ADS在TwinCAT体系里的位置ADS的全称是Automation Device Specification直译过来就是“自动化设备规范”。在倍福TwinCAT的世界里它既不是像EtherCAT那样的实时现场总线也不是像OPC UA那样高层语义互操作协议。它更像是一套面向整个TwinCAT系统的“内部API”和“进程间通信协议”几乎所有非实时层面的数据交换都绕不开它。TwinCAT里的各个模块包括System Service、PLC运行时、NC轴控制、CNC、以及各种TF功能包都是通过ADS对外暴露服务的。如果你在电脑上装了TwinCAT XAE然后用C#写一个程序去读PLC里的变量表面上看是“读变量”底层实际发生的是你的程序通过ADS协议向目标PLC运行时端口发送了一条请求消息PLC运行时处理完后把数据塞进响应消息返回来。HMI、上位机、数据库网关、第三方视觉软件本质上都在干同一件事。把ADS理解成“控制系统的操作系统API”会更直观。和Modbus那种面向寄存器的模型不一样ADS不需要你去维护一张粗糙的寄存器映射表。它是面向服务的、动态的、支持大块数据交换的而且天然支持双向通知。这也解释了为什么倍福官方几乎所有的上层软件组件——ScopeView、TC3 HMI、TF6420数据库服务器——底层都是通过ADS来和运行时打交道。1.2 吃透ADS能带来什么说得功利一点吃透ADS意味着你可以脱离TwinCAT的图形界面用任何一门主流编程语言和倍福控制器对话。我自己就试过用C#、Python、甚至Node.js写过访问PLC数据的工具底层原理都是同一套AMS/ADS报文。这也意味着你能做的事情就多了开发自定义的HMI网关、做产线数据采集、写设备状态的运维诊断脚本、甚至基于ADS协议做一套自己的自动化测试框架。很多工厂里所谓的“MES对接不上PLC”说到底就是ADS层的地址配置、路由关系或数据解析出了偏差而不是协议本身有多难。影响范围再放大一点设备OEM厂商做远程运维、终端工厂做车间级数据平台、系统集成商做多设备联动几乎都要和ADS打交道。所以别觉得ADS只是倍福文档里一个名词它是你真正进入倍福生态后躲不掉的基础设施。2. 正式聊ADS之前先把AMS、NetId、Port、路由表捋清楚2.1 AMS是ADS的“快递网络”ADS协议本身承载在AMS之上。AMS全称Automation Message System它是倍福体系中负责消息寻址和路由的传输层基础设施。ADS和AMS的关系可以类比成“信件内容和快递物流”的关系ADS定义了信件里写什么、格式怎么组织AMS负责把这封信准确送到收件人手里。每台运行TwinCAT的电脑或控制器里都有一个AMS Router也就是路由器组件。所有AMS消息都会先汇总到本机的路由器路由器再根据目标AMSNetId决定是投递给本机进程还是转发到网络上另一台设备的路由器。这个机制和日常用的IP路由有相似之处但是它是一种独立于TCP/IP的寻址逻辑尽管实际传输经常跑在TCP或UDP上面。很多新手不理解为什么配置ADS通信还要配置“路由”。因为ADS不发“广播”也没法“自动发现”目标它不是UPnP那一套。你必须明确告诉本机路由器目标设备的AMSNetId是什么、它住在哪个IP地址上。路由器只有拿到这个映射关系才知道把AMS消息往哪送。2.2 AMSNetId和Port就是邮编加门牌号AMSNetId是一个6字节的地址平时最常见的写法是四段点分十进制再加两个段比如192.168.0.10.1.1。前面四段很多人习惯性写成和目标设备的IP一致这是约定俗成的做法但不是强制要求。后面两段通常用于区分同一个设备里的不同AMS实例。Port则是一个2字节的AMS端口号用来标识设备内部的某个具体服务。倍福有几个默认的AMS端口需要记牢100是System Service也就是TwinCAT系统服务851是TwinCAT 3里第一个PLC运行时PLC Runtime 1的默认端口852是第二个PLC运行时的默认端口。如果以后做了多PLC项目看到端口变化不要慌多半就是运行时编号的问题。顺带提一句非官方程序如果要用ADS通信可以在10000以上的端口段自定义一个AMS端口。实际项目里我就见过有人用Python写一个数据采集服务然后给它分配一个自定义的AMS端口这样PLC里也可以反过来主动往这个端口写数据。这种做法虽然不常见但说明ADS是对称的两边都能做客户端、也都能做服务端。2.3 符号寻址和内存寻址两条路线ADS通信时要定位一个数据对象核心是两个参数IndexGroup和IndexOffset。这两个参数组合起来有点类似于“寄存器块块内偏移”的概念。实际操作中你可以走两条路。第一条是用变量符号名比如直接说“我要读GVL.temp”由ADS系统在后台查符号表把符号名解析成具体地址。这种方式写程序方便人也容易看懂适合开发速度优先的场景。第二条是自己查好IndexGroup和IndexOffset直接把地址数字填进请求里。这种方式省掉了符号解析这一层开销适合嵌入式C代码或者对性能锱铢必较的场景。TwinCAT 3开发环境里可以查看某个变量对应的内存地址信息方法是找到PLC项目的Symbol表或者Memory视图。新手不需要死记硬背IndexGroup的具体数值更重要的是理解两种寻址方式的区别符号寻址是给人看的内存寻址是给机器跑的。2.4 路由表决定“邻居”关系要让两台跑着TwinCAT的设备建立ADS通信前提是两边都知道对方的存在这就是路由表的活。TwinCAT 3里的路由配置可以在XAE环境里双击“System”下的“Router”标签页在“Names and Routes”里添加目标设备。也可以直接编辑配置文件比如TwinCAT 3的TcRoutes.xml。配置时你会填两个关键信息对方的AMSNetId以及对方实际所在IP地址。这两个信息都需要填对否则路由器不知道该把消息发到哪台设备。我见过太多报错案例AMSNetId写错一个数字或者IP地址填了网关而不是目标设备结果就是连着连着报超时。改完路由表之后最好重启TwinCAT服务或重新加载路由配置。有些应用在路由表变更后不会自动重试连接会一直拿着旧的路由缓存跑这也是“改完配置还是连不上”的常见原因。3. 从握手到数据包的完整旅程3.1 一次ADS会话的三层“握手”很多人一听到“握手”就只想到TCP三次握手但ADS场景下的握手其实是分三个层次的。第一层是TCP三次握手。如果通信走的是TCP那么客户端会先和PLC的48898端口建立TCP连接。这一步完成代表“网络通路是通的”但完全不代表ADS通信就能成功。很多现场问题就出在这里网络能ping通、TCP也连上了但AMS路由表是错的所以应用层简直寸步难行。第二层是AMS层的路由握手。TCP连接建立后双方的AMS路由器需要确认彼此知道对方的路由关系。路由表配置不对、路由器服务没有运行、或者目标设备根本没有激活在这一层就会暴露出来。这个阶段一旦出错TCP连接看着好好的但发出去的消息没有响应或者直接收到错误码。第三层才是真正的ADS请求/响应握手。客户端发送一条状态标志为0x0001的请求报文服务端返回一条状态标志为0x0002的响应报文。请求和响应通过一个InvokeId来配对确保“你问的是这一句我回的也是这一句”。想清楚这三个层次非常有用。以后排查ADS通信问题你就不至于一头雾水而是能一步步定位TCP通不通路由对不对请求有没有发出去响应有没有回来3.2 AMS报文的逐字节结构如果抓包看一次ADS通信你会发现在TCP负载的最前面是6字节的AMS/TCP头其中前两个字节保留后四个字节表示后面AMS包的总长度。紧接着就是核心的32字节AMS头最后才是ADS数据区。AMS头里的核心字段包括目标AMSNetId6字节要发给谁目标Port2字节发给对方哪个服务源AMSNetId6字节我是谁源Port2字节从哪个端口发出来的CommandId2字节这条消息要干什么比如0x0002是Read、0x0003是WriteStateFlags2字节请求还是响应DataLength4字节后面ADS数据区的字节数ErrorCode4字节请求包里通常为0响应包里如果非零就说明出错了InvokeId4字节交易流水号用于把请求和响应配对这套结构既清晰又简单但它是整个ADS通信的地基。理解了这些字段你在Wireshark里看到一包数据时就能像看一封标准格式的商务信函一样一眼扫出“谁写给谁、要干嘛、结果如何”。3.3 一次Read请求数据包里到底发生了什么我们模拟一次最简单的场景上位机要读取PLC里的一个REAL变量“GVL.temp”数值是23.56。当请求从上位机发出时TCP层先把这条数据切好片、标好序号然后通过已经建立的连接发送到PLC的48898端口。PLC侧的TCP协议栈组装好数据把它交给AMS路由器路由器看到目标AMSNetId是本机目标Port是851于是投递给PLC运行时。接下来是ADS命令处理。Read命令的CommandId是0x0002请求数据区包含三个部分IndexGroup4字节、IndexOffset4字节、读取长度Length4字节。这里的IndexGroup和IndexOffset如果来自符号解析那么它们已经指向了GVL.temp对应的实际内存位置。Length则是你要读的字节数REAL型变量是4字节所以这里填4。PLC运行时收到后从指定内存地址读出数据生成一条响应报文。响应数据区里的结构是ErrorCode4字节成功时为0、读取的数据长度4字节、以及实际数据4字节。这条响应报文顺着原路返回到上位机的Router最终交给你的上位机程序。整个往返过程中最容易被忽略的一点是如果你用符号名读取变量那么在真正Read之前上位机通常还要额外进行一次“符号名到句柄”的转换通信。也就是说一次看起来简单的读变量底层可能包含了两轮甚至三轮ADS往返。3.4 Write、ReadWrite和往返次数优化Write命令的CommandId是0x0003。请求数据区为IndexGroup、IndexOffset、Length再加上你要写入的实际数据。服务端执行成功后返回的响应数据区只有ErrorCode。还有一个非常实用但很多人不常用的ReadWrite命令CommandId是0x0009。它允许你在一个请求里同时携带写数据和读数据的要求。请求数据区里的结构是IndexGroup、IndexOffset、要读取的长度ReadLength、要写入的长度WriteLength、随后是WriteLength字节的写入数据。服务端执行完写入后响应里返回你指定长度ReadLength的数据。什么时候该用ReadWrite典型场景是某些设备模块需要你先设置一个控制字然后立即读回状态。如果用Read和Write两个命令分开做至少要两轮往返用ReadWrite一次往返就够了。在高频读写或者远程通信场景下这能省下肉眼可见的时间。3.5 所有环节都正确才叫一次成功的ADS通信总结一下完整旅程客户端程序发起TCP连接TCP三次握手建立链路AMS路由器根据NetId确认目标设备可达客户端通过ADS命令查询符号句柄、发起读写请求服务端根据IndexGroup/IndexOffset定位到内存地址执行操作并返回响应客户端根据InvokeId对应到自己的请求拿到数据。任何一个环节出问题用户看到的可能只是“超时”或者“错误码”。但你要知道这个错误码背后有可能发生在TCP层有可能发生在AMS路由层也有可能发生在ADS命令处理层。不同层的错误排查方向完全不一样。这也是为什么我一直强调别只背错误码含义要理解ADS通信的分层模型。4. 用Python实操跑通一次ADS读写4.1 准备环境如果你已经有装了TwinCAT XAE的开发电脑那环境基本就齐了。Python侧只需要安装pyads这个库命令很简单pip install pyads需要提醒的是pyads在Windows上一般依赖本机已经运行着的TwinCAT Router来做AMS消息路由。也就是说你最好在装有TwinCAT的开发机上运行程序或者确保目标PC上至少启动了TwinCAT相关的路由服务否则大概率会报“AMS router not found”之类的错误。通信前还要确认路由表已经正确添加。开发机上能通过TwinCAT XAE的路由配置界面看到目标PLC的AMSNetId和IP这就说明路由关系已经建立了。如果这一步没做好后面代码写得再好也白搭。4.2 最小可运行的读写代码假设PLC的IP地址是192.168.0.10AMSNetId是192.168.0.10.1.1端口是851也就是Python常量pyads.PORT_TC3PLC1对应的值。那么一段最小代码是这样import pyads PLC_IP 192.168.0.10 AMS_NETID 192.168.0.10.1.1 PLC_PORT pyads.PORT_TC3PLC1 plc pyads.Connection(AMS_NETID, PLC_PORT, PLC_IP) plc.open() # 按符号名读取REAL变量 temp plc.read_by_name(GVL.temp, pyads.PLCTYPE_REAL) print(temp:, temp) # 按符号名写入BOOL变量 plc.write_by_name(GVL.startFlag, True, pyads.PLCTYPE_BOOL) plc.close()这段代码虽然短但已经包含了建立连接、按符号名读写、关闭连接这三个核心动作。启动程序之前记得检查PLC程序已经运行并且在TwinCAT里GVL.temp和GVL.startFlag这两个变量真实存在。否则ADS会返回类似“找不到变量”的错误定位方向也就是符号名解析失败。从工程实践角度我建议把plc.close()放进异常处理的finally块里。ADS连接在异常退出时如果没关闭可能会占着句柄不放导致下次启动程序时路由缓存出问题。4.3 句柄用得好性能差不少pyads的read_by_name和write_by_name确实方便但如果你在循环里高频读取同一个变量每次都走完整的符号解析流程性能会有明显浪费。更合理的做法是提前获取变量的句柄然后循环里用句柄来读。这样就把“符号名到内存地址”的解析开销摊薄到只做一次。示例代码如下# 获取句柄 handle plc.get_handle(GVL.temp) # 循环里反复读取 for _ in range(100): value plc.read_by_handle(handle, pyads.PLCTYPE_REAL) print(value) # 用完之后释放句柄 plc.release_handle(handle)这里有个细节句柄本质上是一个在PLC运行时侧创建的临时对象如果你获得了句柄但不释放PLC侧就会一直保留它。短时间内可能无所谓但日积月累或者频繁开关程序容易把句柄资源耗光。另一个小技巧是如果你需要一次读取多个相同类型的变量并且它们的数据类型和地址是连续的那么可以试试一次性读整块内存数据然后在本地拆分成各个变量。这样比一个一个去读减少了很多次往返在数据点特别多的场景下提升非常明显。4.4 轮询和通知选哪个很多人在做上位机数据采集时第一个想法就是搞个定时器循环去读PLC变量。点位少的时候这么做没毛病但点位一多轮询就会造成大量无效请求。比如PLC里变量五分钟变化一次你却每隔100毫秒去读一次这中间绝大部分请求都是浪费的。ADS本身支持设备通知Device Notification机制。你可以通过AddDeviceNotification命令把某个变量的变化监听注册到PLC侧。变量值变化时PLC主动把新值推送给客户端而不是等客户端来问。我的经验是点位少于10个变化频率不高轮询就够用。点位多、变化频繁、或者数据实时性要求高果断用通知。如果数据要进数据库轮询反而有时候更可控因为通知模式下数据到达时间不均匀写入压力会忽高忽低。这个选择题没有标准答案只有适合当前场景的方案。理解ADS通知机制能给你多一个选项这就已经比只会轮询的开发者高一个段位了。5. 常见问题与排查技巧实录5.1 错误码速查表ADS通信中响应包里的ErrorCode是最直接的故障线索。我把实际项目中遇到频率最高的几个错误码整理成了一张表方便你快速参考错误码含义常见场景0x0000成功正常响应0x0701无效的服务ID目标服务端不支持你发的命令0x0702无效的IndexGroup地址参数有问题地址查找错误0x0703无效的IndexOffset地址偏移不正确常见于句柄错误0x0708无效的句柄句柄已释放或者根本不存在别在release后再用0x1011未找到AMS路由器本机Router没跑起来0x1012未找到AMS端口目标端口不对比如PLC运行时没激活0x1013未找到目标设备目标设备不存在或没加载0x1102目标端口超时网络不通、防火墙拦截、路由配置错误、设备忙排查时我的习惯是看到错误码先确认它属于哪一层。0x0702是ADS命令处理层的问题通常和你的变量地址有关0x1011是AMS路由层的问题和你的Router服务有关0x1102则可能横跨网络、路由、目标设备多个层面需要逐层排除。5.2 被反复问的“4132”到底是不是好事最近总有朋友问我“倍福报错4132这到底是好事还是坏事”我得先泼一盆冷水脱离语境谈4132什么事都说明不了。从ADS协议的错误码定义来看标准响应码里没有4132这个数值。把十进制4132转成十六进制是0x1024这个值并不在ADS标准错误码清单里。所以当你看到4132时更应该把它理解为“你的ADS链路在某个环节没有建立成功”而不是倍福官方定义的一个固定错误。网上关于4132的各种说法大多是上位机库封装层抛出来的异常码或者某个特定开发环境里的报错编号。遇到这种情况我的排查思路很固定先看异常是哪个模块抛出来的是本机路由、库函数还是目标设备检查AMSNetId和Port是否和PLC侧完全一致检查路由表里目标设备是否正常加载最后抓包看TCP连接和ADS请求有没有真正发出去。换句话说别盯着4132这个数字“是好事还是坏事”管它是什么码链路没通就是坏事从头到尾排查一遍才是正事。5.3 抓包定位法ADS通信出问题时我最常用的定位工具还是Wireshark。抓包之前要确保上位机和PLC之间的通信没有走加密而ADS over TCP本身是明文的所以抓包能直接看到AMS头里的字段。过滤表达式很简单就一行tcp.port 48898抓包后看几个关键点TCP三次握手有没有完成。如果只有SYN没有SYN-ACK说明网络层有问题。TCP连接建立后有没有ADS请求包发出。没有请求发出去说明问题在上位机侧的路由或者程序逻辑。请求发出后有没有响应包回来。有响应但ErrorCode非零按错误码表查。响应没回来就看有没有TCP重传。有重传说明请求可能丢了或者目标PLC根本没处理。抓包能帮你在“连不上”和“通信慢”这两种情况之间做很好的区分。有一次我排查一个“偶发超时”的案例抓包后发现TCP重传频繁最后定位到是现场的工业交换机端口协商成了半双工跟ADS协议本身一点关系都没有。没有抓包的话这个问题可能要被“背锅侠”协议本身扛很久。5.4 我踩过的几个坑第一个坑是防火墙拦截。Windows防火墙默认可能拦掉非系统的TCP入站连接特别是48898这种不常见端口。很多时候现象是能从PLC这一侧主动连上位机但上位机连PLC就超时。这种不对称的通信行为十有八九就是防火墙的锅。第二个坑是NetId配置成“看起来对但实际不对”。有些人习惯把AMSNetId前四段配置成和IP一致但实际项目中如果路由表是从别的电脑拷贝过来的或者有人手动改过NetId就可能出现IP通、但NetId对不上的情况。这时候TCP能连上但ADS请求永远得不到正常处理。第三个坑是句柄泄漏。程序反复创建句柄却不释放PLC运行时侧的句柄表会被慢慢塞满最终表现为“程序跑一段时间后突然开始报无效句柄”。这个问题在长期运行的上位机服务里尤其隐蔽因为不是立刻报错而是隔几小时甚至几天才爆发一次。第四个坑是路由表缓存。修改路由表后如果TwinCAT Router没有重启或者上位机程序本身缓存了连接信息就会一直拿旧路由去通信。碰到这种“改了配置但没效果”的情况我一般会先重启TwinCAT Router再重启上位机程序把缓存彻底清干净。5.5 或者可以这样扩展ADS这套协议学完之后你会发现它其实是一把钥匙能打开的不只是倍福控制器的大门。类似架构的工业通讯协议还有很多但ADS的文档完整度和工具链成熟度在工控圈里算是相当友好的。以后再做设备数据采集你可以用ADS把PLC里的工艺参数捞出来推给数据库或者MES做设备运维你可以用ADS读取轴状态、报警信息、任务状态甚至可以在PLC侧定义一个自定义AMS端口反向接收上位机推过来的配置数据。如果你已经把基础读写跑通了建议下一步去研究一下ADS的通知机制和批量读写。这两个能力是进阶成倍福通信高手的必经之路。等你能把一条ADS链路从头讲到尾从TCP握手讲到AMS路由再从AMS报文讲到ADS命令你基本就是团队里那个“ADS有问题找他就对了”的人了。
返回列表