diff --git a/docs/superpowers/specs/2026-05-18-operation-system-engine-design.md b/docs/superpowers/specs/2026-05-18-operation-system-engine-design.md new file mode 100644 index 0000000..4a8c73b --- /dev/null +++ b/docs/superpowers/specs/2026-05-18-operation-system-engine-design.md @@ -0,0 +1,631 @@ +# 运转系统顺控引擎设计 + +日期: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`。 diff --git a/运转系统逻辑说明.doc b/运转系统逻辑说明.doc new file mode 100644 index 0000000..fa93b37 Binary files /dev/null and b/运转系统逻辑说明.doc differ