feat(mavlink): adopt the kaptan-native dialect

This commit is contained in:
Onur Özkan committed 2026-08-24 00:56:06 +03:00
1 parent 1c90870634
commit 5f91465e66
87 files changed
+7733 -6850

No files matched your search

@@ -32,11 +32,11 @@ struct MavlinkConfig {
struct TelemetryConfig {
#[config(
id = 103,
key = "telemetry.raw_imu_hz",
key = "telemetry.imu_hz",
unit = "Hz",
range(min = 1, max = 100)
)]
raw_imu_hz: u8,
imu_hz: u8,
#[config(
id = 104,
key = "telemetry.heartbeat_hz",
@@ -91,7 +91,7 @@ struct OneRecord<C>(Option<C>);
impl<C: Clone> ConfigStore<C> for OneRecord<C> {
type Error = ();
fn load(&mut self) -> Result<Option<C>, Self::Error> {
fn load(&mut self, _: &C) -> Result<Option<C>, Self::Error> {
Ok(self.0.take())
}
@@ -109,7 +109,7 @@ fn factory() -> TestUserConfig {
log_level_filter: LevelFilter::Info,
},
telemetry: TelemetryConfig {
raw_imu_hz: 20,
imu_hz: 20,
heartbeat_hz: 2,
},
driver: DriverConfig { driver_gain: 7 },
@@ -123,6 +123,7 @@ struct MalformedProjection {
impl Config for MalformedProjection {
const FIELD_COUNT: usize = TestUserConfig::FIELD_COUNT;
const SNAPSHOT_VALUE_BYTES: usize = TestUserConfig::SNAPSHOT_VALUE_BYTES;
const SCHEMA_ID: [u8; 16] = TestUserConfig::SCHEMA_ID;
fn descriptor(index: usize) -> Option<&'static kaptan_config::FieldDescriptor> {
@@ -159,7 +160,7 @@ fn mavlink_logging_policy_is_projected_and_validated_independently() {
);
let mut candidate = UserConfigModel::<TestUserConfig, F722ImmutableConfig>::new(factory());
candidate
.set_field(FieldId::new(105), ConfigValue::U32(0))
.set_field(FieldId::new(105), ConfigValue::U8(0))
.unwrap();
let active = candidate.validate(&IMMUTABLE).unwrap();
@@ -168,17 +169,13 @@ fn mavlink_logging_policy_is_projected_and_validated_independently() {
}
#[test]
fn runtime_projection_rejects_missing_wrong_kind_and_wide_semantic_values() {
fn runtime_projection_rejects_missing_and_wrong_kind_values() {
for (system_id, rule) in [
(None, kaptan_config::ValidationRule::Inconsistent),
(
Some(ConfigValue::Bool(true)),
kaptan_config::ValidationRule::Inconsistent,
),
(
Some(ConfigValue::U32(256)),
kaptan_config::ValidationRule::AboveMaximum,
),
] {
let malformed = MalformedProjection {
inner: factory(),
@@ -195,7 +192,7 @@ fn runtime_construction_reads_the_persisted_board_owned_user_root() {
let factory = UserConfigModel::<TestUserConfig, F722ImmutableConfig>::new(factory());
let mut persisted = factory.clone();
persisted
.set_field(FieldId::new(1000), ConfigValue::U32(42))
.set_field(FieldId::new(1000), ConfigValue::U8(42))
.expect("driver field must accept a valid persisted value");
let manager = ConfigManager::recover_compatible_or_factory(
@@ -207,7 +204,7 @@ fn runtime_construction_reads_the_persisted_board_owned_user_root() {
assert_eq!(
manager.active().field_value(FieldId::new(1000)),
Some(ConfigValue::U32(42)),
Some(ConfigValue::U8(42)),
"runtime construction must receive the validated persisted field"
);
}
@@ -215,19 +212,19 @@ fn runtime_construction_reads_the_persisted_board_owned_user_root() {
#[test]
fn committing_a_user_edit_does_not_mutate_the_boot_active_view() {
let factory = UserConfigModel::<TestUserConfig, F722ImmutableConfig>::new(factory());
let mut manager = ConfigManager::new(factory, IMMUTABLE, OneRecord(None)).unwrap();
manager.begin().unwrap();
manager
.set(FieldId::new(1000), ConfigValue::U32(42))
let mut manager = ConfigManager::recover(factory, IMMUTABLE, OneRecord(None)).unwrap();
let mut replacement = manager.committed().clone();
replacement
.set_field(FieldId::new(1000), ConfigValue::U8(42))
.unwrap();
manager.commit().unwrap();
manager.replace(replacement).unwrap();
assert_eq!(
manager.active().field_value(FieldId::new(1000)),
Some(ConfigValue::U32(7))
Some(ConfigValue::U8(7))
);
assert_eq!(
manager.committed_value(FieldId::new(1000)),
Some(ConfigValue::U32(42))
manager.committed().field_value(FieldId::new(1000)),
Some(ConfigValue::U8(42))
);
}
@@ -141,13 +141,17 @@ fn ordinary_f722_image_has_direct_configuration_after_output_inhibition() {
"ordinary board images must not hide configuration behind a development feature"
);
for required in [
"configuration: F722ConfigManager",
"config_protocol: ConfigProtocol",
"receiver: IntegrityMavlinkReceiver<2>",
"config_lease_ticks: u16",
"wire::decode_command",
"self.config_protocol.dispatch_from_composition",
"visit_response(response, &mut sender)",
"config_file: F722ConfigFile",
"file_service: FileService",
"receiver: IntegrityMavlinkReceiver<1>",
"file_lease_ticks: u16",
"TRANSFER_REPLAY_LEASE_SECONDS",
"let Some(message) = frame.decode()",
"wire::decode_request",
".dispatch(&mut self.config_file",
"send_file_response",
"let class = kaptan_mavlink::transmitter::FrameClass::Critical",
"control.status != FileStatus::RequestConflict",
] {
assert!(
board.contains(required),
@@ -163,23 +167,15 @@ fn ordinary_f722_image_has_direct_configuration_after_output_inhibition() {
"the MPU runtime may drive only PB2 atomically, not retain GPIOB ownership"
);
assert!(!board.contains("development-config"));
assert!(
!board.contains("ArmRequest"),
"telemetry must not decode arm commands"
);
assert!(
!board.contains("MAV_CMD_COMPONENT_ARM_DISARM") && !board.contains("decode_arm_request"),
"telemetry must not accept an arm command"
);
assert!(
!firmware.contains("shared = [mavlink, motors]"),
"the MAVLink worker must not share a motor resource"
);
assert!(
firmware.contains("const MAVLINK_EVENT_RECEIVED")
&& firmware.contains("mavlink.receive_config_interrupt(event)")
&& firmware.contains("mavlink.receive_mavlink_interrupt(event)")
&& !firmware.contains("development-config"),
"ordinary images must always dispatch received configuration frames"
"ordinary images must always dispatch received MAVLink frames"
);
}
@@ -279,7 +275,7 @@ fn configuration_schema_is_generated_without_board_forwarding_code() {
for semantic_id in [
"MAVLINK_SYSTEM_ID",
"MAVLINK_COMPONENT_ID",
"RAW_IMU_HZ",
"IMU_HZ",
"HEARTBEAT_HZ",
"MAVLINK_LOG_LEVEL_FILTER",
"RANGE_HZ",
@@ -330,8 +326,8 @@ fn f722_link_budget_reserves_only_deterministic_telemetry() {
"ERROR_RECORDS_PER_SECOND",
"LOG_RECORDS_PER_SECOND",
"WORST_CASE_OBSERVABILITY_BYTES_PER_SECOND",
"KAPTAN_ERROR_FRAME_MAXIMUM_BYTES",
"KAPTAN_LOG_FRAME_MAXIMUM_BYTES",
"ERROR_FRAME_MAXIMUM_BYTES",
"LOG_FRAME_MAXIMUM_BYTES",
"budget_ticks",
"error_budget",
"log_budget",
@@ -385,6 +381,7 @@ fn mavlink_transport_faults_do_not_recurse_or_retain_dead_accounting() {
#[test]
fn f722_producers_keep_logs_and_reportable_errors_separate() {
let board = platform_source("src/f722/board.rs");
let config = platform_source("src/f722/config.rs");
let logger = platform_source("../../libs/kaptan-log/src/facade.rs");
let errors = platform_source("../../libs/kaptan-err-reportable/src/ingress.rs");
@@ -400,6 +397,14 @@ fn f722_producers_keep_logs_and_reportable_errors_separate() {
);
}
assert!(board.contains("#[kaptan_err_reportable::propagate]"));
assert!(
config.contains("enum PersistConfigurationError {")
&& config.contains("#[kaptan_error(id = 2210)]")
&& config.contains("#[kaptan_error(id = 2209)]")
&& config.contains("impl From<JournalError<F722FlashError>>")
&& config.contains("fn persist_candidate(")
&& config.contains("#[kaptan_err_reportable::propagate]")
);
assert!(!board.contains("report_error"));
assert!(!board.contains("report_static_error"));
assert!(logger.contains("GLOBAL_INGRESS_CAPACITY"));