设备软件架构与控制模型(五):Recipe 与配置版本管理

设备软件中的“参数”看起来都像键值对,实际上承担着完全不同的责任。产品 Recipe、设备常数、校准结果、通信地址和用户偏好如果共用一份可随时编辑的 JSON,不仅难以追溯,还可能让一次运行前后使用了两套参数。

本文建立一套通用配置模型:先区分参数归属,再为 Recipe 设计验证、审批、激活和执行快照。重点不是选择 JSON、数据库还是某个配置中心,而是保证系统能够回答“当时究竟使用了什么”。半导体设备 Recipe 管理的领域实践见《半导体设备软件(三):Recipe 配方管理与版本控制》。

1. 先按责任区分参数

常见参数至少分为以下几类:

类型示例谁负责变更典型生命周期
Recipe曝光时间、扫描速度、检测阈值工艺或授权用户随产品/工艺版本发布
设备配置轴数量、相机型号、通信端点设备工程师安装或硬件变更时修改
设备常数软限位、脉冲当量、机构偏置调试与维护人员受控维护后更新
校准结果标定矩阵、零点、温度补偿系数校准流程与设备、时间和方法绑定
运行策略超时、重试上限、数据保留策略软件/运维人员随软件或现场策略演进
用户偏好窗口布局、曲线颜色当前用户可自由修改,不影响工艺事实
凭据与密钥令牌、证书私钥安全设施不应作为普通配置明文保存

这种分类决定权限、验证和审计强度。校准数据不是普通 Recipe,操作员也不应通过“导入配方”覆盖运动软限位。凭据应进入操作系统或专用密钥存储,而不是为了方便一起导出。

2. Recipe 是受控版本,不是可变表单

编辑界面中的对象只是草稿。发布后的 Recipe 应成为不可变版本,至少包含:

  • 稳定的 Recipe 标识;
  • 业务版本和数据结构版本;
  • 创建者、创建时间和变更说明;
  • 适用的设备能力或软件版本范围;
  • 完整参数内容;
  • 对保存字节或规范化内容计算的摘要;
  • 审核、发布和停用状态。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public sealed record CaptureRecipe(
    string RecipeId,
    int Revision,
    int SchemaVersion,
    TimeSpan Exposure,
    double ScanSpeedMillimetersPerSecond,
    int FrameCount,
    string CreatedBy,
    DateTimeOffset CreatedAt);

public sealed record RecipeSnapshot(
    CaptureRecipe Recipe,
    string Sha256,
    DateTimeOffset ActivatedAt);

C# recordinit 属性有助于减少意外修改,但它们不会自动让内部集合深度不可变。若 Recipe 含列表或字典,应复制为只读/不可变结构,避免外部仍持有可变引用。

摘要也不能直接理解为数字签名。SHA-256 可以发现内容是否变化,却不能证明修改者身份;需要防篡改或跨组织验真时,还要使用带受控密钥的签名机制。若对 JSON 计算摘要,应对实际保存的字节求值,或先定义稳定的规范化格式,不能假设任意序列化结果的属性顺序和空白永远一致。

3. 验证分为四层

只检查数据类型远远不够。Recipe 验证可以分层执行:

  1. 结构验证:必填字段、类型、数据结构版本;
  2. 范围验证:曝光时间、速度和数量是否在单参数范围内;
  3. 关联验证:速度、采样率和缓存容量组合后是否可行;
  4. 设备验证:目标设备是否具备所需能力,当前校准是否有效。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class CaptureRecipeValidator
{
    public static IReadOnlyList<string> Validate(CaptureRecipe recipe)
    {
        List<string> errors = [];

        if (recipe.Exposure <= TimeSpan.Zero ||
            recipe.Exposure > TimeSpan.FromSeconds(10))
        {
            errors.Add("Exposure must be within (0, 10s].");
        }

        if (recipe.ScanSpeedMillimetersPerSecond is <= 0 or > 500)
        {
            errors.Add("Scan speed must be within (0, 500] mm/s.");
        }

        if (recipe.FrameCount is < 1 or > 100_000)
        {
            errors.Add("Frame count must be within [1, 100000].");
        }

        return errors;
    }
}

示例中的上限只是演示,不能复制成真实设备指标。实际范围应来自机械、电气、控制器和工艺共同确认的能力模型,并与单位一起保存。

对于进程启动配置,.NET Options 模式支持强类型绑定和 ValidateOnStart;但运行中导入 Recipe 时仍应经过领域验证服务。配置框架验证通过,只说明对象满足应用配置规则,不代表它适用于当前硬件和工艺。

4. 激活采用“准备—切换”而不是边跑边改

Recipe 生效可以设计为两个阶段:

4.1 准备阶段

  1. 读取指定版本,并验证摘要;
  2. 执行结构、范围、关联和设备能力校验;
  3. 检查当前整机状态是否允许切换;
  4. 将参数转换为各设备可应用的设置;
  5. 生成完整、不可变的执行快照。

4.2 切换阶段

  1. 阻止新任务进入;
  2. 在安全状态下应用参数;
  3. 读取设备回读值或确认结果;
  4. 原子地替换“当前 Recipe 快照”的引用;
  5. 记录激活者、版本、摘要和结果。

这里的“原子替换”只针对上位机内存中的当前快照,不代表多个物理设备能像数据库事务一样同时提交。若相机参数应用成功而运动控制器失败,应进入受控故障状态,记录每个设备的实际结果,再由恢复策略决定重新应用、回到已知配置或要求人工处置。

5. 每次运行固定一份快照

流程启动时读取一次当前 Recipe,并把其版本和摘要写入任务上下文。后续每个步骤都使用同一份快照,即使管理员在运行期间发布了新版本,正在执行的任务也不应悄悄切换。

1
2
3
4
5
6
7
8
RunId: RUN-20260811-0042
RecipeId: Product-A-Scan
Revision: 17
SchemaVersion: 3
Sha256: 8F...C2
EquipmentConfigVersion: EQ-2026.08.3
CalibrationSet: CAL-CAMERA-20260801
SoftwareVersion: 2.6.0

这组信息把结果数据与其产生条件绑定起来。仅记录文件名不够,因为同名文件可能已经被覆盖;只保存“当前版本”也无法解释历史任务。

6. 迁移、兼容与回滚要分开

数据结构升级时,不要在反序列化失败后默默填默认值。应根据 SchemaVersion 选择显式迁移器,保留原始版本,并验证迁移结果。迁移后的 Recipe 是新版本还是运行时视图,要在审计规则中说清楚。

软件兼容性和工艺版本回滚也是两个问题:

  • 新软件能否读取旧 Recipe,由兼容策略和迁移器决定;
  • 旧软件能否读取新 Recipe,不能默认成立;
  • 回滚 Recipe 只能恢复参数版本,不能撤销已经加工的工件或已经改变的设备状态;
  • 校准或硬件变化后,旧 Recipe 即使格式兼容,也可能不再适用。

生产系统应保留最后一个经过验证的版本,但切换失败时不要盲目“自动回滚并继续生产”。先确认设备回读和物理状态,再决定能否恢复。

7. 权限与审计围绕动作设计

权限不应只有“能否打开配置页面”。至少区分查看、编辑草稿、验证、审核、发布、激活和停用。高风险参数可以要求双人复核或维护模式,但具体控制强度应来自风险评估。

审计记录需要回答:谁在何时基于哪个旧版本做了什么变更,验证结果如何,哪个设备在何时激活,以及激活是否成功。审计日志本身也要限制修改和删除权限,且不能把密钥或完整敏感配置写进去。

总结

Recipe、设备配置、校准和用户偏好虽然都表现为参数,却不能共享同一套生命周期。发布后的 Recipe 应不可变、可验证、可追溯;激活前完成准备,运行时固定快照;多设备应用失败时承认物理世界不具备通用事务回滚。

下一篇将把视角转向失败:如何区分事件、报警和故障,如何设计不会无限重试的恢复流程,以及为什么“清除报警”不等于问题已经消失。

参考资料

Licensed under CC BY-NC-SA 4.0