RealPLC Agent V1.0.0:连接真实PLC的轻量运行时代理与工业通信实践
做PLC项目的人应该都经历过这种场面:仿真环境里逻辑怎么跑都对,一接上真机就像换了一台设备——时序变了、字节序错了、偶尔还来个通信超时。以前我身边不少同事的解决办法是给每个项目写一套Python脚本,用pymodbus、python-snap7这类库直连PLC,项目一多脚本就成了无人敢碰的祖传代码。RealPLC Agent V1.0.0马上就要正式上线了,它要解决的就是这个最痛的问题:把"连接真实PLC、读写数据、编排任务、安全审计"这些活从业务代码里彻底抽出来,做成一个轻量稳定的运行时代理。
V1.0.0的定位很明确:它既不是又一个HMI,也不是重型SCADA,而是一条"设备能力管道"。上层的MES、测试自动化脚本、产线联动逻辑,乃至将来的AI分析系统,都可以通过同一套接口面对真实的PLC硬件。这篇文章我会把V1.0.0的核心能力、上手步骤、开发过程中踩过的坑,以及从Beta到Release的取舍思路完整聊一遍,给正准备在真机上做联调的朋友一个参考。
1. 为什么要有RealPLC Agent:仿真跑得通、真机就翻车的那点事
1.1 虚拟调试和真实硬件之间的差距到底在哪
很多团队做PLC程序前期验证都依赖仿真环境,比如西门子的PLCSIM,或者配合Factory I/O做虚拟联调。这套玩法确实能提前发现大部分逻辑错误,但程序下载到真机之后,问题就像雨后春笋一样冒出来,而且每个问题都带着现场特有的"不讲道理"。
首先是时序差异。仿真环境里扫描周期是理想化的,通信响应几乎零延迟。真实PLC的扫描周期再快也有几毫秒到几十毫秒,加上现场总线的抖动、远端I/O模块的响应时间,外部系统如果还按仿真的节奏去读数据,很容易出现"信号明明置位了,上位机却没读到"的尴尬。更麻烦的是真机的通信资源有限:S7协议下CPU能同时维持的连接数是有上限的,一台PLC同时接HMI、编程器、再挂你的测试脚本,连接资源一满,轻则超时重试,重则CPU直接报通信故障。
还有一个特别隐蔽的问题:数据一致性。仿真环境里读一个32位浮点就是一次内存访问,但真实PLC里这个值可能跨了两个寄存器,或者跨了DB块里不同偏移地址。外部程序如果不做对齐、不做整块读取,取回来的数据可能是撕裂的。这些东西在仿真里根本不会出现,只有真机联调才会暴露。
1.2 从"人肉来回搬运"到"Agent自动接管"
没有专用工具的时候,团队里最常见的做法是各写各的脚本。每换一个协议要改代码,换PLC型号要改地址映射,加一个新点位要动数据结构。更要命的是权限控制几乎为零——任何能跑脚本的人都可能对PLC做写操作,真出了事连日志都查不到。
RealPLC Agent的思路是把这些脏活累活从业务代码里抽出来,做成一个常驻服务。上层系统不再直接面对协议和地址,而是面对统一的数据点位,我们内部叫Tag。Agent负责三件事:管理到各PLC的连接会话、按配置执行周期轮询或事件触发、把读写动作暴露成统一接口并记录完整审计日志。
这也是V1.0.0的核心定位:它不是又一套组态软件,而是一个让设备"可编程、可编排"的底座。你可以在它上面接测试自动化、接MES采集、接产线联动,将来接AI分析也只是多一个接口的事。定位想清楚之后,版本该做什么、不做什么,就都有了依据。
2. V1.0.0能力清单:协议接入、任务编排与安全审计
2.1 协议适配层:把多品牌PLC的差异藏起来
V1.0.0首发支持S7comm和Modbus TCP,这两个协议基本覆盖了国内产线上最常见的一大批设备。OPC UA和EtherNet/IP都在规划里,但第一版我们刻意没做,原因后面会细说。
协议适配层最重要的设计是Tag归一化。不管底层是S7的DB1.DBD4,还是Modbus的40001寄存器,在Agent里都统一映射成语义化的点位名,比如line1.robot.speed。上层系统只认Tag名,不关心底层协议差异。这样一套服务同时管理三四十台不同品牌的设备,业务代码不需要关心哪台是西门子、哪台是国产Modbus设备。
连接管理上,Agent对每台PLC维护独立的长连接会话,做成连接池。每个连接对象内部处理协议握手、PDU协商、重连逻辑,上层拿到的是一个稳定的"可读可写"句柄。实测单台设备在100ms轮询周期下,CPU占用几乎可以忽略,一个普通工控机带几十台设备没有压力。
这里的经验是:协议适配的价值不在"能连上",而在"连上了之后的行为是否可预期"。同样一个读请求,S7和Modbus对超时、断线、数据长度的处理逻辑完全不同,这些差异如果暴露给上层,每个接入方都要踩一遍坑。Agent把这层差异消化掉,上层拿到的一律是统一的数据模型。
2.2 任务编排:读写之外还能做什么
如果只是封装两个协议库,市面上开源方案也能做到,RealPLC Agent真正的价值在任务层。
第一类是周期轮询。配置一组Tag和采样间隔,Agent按固定节奏把数据读上来,通过WebSocket推给订阅方,也可以落在本地缓冲里供后续拉取。默认轮询间隔100ms,可以根据设备CPU负载调成200ms甚至500ms。对电气工程师来说最直观的用法是:在WebSocket那边挂一个实时曲线页面,不用再写任何采集代码。
第二类是事件触发。某个Tag越过阈值才推送一次,避免高频采样把数据库写爆。比如轴承温度超过80度才上报一条,而不是每100ms丢一条"71.2度、71.3度"的流水。设备报警监控场景下这个功能相当省事。
第三类是批量事务写入。很多场景需要同时给多个Tag写关联数据,比如下发一组配方参数。Agent提供multi-write接口,在协议允许范围内把多个写入合并成尽量少的报文,并要求要么全部成功、要么明确报告失败明细,不允许出现"写了一半静默失败"的状态。
2.3 安全与审计:工业现场的写操作不是闹着玩的
把写操作的闸门管好,是我在V1.0.0上最坚持的一件事。PLC不像普通服务器,一个错误的写操作可能直接导致设备急停或者生产事故。这一版实现了三层防护。
第一层是写保护白名单。每个Tag可以标记readonly,只读点位从配置层就拒绝任何写请求,误写根本到不了PLC。第二层是操作认证与角色分级。调用写接口必须携带有效的操作Token,角色分为Observer(只读)、Operator(可读写)、Engineer(可改配置),从机制上避免"谁都能写"。第三层是全量审计日志。每次写操作记录操作者、时间、目标Tag、写入值、结果,同时记录Agent自身的启停和配置变更。出了事不看嘴争,直接查日志。
另外还有一个容易忽略的设计:看门狗。Agent和PLC之间的通信断开,或者Agent进程异常卡死,PLC侧的程序应该能感知到并进入安全状态,而不是傻等外部指令。V1.0.0里我们约定了一条心跳Tag:Agent周期性往PLC写递增计数,PLC程序里用超时判断做兜底。这个做法老生常谈,但真正做进工具里的不多,对产线安全性来说却是实打实的加分项。
3. 上手实测记录:从安装到接管一台真实PLC
3.1 环境准备与最容易忽略的细节
整个上手过程比预想的顺利,但有些坑属于"不看说明必踩"的类型。Agent本体是单二进制文件,支持Windows和Linux,也提供了Docker镜像。内存占用常规情况不到100MB,跑在树莓派级别的设备上都没问题。
部署时最关键的还是网络。Agent必须能直连PLC所在网段,建议用独立网卡或VLAN隔离,别让采集流量和办公网混在一起。用Docker部署时我一般直接走host网络模式,避免端口映射带来的额外麻烦:
docker run -d --name realplc-agent \ --net=host \ -v /etc/realplc/agent.yaml:/etc/realplc/agent.yaml \ realplc/agent:1.0.0--net=host的原因很实际:Agent既需要主动访问PLC的102/502端口,又需要对外提供REST和WebSocket服务,host模式不用绕NAT,少一层不确定性。
常见翻车点有三个:防火墙默认拦截了102端口或502端口;PLC侧连接资源没预留,接完HMI和编程器之后连接数满了;设备固件太老,部分老固件对S7通信支持有已知问题。建议上手前先把这三项排查完,能省大量排障时间。
3.2 配置文件长什么样:核心参数一次说清
V1.0.0把核心配置收敛到一个YAML文件,摘一个最小可用的例子:
agent: name: workshop-agent-01 listen_port: 8800 devices: - name: line1_robot protocol: s7 host: 192.168.10.20 rack: 0 slot: 1 connect_timeout_ms: 3000 poll_interval_ms: 100 tags: - name: line1.robot.speed device: line1_robot address: "DB1.DBD4" data_type: real readonly: false - name: line1.robot.running device: line1_robot address: "DB1.DBX0.0" data_type: bool readonly: true里面没有花哨的东西,但几个参数值得单独拎出来说:
| 参数 | 默认值 | 说明 |
|---|---|---|
| connect_timeout_ms | 3000 | 建连超时,现场网络波动大时建议调大到5000 |
| poll_interval_ms | 100 | 轮询间隔,老设备或同时跑运动控制的CPU建议200以上 |
| batch_read | true | 是否把连续地址合并成批量读,对轮询效率影响很大 |
| retry_max_interval | 30000 | 断线重连最大等待间隔,配合指数退避使用 |
poll_interval_ms是最值得花心思调的参数。100ms是多数场景下的通用值,但如果设备CPU偏老、或者同一个CPU里还跑着运动控制任务,宁可选200ms甚至500ms,也别为了"看起来实时"把设备拖垮。工控现场稳定压倒一切。
3.3 联调实测:只读先行,再开写入
我用一台S7-1500做了完整链路验证。步骤很简单:先把配置里所有Tag设成readonly: true,启动Agent,用REST接口读一遍:
curl http://localhost:8800/api/v1/tags/line1.robot.speed返回的是标准JSON,包含value、timestamp、quality三个字段。quality字段保留了类似OPC UA的数据质量概念,分Good、Uncertain、Bad三种,上层系统拿到Uncertain时就知道这数据不能轻易用于决策。
只读跑通之后,再把需要写的Tag改成可写,用POST接口写入:
curl -X POST http://localhost:8800/api/v1/tags/line1.robot.speed \ -H "X-Operator-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"value": 12.5}'写完立刻去PLC侧监控DB块确认真实值,这个习惯建议一直保持。第一版联调一定是只读跑通再开写入,顺序反过来,出了问题你连是数据错还是API错都分不清。
WebSocket订阅是我觉得最出彩的部分。订阅一组Tag后,Agent按轮询节奏推送数据流,前端直接画实时曲线,不需要自己维护连接和重连。订阅协议支持按Tag名过滤,也支持按质量等级过滤。实测100ms周期、200个Tag推送的场景,数据流稳定,没有明显抖动。
4. 开发V1.0.0踩过的坑:通信时序、断线重连与并发写
4.1 轮询节奏不是越短越好,数据一致性才是关键
半年真机测试下来,最深刻的教训是:通信节奏必须服务于数据一致性,而不是服务于"看起来很快"。
S7协议有PDU协商机制,通信双方会协商报文大小上限。很多库默认PDU只有240字节左右,一次读请求装不下太多数据。如果不懂这个限制,你以为连续地址的Tag一次就读完了,实际却被拆成好几个请求,响应时间成倍增加。我们在连接建立时主动把PDU协商到最大,并把同一设备连续地址块合并成批量读。这直接让200个Tag的单轮轮询时间从几百毫秒降到几十毫秒。
更隐蔽的问题出在32位数据上。假设要读一个Real类型的浮点数,它在DB块里跨了两个字边界。如果上层分别读两个16位区域再拼起来,中间PLC恰好执行了一次写入,拼出来的值就是撕裂的。仿真环境几乎不会出现这种情况,真机里概率不小。我们的解法是整块读取:把连续地址当成一个原子读单元,宁可多读两个用不到的字,也不要分两次拼。
4.2 断线重连:无脑重连会把PLC直接搞挂
这个坑必须单独拎出来讲,因为它真的能把设备搞挂。Beta测试阶段有个版本,检测到通信断开后立即重连,不做频率限制。结果某次现场网络抖动,Agent在几十秒内反复发起握手请求,直接把一台S7-1200的通信资源占满,设备端通信报警哗哗响。
根因很简单:PLC侧建立连接有资源开销,短时间内大量握手会让CPU忙于处理连接请求,正常HMI的通信反而被挤掉。后来重连策略改成指数退避加随机扰动:第一次等1秒,失败后翻倍到2秒、4秒、8秒,上限30秒,每次加0到1000毫秒随机值,避免多台Agent同时重连造成共振。
同时加了健康检查机制:连续三次探活失败才判定真正断线,网络偶尔丢一两个包不该触发重连。重连成功后也不是立刻恢复全部轮询,而是先拉一轮关键Tag确认设备状态,再逐步恢复完整轮询。这套逻辑写下来就几条,但为此填了不少测试用例才放心。
4.3 并发写与写操作的顺序保障
Beta阶段收到过用户反馈:两个上层系统同时往同一个Tag写值,互相打架,最后写入的值完全随机。更危险的场景是批量配方写入不是原子的,写到一半另一个请求插进来,配方数据被污染。
V1.0.0针对这两类问题做了两手处理。第一,对每个Tag的写操作加in-flight限制,同一个Tag同一时刻只允许一个写请求在途,后续请求要么排队要么返回冲突。第二,批量写引入事务语义:协议层支持类似MultiWrite就用原生能力;不支持的,Agent在内部对目标Tag组加互斥锁,确保批量操作不被穿插。
这里要坦诚讲:Agent能保证的是并发冲突可控,不能保证PLC程序内部的逻辑安全。写之前该做的设备状态确认,比如设备是否处于手动模式、是否允许远程写,还是要靠上位机逻辑去做。工具负责管好闸门,业务负责写对逻辑,这个边界我们不想越。
5. 从Beta到V1.0.0:迭代取舍与升级建议
5.1 这半年我们砍掉了什么
V1.0.0能按时上线,很大程度上因为学会了对需求说"不"。Beta期间收到过不少很有吸引力的建议:做图形化配置界面、加梯形图在线监控、塞一个边缘计算引擎进去。每一个单看都合理,但全塞进去的结果必然是一个又重又难用的怪物。
最终我们砍掉了全部图形界面,只保留CLI和YAML配置。原因很朴素:图形界面维护成本高、版本兼容负担重,而真正需要天天改配置的人,用文本配置反而更方便做版本管理和自动化部署。边缘计算也砍了,那根本不是Agent该干的活,数据分析应该有专门的管道。
留下来并做到极致的是三件事:稳定、可观测、API简洁。可观测方面,V1.0.0内置了/metrics端点,暴露连接状态、轮询耗时、错误计数,可以直接对接Prometheus。生产出问题,第一件事看指标,第二件事翻审计日志,不用瞎猜。
5.2 接入前的自查清单:别让工具背锅
根据Beta用户反馈和我们自己的排障经验,整理了一份接入前自查清单,照着过一遍能省大量时间:
| 检查项 | 说明 |
|---|---|
| 网络连通性 | Agent与PLC必须同网段可达,确认102/502端口未被防火墙拦截 |
| PLC连接资源 | 确认CPU剩余连接资源足够,预留Agent连接配额 |
| Tag命名规范 | 建议按"区域.设备.信号"三级命名,配置可维护性全靠它 |
| 只读先行 | 首版联调全部Tag设readonly,跑通只读链路再开写权限 |
| 心跳兜底 | 配置看门狗心跳Tag,PLC侧做好超时安全逻辑 |
| 审计日志留存 | 开启审计日志并设置转存,保证出错时可追溯 |
这套清单适用于绝大多数场景:MES数据采集、测试自动化、产线联动、设备状态监控。区别主要在轮询周期和写权限策略上:纯采集场景全部只读、500ms轮询就够;测试自动化读写都要开,但操作Token必须严格按人分配;产线联动建议把写操作收敛到少数几个经过评审的接口上。
5.3 V1.0.0之后的路线图:稳定永远是第一优先级
V1.0.0的配置schema从这一版开始冻结,后续所有版本保证向后兼容。这意味着你现在写的配置,两年后升级也能直接跑,这是做工业软件的基本信用。
后续规划里优先级最高的是OPC UA服务器模式,让Agent作为OPC UA Server把Tag暴露给标准SCADA客户端;然后是EtherNet/IP协议适配,覆盖罗克韦尔和部分国产设备;再往后是跟边缘AI系统的联动,把PLC数据流直接喂给推理服务。方向都清晰,但节奏上宁慢勿快,每个新协议都要在真机上跑够测试才会进Release。
最后说点个人体会。做这个项目最深的感觉是,工业软件最值钱的东西不是炫酷功能,而是"可预期"。RealPLC Agent V1.0.0不算功能大而全,但它在"连接PLC"这件事上的行为是可预期的:连接会重试但不会崩溃,写操作会被审计但不会被静默丢弃,数据质量如实上报而不是假装一切正常。如果准备在产线上试它,我的建议是先拿一台不影响生产的设备,用只读模式跑两三天,把轮询节奏、数据质量、日志都看顺眼,再考虑让Agent接管写操作。另外一个小技巧:每天固定时间把审计日志和指标拉一份存档,出事时查起来,你会感激自己这个习惯。