# 运转系统顺控引擎设计 日期:2026-05-18 参考来源: - `运转系统逻辑说明.doc`(说明书 14 章) - `docs/运转系统实现方案.md`(高层方案) - `docs/superpowers/specs/2026-04-14-dual-app-shared-core-design.md`(双应用共享核心架构) - 现有 `crates/app_feeder_distributor` 实现作为工程参考 ## 1. 目标 在已经搭好的 `crates/app_operation_system` 骨架内,落地说明书中规定的整线自动控制能力: - 覆盖 8 个业务子系统:回车线、前端码车道、机械臂、摆渡车、1 号干燥/焙烧窑、2 号干燥/焙烧窑、窑尾下摆渡车、卸砖机位。 - 引擎语义遵循说明书第 1.4 与 13 章:"顺序控制 + 联锁保护 + 检测信号闭环确认 + 异常停留人工恢复"。 - 双窑线(1 号 / 2 号)采用同一套段模板,仅通过参数差异化,不写两套代码。 - 复用 `plc_platform_core` 的接入层(OPC UA / 点位 / 设备 / 事件 / WebSocket / 日志)。 - 不引入 `app_feeder_distributor` 的 `unit + run_time/stop_time/acc_time/bl_time` 业务模型。 非目标(首期): - 不做规则引擎或 DSL,只支持固定 `rule_kind` 联锁判定。 - 不做高级排程(最大化吞吐、动态优化),只做基于空位/资源占用的放行决策。 - 不做权限/审计/历史回放。 ## 2. 设计结论 | 决策 | 选择 | 原因 | | --- | --- | --- | | 业务模型 | **station + segment + step + interlock** | 说明书是工位驱动的整线顺控,不是节拍式设备启停 | | `unit` 表 | **不复用** | 语义不匹配;ops 自己建 `process_segment` | | 引擎调度单位 | **段(segment)** | 每个 enabled segment 一个 tokio task,对齐 feeder 引擎结构 | | 双窑线参数化 | **同一段模板 + line_code 区分实例** | 对齐说明书第 11 章 | | 联锁配置 | **数据库表 + 固定 rule_kind 枚举** | 首期不引入表达式语言 | | WebSocket 消息扩展 | **core 保持通用通道,ops 使用业务 payload 分支** | 避免 `plc_platform_core` 反向依赖 ops 领域类型;前端仍只连一处 | | 报警 | **走 `event` 表 + `subject_type/subject_id` + `level=warn/error`** | 复用现有事件表,同时支持按段 / 工位查询 | | 公共资源互斥 | **app 内部命名锁注册表 + 租约/恢复策略** | 摆渡车 / 机械臂 / 卸砖机位等共享资源,防止 task 异常退出后长期占锁 | ## 3. 不沿用 feeder 模型的理由 `ControlUnit` 当前字段是 `run_time_sec / stop_time_sec / acc_time_sec / bl_time_sec`,语义是"运行 N 秒 → 停 M 秒 → 累计 K 秒后启动布料机 → 布料 L 秒"。 运转系统的核心动作完全不是这种节拍: - 说明书 8.2 要求"码车位到车确认 → 输送机构停止",是检测信号驱动,不是定时。 - 说明书 10.1 要求"开门 → 门开到位确认 → 顶车 → 前位确认 → 顶车后退 → 后位确认 → 关门 → 门关到位确认",是 8 步串行带闭环。 - 说明书 13 章明确要求"动作完成不得仅靠时间,必须结合限位、检测或反馈信号"。 因此引擎需要换语义:**段(segment)状态机 + 步骤(step)顺序 + 每步等待闭环信号**。 ## 4. 领域模型 ### 4.1 实体一览 ``` source ──┐ │ point ───┼─→ equipment │ ├─→ station_signal ──→ station ──┐ │ │ └──────────────→ segment_step ──→ process_segment ──→ segment_runtime │ │ │ ├──→ segment_interlock │ └──→ segment_resource │ └──→ action_kind (枚举) ``` `source / point / equipment` 沿用平台层定义,不改动。 信号边界: - `point.signal_role` 是设备信号角色,例如 `rem / flt / home / run / start_cmd / stop_cmd / open_cmd / close_cmd`。 - `station_signal.signal_role` 是工位信号角色,例如 `presence / vacancy / arrived / allow_in / done / fault`。 - 同一个 `point` 可以同时被设备角色和工位角色引用,但两者语义分开维护。 - `vacancy` 可由独立点位绑定,也可由 `presence = false` 推导。首期通过 `station_signal.derived_from_role` 表达推导关系,避免现场必须额外提供空位点。 ### 4.2 新增对象 #### 4.2.1 `station`(工位) 表示流程中的一个位置或交接位。 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | UUID | | | `code` | TEXT UNIQUE | 例 `ST-DRY1-IN` | | `name` | TEXT | 例 "1 号干燥窑进口位" | | `line_code` | TEXT NULL | 例 `KILN_1` / `KILN_2` / `COMMON` | | `segment_code` | TEXT NULL | 用于分组(前端码车 / 双窑线 / 窑尾) | | `station_type` | TEXT | `load / dry_in / dry_step / dry_out / fire_in / fire_step / fire_out / transfer / unload / return` | | `enabled` | BOOL | | | `description` | TEXT NULL | | #### 4.2.2 `station_signal`(工位 ↔ 信号绑定) | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | UUID | | | `station_id` | UUID FK | | | `signal_role` | TEXT | `presence / vacancy / arrived / allow_in / done / fault` | | `point_id` | UUID FK | 绑定到具体点位 | | `derived_from_role` | TEXT NULL | 例 `presence`,表示由同工位其他角色反向推导 | | `invert_value` | BOOL | 推导或读取时是否取反,默认 false | | UNIQUE | (`station_id`, `signal_role`) | | #### 4.2.3 `process_segment`(流程段) | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | UUID | | | `code` | TEXT UNIQUE | 例 `SEG-DRY1-INFEED` | | `name` | TEXT | | | `segment_type` | TEXT | `front_load / robot / front_release / front_transfer / kiln_infeed / kiln_step / kiln_outfeed / tail_transfer / tail_step / unload / return` | | `line_code` | TEXT NULL | `KILN_1` / `KILN_2` / `COMMON` | | `priority` | INT | 公共资源冲突时使用 | | `enabled` | BOOL | | | `mode` | TEXT | `auto / remote_manual / local_manual / disabled` | | `require_manual_ack_after_fault` | BOOL | 故障解除后是否需要人工确认,默认 true | | `description` | TEXT NULL | | 模式语义: - `local_manual`:现场就地优先,软件不推进自动顺控;自动运行中检测到任一相关设备 `rem=false` 时,停止当前自动段并进入人工恢复路径。 - `remote_manual`:允许通过软件发单步 / 单设备命令,但仍必须执行设备故障、通信质量、安全链和关键门位联锁。 - `auto`:允许 supervisor 自动推进段状态机。 - `disabled`:段任务不启动;已运行任务在下一次配置重载后退出。 #### 4.2.4 `segment_step`(段步骤) | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | UUID | | | `segment_id` | UUID FK | | | `step_no` | INT | 序号 | | `step_code` | TEXT | 步骤代号 | | `action_kind` | TEXT | 见下方动作模板表 | | `target_equipment_id` | UUID NULL FK | 例如顶车机 | | `target_station_id` | UUID NULL FK | 例如目标摆渡位 | | `confirm_signal_role` | TEXT NULL | 等待哪个信号角色为真 | | `confirm_point_id` | UUID NULL FK | 直接指定确认点位(覆盖 role) | | `expected_value` | BOOL | 信号到位的期望值(默认 true) | | `timeout_ms` | INT | 超时即报警转 Faulted | | `command_role` | TEXT NULL | 设备命令角色,例 `start_cmd / open_cmd / forward_cmd` | | `stop_command_role` | TEXT NULL | 到位或异常时需要发出的停止命令角色,例 `stop_cmd` | | `pulse_ms` | INT NULL | 脉冲命令宽度;为空时按 action 默认值 | | `hold_until_confirm` | BOOL | true 表示命令保持到确认信号或故障;false 表示脉冲后等待 | | `cancel_on_fault` | BOOL | 故障 / 模式切换 / 通信异常时是否执行停止命令,默认 true | | `next_step_no_on_success` | INT NULL | 成功后跳转;为空表示顺序进入下一 step | | `next_step_no_on_failure` | INT NULL | 失败后跳转;首期通常为空并进入 Faulted | | `on_timeout` | TEXT | `fault / retry / block`,首期默认 `fault` | | `description` | TEXT NULL | | | UNIQUE | (`segment_id`, `step_no`) | | `action_kind` 枚举(首期): | 值 | 含义 | | --- | --- | | `open_door` | 开门:向门机 `open_cmd` 发脉冲 | | `close_door` | 关门 | | `push_forward` | 顶车机前进 | | `push_retract` | 顶车机后退复位 | | `pull_run` | 拉引机拉车 | | `pull_retract` | 拉引机复位 | | `transfer_move_to` | 摆渡车移动到目标工位 | | `step_once` | 节拍步进机执行一步 | | `robot_permit` | 允许机械臂自动作业 | | `robot_release` | 允许码车道放车 | | `wait_signal` | 不发命令,仅等待 `confirm_*` | | `pulse_cmd` | 通用脉冲命令(fallback) | 动作执行策略: - 对 `open_door / close_door / robot_permit` 等短命令,默认 `pulse_ms=300`,命令发出后等待确认信号。 - 对输送、顶车、拉引、步进等持续动作,默认 `hold_until_confirm=true`,到位后执行 `stop_command_role`。 - 对故障、急停、通信质量异常、自动切就地等中断场景,若 `cancel_on_fault=true`,先发停止 / 复位命令,再进入 `Faulted` 或 `ManualAckRequired`。 #### 4.2.5 `segment_interlock`(段联锁) | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | UUID | | | `segment_id` | UUID FK | | | `applies_to` | TEXT | `start_allow / start_deny / run_halt` | | `rule_kind` | TEXT | 见下方 | | `point_id` | UUID NULL FK | | | `station_id` | UUID NULL FK | | | `equipment_id` | UUID NULL FK | | | `expected_value` | BOOL NULL | | | `description` | TEXT NULL | | `rule_kind` 枚举(首期): - `point_eq` —— 指定 point 的值等于 `expected_value` - `station_vacant` —— 工位空(绑定的 `vacancy` 信号 = true 且 `presence` = false) - `station_occupied` —— 工位有车 - `equipment_origin` —— 设备在原位(角色 `home`) - `equipment_no_fault` —— 设备无故障(`flt` = false) - `equipment_remote` —— 设备远程(`rem` = true) - `safety_chain_ok` —— 安全链路正常 未来可扩展 `expression` 类型,但首期不引入。 #### 4.2.6 `segment_runtime`(段运行态,内存) 不落库(与 feeder `UnitRuntime` 一致,重启重置): ```rust pub enum SegmentState { Idle, Checking, Executing, Confirming, Resetting, Completed, Blocked, Faulted, ManualAckRequired, } pub struct SegmentRuntime { pub segment_id: Uuid, pub state: SegmentState, pub auto_enabled: bool, pub current_step_no: Option, pub step_started_at: Option>, pub last_completed_at: Option>, pub blocked_reason: Option, pub fault_message: Option, pub manual_ack_required: bool, pub comm_locked: bool, pub rem_local: bool, pub held_resources: Vec, } ``` #### 4.2.7 `segment_resource`(段资源声明) | 字段 | 类型 | 说明 | | --- | --- | --- | | `segment_id` | UUID FK | | | `resource_key` | TEXT | 例 `transfer_front / transfer_tail / robot_arm / unload_position / return_line` | | UNIQUE | (`segment_id`, `resource_key`) | | #### 4.2.8 `event` 表归因扩展 现有 `event` 表保留 `unit_id / equipment_id / source_id`,为了支持 ops 按段、工位检索,新增通用归因字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `subject_type` | TEXT NULL | `segment / station / equipment / source / platform` | | `subject_id` | UUID NULL | 对应业务对象 ID | ops 事件写入规则: - 段级事件:`subject_type='segment'`,`subject_id=segment_id`。 - 工位状态事件:`subject_type='station'`,`subject_id=station_id`。 - 设备动作事件:优先保留 `equipment_id`,同时可按上下文设置 `subject_type='segment'`。 ### 4.3 双窑线参数化 不写两套硬编码逻辑。1 号与 2 号窑线的差异由: - `process_segment.line_code`(`KILN_1` / `KILN_2`) - `segment_step.target_equipment_id` 与 `target_station_id`(指向各自的门机、顶车机、工位) - `segment_interlock.point_id` / `station_id`(指向各自工位的检测点) 承载。引擎读到的就是统一的 step 列表,与窑线无关。 ## 5. 顺控引擎设计 ### 5.1 结构(与 feeder 对齐) ``` crates/app_operation_system/src/ app.rs // AppState 接入 segment_runtime + event_manager + resource_registry router.rs event.rs // AppEvent(ops.*) control/ mod.rs engine.rs // supervisor + per-segment task runtime.rs // SegmentRuntime / SegmentRuntimeStore state.rs // SegmentState enum step_executor.rs // 按 action_kind 调度 interlock.rs // 通用允许/禁止/停机判定 resource.rs // 摆渡车 / 机械臂 / 卸砖位 互斥 simulate.rs // 开发态信号回灌 handler/ doc.rs (已存在) station.rs // CRUD + 信号绑定 segment.rs // CRUD + step / interlock 配置 control.rs // 段启停 / 手动动作 / 故障确认 runtime.rs // overview / segment detail / station detail ``` ### 5.2 段状态机 对应说明书 13.6: | SegmentState | 含义 | 出口 | | --- | --- | --- | | `Idle` | 等待 auto 启动 | → `Checking` | | `Checking` | 评估 `start_allow` / `start_deny` 联锁 | 通过 → `Executing`;否则 → `Blocked` | | `Executing` | 已发出当前 step 的命令 | → `Confirming` | | `Confirming` | 等待 `confirm_signal` 到位 | 收到 → 下一步;超时 → `Faulted` | | `Resetting` | 等待执行机构复位(如顶车机后退) | → 下一步或 `Completed` | | `Completed` | 段完成,输出完成信号 | 回 `Idle`(自动循环段) | | `Blocked` | 允许条件不满足 | 条件再次满足 → `Checking` | | `Faulted` | 故障或超时 | 故障解除 + 满足复位 → `ManualAckRequired` 或 `Idle` | | `ManualAckRequired` | 等待人工确认 | API ack → `Idle` | ### 5.3 段内执行循环 伪代码: ``` loop { reload segment + steps + interlocks run check_interlocks(state, run_halt) // 运行中停机检测 match state { Idle if auto_enabled => state = Checking, Checking => { if pass(start_allow) && !any(start_deny) { step = first_step state = Executing } else { blocked_reason = ... state = Blocked } } Executing => { execute(step.action_kind, step.target_*) // 发命令 state = Confirming } Confirming => { wait_signal(step.confirm_*, step.timeout_ms) on timeout → fault / retry / block by step.on_timeout on ok → next_step_no_on_success or next step or Completed } Faulted => break and wait manual recovery ... } notify or fault_tick } ``` `wait_signal` 复用与 feeder `wait_phase` 类似的 `tokio::select! { sleep_until(deadline), notify, fault_tick }` 模式,但终止条件是"绑定信号到达期望值"而非时间到。 ### 5.4 step_executor 集中处理 `action_kind` 到具体写点动作: - 短命令类 `action_kind` 调 `plc_platform_core::control::command::send_pulse_command`。 - 持续命令类 `action_kind` 先写 `command_role`,确认到位、超时或故障中断时按 `stop_command_role` 收尾。 - `transfer_move_to`:写目标工位编号到摆渡车定位命令点位,等待 `arrived` 信号。 - `wait_signal`:不发命令。 - 各设备的 `start_cmd / stop_cmd / open_cmd / close_cmd` 信号角色复用 feeder 已有的 `signal_role` 命名空间,equipment 表无需新表结构。 命令执行前必须重新检查: - 设备 `rem=true` - 设备 `flt=false` - 命令点与确认点 `quality=Good` - 当前段仍处于允许执行模式 - 当前 step 仍是 runtime 中的 `current_step_no` ## 6. 联锁与异常 ### 6.1 联锁判定顺序(对齐说明书 8.1 / 13) 1. 通信质量(任一绑定点 quality != Good) → `comm_locked` 2. 就地 / 远程状态(`rem=false`)→ 停止自动并转人工恢复 3. 安全联锁 / 急停 → `Faulted` 4. 设备故障(`flt` = true) → `Faulted` 5. 门位联锁 6. 机械臂联锁 7. 工艺允许条件(空位 / 到位) 8. 普通顺控条件 高优先级不满足时低优先级不再判断。 ### 6.2 通用允许检查(自动注入到每段) 每段无论是否有显式 `segment_interlock`,引擎都执行以下通用检查(说明书 13.1): - 目标工位空位 - 本工位有车或动作前提 - 执行机构原位 - 设备无故障 - 设备处于远程 - 信号质量正常 - 段引用的资源未被占用 ### 6.3 异常恢复(说明书 13.5) - 故障优先停止当前 step 的命令。 - `Faulted` 保留 `current_step_no`,不跳步。 - `remote_manual` 下允许人工执行复位动作,但复位动作仍执行安全、故障、门位和通信检查。 - 故障物理消失后: - 若 `require_manual_ack_after_fault`(默认 true) → `ManualAckRequired` - 否则自动回 `Idle`。 - `POST /api/control/segment/{id}/ack-fault` 用于人工确认。 ## 7. 公共资源调度 说明书 3.3 / 3.4 指出:前端码车系统、窑尾摆渡、回车线、卸砖线为公共段,1 号 / 2 号窑线在此处汇合。 实现: ```rust pub struct ResourceRegistry { inner: RwLock>, } pub struct ResourceLease { pub owner_segment_id: Uuid, pub acquired_at: DateTime, pub heartbeat_at: DateTime, } ``` 资源 key 示例:`transfer_front` / `transfer_tail` / `robot_arm` / `unload_position` / `return_line`。 段配置中以新表 `segment_resource(segment_id, resource_key)` 声明所需资源;段进入 `Executing` 前必须 `try_acquire`,进入 `Completed` 时 `release`。冲突时停留 `Blocked`,附 `blocked_reason = "resource_busy: transfer_front"`。 资源恢复策略: - 资源持有段每个状态循环刷新 `heartbeat_at`。 - 若 owner task 已退出、段被禁用、或 owner 已回到 `Idle/Completed`,supervisor 可回收租约。 - `Faulted` 时是否释放资源按资源类型决定:机械臂区、卸砖位等可释放;摆渡车正在载车时不释放,必须人工确认或到达安全位后释放。 - 资源等待超时只报警和进入 `Blocked`,不抢占低优先级段。首期不做死锁自动解除。 ## 8. 事件与 WebSocket ### 8.1 业务事件命名空间 `ops.*` | event_type | level | | --- | --- | | `ops.segment.auto_started` | info | | `ops.segment.auto_stopped` | info | | `ops.segment.step_advanced` | info | | `ops.segment.completed` | info | | `ops.segment.blocked` | warn | | `ops.segment.fault_locked` | error | | `ops.segment.fault_acked` | info | | `ops.segment.comm_locked` | warn | | `ops.segment.comm_recovered` | info | | `ops.station.state_changed` | info | | `ops.alarm.action_timeout` | error | | `ops.alarm.signal_conflict` | error | | `ops.alarm.resource_busy` | warn | 所有事件经 `record_event` 落 `event` 表(复用平台机制)。 ### 8.2 WebSocket 消息扩展 不把 ops 的 `SegmentRuntime` 类型放进 core。`plc_platform_core::websocket::WsMessage` 增加一个通用业务消息分支,业务 payload 由 app crate 构造: ```rust pub enum WsMessage { // 已有 ... AppEvent(AppWsEvent), } pub struct AppWsEvent { pub app: String, // "operation-system" pub event_type: String, // "segment_runtime_changed" / "station_state_changed" pub data: serde_json::Value, } ``` ops 侧约定: - `event_type="segment_runtime_changed"`:`data` 序列化 `SegmentRuntime`。 - `event_type="station_state_changed"`:`data` 包含 `station_id / presence / vacancy / arrived / updated_at`。 - feeder 前端忽略未知 `AppEvent` 或非本 app 的消息;ops 前端只处理 `app="operation-system"`。 > 这样仍保留单一 websocket 入口,但 core 不需要知道 ops 的领域模型。 ## 9. API 设计 ### 9.1 配置 API ``` GET /api/station POST /api/station GET /api/station/{id} PUT /api/station/{id} DELETE /api/station/{id} POST /api/station/{id}/signal // 绑定信号 DELETE /api/station/{id}/signal/{role} GET /api/segment POST /api/segment GET /api/segment/{id} GET /api/segment/{id}/detail // 含 step / interlock / resource PUT /api/segment/{id} DELETE /api/segment/{id} POST /api/segment/{id}/step PUT /api/segment/{id}/step/{step_no} DELETE /api/segment/{id}/step/{step_no} POST /api/segment/{id}/interlock DELETE /api/segment/{id}/interlock/{interlock_id} ``` ### 9.2 控制 API ``` POST /api/control/segment/{id}/start-auto POST /api/control/segment/{id}/stop-auto POST /api/control/segment/{id}/reset // 强制回 Idle,仅在 Faulted/Blocked 状态可用 POST /api/control/segment/{id}/ack-fault POST /api/control/segment/{id}/manual-step // remote_manual 下单步执行 POST /api/control/segment/batch-start-auto POST /api/control/segment/batch-stop-auto POST /api/control/equipment/{id}/manual-action // remote_manual 下单设备动作,仍执行联锁 ``` ### 9.3 运行态 API ``` GET /api/runtime/overview // 所有段 + 关键工位 + 报警计数 GET /api/runtime/segment/{id} GET /api/runtime/station/{id} GET /api/event?type=ops.* ``` ## 10. 前端 复用 `web/core` 的源码、点位、设备、事件、日志、文档抽屉。 `web/ops/` 增加: - 总览页:双窑线 + 公共段流程图(首版静态 SVG + 区域绑定段 / 工位状态) - 段卡片列表:展示 `state / current_step / blocked_reason / fault_message` - 工位状态视图:有车 / 空位 / 到位 - 配置页:站点 / 段 / step / interlock 表格 + 表单 - 手动操作:段启停 / 故障确认 / 复位 WebSocket 订阅 `AppEvent(app="operation-system")`,按 `event_type` 分发 `segment_runtime_changed` 和 `station_state_changed` 实时刷新。 ## 11. 复用 vs 新增对照 | 模块 | 来源 | 用途 | | --- | --- | --- | | `plc_platform_core::connection` | 复用 | OPC UA 读写 | | `plc_platform_core::control::command::send_pulse_command` | 复用 | 所有动作命令底层 | | `plc_platform_core::event::record_event` + `EventInsert` | 复用 | 事件落库 | | `plc_platform_core::event::MetadataCache` | 复用 + 扩展 | 通用化为按 `(table, id)` 查 code;feeder 用 unit/equipment,ops 加 station/segment | | `plc_platform_core::websocket::WsMessage` | 重构 | 删除 `UnitRuntimeChanged`(feeder 业务),新增通用 `AppEvent(AppWsEvent)`;feeder 和 ops 都走 AppEvent | | `plc_platform_core::handler::platform_routes` | 复用 | source / point / equipment / tag / page | | `plc_platform_core::model::ControlUnit` | **迁出 core** | P-1 阶段下放到 feeder;语义本就是 feeder 业务 | | `plc_platform_core::control::runtime::{UnitRuntime, ControlRuntimeStore}` | **迁出 core** | 同上,含 `DistributorRunning` 这种 feeder 专属状态 | | `plc_platform_core::service::control` unit CRUD | **迁出 core** | 下放到 feeder;event 查询留 core | | `app_feeder_distributor::control::*` | **不复用** | 结构参考 | > **P-1 阶段说明**:上表中的"迁出 core"是清理动作,发生在 P0 之前。详见 §12。 ## 12. 阶段计划 | 阶段 | 目标 | 主要工作 | | --- | --- | --- | | **P-1 Core 业务清理** | core 不再持有 feeder 业务模型 | 把 `UnitRuntime / UnitRuntimeState / ControlRuntimeStore / ControlUnit / unit CRUD / WsMessage::UnitRuntimeChanged` 从 `plc_platform_core` 迁到 `app_feeder_distributor`;`WsMessage` 新增 `AppEvent(AppWsEvent)` 分支并删除 `UnitRuntimeChanged`;feeder 引用全部调整;前端 ws 客户端按 `app + event_type` 分发;`MetadataCache` 通用化为 `entity_code(table, id)`。零行为变更,feeder 通过现有 smoke test | | **P0 骨架对齐** | `app_operation_system` 与 feeder 在依赖、AppState、bootstrap、tray、启动/退出链路对齐 | Cargo.toml 补依赖;AppState 加 `EventManager` + `SegmentRuntimeStore` + `ResourceRegistry`;启动接 `connect_all_enabled_sources`;启动 engine supervisor;退出时断开数据源 | | **P1 数据库迁移 & 模型** | ops 配置表 + event 归因字段 + Rust model | 新 migration `2026-05-1x_create_operation_system.sql`;新增 `station / station_signal / process_segment / segment_step / segment_interlock / segment_resource`;扩展 `event.subject_type/subject_id`;`app_operation_system::model` 模块 | | **P2 配置 API** | 站点 / 段 / step / interlock CRUD | `service::station / segment`;handler;router | | **P3 引擎 MVP** | 跑通 1 个段端到端(前端码车位进车段,说明书 8.2) | `engine`、`step_executor`、`interlock`、`runtime`;通用 `AppEvent` WebSocket 推送 | | **P4 动作模板补全** | 覆盖 8 章 + 10 章典型动作 | 各 `action_kind` 实现 + simulate 反馈 | | **P5 双窑线段模板化** | 通过段配置实现 1 号 / 2 号窑线 4 段(进口 / 内前移 / 出口) | 段配置 seed;端到端跑通 | | **P6 资源调度** | 公共段互斥 | `ResourceRegistry`;`segment_resource` 表;Blocked 路径完善 | | **P7 公共段** | 摆渡车 / 卸砖 / 回车线 | 段实例 + 段间交接 | | **P8 报警 & 异常恢复** | 超时报警、信号冲突、人工确认完整链路 | `AppEvent::Alarm*`;ack-fault API | | **P9 前端监控页** | 段卡片 + 工位状态 + 流程图 | `web/ops/html` + JS | | **P10 配置前端** | 段 / 工位 / 联锁可视化配置 | `web/ops/html` 表格表单 | 每阶段都要求: - `cargo build -p app_operation_system` 通过 - 至少 1 个单元测试或 smoke test - 不破坏 `app_feeder_distributor` 编译 ## 13. 风险与约束 ### 13.1 主要风险 - **P-1 迁移破坏 feeder**:从 core 把 unit 模型迁到 feeder 时容易漏改 import 或 ws 客户端调用。要求迁移单独成 commit,feeder 启动 + 单元测试 + ws 推送链路逐项验证。 - **现场 I/O 清单缺失**:说明书描述了逻辑关系但未明确每个工位 / 设备对应的具体点位。落地前必须补 I/O 对照表。 - **段切分粒度**:段切得太细 → 状态机膨胀;切得太粗 → 段内步骤过多。首期建议按说明书章节级切(一节 = 一段)。 - **WebSocket 领域边界**:不得把 `SegmentRuntime` 放入 core,否则 core 会反向依赖 ops 业务模型;采用通用 `AppEvent` payload。 - **公共资源死锁**:例如摆渡车被段 A 占用、段 A 又等卸砖位空(被段 B 占用)。首期通过段优先级与超时报警缓解,不引入死锁检测。 - **持续命令收尾**:输送、顶车、拉引等不是纯脉冲动作,必须在超时、故障和模式切换时明确停止命令。 ### 13.2 约束 - 首期不做规则引擎,所有联锁靠固定 `rule_kind` 枚举。 - 首期段 / step 改动不做热加载——supervisor 每 10s 重读配置,与 feeder 一致。 - 首期 `segment_runtime` 不持久化,重启全部回 `Idle`。 - 首期不做资源抢占;资源冲突只阻塞、报警和等待人工处理。 ## 14. 验收标准 完成 P0–P5 后应达到: - 仓库新增 6 张 ops 业务配置表,并扩展 `event.subject_type/subject_id`,与 feeder 业务表互不干扰。 - `app_operation_system` 可独立编译为 exe,可启动并连接 OPC UA 数据源。 - 启动后具备 `EventManager`、`SegmentRuntimeStore`、`ResourceRegistry`、engine supervisor,退出时可断开数据源。 - 至少 1 条段(例如 2 号干燥窑进口段,含 8 步)可通过配置驱动跑通: - 自动启停 - 步骤顺序推进 - 闭环信号确认 - 持续动作到位后停止命令 - 故障停步 + 人工确认 - WebSocket 通过 `AppEvent(app="operation-system")` 推送段运行态变化、工位状态变化。 - 前端可见段卡片与当前步骤进度。 - `event` 表能按 `ops.*` 和 `subject_type/subject_id` 查到全链路事件。 完成 P6–P10 后应达到: - 1 号 / 2 号窑线全部 6 段(进口 / 内前移 / 出口 × 2 窑)跑通。 - 公共段(前端码车、摆渡车、窑尾、卸砖、回车)跑通。 - 报警分类齐全(说明书 13.4 全部 10 类)。 - 监控前端 + 配置前端可用。 ## 15. 后续可演进项(非首期) - 联锁 `expression` 类型:引入简单布尔表达式语言,替代 `rule_kind` 枚举。 - 段历史持久化:将每段每次完成 / 故障写入 `segment_run_history`,支持时间线回放。 - 现场调试视图:模拟点位值、单步推进、跳步授权(带操作员签名)。 - 公共能力下沉:若后续出现第三套类似业务,再把 segment 引擎抽到 `plc_platform_core::control::segment`。