严格状态机已经能拒绝部分非法操作,但距离“工站唯一状态来源”和工业级架构还有以下问题。
高优先级问题
- 状态仍然不是唯一来源
当前同时存在多套状态:
StationWorkflowStateMachineStationRuntimeIsBatchOpen_isWorkflowRunning_isResetting_isBatchClosingRunModeEnvironmentData.StationStatusEnvironmentData.ElectricalTestStateAutomaticPipelineActivity
例如正式测试开始时:
StationControlViewModel.cs 设置_isWorkflowRunning = true,同时调用状态机进入WorkflowRunning;但测试结束时只设置_isWorkflowRunning = false,并没有同步更新状态机。
可能出现:
_isWorkflowRunning = false WorkflowState = WorkflowRunning因此状态机和实际业务状态可能不一致。
建议后续由状态机统一维护生命周期状态,StationRuntime只维护运行数据,不能再让业务直接修改多个布尔字段。
- 命令准入不是原子操作
当前命令大致是:
检查状态 进入业务方法 业务方法再次进入邮箱 再次检查状态 执行具体业务虽然已经增加了邮箱内二次校验,但“状态检查”和“业务占用”仍不是一个原子过程。
例如:
StartLot 检查通过 Stop 命令改变状态 StartLot 继续进入业务目前只能依赖后续业务再次判断,不能从架构上保证命令已经成功占用工站。
建议增加:
CommandContext ExpectedState StateVersion OperationId命令必须携带状态版本,执行时使用:
当前版本未变化 且状态符合预期 才允许占用并执行- 停止状态与实际流水线状态含义不完全一致
当前停止按钮只停止电学测试,之后仍然要继续:
吹扫 下料搬运 人工取料但是停止流程可能很快将状态设置为:
StoppedAwaitingReset此时实际上流水线可能仍有产品在吹扫或等待人工取料。
因此:
StoppedAwaitingReset不能简单表示“所有生产流程都已经停止”,它实际表示:
测试已停止,流程正在收尾,等待复位建议增加明确的状态或状态组合:
TestingStopped Draining WaitingForUnload StoppedAwaitingReset或者保留生命周期状态,同时通过PipelineActivity明确区分“测试停止”和“流水线收尾”。
- 状态机允许 Offline 从任意状态进入
当前迁移规则允许:
任意状态 -> Offline但软件状态进入Offline不代表硬件已经停止,也不代表器件已经离开设备。
如果将来某个异常分支直接调用:
ApplyButtonState(StationButtonState.Offline);可能出现:
界面显示离线 后台 Worker 仍在运行 PLC 仍有器件 主控板仍在测试建议Offline只能由生命周期服务在完成以下动作后进入:
- 停止测试;
- 停止自动上料;
- 等待流水线退出;
- 关闭主控输出;
- PLC 复位或安全释放;
- 确认硬件状态。
中优先级问题
- StationControlViewModel 仍然承担过多业务职责
虽然已经拆分为多个.partial.cs,但本质仍然是一个巨大类,仍包含:
- 测试流程;
- 自动上料;
- 自动化 SOT;
- EAP Host 放行;
- 复位;
- 停止;
- 结批;
- PLC 判断;
- 窗体弹窗;
- 按钮状态;
- 数据文件;
- 日志;
- 设备状态。
例如自动化方法仍在:
StationControlViewModel.Automation.cs
后续应将HandleEapStartLotAsync、HandleEapSOTAsync等方法迁移到:
StationAutomationService StationEapService StationLifecycleService StationMaterialService StationTestServiceViewModel 最终只保留命令转发和界面属性更新。
- Automation 和 EAP 仍共用过于靠近协议的服务
当前 EAP 网关和 Automation 服务最终都依赖:
StationAutomationService共享底层工站业务是合理的,但不应让 EAP 直接依赖名称带有 Automation 语义的服务。
当前容易造成误解:
EAP -> StationAutomationService Automation -> StationAutomationService建议调整为:
Automation -> StationWorkflowFacade EAP -> StationWorkflowFacade协议层分别独立:
AutomationTcpProtocolAdapter AutomationCommandRouter StationAutomationService SecsGemService EapCommandMapper StationEapService二者只在领域业务层汇合,不共用协议语义。
- 命令名称仍使用字符串映射
StationAutomationService.cs
当前通过字符串判断:
"StartLot" "SOTReady" "SOT" "EQPUnload"存在风险:
- 拼写错误时无法被状态机拦截;
- 新增命令容易忘记加入映射;
- 未识别命令可能绕过状态准入;
- 重命名命令时编译器不会提示。
建议将EnqueueCommandAsync改为接收强类型:
StationWorkflowCommand.StartLot StationWorkflowCommand.Sot StationWorkflowCommand.EndLot不要让核心业务依赖字符串命令名。
- 硬件资源锁还不是完整的工站级互斥
StationDeviceContext.cs
当前 PLC、主控板、负载板各自有独立锁。这可以防止同一设备同时访问,但不能保证一个完整工艺动作的原子性。
例如:
先写主控板 再写 PLC 再写负载板如果中途插入复位命令,可能出现:
主控板已配置 PLC 尚未配置 复位已经开始建议增加两种锁:
设备级锁:保护单个设备通讯 工站级事务锁:保护跨多个设备的完整动作涉及多个设备的初始化、复位、测试启动、结批动作,必须持有工站级锁。
- PLC 快照和实时读取仍未完全统一
当前项目中同时存在:
ReadSignal() ReadSignalFresh() AutomaticRealtimeSnapshot PLC 直接读取这会造成不同业务看到不同时间点的数据。
例如:
实时服务看到允许上料 = 1 SOTReady 直接读取到允许上料 = 0建议统一为:
PlcSnapshot SnapshotSequence CapturedAt IsCommunicationValid SignalValues业务只消费带时间戳的快照;只有必须确认最新硬件状态的安全动作,才使用明确标注的 Fresh Read。
- 状态变化事件缺少异常隔离
TryTransitionStrict更新状态后触发:
StateChanged?.Invoke(...)如果后续订阅者抛出异常,可能出现:
状态已经改变 调用方却收到异常 按钮未刷新 业务认为转移失败建议:
- 状态变更事件内部独立捕获异常;
- 状态提交和 UI 通知分离;
- UI 通知失败不能回滚状态;
- 日志记录订阅者异常。
- 运行态字段仍可能组成矛盾状态
StationRuntime.cs
当前多个字段分别使用Volatile读写,例如:
Resetting StopRequested WorkflowRunning BatchClosing AutomaticInitializationCompleted单字段读写是线程安全的,但多个字段组合不是原子的,可能读到:
Resetting = false StopRequested = true BatchClosing = false WorkflowRunning = true建议改成不可变快照整体替换:
StationRuntimeSnapshot所有运行态变化通过一个原子方法更新,避免读到半更新状态。
- 操作日志数据库写入方式会阻塞业务
StationOperationJournal.cs
当前每次记录都同步打开数据库、建表、插入,并且数据库异常被直接吞掉。
风险:
- PLC 等待线程可能被数据库阻塞;
- 高速阶段变化产生大量 SQLite 操作;
- 数据库异常时不容易被发现;
- 日志事实可能丢失。
建议:
业务线程 -> 内存日志队列 后台日志 Worker -> SQLite同时保留文本日志作为故障兜底,并记录数据库写入失败次数。
建议优化顺序
- 增加状态机转移矩阵单元测试;
- 将
StationWorkflowStateMachine移到Services.Workflow; - 用
StationRuntimeSnapshot替换多个独立布尔字段; - 所有控制命令改成强类型命令;
- 将状态检查和命令占用合并为原子操作;
- 增加工站级事务锁;
- 统一 PLC 快照和 Fresh Read 规则;
- 将停止、收尾、复位状态重新定义清楚;
- 继续迁移 ViewModel 中的测试、生命周期和协议业务;
- 最后完善操作日志队列、恢复机制和统一释放流程。
目前最需要优先处理的是前三项:状态唯一来源、命令原子占用、停止状态语义。这三项如果不先解决,继续拆分文件只能降低代码长度,不能彻底解决状态错乱和并发竞态。