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::EngineMech | avionics::device::ecu::EcuState |
| 伺服 | plant::hardware::ServoMech | avionics::device::scu::ScuState |
| 舵翼 | plant::hardware::FinMech | avionics::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::hardware2.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::hardware3.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
机械面只接受已经解码的具体命令(EcuDriveCommand、Angle 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 放到 ServoSpec 里 | Spec 是型号常量,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 设计裁定(双重实体论)