ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

目前框架已经完成了较多拆分,但距离稳定的工业级上位机,仍有以下优化点。重点不在继续增加类,而在于统一“状态、资源、异常和恢复”

目前框架已经完成了较多拆分,但距离稳定的工业级上位机,仍有以下优化点。重点不在继续增加类,而在于统一“状态、资源、异常和恢复”

目前框架已经完成了较多拆分,但距离稳定的工业级上位机,仍有以下优化点。重点不在继续增加类,而在于统一“状态、资源、异常和恢复”。

一、工站设备上下文还不够统一

当前业务中仍能看到:

  • DeviceManager.Instance.GetStationDevices(StationNo)
  • ViewModel 自己保存站号;
  • 服务内部再次根据站号查设备;
  • PLCController 内部又保存自己的站号。

这会造成“业务站号”和“设备实际站号”不一致。

建议后续每个工站只创建一个不可变的StationDeviceContext,统一包含:

  • StationId;
  • StationNo;
  • PLC;
  • 主控板;
  • 负载板;
  • 扫描器;
  • RFID;
  • 设备连接状态;
  • 资源锁。

所有服务通过该上下文访问硬件,不再在业务过程中反复调用全局DeviceManager查找设备。


二、全局静态对象较多,工站边界仍不够清晰

目前仍存在全局状态或静态入口,例如:

  • DeviceManager.Instance
  • EnvironmentData
  • UniverseData
  • LogHelper
  • WeakReferenceMessenger
  • 静态服务或静态等待器。

静态等待器如果没有可变状态,本身问题不大;但全局业务状态容易导致:

  • A 站操作影响 B 站;
  • 页面关闭后事件仍然存在;
  • 旧批次状态被新批次读取;
  • 单元测试困难;
  • 多工站并行时难以定位状态来源。

建议:

  • 设备对象按工站注册;
  • Runtime 按工站创建;
  • Pipeline 按工站创建;
  • 协议请求必须带 StationId;
  • 全局对象只保留真正的基础设施,例如日志、配置和设备注册表;
  • 批次状态、占用状态、流程状态全部归属到工站实例。

三、工站状态和产品状态仍然是两套半独立状态

当前已有:

  • StationWorkflowStateMachine
  • StationRuntime
  • AutomaticPipelineStage
  • PLC 光电实际状态;
  • ViewModel 按钮状态。

这些状态之间还没有完全建立权威关系。

目前状态机虽然支持严格转移,但部分 ViewModel 状态更新仍然是直接执行,状态机调用还存在被注释的情况。因此可能出现:

实际正在测试 Runtime 显示停止 按钮显示可准备 PLC 仍然处于强制锁定

三工位设备必须使用两层状态:

  1. 工站生命周期状态
    负责初始化、开批、运行、停止、排料、复位和结批。

  2. 产品物理位置状态
    负责记录产品位于:

    • 上料/下料位;
    • 测试位;
    • 吹扫位;
    • 搬运中;
    • 位置未知;
    • 等待人工处理。

不能用一个全局WorkflowRunning表示所有现场情况。


四、产品和物理位置还没有建立可靠关联

当前流水线能知道某个产品处于:

Testing Purging ManualUnloading

但如果发生以下情况:

  • 软件重启;
  • PLC 断线;
  • 网络异常;
  • 程序崩溃;
  • Worker 异常退出;
  • 操作员人工移动器件;

上位机可能只知道“有光电”,但不知道是哪一颗产品,也不知道它处于哪个业务阶段。

建议增加现场恢复模型:

  • PLC 光电作为物理事实;
  • 本地操作记录作为业务事实;
  • 两者不一致时标记为Unknown;
  • Unknown状态下禁止自动上料;
  • 必须经过复位、清料或人工确认;
  • 恢复完成后重新建立批次和产品上下文。

这是工业设备必须具备的“断电恢复和异常恢复”能力。


五、搬运资源需要从“互斥”升级为“动作调度”

当前已经意识到搬运资源需要互斥,但简单使用一把SemaphoreSlim仍然不够。

设备真正需要管理的是不同动作:

  • 上料位 -> 测试位;
  • 测试位 -> 吹扫位;
  • 吹扫位 -> 下料位;
  • 下料完成确认;
  • 上料位和下料位共用资源。

需要明确动作优先级:

安全停止和清料 当前产品排料 测试位释放 吹扫位下料 上料位到测试位 新产品上料

如果把整个吹扫过程放在一把大锁中,会阻塞下一颗产品进入测试位;如果完全不加锁,又可能造成两个搬运动作同时写 PLC。

因此后续应采用“动作级资源调度”,而不是“阶段级大锁”。


六、PLC 信号还需要增加去抖、边沿和周期概念

当前 PLC 等待已经从独立轮询改成快照变化等待,这是正确方向。但工业现场还需要处理:

  • 信号短暂抖动;
  • 信号持续为真;
  • 上一周期信号未清零;
  • PLC 断线后保留旧缓存;
  • 新动作直接读取到旧完成状态;
  • 多个信号不同步刷新。

尤其是以下信号不能只判断当前值:

  • 允许上料;
  • 允许取料;
  • 开始吹扫;
  • 吹扫完成;
  • 搬运完成;
  • 初始化完成。

每次动作应关联:

  • 动作编号;
  • 开始时的快照序号;
  • 开始前状态;
  • 期望变化;
  • 完成快照序号;
  • 超时时最后一次快照。

否则可能出现“上一颗产品的完成信号被下一颗产品立即消费”。


七、取消语义还需要彻底分类

当前代码中已经区分了部分取消场景,但还需要形成统一规则:

  1. 停止当前测试
    当前产品测试转 FAIL,继续吹扫和下料。

  2. 停止接收新产品
    禁止新上料和新 SOT,但已有产品继续排料。

  3. 复位
    取消新业务,等待旧业务退出,执行硬件复位和清料。

  4. 结批
    禁止新产品,等待所有在制品清空。

  5. 页面关闭或软件退出
    停止所有 Worker,等待硬件访问完成,再释放设备。

不能所有场景都调用同一个取消源,也不能只通过一个StopRequested布尔值表达所有含义。


八、阶段失败后的恢复仍需要闭环

当前已经定义了RecoveryAction,但后续还需要确认每种策略真正执行:

  • ContinueToPurgeAndUnload
  • UnloadFromLoadingPosition
  • RequireReset
  • StopProduct

尤其要防止:

业务方法返回失败 产品实际还在设备内 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;
  • PLCSnapshotChanged是否解除订阅;
  • 自动化事件是否解除注册;
  • EAP 事件是否解除注册;
  • 定时器是否停止;
  • PLC 轮询是否等待退出;
  • TCP 接收线程是否退出;
  • Channel Worker 是否全部完成;
  • CancellationTokenSource 是否释放;
  • 旧工站 Pipeline 是否不会被新页面继续使用。

这类问题在短时间测试时不明显,但长时间运行后会表现为:

  • 重复执行一次业务;
  • 同一日志写多次;
  • 关闭软件卡住;
  • 同一站出现多个 Worker;
  • 内存持续增长。

十五、仿真模式可能掩盖真实问题

目前部分仿真逻辑会直接认为 PLC 条件满足。这样虽然方便开发,但可能掩盖:

  • 信号没有正确加载;
  • 快照无效;
  • 等待顺序错误;
  • 边沿没有处理;
  • 资源锁没有生效;
  • 超时和取消没有测试。

建议仿真模式也支持可控信号:

  • 手动改变 U/V/W 光电;
  • 模拟测试位占用;
  • 模拟吹扫完成;
  • 模拟取料;
  • 模拟 PLC 断线;
  • 模拟信号抖动;
  • 模拟 PLC 写入失败。

否则仿真通过不代表现场流程可靠。


建议剩余优化顺序

第一优先级:现场安全和卡料问题

  1. 整站最大在制品数量限制;
  2. 上料/下料共用位置管理;
  3. 动作级搬运调度;
  4. 停止后的排料策略;
  5. 阶段失败后的物料恢复;
  6. 去除重复 PLC 握手;
  7. 所有关键 PLC 写入检查返回值。

第二优先级:状态和恢复

  1. 工站生命周期状态机作为唯一写入口;
  2. 建立产品物理位置状态;
  3. PLC 快照增加序号和有效期;
  4. 动作增加周期编号;
  5. 软件重启后进行现场状态恢复;
  6. 未知状态禁止自动生产。

第三优先级:协议和数据可靠性

  1. 自动化/EAP 请求统一幂等;
  2. 协议请求进入工站命令邮箱;
  3. 增加响应补偿机制;
  4. CSV、数据库、备份文件采用可恢复写入;
  5. EOT/EAP 报文与测试结果建立关联。

第四优先级:工程质量

  1. 完善设备上下文和依赖注入;
  2. 减少全局静态状态;
  3. 完善释放和关闭流程;
  4. 增强仿真测试;
  5. 增加三工位并行和异常恢复测试;
  6. 最后再继续清理 ViewModel 兼容代理。

结论

当前框架最大的问题已经不是“类拆得不够多”,而是:

物理位置约束还没有完全进入调度层; 停止和异常恢复还没有形成闭环; 状态机还没有成为唯一状态来源; PLC 信号还缺少动作周期关联; 数据和协议结果还缺少补偿机制。

后续应优先围绕这五点优化。否则继续拆分服务,只会让代码结构更复杂,但现场卡料和异常恢复问题仍可能存在。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进