Skip to content

Plant Hardware: Mechanical State Evolution

> Aligned with PCR Master Blueprint v1.0 — see Blueprint §2.4, §7.5(device-as-unified-entity). > 职责:定义火箭设备的物理机械面(mech)的状态结构与单步演化函数。机械面不是"完整设备"——它只是同一设备的物理半边,电子半边在 avionics/devices/。 > C-Distillation note*Mech 是纯 POD;step_*_mech 是无状态的 C 风格自由函数,蒸馏阶段一对一翻译。


1. 双面实体:Mech vs Electronic

火箭上的每个有源设备(发动机、伺服、舵机)都同时拥有:

  • 机械/液压/光学面:受物理规律演化(推进剂消耗、油压变化、机械响应)
  • 电子/数字面:受软件指令驱动(FSM、解码 Bus 命令、产出 EngineEffect)

两面焊接在同一物理对象上,但代码上属于不同子域:

设备Mech(本目录)Electronic(avionics/devices/
发动机plant::hardware::EngineMechavionics::device::ecu::EcuState
伺服plant::hardware::ServoMechavionics::device::scu::ScuState
舵翼plant::hardware::FinMechavionics::device::fin_ctrl::FinCtrlState
IMU / GPS无(被动测量,不持有机械状态)
ICU

sim::RocketBody 中,两面装在同一 device struct 内

cpp
struct Engine {
    contracts::EngineId             id;
    plant::model::InstallParams     install;
    const plant::model::EngineSpec* spec;

    plant::hardware::EngineMech     mech;     // ← 本文档
    avionics::device::ecu::EcuState ecu;      // ← 见 03_Avionics_and_Bus/
};

详见 06_Simulation/RocketBody_Composite.md


2. 三个机械状态

2.1 EngineMech:发动机机械状态

cpp
// src/plant/hardware/Mech.h
namespace plant::hardware {

struct EngineMech {
    // 燃烧室
    double chamber_pressure;        // [Pa] 实际燃烧室压力
    double chamber_temperature;     // [K]

    // 输出
    double true_thrust;             // [N] 当前真实推力(含瞬态)
    double mass_flow;               // [kg/s] 当前总质量流

    // 推进剂消耗
    double fuel_consumed;           // [kg] 累计燃料消耗
    double ox_consumed;             // [kg] 累计氧化剂消耗

    // 内部状态
    double valve_opening;           // [0..1] 主阀门开度
    Time   ignition_time;           // 点火时刻(用于查 thrust_curve)
};

} // namespace plant::hardware

2.2 ServoMech:伺服机械状态

cpp
struct ServoMech {
    Angle  actual_angle;            // 实际角度
    Angle  target_angle;            // 跟踪目标(来自 ScuPayload)
    double angular_rate;            // [rad/s] 当前角速度
    double oil_pressure;            // [Pa] 液压油压
    double load_torque;             // [N·m] 当前承受外力矩
};

2.3 FinMech:舵翼机械状态

cpp
struct FinMech {
    Angle  actual_angle;            // 当前偏转
    Angle  target_angle;            // 来自 FinCtrlPayload
    double angular_rate;
};

3. 单步演化函数

每个 Mech 配套一个 step_* 函数。统一签名:接受当前 Mech、控制命令、时间步长;原地修改 Mech

cpp
namespace plant::hardware {

// 发动机:消费 ECU 的驱动命令,演化燃烧室与流量
void step_engine_mech(
    EngineMech& mech,
    const EcuDriveCommand& cmd,    // 来自 avionics::device::ecu::step 的输出
    const plant::model::EngineSpec& spec,
    Time dt
);

// 伺服:跟踪目标角,受 ServoSpec 限制
void step_servo_mech(
    ServoMech& mech,
    Angle target_angle,
    const plant::model::ServoSpec& spec,
    Time dt
);

// 舵翼:与 servo 类似的一阶跟踪
void step_fin_mech(
    FinMech& mech,
    Angle target_angle,
    const plant::model::FinSpec& spec,
    Time dt
);

} // namespace plant::hardware

3.1 step_engine_mech 实现要点

cpp
void step_engine_mech(EngineMech& mech, const EcuDriveCommand& cmd,
                      const EngineSpec& spec, Time dt)
{
    if (cmd.is_ignite_request) {
        mech.ignition_time = cmd.now;
    }

    double t_burn = (cmd.now - mech.ignition_time).to_seconds();

    // 1. 查表得标称推力 / 流量
    double thrust_nom = spec.thrust_curve.lookup(t_burn);
    double mflow_nom  = spec.mass_flow_curve.lookup(t_burn);

    // 2. 一阶燃烧室压力响应(捕捉启动瞬态)
    double tau = spec.start_duration.to_seconds();
    double alpha = dt.to_seconds() / tau;
    mech.chamber_pressure += alpha * (cmd.target_chamber_p - mech.chamber_pressure);

    // 3. 推进剂消耗积分
    mech.fuel_consumed += mflow_nom * spec.fuel_fraction * dt.to_seconds();
    mech.ox_consumed   += mflow_nom * (1.0 - spec.fuel_fraction) * dt.to_seconds();

    // 4. 写出当前真实推力(供 ECU 读取 → 产出 EngineEffect)
    mech.true_thrust = thrust_nom * (mech.chamber_pressure / spec.design_chamber_p);
    mech.mass_flow   = mflow_nom;
}

3.2 step_servo_mech 实现要点

cpp
void step_servo_mech(ServoMech& mech, Angle target,
                     const ServoSpec& spec, Time dt)
{
    mech.target_angle = clamp(target, spec.range_min, spec.range_max);

    // 一阶响应 + rate limit
    double tau = spec.time_constant;
    double max_rate = spec.max_rate_deg_s * deg_to_rad;
    Angle  desired_delta = mech.target_angle - mech.actual_angle;

    double rate = clamp(desired_delta.rad() / tau, -max_rate, max_rate);
    mech.angular_rate = rate;
    mech.actual_angle = mech.actual_angle + Angle::from_rad(rate * dt.to_seconds());
}

4. 调用时机

sim::body_tick 内,机械面演化在电子面演化之后

cpp
// 简化示意(详见 06_Simulation/Body_World_Tick.md)
BodyRWS body_tick(BodyEnv env, RocketBody body, Time dt) {
    // 1. 探测器构建本地 ctx(probe_aero / probe_traj / probe_mass_props)
    BodyEnv local_env = sim::probe::assemble_body_env(env, body, dt);

    // 2. 电子面演化:ECU 读 Bus 命令 + 写驱动命令 → mech
    for (auto& eng : body.engines) {
        auto drive_cmd = avionics::device::ecu::step(
            eng.ecu, eng.mech, body.bus, *eng.spec, dt);

        // 3. 机械面演化(本文档)
        plant::hardware::step_engine_mech(eng.mech, drive_cmd, *eng.spec, dt);

        // 4. ECU 读取演化后的 mech.true_thrust,写 EngineEffect
        eng.effect = eng.ecu.produce_effect(eng.mech);
    }

    // 5. 力学计算(plant/physics)→ Forces
    Forces F = plant::physics::compute_thrust(local_env, body)
             + plant::physics::compute_drag  (local_env, body)
             + plant::physics::compute_gravity(local_env, body);

    // 6. 积分(dynamics_core)→ 新 body.spatial
    body = dynamics::ode::rk4_step(body, eom_rhs(F), now, dt);

    return BodyRWS::pure(body);
}

5. 设计原则

5.1 纯演化函数

step_*_mech 必须满足:

  • 输入:当前 mech state + 控制命令 + spec 表
  • 输出:原地修改 mech
  • 无副作用:不打 log(log 由 caller 在 Writer monad 中处理)、不调 Bus、不调 dynamics_core

5.2 Mech 不感知 Bus

机械面只接受已经解码的具体命令EcuDriveCommandAngle target)。Bus 解码是电子面(ECU)的事情。

cpp
// 反模式
void step_engine_mech(EngineMech& mech, const bus::BusBuffer& bus, ...) {  // 错
    auto cmd = bus.poll<EcuPayload>(...);  // ← 机械面不应直接读 bus
}

// 正确
void step_engine_mech(EngineMech& mech, const EcuDriveCommand& cmd, ...) {
    // 命令已由 ECU 解析过
}

5.3 EcuDriveCommand 是电气-机械桥

cpp
// src/plant/hardware/EcuDriveCommand.h
struct EcuDriveCommand {
    Time   now;
    bool   is_ignite_request;
    bool   is_shutdown_request;
    double target_chamber_p;    // 目标燃烧室压力(由 ECU 根据 throttle 算)
    double valve_opening;       // 主阀门开度命令
};

这是 ECU → EngineMech 的唯一通信契约。Servo / Fin 通常直接传 Angle target,无需独立 Command 类型。


6. 测试策略

机械面适合 L1 bench 测试:

cpp
// tests/bench/test_engine_mech.cpp
TEST(EngineMech, IgnitionTransient) {
    EngineMech mech{};
    EngineSpec spec = load_test_spec();

    EcuDriveCommand ignite{ .is_ignite_request = true, .now = Time::zero(),
                            .target_chamber_p = spec.design_chamber_p };

    for (int i = 0; i < 1000; ++i) {
        step_engine_mech(mech, ignite, spec, Time::from_ms(1));
    }

    EXPECT_NEAR(mech.chamber_pressure, spec.design_chamber_p, 1e3);
    EXPECT_NEAR(mech.true_thrust, spec.thrust_curve.lookup(1.0), 100.0);
}

详见 08_Cross_Cutting/Testing_Framework.md 第 4 层 TDD 层级。


7. 反模式

反模式为什么不行
EngineMech 里持有 const EngineSpec*Spec 由 device 容器持有,mech 是纯状态
step_engine_mech 调用 bus.publish(...)机械面不感知 Bus;产出由 ECU 决定
step_*_mech 里修改 body.spatial越权;动力学积分是 dynamics_core 的事
EngineMech::compute_thrust() 成员方法力计算属 plant/physics/,不属机械
step_engine_mech 直接读 env::Atmosphere机械面不应该自己采样环境;这是 plant_physics + probe 的事
actual_angle 放到 ServoSpecSpec 是型号常量,actual 是运行时状态

8. Cross References

  • 电子面(ECU/SCU/FinCtrl)→ 03_Avionics_and_Bus/Device_Dual_Face.md
  • Spec 表如何持有 → 02_Physical_World/Plant_Model_Assets.md
  • 力学函数如何读 mech → 02_Physical_World/Plant_Physics_Constitutive.md
  • 双面实体在 RocketBody 内的装配 → 06_Simulation/RocketBody_Composite.md
  • Blueprint §7.5 设计裁定(双重实体论)