Semantic Bus Pattern
> Aligned with PCR Master Blueprint v1.0 — see Blueprint §1.1(Bus 视角)、§7.1(Bus 裁定)、§3.2(FCC ↔ Bus 契约)。 > 职责:Bus 是 FCC ↔ Devices 之间的语义信道,采用 algebra + interpreter 模式:上层只看 BusMessage(语义),底层 interpreter 决定是否走 1553B / TTE / Semantic-passthrough。 > C-Distillation note:interpreter 的 vtable 在蒸馏后退化为函数指针表;SIL 路径下的 SemanticInterpreter 完全 inline 掉。
> Architectural Note (SIL vs HIL): > 主仿真路径优先用 Pure Delay Queue(Semantic Pass-Through)——不做协议序列化,零开销。 > 重 interpreter(1553B / TTE)仅作为 HIL 与硬件接驳时启用。
1. 核心概念
Bus 是语义解释器,把协议无关的 BusMessage 翻译成具体协议帧。带来三个性质:
- 协议无关:FCC / Device step 函数只看
BusMessage,与 1553B / TTE / 内存信道无关 - SIL ↔ HIL 平滑迁移:换 interpreter 不动控制逻辑
- 可测试:Mock interpreter 用于单元测试,真实 interpreter 用于硬件集成
与 Free Monad 模式的对称性
| 层 | 纯描述 | 解释器 | 执行 |
|---|---|---|---|
| FCC | Free<FccOp, A> | FccInterpreter | RWS<FccEnv, ...> |
| Bus | BusMessage | BusInterpreter | Frame<P> |
Bus 是 FCC algebraic interpretation 模式在通信层的对称结构。
2. 当前代码实现(与 Blueprint 略有差异)
实际代码位于 src/bus/:
// src/bus/IBus.h
namespace pcr::bus { // ⚠ namespace 仍为 pcr::bus;Blueprint 用 bus::
// 设备 step 函数用 forward decl `namespace bus { class IBus; }`
// 待统一为单一 namespace `bus::`
struct BusMessage {
uint32_t src; // 源地址(IMU_1_ADDR / FCC_MAIN_ADDR / …)
uint32_t dst; // 目的地址
Time emitted_at; // 仿真时戳
BusMessagePayload payload; // variant 类型,见下
};
class IBus {
public:
virtual ~IBus() = default;
virtual void publish(BusMessage msg) = 0;
virtual std::vector<BusMessage> poll(uint32_t local_addr) = 0;
virtual Time now() const = 0;
};
}BusMessagePayload 是 strong-typed variant,覆盖 4 类核心消息:
// src/bus/BusPayloads.h
using BusMessagePayload = std::variant<
ImuPayload, // IMU -> FCC: { delta_v_body, delta_theta_body }
GpsPayload, // GPS -> FCC: { pos_ecf, vel_ecf }
ScuPayload, // FCC -> SCU: { engine gimbal commands }
IcuPayload // FCC -> ICU: { Cmd { IgniteEngines / ShutdownEngines / FireSeparationBolt } + targets }
>;> Blueprint §7.1 裁定:"vtable + 强类型 payload variant。删 BusChannel.h + SignalTag。" 当前代码尚有 BusChannel.h / TransparentBus.h 等遗留文件,将在 Wave 1 清理。
3. Algebra:BusMessage(语义层)
BusMessage 是协议无关的语义数据包。代码中已实现为 pcr::bus::BusMessage,4 种 payload 通过 std::variant 标签。
未来扩展的语义消息(如 FCC ↔ FinCtrl、ICU 自身发往 FCC 的离散事件回执)按相同形态添加 payload struct + 加入 variant alternates。
4. 协议帧(蓝图扩展,HIL 阶段实装)
每个物理协议有自己的帧结构。当前 SIL 路径下未实装,作为 HIL roadmap 保留。
4.1 MIL-STD-1553B
struct Frame1553B {
struct CommandWord {
uint16_t rt_address : 5;
uint16_t tx_rx : 1;
uint16_t sub_addr : 5;
uint16_t word_count : 5;
};
struct StatusWord {
uint16_t rt_address : 5;
uint16_t msg_error : 1;
// … 其余位见标准
};
CommandWord command;
std::vector<uint16_t> data_words; // 最大 32 字
StatusWord status;
std::optional<PhysicalAttrs> physical; // 仅物理仿真时使用
};4.2 TTE(Time-Triggered Ethernet)
struct FrameTTE {
struct Header {
uint32_t integration_cycle;
uint16_t partition_id;
uint16_t message_id;
uint64_t sync_timestamp;
};
Header header;
std::vector<uint8_t> payload;
Time scheduled_arrival;
Time actual_arrival;
std::optional<PhysicalAttrs> physical;
};4.3 Semantic Pass-Through(SIL 默认)
struct FrameSemantic {
BusMessage msg; // 直接透传,零序列化
};5. Interpreter 接口
template <typename Frame>
class BusInterpreter {
public:
virtual ~BusInterpreter() = default;
virtual Frame encode(const BusMessage&) const = 0;
virtual std::expected<BusMessage, BusError> decode(const Frame&) const = 0;
virtual Frame apply_physical_effects(const Frame& f, const PhysicalConfig&) const {
return f; // 默认 no-op
}
};
struct BusError {
enum class Code { CRCFailure, Timeout, InvalidAddress, FrameDropped, ParityError };
Code code;
std::string message;
};6. 具体 Interpreter
6.1 SemanticInterpreter(SIL 默认)
class SemanticInterpreter : public BusInterpreter<FrameSemantic> {
public:
FrameSemantic encode(const BusMessage& m) const override { return {m}; }
std::expected<BusMessage, BusError> decode(const FrameSemantic& f) const override {
return f.msg;
}
};零开销,是 SIL 默认。
6.2 1553BInterpreter(HIL 用)
class Bus1553BInterpreter : public BusInterpreter<Frame1553B> {
std::map<uint32_t /*device addr*/, std::pair<uint8_t, uint8_t> /*RT, Subaddr*/> addr_map_;
PhysicalConfig physical_cfg_;
public:
Frame1553B encode(const BusMessage& m) const override {
Frame1553B fr;
std::visit([&](auto& p) {
using P = std::decay_t<decltype(p)>;
if constexpr (std::is_same_v<P, ImuPayload>) {
fr.command.rt_address = addr_map_.at(m.src).first;
fr.command.tx_rx = 1; // RT 向 BC 发送
fr.command.word_count = 8;
fr.data_words = pack_imu_1553b(p);
} else if constexpr (std::is_same_v<P, ScuPayload>) {
fr.command.tx_rx = 0; // BC 写到 RT
fr.data_words = pack_scu_1553b(p);
}
// … 其余 payload
}, m.payload);
return fr;
}
std::expected<BusMessage, BusError> decode(const Frame1553B& f) const override {
if (physical_cfg_.enabled && f.physical && !f.physical->crc_valid) {
return std::unexpected(BusError{BusError::Code::CRCFailure, "CRC failed"});
}
// … 根据 rt_address 派发 unpack
}
};6.3 TTEInterpreter(HIL 用)
略,结构类似,关键是 scheduled_arrival 与 actual_arrival 之间的 jitter 检查。
7. 物理仿真(可选)
启用后,interpreter 在帧上注入时延 / 抖动 / 比特错误:
struct PhysicalConfig {
bool enabled = false;
struct {
Time base_delay{Time::zero()};
Time jitter{Time::zero()};
std::optional<std::function<double()>> distribution; // e.g. Gaussian
} latency;
struct {
double bit_error_rate = 0.0;
double drop_rate = 0.0;
bool simulate_crc = false;
} errors;
};apply_physical_effects 在 encode 后调用:
auto frame = interpreter_->encode(msg);
frame = interpreter_->apply_physical_effects(frame, physical_cfg_);8. BusManager / TransparentBus
当前代码用 TransparentBus 作为 IBus 的简单内存实现(一个 FIFO 队列 per address)。它本身不是 interpreter——只是个邮局。Interpreter 模式将在 HIL 阶段引入 BusManager 持有 interpreter_ 与 transport_。
SIL 路径下,TransparentBus 自身就够用:BusMessage 直接走变体,零编解码。
// 简化:SIL 路径下 main loop
sim::body_tick(body_env, body) 中:
avionics::device::ecu::step(body.engines[i].ecu, body.engines[i].mech, *bus, cfg_ecu, dt);
avionics::device::imu::step(body.imus[i], body.aux, *bus, cfg_imu, dt);
// … 各设备 step 自己 publish / poll
fcc::tick(... , bus_msgs_in, fcc_out); // FCC 通过 BusManager 装解包 FccIn/FccOut Frame9. FCC 与 Bus 的耦合(Blueprint §3.2)
FCC 不直接依赖 bus::IBus。它的输入是 FccInFrame,输出是 FccOutFrame。 组装 / 拆解发生在 sim::body_tick 内:
Bus.poll(fcc.addr) → [BusMessage]
→ BusManager::decode_to_fcc_in (variant unpack)
→ FccInFrame
→ fcc::tick()
→ FccOutFrame
→ BusManager::encode_from_fcc_out (variant pack)
→ [BusMessage]
→ Bus.publish(...)这层装解包是 simulation 的责任,不是 FCC 的责任。FCC 与 Bus 协议的隔离由 simulation 强制保证。
10. 配置(YAML)
bus:
protocol: "Semantic" # "Semantic" | "1553B" | "TTE"
# SIL 仿真常用:只模拟时延
physical_simulation:
enabled: true
latency:
base_delay_ms: 0.5
jitter_ms: 0.1
errors:
bit_error_rate: 0.0
drop_rate: 0.0
simulate_crc: false
# 仅 1553B 用得到
address_map:
IMU_Primary: { rt: 0x01, subaddr: 0x01 }
Engine_1: { rt: 0x10, subaddr: 0x02 }11. 收益总结
| 属性 | 实现机制 |
|---|---|
| 协议无关 | FCC / Device step 只见 BusMessage,从未触碰物理协议字段 |
| SIL ↔ HIL 切换 | 换 interpreter,FCC / Device 代码 0 改动 |
| 可测试 | Mock IBus + Mock Interpreter 注入 |
| 物理精度可选 | PhysicalConfig.enabled 切位 |
| 类型安全 | std::variant<...Payload> 比 untagged buffer 更安全 |
12. 反模式
| 反模式 | 为什么不行 |
|---|---|
| 在 device step 内手动序列化为 1553B 字 | 破坏协议无关性;应交给 interpreter |
FCC 内直接调用 IBus::publish | 违反 §3.2:FCC 只看 FccIn/FccOut Frame |
把 BusMessage 的 payload 改为 void* / 裸 buffer | 失去 variant 类型安全 |
| 不同 Device 用不同 IBus 实现 | Bus 应该是单数;HIL 切换是替换整个 BusManager |
| 在 SIL 路径下也跑 1553BInterpreter | 浪费算力,违反 §7.12 "PoC 优先简洁" |
13. Cross References
- Bus 在系统中的视角定位 → Blueprint §1.1 "Bus 视角"
- Bus 与 FCC 的契约 → Blueprint §3.2
- 当前 BusMessage / payload 定义 →
src/bus/{IBus.h, BusPayloads.h} - 设备如何使用 IBus →
Device_Dual_Face.md(同目录)§3.1 - FCC Free Monad →
04_FCC/FCC_Algebraic_Interpretation.md - DynChannel(FCC↔Plant 边界)→ Blueprint §1.3