配方数据结构设计:把工艺工程师的经验装进JSON
这是「从零搭建工业控制系统」系列第6篇。前面铺了通信和监控的底子,从这篇开始进入工艺执行——怎么让设备按配方自动跑起来。本篇只讲配方层:数据怎么组织,参数怎么映射,配置怎么管理。
从手动操作到配方驱动
项目早期,所有操作都是手动的。操作员在界面上点按钮:开阀门、设温度、启动加工……一个流程下来要点很多下。
手动操作的问题显而易见:
- 容易漏步骤,少开一个阀门可能导致工艺失败
- 顺序没法保证,先开流量计再开阀门和反过来效果完全不同
- 没法复现,这批和上批的操作可能不完全一样
后来我做了配方系统。核心思路很简单:把操作步骤写成配置文件,机器按配置一步步执行。
但这里有个前提问题——配置文件里写什么?怎么组织?工艺工程师看不懂代码,但他们懂工艺。所以配置必须是他们能看懂、能改的格式。
配方是什么:一个步骤序列
一个配方就是一组有序的步骤。每步设定工艺参数,设备按顺序执行。
先看一个最小化的配方配置长什么样:
{
"name": "示例配方A",
"description": "用于演示的简化配方",
"steps": [
{
"step": 1,
"name": "抽真空",
"pressure": 50,
"flow": { "通道A": 100, "通道B": 0 },
"duration": 30,
"delay": 5,
"next": true
},
{
"step": 2,
"name": "稳压加工",
"pressure": 30,
"flow": { "通道A": 50, "通道B": 50 },
"duration": 60,
"delay": 3,
"next": false
}
]
}
每个步骤的字段含义:
| 字段 | 类型 | 说明 |
|---|---|---|
step | int | 步骤编号,从1开始 |
name | string | 步骤名称,显示在UI上 |
pressure | double | 目标压力值 |
flow | object | 各通道流量设定,键是通道名,值是流量 |
duration | int | 加工持续时间(秒) |
delay | int | 步骤间延迟(秒),等参数稳定 |
next | bool | 是否自动进入下一步 |
next 字段值得多说一句。不是所有步骤都要自动往下跑。有些步骤完成后需要操作员确认——比如"检查物料到位了吗"。next: false 意味着这步完成后停下来等确认。
PPS参数:被引用的阈值
配方里的数字不全是写死的。有些值是工艺参数集(PPS,Process Parameter Set)的引用。
PPS是什么?简单说就是一组可运行时编辑的阈值。比如:
- PPS1 = 大气压判定阈值(压力低于这个值算真空)
- PPS2 = 真空判定阈值(压力高于这个值算大气)
- PPS3 = 安全压力上限
配方里可以引用这些PPS值,而不是硬编码数字。好处是:改阈值不用改配方,在PPS面板里调就行。
{
"step": 1,
"pressure": "PPS2",
"flow": { "通道A": 100 },
"duration": 30
}
解析的时候,"PPS2" 会被替换成当前PPS面板里的实际值。如果工艺工程师觉得真空度不够,调大PPS2,所有引用它的配方自动生效。
这个设计是踩过坑才加的。一开始所有值都写死在配方里。后来发现每次调阈值都要改一堆配方文件,改漏一个就出问题。引入PPS引用后,改一处全局生效。
配方注册表:统一管理
配方文件不止一个。一个设备可能有好几个配方,对应不同的工艺。需要统一加载、索引和管理。
做法是注册表模式——启动时扫描配置目录,加载所有配方JSON,按名称索引:
配方注册表初始化():
扫描 配方配置目录
遍历每个JSON文件:
解析JSON → 验证字段完整性 → 存入字典(键=配方名)
返回注册表
注册表暴露查找方法:
获取配方(名称):
如果注册表里有 → 返回配方配置
否则 → 报错"配方不存在"
UI上的配方下拉框就是从注册表里拿的列表。操作员选一个,点开始,执行器从注册表里拿到配置开始跑。
配方验证:加载时就发现问题
配置文件是工艺工程师写的,写错很正常。与其等执行时才报错,不如加载时就验证。
验证规则:
| 规则 | 说明 |
|---|---|
| 步骤编号连续 | 1、2、3…,不能跳号或重复 |
| 必填字段检查 | step、duration必须有 |
| 流量通道存在 | flow里的通道名必须在系统定义的通道列表中 |
| PPS引用有效 | "PPS2"必须是系统定义过的PPS名称 |
| 压力范围合理 | 不能是负数,不能超过安全上限 |
验证失败时,注册表拒绝加载这个配方,并在日志里记录原因。启动时就能看到错误,不用等运行时才发现。
我一开始没做验证,觉得"谁会写错呢"。结果第一次交付给工艺工程师,他们把通道名拼错了,配方跑起来直接报异常。加了验证后,启动时就能提示"配方X的通道C不存在",省了很多排查时间。
配方与命令的关系
配方定义"做什么",命令定义"怎么做"。
一个配方步骤说"设压力30",但具体怎么设——写哪个寄存器、写什么值、要不要等反馈——这是命令的事。
配方执行器拿到一个步骤后,会把它翻译成一组命令调用:
执行配方步骤(步骤):
如果有压力设定值 → 调用"设置压力"命令
如果有流量配置 → 逐通道调用"设置流量"命令
如果有加工时间 → 启动加工计时
如果需要监控 → 启动偏差检测
命令系统是JSON配置驱动的(下一篇会讲),配方也是JSON配置驱动的。两层配置独立维护,互不耦合。改命令实现不影响配方,改配方不影响命令。
配方文件的组织
实际项目里,配方文件按目录组织:
配置目录/
├── Recipes/
│ ├── 工艺A_标准配方.jsonc
│ ├── 工艺A_快速配方.jsonc
│ ├── 工艺B_标准配方.jsonc
│ └── 工艺B_清洗配方.jsonc
├── Sequences/
│ └── ... # 序列配置(下篇讲)
└── Flushes/
└── ... # 清理序列配置
用JSONC(带注释的JSON)而不是纯JSON。工艺工程师可以在文件里加注释说明为什么这个值是30而不是50:
{
"name": "示例配方A",
"steps": [
{
// 压力50是经过验证的稳压值,不要随便改
"step": 1,
"pressure": 50,
"duration": 30
}
]
}
注释在解析时会被去掉,不影响程序读取。但对人来说,有注释和没注释是两个东西。
配方变更:热加载还是重启?
工艺工程师改了配方文件,要不要重启软件才生效?
两种做法:
重启生效:简单可靠,但生产中重启意味着停机。
热加载生效:用文件监视器监听配方目录,文件变化时重新加载注册表。不用重启,但需要处理并发——如果执行器正在用某个配方,热加载会不会影响正在跑的工艺?
实际做法是折中:热加载注册表,但正在执行的配方用快照。
文件监视器检测到变化:
重新加载配方注册表(新字典)
正在执行的工艺继续用旧配方(快照)
下次执行时自动用新配方
这样既不用重启,又不会在执行中途换配方导致行为不确定。
踩坑记录
坑1:流量配置漏通道
一开始配方里的flow是数组,按位置对应通道。后来发现工艺工程师经常漏写某个通道,导致那个通道用默认值0——也就是关掉。改成键值对后,至少能从验证阶段就发现"通道C没配"。
坑2:PPS引用写错大小写
"PPS2"和"pps2"在JSON里是不同的字符串。工艺工程师有时候大小写写错,程序找不到对应的PPS值,用了默认值0,差点出事故。后来在验证阶段加了大小写不敏感的匹配。
坑3:配方文件编码问题
工艺工程师用记事本编辑配方,有时候保存成了GBK编码,程序按UTF-8读取中文乱码。后来统一要求用VS Code编辑,并在加载时检测编码。
本篇小结
| 概念 | 职责 |
|---|---|
| 配方(Recipe) | 一组有序步骤,每步设定工艺参数 |
| PPS参数 | 可运行时编辑的阈值,配方里引用而非硬编码 |
| 配方注册表 | 启动时加载所有配方,按名称索引 |
| 配方验证 | 加载时检查字段完整性和合理性 |
| 配方与命令 | 配方定义"做什么",命令定义"怎么做" |
| 热加载 | 文件变化时更新注册表,执行中用快照 |
配方系统的核心思想:把工艺知识从代码里抽出来,放进配置文件。工艺工程师改参数不用改代码,程序员改实现不影响配方。
下期预告
第7篇:序列执行引擎
配方只是最小单元。实际生产中,一个完整工艺需要跑多个配方,还要循环、跳转、状态管理。下一篇讲序列层怎么编排配方,怎么处理暂停取消,怎么管理执行状态。
:配方数据结构设计:把工艺工程师的经验装进JSON&spm=1001.2101.3001.5002&articleId=163832683&d=1&t=3&u=a84a89bbe8294a98863d8efa8f15b1f0)
397

被折叠的 条评论
为什么被折叠?



