项目背景
这是一份用于展示案例写法的匿名化示例。假设目标是一条由多个加工与检测工位组成的自动化产线:现场存在运动控制器、PLC、扫码器、视觉设备和多台 IPC,操作员通过统一 HMI 启停任务、处理报警并查看追踪记录。
系统真正困难的地方不是“让某个轴移动”,而是保证设备在不同节拍、网络抖动和人工干预下仍然保持一致。任何通信超时、急停或半成品滞留,都可能让各工位对当前批次产生不同理解。
系统规模
示例规模用于说明架构边界,不代表真实项目指标:
- 3 台设备主机,6 个逻辑工位;
- 多组运动轴和数字 IO;
- PLC、扫码器与上位机通过 TCP 或 Modbus TCP 通信;
- 本地保存配方、报警、生产事件与追踪索引;
- 要求断线重连、任务取消和人工恢复不会破坏设备状态。
我的职责
案例中的职责范围包括控制层架构、设备通信抽象、全局状态机、报警与恢复流程,以及 HMI 与底层设备之间的数据契约。业务界面不直接操作驱动,而是通过应用服务提交意图并观察状态。
技术架构
LineMaster
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
DeviceHost DeviceHost DeviceHost
│ │ │
┌────┴────┐ │ ┌───┴────┐
▼ ▼ ▼ ▼ ▼
Motion IO PLC Scanner Vision
│ │ │
└──────────┴──────────┘
▼
Event / Trace Store
LineMaster 只负责跨工位编排,不直接拼装协议报文。每个 DeviceHost 管理设备生命周期、连接状态和能力集合;Motion、IO、Scanner 等能力通过明确接口暴露。这样既避免一个超大控制类,也能在离线测试中替换为模拟实现。
核心模块
分层状态机
设备状态和产线状态分开维护。设备可以处于 Disconnected、Ready、Running、Faulted,产线则拥有 Idle、Preparing、Executing、Recovering 等状态。产线只消费设备发布的稳定状态,不依赖瞬时 UI 标志。
通信调度器
同一物理连接上的请求串行执行,每次请求携带关联 ID、超时和取消令牌。驱动层负责帧边界与重试策略,业务层决定一次失败是否允许重新执行,避免把非幂等命令盲目重发。
追踪与报警
状态转换、关键命令和恢复动作写入事件记录。报警对象包含来源、发生时间、复位条件和关联任务,而不只是显示给操作员的一段文本。
技术难点
问题一:局部成功造成全局状态分裂
现象
上游工位已经放行工件,下游工位却在启动阶段超时。简单地把全线状态改为 Faulted,无法回答“哪些动作已经发生”和“恢复时应从哪里继续”。
原因
把一系列跨设备动作当成了不可分割的调用,但现场动作无法像数据库事务一样回滚。超时也不代表设备一定没有执行,重复发送可能造成二次动作。
方案
将一次产线任务拆成带持久化检查点的步骤。每一步记录开始、确认和结束事件;恢复流程先重新读取设备真实状态,再依据检查点决定继续、补偿或请求人工确认。所有非幂等命令必须有业务序列号,设备端能够识别重复请求。
问题二:界面线程承担了设备生命周期
现象
页面关闭或视图切换会意外取消设备连接,后台异常通过 async void 丢失,最终表现为偶发卡死或无法复位。
原因
设备对象由 ViewModel 创建,生命周期与视图绑定;同时业务命令直接修改大量界面属性,缺少单一状态来源。
方案
把 DeviceHost 注册为应用级服务,启动和停止由宿主生命周期控制。ViewModel 只订阅不可变状态快照,并通过命令总线提交操作。所有长期任务接受统一的 CancellationToken,关闭应用时按“停止接单—取消任务—断开设备”的顺序退出。
最终架构
系统最终形成三条清晰链路:命令从 HMI 流向应用服务和设备能力;状态从驱动层汇总为快照再回到 UI;事件独立写入日志与追踪存储。三条链路互不借用临时字段传递隐含状态。
技术栈
| 层级 | 示例技术 | 职责 |
|---|---|---|
| UI | WPF、MVVM | 操作意图与状态展示 |
| 应用 | .NET、状态机 | 编排、取消、恢复 |
| 设备 | Motion、IO、Modbus TCP | 协议与设备能力 |
| 数据 | SQLite | 配方、事件、追踪索引 |
项目复盘
自动化系统的可靠性来自明确的失败语义,而不是更多 try/catch。如果重新连接、重复命令、取消和人工介入没有在架构阶段定义,现场最终会用难以复现的条件分支补齐这些缺口。
发布真实案例时,应把这里的示例规模、职责和结果替换为可验证事实,并删除客户名称、真实 IP、工艺参数与未公开图纸。