diff --git a/boards/geprc-f722-v2/src/board.rs b/boards/geprc-f722-v2/src/board.rs index 8fac9d3..283cda8 100644 --- a/boards/geprc-f722-v2/src/board.rs +++ b/boards/geprc-f722-v2/src/board.rs @@ -1,8 +1,8 @@ //! Declarative support for the GEPRC TAKER F722 BLS 60A V2 stack. use kaptan_board::{ - BoardConfig, BoardProfile, DataChannelRates, MavlinkEndpoint, TelemetryProfile, - VerticalSensingKind, + BoardConfig, BoardProfile, DataChannelRates, ImuDevice, ImuMount, MavlinkEndpoint, MavlinkPort, + Severity, TelemetryProfile, VerticalSensingKind, }; use platform_stm32f7::{ BoardError, F7Board, F7BoardBuilder, F722BoardRoutes, Icm42688, Mavlink, Pc3, VerticalSensor, @@ -17,8 +17,12 @@ impl F7Board for GeprcF722V2 { mavlink: MavlinkEndpoint { system_id: 1, component_id: 1, + port: MavlinkPort::Serial2, + baud: 115_200, }, profile: BoardProfile::TelemetryOnly(TelemetryProfile { + imu: ImuDevice::Icm42688, + imu_mount: ImuMount::Yaw180, vertical_sensing: VerticalSensingKind::None, data_channel: DataChannelRates { service_hz: 100, @@ -27,6 +31,7 @@ impl F7Board for GeprcF722V2 { optical_flow_hz: None, heartbeat_hz: 2, }, + maximum_log_level: Severity::Info, }), }; diff --git a/boards/kakute-f7-mini-v3/src/board.rs b/boards/kakute-f7-mini-v3/src/board.rs index a334932..dc10554 100644 --- a/boards/kakute-f7-mini-v3/src/board.rs +++ b/boards/kakute-f7-mini-v3/src/board.rs @@ -5,8 +5,8 @@ //! belong to the STM32F7 platform crate. use kaptan_board::{ - BoardConfig, BoardProfile, DataChannelRates, MavlinkEndpoint, TelemetryProfile, - VerticalSensingKind, + BoardConfig, BoardProfile, DataChannelRates, ImuDevice, ImuMount, MavlinkEndpoint, MavlinkPort, + Severity, TelemetryProfile, VerticalSensingKind, }; use platform_stm32f7::{ BoardError, F7Board, F7BoardBuilder, F722BoardRoutes, Mavlink, Mpu6000, Pa4, VerticalSensor, @@ -20,8 +20,12 @@ const CONFIG: BoardConfig = BoardConfig { mavlink: MavlinkEndpoint { system_id: 1, component_id: 1, + port: MavlinkPort::Serial1, + baud: 115_200, }, profile: BoardProfile::TelemetryOnly(TelemetryProfile { + imu: ImuDevice::Mpu6000, + imu_mount: ImuMount::BodyAligned, vertical_sensing: VerticalSensingKind::RangeAndOpticalFlow, data_channel: DataChannelRates { service_hz: 100, @@ -30,6 +34,7 @@ const CONFIG: BoardConfig = BoardConfig { optical_flow_hz: Some(20), heartbeat_hz: 2, }, + maximum_log_level: Severity::Info, }), }; diff --git a/libs/kaptan-board/Cargo.toml b/libs/kaptan-board/Cargo.toml index 27523a2..a540606 100644 --- a/libs/kaptan-board/Cargo.toml +++ b/libs/kaptan-board/Cargo.toml @@ -10,3 +10,4 @@ version.workspace = true workspace = true [dependencies] +kaptan-log = { path = "../kaptan-log" } diff --git a/libs/kaptan-board/src/lib.rs b/libs/kaptan-board/src/lib.rs index a6fab9b..343d226 100644 --- a/libs/kaptan-board/src/lib.rs +++ b/libs/kaptan-board/src/lib.rs @@ -6,6 +6,43 @@ #![no_std] +use kaptan_log::KaptanError; +pub use kaptan_log::Severity; + +/// Physical IMU device selected by a board declaration. +/// +/// This is immutable board topology, not a runtime-selectable sensor driver. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ImuDevice { + /// InvenSense MPU-6000 on the board's selected SPI route. + Mpu6000, + /// TDK ICM-42688-P on the board's selected SPI route. + Icm42688, +} + +/// Fixed board-to-body IMU mounting transform. +/// +/// Calibration remains a separate, safety-gated workflow. This declaration +/// only selects the compiled transform for the physical board route. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ImuMount { + /// Sensor axes already match the controller body axes. + BodyAligned, + /// Sensor is yawed 180° relative to the controller body axes. + Yaw180, +} + +/// Logical outbound MAVLink serial endpoint selected by a board declaration. +/// +/// The platform maps these logical names to its checked pin/DMA routes. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MavlinkPort { + /// First supported board serial endpoint. + Serial1, + /// Second supported board serial endpoint. + Serial2, +} + /// The vertical-sensing data a board support package makes available. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] @@ -25,6 +62,10 @@ pub struct MavlinkEndpoint { pub system_id: u8, /// MAVLink component identifier. pub component_id: u8, + /// Immutable physical serial endpoint selected for this board image. + pub port: MavlinkPort, + /// UART line rate used by that physical endpoint. + pub baud: u32, } /// Fixed outbound data-channel rates driven by a platform service timer. @@ -45,10 +86,20 @@ pub struct DataChannelRates { /// Telemetry-only physical-sensor and outbound-data policy. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TelemetryProfile { + /// Immutable physical IMU driver selected by this board image. + pub imu: ImuDevice, + /// Immutable board-to-body transform applied to that IMU's samples. + pub imu_mount: ImuMount, /// Supported physical vertical-sensing capability. pub vertical_sensing: VerticalSensingKind, /// Data-channel timer and publication rates. pub data_channel: DataChannelRates, + /// Highest optional diagnostic severity forwarded to MAVLink. + /// + /// Errors remain observable even when optional debug/info diagnostics are + /// filtered. This setting is compile-time board policy until the target + /// has the reviewed configuration activation path. + pub maximum_log_level: Severity, } /// Exclusive build-time image selection. @@ -68,12 +119,10 @@ pub enum BoardProfile { /// The selected platform trait determines the MCU family. Keeping a free-form /// MCU string here would duplicate that type-level fact without validating it. /// -/// TODO(configuration): board images currently receive this complete profile -/// at build time. Ground-station configuration must stage and validate a -/// whole profile (identity, sensor topology, link rates, actuator profile, -/// and calibration revision) before it is persisted. It must never apply -/// independent field updates or permit an active output while a profile is -/// staged, committed, or restored after power loss. +/// Board policy is compiled and non-deserializable. The current F722 platform +/// validates it against typestate-selected electrical routes before it touches +/// hardware. Target-side runtime configuration remains disabled until its +/// separate authenticated, journalled, disarmed activation contract exists. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct BoardConfig { /// Stable board name used in build and diagnostic tooling. @@ -95,6 +144,12 @@ impl BoardConfig { /// Validates the selected exclusive profile. pub const fn validate(self) -> Result<(), BoardConfigError> { + if self.mavlink.system_id == 0 || self.mavlink.component_id == 0 { + return Err(BoardConfigError::InvalidMavlinkEndpoint); + } + if self.mavlink.baud == 0 { + return Err(BoardConfigError::ZeroMavlinkBaud); + } match self.profile { BoardProfile::TelemetryOnly(profile) => Self::validate_telemetry(profile), BoardProfile::ExternalControlOnly => { @@ -173,8 +228,12 @@ impl BoardConfig { } /// Invalid board-support configuration. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)] pub enum BoardConfigError { + /// MAVLink system/component identifiers must be exact nonzero values. + InvalidMavlinkEndpoint, + /// A physical MAVLink endpoint needs a nonzero UART rate. + ZeroMavlinkBaud, /// A data-channel schedule needs a nonzero service frequency. ZeroServiceFrequency, /// Raw IMU and heartbeat are mandatory on every flight board. @@ -192,17 +251,22 @@ pub enum BoardConfigError { #[cfg(test)] mod tests { use super::{ - BoardConfig, BoardConfigError, BoardProfile, DataChannelRates, MavlinkEndpoint, - TelemetryProfile, VerticalSensingKind, + BoardConfig, BoardConfigError, BoardProfile, DataChannelRates, ImuDevice, ImuMount, + MavlinkEndpoint, MavlinkPort, TelemetryProfile, VerticalSensingKind, }; + use kaptan_log::Severity; const BASE: BoardConfig = BoardConfig { name: "test", mavlink: MavlinkEndpoint { system_id: 1, component_id: 1, + port: MavlinkPort::Serial1, + baud: 115_200, }, profile: BoardProfile::TelemetryOnly(TelemetryProfile { + imu: ImuDevice::Mpu6000, + imu_mount: ImuMount::BodyAligned, vertical_sensing: VerticalSensingKind::RangeAndOpticalFlow, data_channel: DataChannelRates { service_hz: 100, @@ -211,6 +275,7 @@ mod tests { optical_flow_hz: Some(20), heartbeat_hz: 2, }, + maximum_log_level: Severity::Info, }), }; @@ -257,4 +322,18 @@ mod tests { Err(BoardConfigError::UnsupportedFlightProfile) ); } + + #[test] + fn rejects_invalid_immutable_link_policy() { + let mut config = BASE; + config.mavlink.baud = 0; + assert_eq!(config.validate(), Err(BoardConfigError::ZeroMavlinkBaud)); + + let mut config = BASE; + config.mavlink.component_id = 0; + assert_eq!( + config.validate(), + Err(BoardConfigError::InvalidMavlinkEndpoint) + ); + } }