refactor(firmware): centralize configuration and diagnostics
This commit is contained in:
1 parent
7048ed89d0
commit
387fdaad23
140 files changed
+6891
-4541
No files matched your search
@@ -0,0 +1,250 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use kaptan_config::{Config, ConfigManager, ConfigModel, ConfigStore, ConfigValue, FieldId};
|
||||
use platform_stm32f7::{
|
||||
F7ImmutableConfig, F722Hardware, F722ImmutableConfig, ImuMount, Mavlink, Mpu6000,
|
||||
Mpu6000Config, Pa4, Pb0Pb1Pb4Pb5Low, Pd2ActiveLow, Severity, VerticalSensor,
|
||||
};
|
||||
|
||||
// Include the implementation as a child module so this platform-internal test
|
||||
// can exercise the crate-private user-candidate adapter without flash hardware.
|
||||
#[path = "../src/f722/config.rs"]
|
||||
mod target_config;
|
||||
|
||||
use target_config::UserConfigModel;
|
||||
|
||||
#[derive(Clone, kaptan_config::Config)]
|
||||
struct MavlinkConfig {
|
||||
#[config(
|
||||
id = 101,
|
||||
key = "mavlink.system_id",
|
||||
unit = "id",
|
||||
range(min = 1, max = 255)
|
||||
)]
|
||||
system_id: u8,
|
||||
#[config(
|
||||
id = 102,
|
||||
key = "mavlink.component_id",
|
||||
unit = "id",
|
||||
range(min = 1, max = 255)
|
||||
)]
|
||||
component_id: u8,
|
||||
#[config(id = 108, key = "mavlink.logging.enabled", unit = "bool")]
|
||||
logging_enabled: bool,
|
||||
#[config(
|
||||
enum,
|
||||
id = 105,
|
||||
key = "mavlink.logging.maximum_severity",
|
||||
unit = "mav_severity"
|
||||
)]
|
||||
maximum_log_severity: Severity,
|
||||
}
|
||||
|
||||
#[derive(Clone, kaptan_config::Config)]
|
||||
struct TelemetryConfig {
|
||||
#[config(
|
||||
id = 103,
|
||||
key = "telemetry.raw_imu_hz",
|
||||
unit = "Hz",
|
||||
range(min = 1, max = 100)
|
||||
)]
|
||||
raw_imu_hz: u8,
|
||||
#[config(
|
||||
id = 104,
|
||||
key = "telemetry.heartbeat_hz",
|
||||
unit = "Hz",
|
||||
range(min = 1, max = 10)
|
||||
)]
|
||||
heartbeat_hz: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, kaptan_config::Config)]
|
||||
struct DriverConfig {
|
||||
#[config(
|
||||
id = 1000,
|
||||
key = "test_driver.gain",
|
||||
unit = "count",
|
||||
range(min = 1, max = 100)
|
||||
)]
|
||||
driver_gain: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, kaptan_config::Config)]
|
||||
struct TestUserConfig {
|
||||
mavlink: MavlinkConfig,
|
||||
telemetry: TelemetryConfig,
|
||||
driver: DriverConfig,
|
||||
}
|
||||
|
||||
const IMMUTABLE: F722ImmutableConfig = F722ImmutableConfig {
|
||||
name: "active-candidate-test",
|
||||
service_hz: 100,
|
||||
hardware: F722Hardware::new()
|
||||
.output_inhibit(Pb0Pb1Pb4Pb5Low)
|
||||
.imu(
|
||||
Mpu6000::on_spi1()
|
||||
.with_drdy(Pa4)
|
||||
.mounted(ImuMount::BodyAligned)
|
||||
.configured(Mpu6000Config {
|
||||
accelerometer: platform_stm32f7::AccelerometerRange::PlusMinus4G,
|
||||
gyroscope: platform_stm32f7::GyroscopeRange::PlusMinus500DegreesPerSecond,
|
||||
digital_low_pass_filter: platform_stm32f7::DigitalLowPassFilter::Bandwidth42Hz,
|
||||
sample_rate_divider: 9,
|
||||
}),
|
||||
)
|
||||
.mavlink(Mavlink::on_usart1().baud(115_200))
|
||||
.vertical_sensor(VerticalSensor::none())
|
||||
.status_led(Pd2ActiveLow)
|
||||
.telemetry_only(),
|
||||
};
|
||||
|
||||
struct OneRecord<C>(Option<C>);
|
||||
|
||||
impl<C: Clone> ConfigStore<C> for OneRecord<C> {
|
||||
type Error = ();
|
||||
|
||||
fn load(&mut self) -> Result<Option<C>, Self::Error> {
|
||||
Ok(self.0.take())
|
||||
}
|
||||
|
||||
fn persist(&mut self, candidate: &C) -> Result<(), Self::Error> {
|
||||
self.0 = Some(candidate.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn factory() -> TestUserConfig {
|
||||
TestUserConfig {
|
||||
mavlink: MavlinkConfig {
|
||||
system_id: 1,
|
||||
component_id: 1,
|
||||
logging_enabled: true,
|
||||
maximum_log_severity: Severity::Info,
|
||||
},
|
||||
telemetry: TelemetryConfig {
|
||||
raw_imu_hz: 20,
|
||||
heartbeat_hz: 2,
|
||||
},
|
||||
driver: DriverConfig { driver_gain: 7 },
|
||||
}
|
||||
}
|
||||
|
||||
struct MalformedProjection {
|
||||
inner: TestUserConfig,
|
||||
system_id: Option<ConfigValue>,
|
||||
}
|
||||
|
||||
impl Config for MalformedProjection {
|
||||
const FIELD_COUNT: usize = TestUserConfig::FIELD_COUNT;
|
||||
const SCHEMA_ID: [u8; 16] = TestUserConfig::SCHEMA_ID;
|
||||
|
||||
fn descriptor(index: usize) -> Option<&'static kaptan_config::FieldDescriptor> {
|
||||
TestUserConfig::descriptor(index)
|
||||
}
|
||||
|
||||
fn field_value(&self, id: FieldId) -> Option<ConfigValue> {
|
||||
if id == FieldId::new(101) {
|
||||
self.system_id
|
||||
} else {
|
||||
self.inner.field_value(id)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_field(
|
||||
&mut self,
|
||||
id: FieldId,
|
||||
value: ConfigValue,
|
||||
) -> Result<(), kaptan_config::FieldWriteError> {
|
||||
self.inner.set_field(id, value)
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), kaptan_config::ConfigViolation> {
|
||||
self.inner.validate()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mavlink_logging_policy_is_projected_and_validated_independently() {
|
||||
assert_eq!(
|
||||
core::mem::size_of::<target_config::F722UserRuntime>(),
|
||||
10,
|
||||
"runtime projection must retain narrow board-native scalar widths"
|
||||
);
|
||||
let mut candidate = UserConfigModel::<TestUserConfig, F722ImmutableConfig>::new(factory());
|
||||
candidate
|
||||
.set_field(FieldId::new(108), ConfigValue::Bool(false))
|
||||
.unwrap();
|
||||
candidate
|
||||
.set_field(FieldId::new(105), ConfigValue::U32(0))
|
||||
.unwrap();
|
||||
|
||||
let active = candidate.validate(&IMMUTABLE).unwrap();
|
||||
let runtime = target_config::project_runtime(&active, &IMMUTABLE).unwrap();
|
||||
assert!(!runtime.mavlink_logging_enabled);
|
||||
assert_eq!(runtime.mavlink_maximum_log_severity, Severity::Emergency);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_projection_rejects_missing_wrong_kind_and_wide_semantic_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(),
|
||||
system_id,
|
||||
};
|
||||
let issue = target_config::project_runtime(&malformed, &IMMUTABLE).unwrap_err();
|
||||
assert_eq!(issue.primary(), Some(FieldId::new(101)));
|
||||
assert_eq!(issue.rule(), rule);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
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))
|
||||
.expect("driver field must accept a valid persisted value");
|
||||
|
||||
let manager = ConfigManager::recover_compatible_or_factory(
|
||||
factory,
|
||||
IMMUTABLE,
|
||||
OneRecord(Some(persisted)),
|
||||
)
|
||||
.expect("synthetic persisted user configuration must recover");
|
||||
|
||||
assert_eq!(
|
||||
manager.active().field_value(FieldId::new(1000)),
|
||||
Some(ConfigValue::U32(42)),
|
||||
"runtime construction must receive the validated persisted field"
|
||||
);
|
||||
}
|
||||
|
||||
#[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))
|
||||
.unwrap();
|
||||
manager.commit().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
manager.active().field_value(FieldId::new(1000)),
|
||||
Some(ConfigValue::U32(7))
|
||||
);
|
||||
assert_eq!(
|
||||
manager.committed_value(FieldId::new(1000)),
|
||||
Some(ConfigValue::U32(42))
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user