ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PLC-IoT结合AI Agent:工业设备智能交互落地实践

PLC-IoT结合AI Agent:工业设备智能交互落地实践 这标题念起来有点“混搭”“PLC-IOT 结合 AI Agent 编程”。工控圈待过几年的人再摸过一阵大模型看到这词的第一反应大概率是——PLC这种“万年稳定优先”的东西跟AI Agent这种“天生爱自由发挥”的玩意儿怎么往一块儿凑我最近正好完整跑通了一个落地方案把PLC-IoT的数据采集链路打通用MQTT把产线数据推送出来再在AI Agent里通过Function Calling函数调用把这些“读温度、写阀门开度”的能力封装成工具让大模型在对话中按需调用实现自然语言交互式的设备监控与操作辅助。整个过程踩了无数坑也验证了一个判断这思路不是玩具是真能落地的方向。这篇文章就把设计思路、技术选型、可复现代码和踩坑记录全部摊开讲。内容偏工程实践从零开始适合做工业数字化、IoT平台、或者想给传统工控设备加一层“智能交互大脑”的朋友参考。1. 先把概念对齐PLC-IoT到底在做什么AI Agent又能补什么1.1 PLC-IoT是工业数据流量的“最后一公里”PLC这玩意儿工控人都懂——梯形图、结构化文本、几百毫秒的扫描周期稳定得一批。但问题也恰恰出在这里PLC本身不是为“对外数据开放”设计的。很多老产线上的PLC数据只在本地HMI人机界面上显示顶多走走Modbus串口外人想拿数据比登天还难。PLC-IoT做的事情简单说就是把PLC这块“信息孤岛”通过网关、协议转换、边缘采集等方式接进物联网体系。典型链路长这样设备层PLC/传感器 → 协议转换层Modbus / OPC UA / S7协议转MQTT → 数据接入层MQTT Broker → 应用层监控大屏 / 数据平台 / 业务系统这一层打通之后PLC就不再是孤立的控制设备而是源源不断产生数据的数据源。温度、压力、转速、产量、报警信息全部可以实时流到平台侧。1.2 先把LLM、AI模型和AI Agent的区别说清楚最近很多人在问“DeepSeek到底算哪种”“Agent和模型是不是一回事”。我在这篇文章里也统一梳理一遍因为后面所有设计都基于这个概念区分。AI模型大语言模型/LLM就是那个“大脑”能做文字理解、推理、生成。DeepSeek、GPT、Qwen都属于这一类。只给你一个对话框它不会主动去查设备、不会自己操作PLC它只会“说”。Agent智能体是在大模型外面套了一圈“手脚”的系统。模型负责理解意图、做规划外面接上工具调用、记忆、执行反馈这些模块才能完成“感知-决策-执行-反馈”的闭环。可以理解为模型是员工的大脑Agent是完整雇佣的这个员工——大脑、手脚、工作台账都要配齐。LLM 工具调用这是目前Agent落地的核心机制英语叫Function Calling/Tool Calling。模型不是直接执行代码而是从你提供的工具清单里挑一个返回一个结构化的“我想调用XX函数参数是YY”然后由外部程序真正执行结果再喂回给模型让模型继续推理回答。搞清楚这个区别就知道为什么不能让大模型直接去“碰”PLC——它连PLC在哪都不知道上下文窗口也装不下工业协议。正确的姿势是LLM当大脑工具函数当手脚外部代码负责真正跟PLC-IoT链路打交道。1.3 为什么非要把这两样凑到一起传统PLC监控系统有两个痛点第一操作门槛高。老师傅看产线设备想查个参数得摸到HMI前一层层菜单翻想改个参数更得小心翼翼甚至要工程师拿电脑连上编程软件改。每次改完还得记录出了事要追溯。第二数据是“死”的。数据接上来了一般就是做个大屏做展示或者存进数据库供事后分析很少有系统能在“运行当下”跟你对话更别说听懂你一句“把三号线的冷却温度调到目标范围”。AI Agent的价值就在这里——它是现有系统之上的一层“自然语言交互和自主编排层”。用户不用懂Modbus寄存器地址不用记菜单路径直接用白话说需求“帮我看看2号反应釜现在的温度和压力”“冷却水进水阀开度低于40%时提醒我”“把设定温度改为62度注意不要超过报警值”Agent做的事是理解这句话 → 判断需要哪些工具 → 调工具读数据 → 汇总分析 → 用自然语言回答。甚至接到“把设定温度改为62度注意不要超过报警值”这种指令时它能自己先读当前值、读报警阈值、判断安全性再决定要不要执行写入操作。这就是“结合”的价值所在PLC-IoT解决数据通路AI Agent解决人机交互和意图判断各干各擅长的活。2. 总体方案设计我为什么选择“工具调用”而不是“直接改PLC逻辑”2.1 像做ISR一样拆功能需求PLC的系统讲求严谨。一个靠谱的工控方案设计流程其实和Isolation Region这个概念无关但思维方式类似先划边界再拆需求。整个方案我把它分成四层现场层PLC/传感器负责控制逻辑执行和采集这部分不动稳定压倒一切。边缘采集层PLC-IoT网关负责协议转换把Modbus/OPC UA等工业协议翻译成MQTT。平台服务层MQTT Broker 数据服务负责消息订阅、存储、对外API。Agent智能层LLM 工具函数 策略编排负责用户交互、意图理解、工具调用、安全校验。每层之间的接口用标准协议和明确的JSON结构来定义。核心原则是Agent永远不直连工控网络Agent永远不直接写寄存器Agent能做的是“调用工具”和“发起写指令”真正的写权限控制在“工具函数”内部实现并且在工具函数里做安全校验。2.2 面向开放架构选对交互方式刚开始做这个方案时我其实纠结过两条路线路线A让LLM直接生成梯形图或结构化文本代码帮工程师写PLC程序。这就是很多人说的“AI编程”方向让大模型辅助生成PLC代码。听起来很酷但实际落地产线场景非常难PLC的编程软件、固件版本、指令集各不相同生成的代码有没有编译错误逻辑能不能裸跑在产线上没有人敢直接拍板。而且这种“AI生成代码”做得再好也只是辅助工程师不是面向最终生产运营人员的交互提升。它更像IDE里的智能提示而不是完整的Agent产品。路线B把PLC的数据接入IoT再让Agent通过Function Calling调用读写工具。这条路线的好处第一是“不动现场”。PLC本体逻辑完全不碰所有智能层嵌套在数据链路里坏了大不了Agent挂掉PLC照样跑原来的逻辑产线不会因为Agent出Bug停摆。第二是见效快不需要几周的PLC编程基础只要能把数据搞出来用Python写函数封装当天就能看到第一个“对话式Demo”跑通。第三是可叠加以后想加新的控制指令写一个新的工具函数就够了完全不需要改PLC。我最终选了路线B。原因很朴素在工控这个行业“稳定性”是第一原则。不要试图让AI在产线上“自由发挥”先让它在数据通路上做一个聪明的“接口人”。提示如果你的项目核心目标是“用AI辅助工程师编写PLC代码”请走路线A的衍生分支——LLM代码生成工程师人工审查不要试图让AI直接改动正在运行的PLC逻辑。但在本篇文章讨论的PLC-IoT结合AI Agent场景中路线B是更普适的落地姿势。2.3 Agent的核心机制Function Calling不是“让模型执行代码”很多第一次做Agent的同学会有一个误区以为让Agent“控制设备”就是把Python代码作为函数传给大模型执行。其实不是。Function Calling的完整流程是这样的开发者预先写一堆“工具函数”比如read_temperature(device_id)、read_pressure(device_id)。把每个工具函数描述成一段JSON Schema函数名、参数列表、参数类型、功能描述注册到Agent配置里。用户提问时系统把对话历史工具描述一起发给LLM。LLM判断当前需要哪个工具返回一个特殊的JSON结构{“name”: “read_temperature”, “arguments”: {“device_id”: “reactor_2”}}。外部代码解析这个JSON真正调用Python函数拿到结果。结果作为“工具执行结果消息”回传给LLM。LLM结合结果生成最终自然语言回答。这里第二关键点是“大模型不直接输出SQL、不直接连数据库、不直接写寄存器”——它只负责“选工具”和“填参数”。执行靠外部代码。这层设计让安全边界清晰可控你可以在执行函数内部拦一道、加校验、加日志、加审批。3. 实操从零到一搭建最小可复现实验环境3.1 实验环境准备没有PLC也能跑通工控从业者上手成本的一大障碍是“没有设备”。好消息是后面这套链路里PLC-IoT采集层用两种方式都能模拟方案一利用Modbus模拟器。推荐用pymodbus库自带的Server模式或者开源的Modbus Slave模拟软件。写几个寄存器模拟一个“温度设备”。方案二直接用实物PLC。西门子S7-1200的PLC如果支持Modbus TCP可以直接用Python工具库去连三菱FX系列的走485串口链路的场景同样可以用串口服务器接入再用pymodbus通过TCP把串口转出来。实验环境清单组件推荐选型说明PLC/设备模拟pymodbus Server / Modbus Slave模拟器方便本地调试无需硬件PLC-IoT网关Python脚本Modbus TCP取数 - 转MQTT发布不依赖硬件网关适合验证MQTT BrokerEMQX或者Mosquitto本地起一个即可Agent框架手写轻量Function Calling循环 或 LangChain/Spring AI先用简单方案跑通再上框架LLMDeepSeek、Qwen、GPT等OpenAI兼容接口或本地Ollama部署Qwen有外网用API否则本地模型也可注意如果你用Modbus模拟器注意Modbus寄存器地址有“零基地址0xxxx”和“协议地址1-9999开头”的差异Simulator里显示的地址经常跟pymodbus里读取时用的寄存器偏移量对不上这是一个非常经典的坑后面详说。3.2 数据链路打通PLC数据从Modbus到MQTT为了不限于某一品牌硬件下面统一用Modbus TCP来演示S7-1200、多数支持Modbus的PLC都能配置成这个协议同时兼容模拟器。第一步写一个PLC-IoT采集网关脚本把Modbus上的数据转换成MQTT消息。# plc_gateway.py # 作用读取Modbus从设备寄存器发布到MQTT主题 from pymodbus.client import ModbusTcpClient import paho.mqtt.client as mqtt import json, time, configparser # 1. 配置区 PLC_HOST 127.0.0.1 PLC_PORT 5020 BROKER_HOST 127.0.0.1 BROKER_PORT 1883 TOPIC_PREFIX iot/plc/device_1 # 2. 连接PLCModbus plc ModbusTcpClient(PLC_HOST, portPLC_PORT) assert plc.connect(), PLC连接失败检查模拟器是否启动 # 3. 连接MQTT Broker twin mqtt.Client() twin.connect(BROKER_HOST, BROKER_PORT) twin.loop_start() def collect_and_publish(): # 读取保持寄存器地址0对应PLC侧地址40001 rr plc.read_holding_registers(0, 10, slave1) if rr.isError(): print(读寄存器失败:, rr) return data { timestamp: int(time.time()), device_id: device_1, temperature: rr.registers[0] / 10.0, pressure: rr.registers[1] / 100.0, valve_open: bool(plc.read_coils(0, 1, slave1).bits[0]), alarm_code: rr.registers[3] } # 发布JSON消息 twin.publish(f{TOPIC_PREFIX}/telemetry, json.dumps(data)) while True: collect_and_publish() time.sleep(5)这段代码做的事情每5秒钟从PLC读一次数据温度、压力、阀门开闭状态、报警码转成JSON消息发到MQTT Broker。这个“JSON报文”就是后面Agent能够理解数据的“接口约定”。我实际测试下来面对跑得快的现场系统发布频率不要太高5秒一轮足够实时监控使用。要是高频到毫秒级MQTT信道和存储都扛得住但Agent反而会因为“数据刷新太快”在对话上下文里失去焦点。说明为了适配不同PLC这里把Modbus寄存器地址映射成了“JSON语义字段”后面Agent只认识这个语义明确的JSON不关心Modbus位序问题。这就是“IoT化”的价值。3.3 Agent开发用Function Calling封装“设备工具”接下来是整个方案的重头戏——让Agent具备“懂设备、会操作”的能力。我这里先手写一个最简化的Function Calling主循环不依赖任何重型框架方便你理解原理。后面要上生产再换LangChain或者Spring AI做工程化封装。核心代码逻辑# agent_core.py # 手写Function Calling主循环OpenAI兼容接口 from openai import OpenAI import json client OpenAI(base_urlhttps://你的服务地址/v1, api_key你的key) # 1. 定义两个工具函数读温度、写设定温度 def get_current_temperature(device_id: str) - str: 模拟从MQTT/API取实时温度 # 实际工程里这里是查询IoT平台API或订阅缓存 return json.dumps({device_id: device_id, temperature: 61.5}) def set_temperature_setpoint(device_id: str, value: float) - str: 模拟下发设定温度带安全校验 if value 40 or value 90: return json.dumps({error: 设定值超出允许范围 40-90已拒绝}) # 实际工程里这里是经过权限校验后调用PLC写寄存器接口 return json.dumps({device_id: device_id, setpoint_set: value, status: ok}) # 2. 工具描述Schema——这是Agent理解“能干什么”的关键 tools [ { type: function, function: { name: get_current_temperature, description: 获取指定设备的当前实际温度值单位摄氏度。, parameters: { type: object, properties: { device_id: {type: string, description: 设备编号比如device_1} }, required: [device_id] } } }, { type: function, function: { name: set_temperature_setpoint, description: 设置指定设备的温度设定值写入前会校验安全范围。只能在得到用户明确确认后调用。, parameters: { type: object, properties: { device_id: {type: string, description: 设备编号}, value: {type: number, description: 设定温度范围40-90摄氏度} }, required: [device_id, value] } } } ] def run_agent(user_input: str): messages [{role: user, content: user_input}] while True: resp client.chat.completions.create( model你的模型名, messagesmessages, toolstools ) msg resp.choices[0].message messages.append(msg) if msg.tool_calls: for tc in msg.tool_calls: fn_name tc.function.name args json.loads(tc.function.arguments) # 真正执行外部函数这里演示的是本地模拟 if fn_name get_current_temperature: result get_current_temperature(**args) elif fn_name set_temperature_setpoint: result set_temperature_setpoint(**args) else: result json.dumps({error: 未知工具}) messages.append({ role: tool, tool_call_id: tc.id, content: result }) else: # 模型不再要求调用工具输出最终回答 return msg.content if __name__ __main__: print(run_agent(帮我看看device_1现在温度多少)) # 输出: 当前device_1的实测温度是61.5摄氏度这代码看着简单但它是整个方案的骨架。实际生产环境里get_current_temperature里会去查MQTT最新一帧遥测缓存set_temperature_setpoint里会先做权限校验、再通过Modbus写寄存器、再记录审计日志。这些动作都是外部代码控制的Agent本身只负责“要不要调用、传什么参数”。3.4 提示词与工具描述怎么写才能减少Agent“自作主张”这是踩了很多坑后总结的经验。同样一个Function Calling工具描述写得含糊Agent就会乱来。踩坑一工具描述写得太笼统。 错误写法description: “获取设备温度”。 问题如果场景里有“当前温度”“设定温度”“历史最高温度”多个概念模型可能拿错工具。 正确写法明确说明参数含义和单位。比如“获取指定设备的当前实际温度值单位摄氏度实时测量值”。踩坑二没在描述里写“需要用户明确确认才能调用写操作”。 不加这句话Agent很可能直接把写操作执行了——用户说“温度有点高”它自作主张把设定值改了。把“只能在得到用户明确确认后调用”写进工具描述后模型会更谨慎。这句话本质上是把“人的审批环节”嵌入了工具契约里。踩坑三没写安全边界。 再补一条更狠的在参数Schema里直接标注数值范围并同时在工具函数内部校验。比如设定值只有40-90参数里写“范围40-90摄氏度”函数里也写上if判断。双保险。注意大模型输出的工具调用参数并非100%符合要求所以安全校验绝不能只靠提示词工具函数的入参校验、权限系统和审计日志才是不出事的真正防线。3.5 完整运行效果演示一次自然语言指令怎么变成控制动作我把上面两部分连接起来跑一个实际对话流程用户输入“帮我查一下device_1现在的温度然后如果超过60度就把设定温度调回58度注意不要超过我司安全阈值90度”Agent逻辑判定需要用get_current_temperature。外部执行返回temperature: 61.5。Agent看到61.5大于60决定调用set_temperature_setpoint参数{device_id: “device_1”, value: 58}。工具执行前校验58在40-90内执行成功。Agent回答“当前温度61.5已按您要求将设定温度调整为58摄氏度未超过安全阈值。”这整个链路里Agent没有直接写任何一个寄存器。真正写动作发生在set_temperature_setpoint内部的Modbus写入代码里。这就是结合的最佳姿势PLC侧不动逻辑Agent侧管交互和编排。4. 工业协议选型与数据模型设计底子打牢才不会翻车4.1 Modbus TCP、OPC UA、MQTT怎么取舍这个方案里最少涉及三种协议很多人第一次做的时候容易混协议定位优点缺点适用场景Modbus TCP设备/PLC数据访问简单、老设备基本都支持、寄存器操作直接没有语义信息地址靠人工约定安全性弱中小PLC、传感器、仪表数据采集OPC UA工业自动化标准通信语义建模强、加密安全、跨平台实现复杂老工程师上手成本高大型工厂、多设备异构集成MQTTIoT平台与服务端通信轻量、发布订阅解耦、适合高并发不提供设备语义需要应用层定义云端/平台侧数据流转、边缘到中心我的建议是对PLC做数据采集用Modbus/OPC UA从边缘网关向平台上报用MQTTAgent侧通过MQTT或平台API获取数据。别反过来让Agent直接走Modbus——那会把Agent和具体设备协议强耦合换一台PLC就得改代码。这不是“智能体”这是把Agent变成了一个网络协议客户端。4.2 数据如何建模从Modbus寄存器到JSON语义字段Modbus侧数据就是一堆地址和值40001当前温度乘以10存储实际61.5度 - 存储61540002当前压力除以100实际0.85MPa - 存储8540003设定温度乘以1000001设备启停线圈IoT侧我们把它建模成语义JSON{ timestamp: 1735450000, device_id: device_1, current_temperature: 61.5, setpoint_temperature: 58.0, pressure: 0.85, run_state: true, alarm: false }Agent工具函数返回的结果也是类似结构只不过加一层“语义说明”。这里有一个细节数据单位直接交给Agent时一定在字段名里带单位比如用current_temperature_celsius而不是模糊的temp。模型对单位太敏感了少一个“celsius”就可能把华氏度混进来。4.3 时序数据与Agent上下文怎么防“信息过载”PLC-IoT产生的是高频时序数据。但Agent对话上下文是有限的。如果每次工具返回都把10秒内的全部历史数据堆给模型很快上下文就满了。我实践下来的经验工具函数返回“摘要最新值”模式。比如读温度返回“当前最新实测值61.5过去30分钟内平均温度59最大62最小57处于正常范围”这种语义摘要比几十个原始数据点更省上下文、也更有用。如果要看趋势再单独提供一个“查询过去N分钟趋势”工具返回简化的点序列。对话历史本身要控制轮数太长的历史可以截断或用摘要替代。提示在很多Agent框架里这一步对应“记忆管理”。但在工业场景下我的理解是做“数据摘要管理”让模型拿到的永远是“刚刚好”的信息量而不是把所有原始数据硬塞给它。5. 常见问题与排查技巧实录实操中踩过的坑5.1 Modbus寄存器地址对不上的问题这是第一个遇到的坑。Modbus模拟器界面显示“40001”但用pymodbus的read_holding_registers(0, 10)去读能读到值。很多朋友会直接填read_holding_registers(40001, 10)结果读出来一片乱码或异常。原因很简单Modbus协议层的数据地址和PLC组态时的逻辑地址有个转换规则。PLC显示地址40001对应协议地址040201对应200依此类推。西门子S7-1200走Modbus TCP时组态里40001对应寄存器地址0三菱走485的地址映射规则还有别的偏移。排查方法先用Modbus Poll这种调试工具对着读一遍确认地址映射关系然后在代码里写死偏移量并加注释说明这是“协议地址”还是“逻辑地址”。5.2 MQTT断线导致网关数据丢失网关脚本跑久了MQTT偶尔断开重连没做好的话数据就静默丢了。这问题潜伏期很久等发现时可能已经缺了几小时数据。解决方案twin mqtt.Client() twin.reconnect_delay_set(min_delay1, max_delay60) twin.on_connect lambda client, userdata, flags, rc: print(MQTT重新连接成功) # 同时在主循环里检测连接状态断开则重连 if not twin.is_connected(): twin.connect(BROKER_HOST, BROKER_PORT) twin.loop_start()另外把采集端做成“先落本地、再上报”的模式更稳。PLC数据先写本地SQLite或CSVMQTT恢复后再补发避免传输层抖动干扰产线数据完整性。5.3 Agent把参数传错单位、负数、越界大模型调函数时偶尔会把参数填错华氏度当摄氏度、负数填进非负字段、把“58”理解成“设定值降58度还是设为58度”傻傻分不清。解决思路在工具Schema的description里明确单位、范围、默认值工具函数内部做严格校验不合法就返回结构化错误信息error code错误描述不要直接执行对写操作加“二次确认”提示词工具描述里写明“需要用户明确确认才能执行”让人参与决策关键操作再加一层人工审批流埋点比如MQTT侧出一个“待审批指令”工程师在Web面板点击确认才下发。5.4 Agent在开放域对话里“乱跑”有时用户问“今天天气怎么样”Agent可能会用工具去读设备然后瞎扯一堆。这不算Bug而是Agent边界没有守好。对策在系统提示词里加上角色设定你是工业设备运维助理只负责设备数据查询与设置类任务与设备无关的话题请礼貌拒绝。在工具选择上只注册跟产线相关的工具不注册无关API。5.5 实时数据抖动导致Agent出“数字幻觉”IoT数据本身会有测量噪声温度在61.5和61.4之间跳Agent在解释时可能会把小数位说错或者是趋势判断不准。我的做法工具函数返回数据时先把小数位规整比如一位小数再附上“几秒内的稳定状态”标记。比如“数值稳定无明显波动”或“最近3个采样周期持续上升”。让Agent基于摘要做判断而不是裸数据。5.6 关于多Agent与企业级集成的扩展思路如果你在一个中大型企业PLC数量多、平台是Java技术栈Spring AI是一个值得看的Agent开发框架。它支持将LLM接入、工具调用、记忆管理整合到Spring Boot应用中可以跟你现有的设备管理服务、数据库、消息中间件无缝集成。企业级场景里每个工厂配一个“设备Agent”不同Agent又可以通过协调机制处理跨线联动这种多Agent编排的思路技术上完全可行但落地顺序建议是先单Agent跑通单个产线场景再考虑多Agent协作。6. 落到具体项目时我的执行顺序建议最后分享一些从零做这个项目时的执行顺序少走弯路先打通数据别急着训练Agent或研究提示词先把Modbus/MQTT链路搞定确保有持续稳定的数据流。做最简读操作先让Agent能回答“现在温度多少”。这一步跑通了说明Function Calling链路没问题。补安全校验再加“改设定值”这类写操作务必先把校验逻辑写进工具函数再开放给用户。完善可观测性给每次Agent工具调用加日志记录用户问题、模型选择哪个工具、参数是什么、执行结果是什么。出了问题能追溯。再上框架跑通了才能上LangChain、Spring AI之类的框架做工程化不然框架的复杂性会成为新的坑。我个人在实际操作中的体会是PLC-IOT结合AI Agent真正的难点不在“AI”那一侧而在“工业数据干净、连续、可解释”这一侧。数据干净了Agent的聪明才能真正呈现数据脏乱再强的大模型也只是个话痨。顺着这个思路你完全可以在自己的产线数据基础上搭出一个懂业务、能对话、敢操作的智能体出来。这个方向够新也够实用值得投入时间。
返回列表