目前框架已经完成了较多拆分,但距离稳定的工业级上位机,仍有以下优化点。重点不在继续增加类,而在于统一“状态、资源、异常和恢复”。
一、工站设备上下文还不够统一
当前业务中仍能看到:
DeviceManager.Instance.GetStationDevices(StationNo)- ViewModel 自己保存站号;
- 服务内部再次根据站号查设备;
- PLCController 内部又保存自己的站号。
这会造成“业务站号”和“设备实际站号”不一致。
建议后续每个工站只创建一个不可变的StationDeviceContext,统一包含:
- StationId;
- StationNo;
- PLC;
- 主控板;
- 负载板;
- 扫描器;
- RFID;
- 设备连接状态;
- 资源锁。
所有服务通过该上下文访问硬件,不再在业务过程中反复调用全局DeviceManager查找设备。
二、全局静态对象较多,工站边界仍不够清晰
目前仍存在全局状态或静态入口,例如:
DeviceManager.InstanceEnvironmentDataUniverseDataLogHelperWeakReferenceMessenger- 静态服务或静态等待器。
静态等待器如果没有可变状态,本身问题不大;但全局业务状态容易导致:
- A 站操作影响 B 站;
- 页面关闭后事件仍然存在;
- 旧批次状态被新批次读取;
- 单元测试困难;
- 多工站并行时难以定位状态来源。
建议:
- 设备对象按工站注册;
- Runtime 按工站创建;
- Pipeline 按工站创建;
- 协议请求必须带 StationId;
- 全局对象只保留真正的基础设施,例如日志、配置和设备注册表;
- 批次状态、占用状态、流程状态全部归属到工站实例。
三、工站状态和产品状态仍然是两套半独立状态
当前已有:
StationWorkflowStateMachineStationRuntimeAutomaticPipelineStage- PLC 光电实际状态;
- ViewModel 按钮状态。
这些状态之间还没有完全建立权威关系。
目前状态机虽然支持严格转移,但部分 ViewModel 状态更新仍然是直接执行,状态机调用还存在被注释的情况。因此可能出现:
实际正在测试 Runtime 显示停止 按钮显示可准备 PLC 仍然处于强制锁定三工位设备必须使用两层状态:
工站生命周期状态
负责初始化、开批、运行、停止、排料、复位和结批。产品物理位置状态
负责记录产品位于:- 上料/下料位;
- 测试位;
- 吹扫位;
- 搬运中;
- 位置未知;
- 等待人工处理。
不能用一个全局WorkflowRunning表示所有现场情况。
四、产品和物理位置还没有建立可靠关联
当前流水线能知道某个产品处于:
Testing Purging ManualUnloading但如果发生以下情况:
- 软件重启;
- PLC 断线;
- 网络异常;
- 程序崩溃;
- Worker 异常退出;
- 操作员人工移动器件;
上位机可能只知道“有光电”,但不知道是哪一颗产品,也不知道它处于哪个业务阶段。
建议增加现场恢复模型:
- PLC 光电作为物理事实;
- 本地操作记录作为业务事实;
- 两者不一致时标记为
Unknown; Unknown状态下禁止自动上料;- 必须经过复位、清料或人工确认;
- 恢复完成后重新建立批次和产品上下文。
这是工业设备必须具备的“断电恢复和异常恢复”能力。
五、搬运资源需要从“互斥”升级为“动作调度”
当前已经意识到搬运资源需要互斥,但简单使用一把SemaphoreSlim仍然不够。
设备真正需要管理的是不同动作:
- 上料位 -> 测试位;
- 测试位 -> 吹扫位;
- 吹扫位 -> 下料位;
- 下料完成确认;
- 上料位和下料位共用资源。
需要明确动作优先级:
安全停止和清料 当前产品排料 测试位释放 吹扫位下料 上料位到测试位 新产品上料如果把整个吹扫过程放在一把大锁中,会阻塞下一颗产品进入测试位;如果完全不加锁,又可能造成两个搬运动作同时写 PLC。
因此后续应采用“动作级资源调度”,而不是“阶段级大锁”。
六、PLC 信号还需要增加去抖、边沿和周期概念
当前 PLC 等待已经从独立轮询改成快照变化等待,这是正确方向。但工业现场还需要处理:
- 信号短暂抖动;
- 信号持续为真;
- 上一周期信号未清零;
- PLC 断线后保留旧缓存;
- 新动作直接读取到旧完成状态;
- 多个信号不同步刷新。
尤其是以下信号不能只判断当前值:
- 允许上料;
- 允许取料;
- 开始吹扫;
- 吹扫完成;
- 搬运完成;
- 初始化完成。
每次动作应关联:
- 动作编号;
- 开始时的快照序号;
- 开始前状态;
- 期望变化;
- 完成快照序号;
- 超时时最后一次快照。
否则可能出现“上一颗产品的完成信号被下一颗产品立即消费”。
七、取消语义还需要彻底分类
当前代码中已经区分了部分取消场景,但还需要形成统一规则:
停止当前测试
当前产品测试转 FAIL,继续吹扫和下料。停止接收新产品
禁止新上料和新 SOT,但已有产品继续排料。复位
取消新业务,等待旧业务退出,执行硬件复位和清料。结批
禁止新产品,等待所有在制品清空。页面关闭或软件退出
停止所有 Worker,等待硬件访问完成,再释放设备。
不能所有场景都调用同一个取消源,也不能只通过一个StopRequested布尔值表达所有含义。
八、阶段失败后的恢复仍需要闭环
当前已经定义了RecoveryAction,但后续还需要确认每种策略真正执行:
ContinueToPurgeAndUnloadUnloadFromLoadingPositionRequireResetStopProduct
尤其要防止:
业务方法返回失败 产品实际还在设备内 Pipeline 却把产品从活动表删除产品只有在以下条件成立后才能最终完成:
- 设备确认已取走;
- PLC 光电状态符合预期;
- 必要的 PLC 完成信号写入成功;
- 数据已经持久化;
- 协议回报已经发送或记录待发送。
否则应进入“故障待处理”,不能进入普通完成状态。
九、协议业务需要增加业务队列和响应超时
自动化和 EAP 已经进行了协议拆分,但还需要统一处理:
- 相同请求重复发送;
- 请求超时后对方重发;
- 响应发送失败;
- 业务已完成但回复丢失;
- 一个站点同时收到多个外部指令;
- 外部指令和人工按钮同时操作。
每个协议请求应具备:
- RequestId;
- StationId;
- 请求接收时间;
- 当前业务状态;
- 是否已经执行;
- 是否已经回复;
- 回复内容;
- 超时和重试次数。
协议线程不应直接操作 PLC 或等待长时间业务,应进入工站命令邮箱,由工站统一串行处理。
十、日志还可以增加统一关联信息
目前日志已经分为:
- 正常日志;
- 错误日志;
- 电学错误;
- 辅控错误;
- 自动化日志;
- EAP 日志。
后续还应统一增加:
- StationId;
- LotId;
- ProductId;
- RequestId;
- ActionId;
- 阶段;
- PLC 快照序号;
- 发送/接收方向;
- 开始时间和结束时间;
- 耗时;
- 返回码。
否则同一时间多颗产品、多种协议并发时,仍然只能依赖文本猜测上下文。
十一、数据文件、数据库和协议结果需要一致性策略
当前已经有 CSV、临时文件和TestData数据库,但仍需考虑:
- CSV 写成功、数据库写失败;
- 数据库写成功、CSV 写失败;
- 软件在写文件时崩溃;
- 结批过程中复制文件失败;
- 网络盘不可用;
- 同一器件复测;
- 数据写入后 EOT 发送失败。
建议把测试结果先保存为本地可靠记录,再分别执行:
本地缓存记录 本地正式数据 数据库记录 备份路径 EAP/EOT 报告每个步骤都记录完成状态,支持程序重启后补偿,而不是只依赖一次业务调用成功。
十二、配置和配方应使用运行时快照
开批后不能继续直接读取界面上的配方对象或配置文件。
开批成功时应固化:
- 配方内容;
- 配方名称;
- 封装类型;
- 光电位置;
- 测试参数;
- 数据路径;
- 设备参数;
- 配置校验码;
- CPU 绑定信息。
后续测试、数据保存、EAP 报文和复测判断全部使用这份批次快照。
这样可以避免操作员在测试过程中修改或重新打开配方,导致:
测试使用旧参数 数据使用新配方名 EAP 报文使用另一份配方十三、硬件访问需要明确读写优先级和统一超时
PLC、主控板、负载板、扫描器和 RFID 都有异步访问。还需要统一:
- 同一设备是否允许多个命令并发;
- 读操作是否可以抢占写操作;
- 测试期间实时读取是否允许;
- 设备断线后是否自动重连;
- 重连期间是否禁止工艺继续;
- 每类命令的超时;
- 超时后的重试次数;
- 重试后是否需要复位。
尤其不能只依赖底层 Socket 或 Modbus 的线程安全。通讯串行不等于业务命令串行。
十四、页面关闭和服务释放还应做完整验证
需要逐项验证:
- ViewModel 销毁是否调用
DisposeAsync; - PLC
SnapshotChanged是否解除订阅; - 自动化事件是否解除注册;
- EAP 事件是否解除注册;
- 定时器是否停止;
- PLC 轮询是否等待退出;
- TCP 接收线程是否退出;
- Channel Worker 是否全部完成;
- CancellationTokenSource 是否释放;
- 旧工站 Pipeline 是否不会被新页面继续使用。
这类问题在短时间测试时不明显,但长时间运行后会表现为:
- 重复执行一次业务;
- 同一日志写多次;
- 关闭软件卡住;
- 同一站出现多个 Worker;
- 内存持续增长。
十五、仿真模式可能掩盖真实问题
目前部分仿真逻辑会直接认为 PLC 条件满足。这样虽然方便开发,但可能掩盖:
- 信号没有正确加载;
- 快照无效;
- 等待顺序错误;
- 边沿没有处理;
- 资源锁没有生效;
- 超时和取消没有测试。
建议仿真模式也支持可控信号:
- 手动改变 U/V/W 光电;
- 模拟测试位占用;
- 模拟吹扫完成;
- 模拟取料;
- 模拟 PLC 断线;
- 模拟信号抖动;
- 模拟 PLC 写入失败。
否则仿真通过不代表现场流程可靠。
建议剩余优化顺序
第一优先级:现场安全和卡料问题
- 整站最大在制品数量限制;
- 上料/下料共用位置管理;
- 动作级搬运调度;
- 停止后的排料策略;
- 阶段失败后的物料恢复;
- 去除重复 PLC 握手;
- 所有关键 PLC 写入检查返回值。
第二优先级:状态和恢复
- 工站生命周期状态机作为唯一写入口;
- 建立产品物理位置状态;
- PLC 快照增加序号和有效期;
- 动作增加周期编号;
- 软件重启后进行现场状态恢复;
- 未知状态禁止自动生产。
第三优先级:协议和数据可靠性
- 自动化/EAP 请求统一幂等;
- 协议请求进入工站命令邮箱;
- 增加响应补偿机制;
- CSV、数据库、备份文件采用可恢复写入;
- EOT/EAP 报文与测试结果建立关联。
第四优先级:工程质量
- 完善设备上下文和依赖注入;
- 减少全局静态状态;
- 完善释放和关闭流程;
- 增强仿真测试;
- 增加三工位并行和异常恢复测试;
- 最后再继续清理 ViewModel 兼容代理。
结论
当前框架最大的问题已经不是“类拆得不够多”,而是:
物理位置约束还没有完全进入调度层; 停止和异常恢复还没有形成闭环; 状态机还没有成为唯一状态来源; PLC 信号还缺少动作周期关联; 数据和协议结果还缺少补偿机制。后续应优先围绕这五点优化。否则继续拆分服务,只会让代码结构更复杂,但现场卡料和异常恢复问题仍可能存在。