plc_control/docs/superpowers/specs/2026-05-18-operation-system...

632 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 运转系统顺控引擎设计
日期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<i32>,
pub step_started_at: Option<DateTime<Utc>>,
pub last_completed_at: Option<DateTime<Utc>>,
pub blocked_reason: Option<String>,
pub fault_message: Option<String>,
pub manual_ack_required: bool,
pub comm_locked: bool,
pub rem_local: bool,
pub held_resources: Vec<String>,
}
```
#### 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 // AppEventops.*
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<HashMap<String, ResourceLease>>,
}
pub struct ResourceLease {
pub owner_segment_id: Uuid,
pub acquired_at: DateTime<Utc>,
pub heartbeat_at: DateTime<Utc>,
}
```
资源 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)` 查 codefeeder 用 unit/equipmentops 加 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** | 下放到 feederevent 查询留 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`handlerrouter |
| **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 客户端调用。要求迁移单独成 commitfeeder 启动 + 单元测试 + 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. 验收标准
完成 P0P5 后应达到:
- 仓库新增 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` 查到全链路事件。
完成 P6P10 后应达到:
- 1 号 / 2 号窑线全部 6 段(进口 / 内前移 / 出口 × 2 窑)跑通。
- 公共段(前端码车、摆渡车、窑尾、卸砖、回车)跑通。
- 报警分类齐全(说明书 13.4 全部 10 类)。
- 监控前端 + 配置前端可用。
## 15. 后续可演进项(非首期)
- 联锁 `expression` 类型:引入简单布尔表达式语言,替代 `rule_kind` 枚举。
- 段历史持久化:将每段每次完成 / 故障写入 `segment_run_history`,支持时间线回放。
- 现场调试视图:模拟点位值、单步推进、跳步授权(带操作员签名)。
- 公共能力下沉:若后续出现第三套类似业务,再把 segment 引擎抽到 `plc_platform_core::control::segment`