Device Dual-Face Pattern
> Aligned with PCR Master Blueprint v1.0 — see Blueprint §1.1, §2.4, §2.5, §7.4, §7.10. > 职责:定义"设备"在本架构中的二元结构——一个 device 是物理机械面(Mech)+ 电子控制面(Electronic) 的复合体,两面归属于不同的子域,由不同的步进函数演化,但同属一个 Engine / Servo / Fin 实体。
1. 一个 Device,两张面
┌───── 电子控制面 ─────┐
│ avionics/devices/ │
│ (EcuState / ScuState│
│ / FinCtrl / Imu / │
│ Gps / Icu) │
│ │
│ step(): Bus 命令 → │
│ 驱动 Mech → │
│ 发 Bus 消息 │
└──────────┬───────────┘
│ drives
▼
┌───── 物理机械面 ─────┐
│ plant/hardware/ │
│ (EngineMech / │
│ ServoMech / │
│ FinMech) │
│ │
│ step_*_mech(): │
│ 一阶/二阶演化 + │
│ 限幅 + 残差 │
└──────────────────────┘两面合体在 simulation/state/RocketBody.h:
// simulation/state/RocketBody.h
struct Engine {
contracts::EngineId id;
plant::model::InstallParams install;
const plant::model::EngineSpec* spec;
plant::hardware::EngineMech mech; // 物理机械面
avionics::device::ecu::EcuState ecu; // 电子控制面
};
struct Servo {
contracts::ServoId id;
plant::hardware::ServoMech mech; // 物理机械面
avionics::device::scu::ScuState scu; // 电子控制面(部分情况下 SCU 一对多)
};
struct Fin {
contracts::FinId id;
plant::hardware::FinMech mech;
avionics::device::fin_ctrl::FinCtrlState fin_ctrl;
};2. 三类 Device 的拓扑
不是所有 device 都"双面"。根据是否含机械面,分三类:
| 类别 | 实例 | 物理机械面 | 电子控制面 | 物理输入来源 |
|---|---|---|---|---|
| 双面 | Engine / Servo / Fin | ✅ 有 EngineMech / ServoMech / FinMech | ✅ 有 EcuState / ScuState / FinCtrlState | 自身 Mech |
| 只有电子面(含传感) | IMU / GPS | ❌ 没有独立 Mech | ✅ 有 ImuState / GpsState | body.aux(IMU)/ body.spatial(GPS) |
| 纯电子 | ICU | ❌ 没有 Mech | ✅ 有 IcuState | 无(只听 Bus 命令计时器) |
> 传感器没有独立 Mech,是因为它们的"物理输入"就是 body 本身的状态(如 IMU 感测的就是 aux.spec_force_b)。把"传感器 Mech"剥离出来反而冗余。
3. step 函数签名(与代码对齐)
3.1 双面 Device:两个 step
电子面(位于 src/avionics/devices/):
// avionics/devices/Ecu.h
namespace avionics::device::ecu {
void step(EcuState& ecu,
plant::hardware::EngineMech& target_mech, // 直接驱动 Mech(输出端)
bus::IBus& bus,
const EcuConfig& cfg,
Time dt);
}注意:
- ECU step 直接持有 EngineMech 引用 → 它有权"驱动机械面"。
- 同时接
bus::IBus&→ 读 Bus 命令、发 Bus 状态。 cfg.fidelity决定 Transparent / Realistic / Fault 三档行为。
机械面(位于 src/plant/hardware/):
// plant/hardware/Mech.h
namespace plant::hardware {
void step_engine_mech(EngineMech& mech,
EcuDriveCommand cmd, // 由 ECU step 计算出的"驱动量"
Time dt);
void step_servo_mech (ServoMech& mech, Angle target, Time dt);
void step_fin_mech (FinMech& mech, Angle target, Time dt);
}注意:
- 机械面 step 无 Bus 依赖 →
plant/hardware/不知道 Bus 存在。 - 接收"驱动指令"(
EcuDriveCommand/Angle target)作为输入,演化机械状态。 - 这一层是"一阶惯性环节 / 二阶阻尼 / 限幅"的物理拟合。
3.2 传感器:只有电子 step
// avionics/devices/Imu.h
namespace avionics::device::imu {
void step(ImuState& imu,
const dynamics::AuxState& aux_truth, // 物理输入:body.aux
bus::IBus& bus,
const ImuConfig& cfg,
Time dt);
}
// avionics/devices/Gps.h
namespace avionics::device::gps {
void step(GpsState& gps,
const dynamics::SpatialState& spatial_truth, // 物理输入:body.spatial
const plant::model::FrameConfig& frame_cfg,
bus::IBus& bus,
const GpsConfig& cfg,
Time dt);
}注意:
- 物理输入是
const&→ 传感器只读物理真值,不修改。 - 写出口在 Bus(不修改 RocketBody)。
3.3 ICU:纯电子
// avionics/devices/Icu.h
namespace avionics::device::icu {
void step(IcuState& icu, bus::IBus& bus, const IcuConfig& cfg, Time dt);
}无物理输入。只在 Bus 上听命令、计时,到点产生 DiscreteEvent。
4. 演化顺序(在 sim::body_tick 中)
一拍内的 device 演化遵循固定序:
拍 k+1 开始
│
├─ ① 传感器 step(IMU / GPS)
│ 输入:body.spatial / body.aux (上拍的真值)
│ 输出:Bus 消息
│
├─ ② FCC tick (仅当 fcc_dt 边界到达)
│ 接 Bus → 计算 → 发 Bus
│
├─ ③ 执行机构电子面 step(ECU / SCU / FinCtrl / ICU)
│ 接 Bus 命令 → 计算 EcuDriveCommand / 目标角
│ ECU 还会顺便查 spec 表生成 EngineEffect
│
├─ ④ 物理机械面 step(step_engine_mech / step_servo_mech / step_fin_mech)
│ 接 EcuDriveCommand / 目标角 → 演化 Mech
│
├─ ⑤ plant::physics 力计算
│ 用 EngineEffect(含真实推力曲线) + ServoMech.actual_angle 算 Forces
│
└─ ⑥ dynamics_core::ode::rk4_step
积分得新的 spatial / inertial / aux关键时序:传感器在拍头读上一拍真值(先采样),执行机构在拍尾驱动机械面(后驱动)。这样在一个 body_dt 内,FCC 看到的传感器值是"上一拍末态",FCC 决策再驱动当前拍的执行机构,匹配真实硬件的时序。
5. 跨域归属的所有权
| 数据成员 | 归属子域 | 谁负责定义 | 谁负责演化 |
|---|---|---|---|
Engine.mech: EngineMech | plant/hardware/ | plant::hardware::EngineMech | plant::hardware::step_engine_mech |
Engine.ecu: EcuState | avionics/devices/ | avionics::device::ecu::EcuState | avionics::device::ecu::step |
Engine.spec: const EngineSpec* | plant/model/ | plant::model::EngineSpec | 装配期注入,运行期只读 |
Engine.install: InstallParams | plant/model/ | plant::model::InstallParams | 装配期注入,运行期只读 |
Engine 本身 | simulation/state/ | sim::Engine(跨域复合) | 不被"整体演化",只各面分别演化 |
重要的"无知性"链条:
plant::hardware::EngineMech不知道EcuState长什么样avionics::device::ecu::EcuState不知道EngineMech的内部场量,但持引用驱动plant::model::EngineSpec既不知道 Mech 也不知道 ECU,它是静态资产sim::Engine是唯一一个知道所有三者的类型,它就是为聚合而存在
6. 配置 fidelity:局部决策 vs interpreter
Blueprint §7.4:Device 不用 algebra+interpreter 模式。原因:
| 层 | algebra+interpreter? | 原因 |
|---|---|---|
| Bus | ✅ 用 | 多消息类型共信道 + 多物理协议(1553B / TTE / Semantic) |
| FCC | ✅ 用 | 策略 YAML 驱动 + Sim/RTOS 部署需要不同 interpreter |
| Device | ❌ 不用 | 操作集合固定 + fidelity 是局部决策 |
Device fidelity 通过 config enum 切换,不是 interpreter swap:
// avionics/devices/Imu.h
struct ImuConfig {
enum class Fidelity : uint16_t {
Transparent, // 直接转写真值
Realistic, // 加噪 + 量化 + 残差累积
Fault // 故障注入
} fidelity = Fidelity::Transparent;
};
// step 内部直接 switch
void step(ImuState& imu, const AuxState& truth, ...) {
switch (cfg.fidelity) {
case Transparent: emit_transparent(truth, bus); break;
case Realistic: emit_realistic(imu, truth, bus, dt); break;
case Fault: emit_fault(imu, truth, bus); break;
}
}为什么 device 不上 interpreter:操作集合固定(不变化),fidelity 之间的差异是数值/噪声层面,不是协议层面。引入 interpreter 是冗余抽象。
7. 与传统遗留代码的对照
| 传统 OOP 设计 | 本架构 v1.0 |
|---|---|
class EngineModel { ... } 单类持有所有状态 + 计算 | Engine = { Mech in plant/hardware/ , EcuState in avionics/devices/ , spec in plant/model/ } 拆分 |
engine.step(dt) 一个函数处理所有 | ecu::step(...) ; step_engine_mech(...) 两阶段 |
| 改伺服阻尼比 = 改 EngineModel = 改物理引擎 | 改 ServoMech 的演化方程 = 只改 plant/hardware/ |
| 改控制延迟 / fidelity = 改 EngineModel | 改 avionics::device::ecu::step |
| 测试需要拉起整个 EngineModel | 三面(mech / ecu / spec)可独立 bench 测试 |
反模式:把 ECU 状态和 Mech 状态混进一个 struct,用一个 step 函数操作。会立即破坏:
plant/hardware/应当#include不到bus/(无知性原则)- 测试单独跑 mech 演化(不挂 Bus)的能力
- fidelity 切换的局部性
8. C-Distillation 视角
C++ 阶段:
Engine是聚合 struct,跨命名空间引用step函数族在不同 namespace 里独立编译
C 蒸馏阶段:
Engine退化为struct EngineFull { EngineMech mech; EcuState ecu; const EngineSpec* spec; ... }step函数族失去 namespace,变成ecu_step(...)/engine_mech_step(...)自由函数- 跨域调用关系(
ecu_step内调用 mech 引用)保持不变 - 编译期可见性约束(
plant/hardware不见bus/)退化为代码评审约束
双面拆分的好处在 C 阶段依然成立:调试时打印 engine.mech 与 engine.ecu 分别可读,不会出现"整个 EngineModel 黑箱"。
9. 测试策略
| 测试目标 | 适用层级 | 示例 |
|---|---|---|
| Mech 一阶演化数值正确 | bench | 给 ServoMech 喂 target=10°,验证 1s 后位置 + rate 限幅 |
| ECU 状态机时序 | bench | 喂 IgniteEngines 命令,验证 mech.true_thrust 在 3s 启动期内的曲线 |
| 双面联合启动 | subsystem | 30s 仿真 ECU + EngineMech,记录 EngineEffect → 力 → 加速度 |
| Bus 协议层时延 | bench | 喂 SemanticInterpreter,验证 base_delay + jitter |
| Fidelity 切换 | bench | 切换 IMU fidelity = Realistic,验证残差累积 0-bias |
详见 08_Cross_Cutting/Testing_Framework.md。
10. Cross References
- 三类设备的具体 step 实现 → 各自源文件
src/avionics/devices/*.cpp - Mech 的演化方程 →
plant/hardware/Mech.{h,cpp}+02_Physical_World/Plant_Hardware_Mech.md - Bus 接口 →
Semantic_Bus_Pattern.md(同目录) - 物理与硬件边界的原则讨论 →
Hardware_Physics_Boundary.md(同目录) - 传感器细节 →
Sensor_Modeling.md(同目录) - 推力跨域数据流(Stage 3 涉及 ECU+Mech 双面)→ Blueprint §4.1
- RocketBody 的 device 聚合形态 →
06_Simulation/Body_World_Tick.md(待写)§3