☰
严格状态机已经能拒绝部分非法操作,但距离“工站唯一状态来源”和工业级架构还有以下问题
2026/11/5 14:03:15 网站建设 项目流程

严格状态机已经能拒绝部分非法操作,但距离“工站唯一状态来源”和工业级架构还有以下问题。

高优先级问题

  1. 状态仍然不是唯一来源

当前同时存在多套状态:

  • StationWorkflowStateMachine
  • StationRuntime
  • IsBatchOpen
  • _isWorkflowRunning
  • _isResetting
  • _isBatchClosing
  • RunMode
  • EnvironmentData.StationStatus
  • EnvironmentData.ElectricalTestState
  • AutomaticPipelineActivity

例如正式测试开始时:

StationControlViewModel.cs 设置_isWorkflowRunning = true,同时调用状态机进入WorkflowRunning;但测试结束时只设置_isWorkflowRunning = false,并没有同步更新状态机。

可能出现:

_isWorkflowRunning = false WorkflowState = WorkflowRunning

因此状态机和实际业务状态可能不一致。

建议后续由状态机统一维护生命周期状态,StationRuntime只维护运行数据,不能再让业务直接修改多个布尔字段。

  1. 命令准入不是原子操作

当前命令大致是:

检查状态 进入业务方法 业务方法再次进入邮箱 再次检查状态 执行具体业务

虽然已经增加了邮箱内二次校验,但“状态检查”和“业务占用”仍不是一个原子过程。

例如:

StartLot 检查通过 Stop 命令改变状态 StartLot 继续进入业务

目前只能依赖后续业务再次判断,不能从架构上保证命令已经成功占用工站。

建议增加:

CommandContext ExpectedState StateVersion OperationId

命令必须携带状态版本,执行时使用:

当前版本未变化 且状态符合预期 才允许占用并执行
  1. 停止状态与实际流水线状态含义不完全一致

当前停止按钮只停止电学测试,之后仍然要继续:

吹扫 下料搬运 人工取料

但是停止流程可能很快将状态设置为:

StoppedAwaitingReset

此时实际上流水线可能仍有产品在吹扫或等待人工取料。

因此:

StoppedAwaitingReset

不能简单表示“所有生产流程都已经停止”,它实际表示:

测试已停止,流程正在收尾,等待复位

建议增加明确的状态或状态组合:

TestingStopped Draining WaitingForUnload StoppedAwaitingReset

或者保留生命周期状态,同时通过PipelineActivity明确区分“测试停止”和“流水线收尾”。

  1. 状态机允许 Offline 从任意状态进入

当前迁移规则允许:

任意状态 -> Offline

但软件状态进入Offline不代表硬件已经停止,也不代表器件已经离开设备。

如果将来某个异常分支直接调用:

ApplyButtonState(StationButtonState.Offline);

可能出现:

界面显示离线 后台 Worker 仍在运行 PLC 仍有器件 主控板仍在测试

建议Offline只能由生命周期服务在完成以下动作后进入:

  1. 停止测试;
  2. 停止自动上料;
  3. 等待流水线退出;
  4. 关闭主控输出;
  5. PLC 复位或安全释放;
  6. 确认硬件状态。

中优先级问题

  1. StationControlViewModel 仍然承担过多业务职责

虽然已经拆分为多个.partial.cs,但本质仍然是一个巨大类,仍包含:

  • 测试流程;
  • 自动上料;
  • 自动化 SOT;
  • EAP Host 放行;
  • 复位;
  • 停止;
  • 结批;
  • PLC 判断;
  • 窗体弹窗;
  • 按钮状态;
  • 数据文件;
  • 日志;
  • 设备状态。

例如自动化方法仍在:

StationControlViewModel.Automation.cs

后续应将HandleEapStartLotAsync、HandleEapSOTAsync等方法迁移到:

StationAutomationService StationEapService StationLifecycleService StationMaterialService StationTestService

ViewModel 最终只保留命令转发和界面属性更新。

  1. Automation 和 EAP 仍共用过于靠近协议的服务

当前 EAP 网关和 Automation 服务最终都依赖:

StationAutomationService

共享底层工站业务是合理的,但不应让 EAP 直接依赖名称带有 Automation 语义的服务。

当前容易造成误解:

EAP -> StationAutomationService Automation -> StationAutomationService

建议调整为:

Automation -> StationWorkflowFacade EAP -> StationWorkflowFacade

协议层分别独立:

AutomationTcpProtocolAdapter AutomationCommandRouter StationAutomationService SecsGemService EapCommandMapper StationEapService

二者只在领域业务层汇合,不共用协议语义。

  1. 命令名称仍使用字符串映射

StationAutomationService.cs

当前通过字符串判断:

"StartLot" "SOTReady" "SOT" "EQPUnload"

存在风险:

  • 拼写错误时无法被状态机拦截;
  • 新增命令容易忘记加入映射;
  • 未识别命令可能绕过状态准入;
  • 重命名命令时编译器不会提示。

建议将EnqueueCommandAsync改为接收强类型:

StationWorkflowCommand.StartLot StationWorkflowCommand.Sot StationWorkflowCommand.EndLot

不要让核心业务依赖字符串命令名。

  1. 硬件资源锁还不是完整的工站级互斥

StationDeviceContext.cs

当前 PLC、主控板、负载板各自有独立锁。这可以防止同一设备同时访问,但不能保证一个完整工艺动作的原子性。

例如:

先写主控板 再写 PLC 再写负载板

如果中途插入复位命令,可能出现:

主控板已配置 PLC 尚未配置 复位已经开始

建议增加两种锁:

设备级锁:保护单个设备通讯 工站级事务锁:保护跨多个设备的完整动作

涉及多个设备的初始化、复位、测试启动、结批动作,必须持有工站级锁。

  1. PLC 快照和实时读取仍未完全统一

当前项目中同时存在:

ReadSignal() ReadSignalFresh() AutomaticRealtimeSnapshot PLC 直接读取

这会造成不同业务看到不同时间点的数据。

例如:

实时服务看到允许上料 = 1 SOTReady 直接读取到允许上料 = 0

建议统一为:

PlcSnapshot SnapshotSequence CapturedAt IsCommunicationValid SignalValues

业务只消费带时间戳的快照;只有必须确认最新硬件状态的安全动作,才使用明确标注的 Fresh Read。

  1. 状态变化事件缺少异常隔离

TryTransitionStrict更新状态后触发:

StateChanged?.Invoke(...)

如果后续订阅者抛出异常,可能出现:

状态已经改变 调用方却收到异常 按钮未刷新 业务认为转移失败

建议:

  • 状态变更事件内部独立捕获异常;
  • 状态提交和 UI 通知分离;
  • UI 通知失败不能回滚状态;
  • 日志记录订阅者异常。
  1. 运行态字段仍可能组成矛盾状态

StationRuntime.cs

当前多个字段分别使用Volatile读写,例如:

Resetting StopRequested WorkflowRunning BatchClosing AutomaticInitializationCompleted

单字段读写是线程安全的,但多个字段组合不是原子的,可能读到:

Resetting = false StopRequested = true BatchClosing = false WorkflowRunning = true

建议改成不可变快照整体替换:

StationRuntimeSnapshot

所有运行态变化通过一个原子方法更新,避免读到半更新状态。

  1. 操作日志数据库写入方式会阻塞业务

StationOperationJournal.cs

当前每次记录都同步打开数据库、建表、插入,并且数据库异常被直接吞掉。

风险:

  • PLC 等待线程可能被数据库阻塞;
  • 高速阶段变化产生大量 SQLite 操作;
  • 数据库异常时不容易被发现;
  • 日志事实可能丢失。

建议:

业务线程 -> 内存日志队列 后台日志 Worker -> SQLite

同时保留文本日志作为故障兜底,并记录数据库写入失败次数。

建议优化顺序

  1. 增加状态机转移矩阵单元测试;
  2. 将StationWorkflowStateMachine移到Services.Workflow;
  3. 用StationRuntimeSnapshot替换多个独立布尔字段;
  4. 所有控制命令改成强类型命令;
  5. 将状态检查和命令占用合并为原子操作;
  6. 增加工站级事务锁;
  7. 统一 PLC 快照和 Fresh Read 规则;
  8. 将停止、收尾、复位状态重新定义清楚;
  9. 继续迁移 ViewModel 中的测试、生命周期和协议业务;
  10. 最后完善操作日志队列、恢复机制和统一释放流程。

目前最需要优先处理的是前三项:状态唯一来源、命令原子占用、停止状态语义。这三项如果不先解决,继续拆分文件只能降低代码长度,不能彻底解决状态错乱和并发竞态。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询