小批量多品种的产线换型时最费时间的环节往往是参数下发。这篇记录我在工业一体机上用 Python 实现配方管理系统的过程包含配置结构设计、参数下发、设备通信和切换流程代码可直接改造使用。## 一、先明确要解决什么问题场景描述一台工业一体机装在产线工位上需要对接一台 PLC。产线生产 6 款产品每款产品的工艺参数不同参数数量在 40 到 120 项之间。旧做法是操作工在界面上人工输入参数问题有三个慢、容易错、没有追溯记录。目标是用配方管理替代人工输入。操作工在界面选择产品型号系统自动完成参数校验与下发并记录切换日志。## 二、配方数据的设计配方不适合存成程序里的常量也不适合硬编码。推荐的做法是用结构化文件管理方便版本控制和人工核对。用 JSON 描述配方结构如下json{product_code: A-1024,product_name: 外壳件-小号,version: 2026.10.1,params: [{addr: D100, name: 注射压力, value: 82.5, unit: MPa, min: 60, max: 95},{addr: D102, name: 保压时间, value: 3.2, unit: s, min: 2.0, max: 5.0},{addr: D104, name: 模温, value: 68, unit: ℃, min: 50, max: 80}],comm: {protocol: modbus_tcp,host: 192.168.10.21,port: 502,unit_id: 1}}几点设计考虑**参数带范围校验。** 每个参数记录 min 和 max下发前先校验避免误配超限值导致设备异常。**通信配置放进配方。** 不同产品可能对应不同设备或不同从站地址把通信参数纳入配方管理切换产品时通信配置一并切换不用另外改。**版本号必填。** 工艺参数会修订带版本号便于追溯哪一批产品用的是哪一版参数。**用产品编码做键。** 文件名直接用产品编码如 A-1024.json加载时按编码查找简单可靠。配方文件建议放在独立目录比如 /opt/recipes/和其他程序文件分开便于备份和权限管理。## 三、Modbus TCP 参数下发实现用 pymodbus 实现寄存器写入。下面的代码包含批量写入、分块处理和写入校验pythonimport jsonimport osimport loggingfrom datetime import datetimefrom pymodbus.client import ModbusTcpClientRECIPE_DIR /opt/recipesLOG_PATH /var/log/recipe_switch.loglogging.basicConfig(filenameLOG_PATH,levellogging.INFO,format%(asctime)s %(levelname)s %(message)s)def load_recipe(product_code):path os.path.join(RECIPE_DIR, f{product_code}.json)if not os.path.exists(path):raise FileNotFoundError(f配方不存在: {product_code})with open(path, r, encodingutf-8) as f:return json.load(f)def validate_params(recipe):下发前校验参数范围errors []for p in recipe[params]:if p[value] p[min] or p[value] p[max]:errors.append(f{p[name]} 值 {p[value]} 超出范围 [{p[min]}, {p[max]}])return errorsdef parse_addr(addr):把 D100 这类地址转成寄存器偏移return int(addr[1:])def write_params(recipe, chunk_size32):按块下发参数避免单次写入过长comm recipe[comm]client ModbusTcpClient(hostcomm[host], portcomm[port], timeout3)if not client.connect():raise ConnectionError(f无法连接 {comm[host]}:{comm[port]})try:params sorted(recipe[params], keylambda x: parse_addr(x[addr]))for i in range(0, len(params), chunk_size):chunk params[i:i chunk_size]# 按地址连续性组织这里假设地址有序start_addr parse_addr(chunk[0][addr])raw_values []for p in chunk:# 压力、时间等带小数的参数按 10 倍放大存整数raw_values.append(int(round(p[value] * 10)))unit_id comm[unit_id]resp client.write_registers(start_addr, raw_values, slaveunit_id)if resp.isError():raise IOError(f写入失败, 起始地址 {start_addr})return Truefinally:client.close()def switch_product(product_code):started datetime.now()try:recipe load_recipe(product_code)errors validate_params(recipe)if errors:msg ; .join(errors)logging.error(f配方校验失败 {product_code}: {msg})return False, msgwrite_params(recipe)elapsed (datetime.now() - started).total_seconds()logging.info(f切换成功 产品{product_code} 版本{recipe[version]} 耗时{elapsed:.2f}s)return True, f切换完成耗时 {elapsed:.1f} 秒except Exception as e:logging.exception(f切换异常 {product_code})return False, str(e)几个实操细节**地址解析要做防护。** 不同 PLC 的地址格式不一样三菱是 D 寄存器西门子是 DB 块。上面按 D 寄存器示例实际用的时候按现场的地址规则改 parse_addr。**小数参数的处理方式要和 PLC 侧约定。** 常见做法是放大 10 倍或 100 倍存成整数PLC 侧再除回来。这个约定必须写进文档否则两边不一致会出现参数差十倍的问题。**分块写入。** Modbus 单次写入寄存器数量有限制通常建议控制在 32 个以内。超长写入容易被拒绝或者超时。**超时设置不要省。** 工业现场网络抖动很常见设一个合理的超时3 秒左右比无限等待好。## 四、配方切换的服务化如果希望界面和逻辑解耦可以把切换逻辑做成一个本地 HTTP 服务界面通过请求调用。用 Flask 写一个极简版pythonfrom flask import Flask, request, jsonifyfrom recipe import switch_product, load_recipeapp Flask(__name__)app.route(/api/recipes, methods[GET])def list_recipes():import glob, osfiles glob.glob(/opt/recipes/*.json)codes [os.path.splitext(os.path.basename(f))[0] for f in files]return jsonify({recipes: codes})app.route(/api/switch, methods[POST])def switch():product_code request.json.get(product_code)if not product_code:return jsonify({ok: False, msg: 缺少产品编码}), 400ok, msg switch_product(product_code)return jsonify({ok: ok, msg: msg})app.route(/api/recipe/code, methods[GET])def detail(code):try:return jsonify(load_recipe(code))except FileNotFoundError:return jsonify({error: not found}), 404if __name__ __main__:app.run(host127.0.0.1, port8080)服务只监听本机地址不对外暴露避免被意外访问。界面侧用什么技术都行只要能发 HTTP 请求。这样做的好处是以后要改参数下发逻辑、换通信协议只改服务界面不用动。## 五、开机自启与容错工业设备要保证断电重启后系统自动恢复运行。用 systemd 管理服务ini[Unit]DescriptionRecipe Manager ServiceAfternetwork.target[Service]TypesimpleUseroperatorWorkingDirectory/opt/recipe_managerExecStart/usr/bin/python3 /opt/recipe_manager/app.pyRestartalwaysRestartSec3[Install]WantedBymulti-user.target关键配置是 Restartalways 和 RestartSec3程序异常退出后 3 秒自动拉起。在 7×24 运行的产线上这个配置能省下不少现场处理时间。部署命令bashsudo cp recipe-manager.service /etc/systemd/system/sudo systemctl daemon-reloadsudo systemctl enable recipe-managersudo systemctl start recipe-managersudo systemctl status recipe-manager## 六、日志与追溯上面代码把切换记录写进了日志文件。为了后续能按批次追溯建议至少记录这几项切换时间、产品编码、配方版本、执行结果、耗时。如果产线有 MES 或数据平台可以在切换成功后把这条记录通过 MQTT 上报出去和生产批次号关联。pythonimport paho.mqtt.client as mqttimport jsonfrom datetime import datetimedef report_switch(product_code, version, operator, result):payload {product_code: product_code,recipe_version: version,operator: operator,result: result,ts: datetime.now().isoformat()}client mqtt.Client()client.connect(192.168.20.5, 1883, 60)client.publish(line/station01/switch, json.dumps(payload), qos1)client.disconnect()用 qos1 至少保证消息送达一次避免切换记录丢失。## 七、经验总结这套东西实现下来换型时的参数下发时间从人工输入的十几分钟降到了几秒钟。真正花时间的部分在设计配方结构写代码本身不复杂。几点经验配方文件一定要带版本号工艺改过多少次要能查。参数范围校验不能省这是防止人为事故的最后一道防线。日志要写全出事的时候翻日志比问人快。服务化以后界面和逻辑解耦后面维护省心很多。工业一体机的选择上重点看系统是否开放。能装 Python 环境、能配 systemd、能接 GPIO 的设备这套方案就能跑。广州微嵌的工业一体机在系统层面支持 Linux、安卓、Windows、鸿蒙X86 和 ARM 平台都有对应型号做这类二次开发的适配空间比较大。他们每台设备出厂前经过十大类、100 多项测试宽温范围 -10℃ 到 60℃支持 7×24 小时不间断运行这类需要长期挂服务的场景比较适合。## FAQ**问配方参数里的浮点数一定要放大成整数吗**答取决于 PLC 侧的寄存器类型。如果 PLC 支持浮点寄存器可以直接写浮点。如果是整型寄存器通常放大 10 倍或 100 倍后取整PLC 侧再除回来。关键是两边约定一致并把约定写进接口文档。**问Modbus TCP 和 Modbus RTU 在实现上差别大吗**答协议语义一致差别在传输层。pymodbus 里用 ModbusTcpClient 还是 ModbusSerialClient其余调用方式基本相同需要注意 RTU 模式下有串口配置和帧间隔要求。**问参数写入到 PLC 以后怎么确认生效了**答推荐写入后回读比对。把下发的值和回读值逐项比较不一致就报警。这一步能挡住网络异常、从站地址错配等问题。回读会增加一点时间开销但比事后返工划算。**问这套方案能不能跑在 ARM 平台的工业一体机上**答可以。Python 和 pymodbus 在 ARM Linux 上运行没有问题。需要注意 ARM 平台的部分库需要重新编译建议在目标平台上直接安装依赖而不是从 X86 环境拷贝。另外低配 ARM 机型上运行 Flask 服务时建议关闭调试模式并限制并发。
阅读完成 · 觉得有帮助?