feat(firmware): validate configuration and migrate sensor-driven flight APIs

Validate configuration through its owning schemas and keep board
declarations separate from shared F722 resource ownership. Bound USB
transitions, isolate transport failures and retain restart-only activation.

Migrate navigation and flight consumers with the sensor and schema
contracts. Require explicit installation data and synchronous output
authority; board motor outputs remain inhibited.

Validation: assembled-prefix host tests, strict Clippy, platform/USB tests
and both ARM release builds. Final-source coverage and scoped Miri/Kani
evidence are retained separately from hardware qualification.
This commit is contained in:
Onur Özkan committed 2026-11-07 18:12:11 +03:00
1 parent fb1b8af2d1
commit f9a61a086f
211 files changed
+45657 -12740

No files matched your search

@@ -2,28 +2,10 @@ use kaptan_config::{Config, ConfigViolation, ValidationRule};
use super::ids;
#[derive(Clone, Copy, Config)]
pub(super) struct AidingCandidate {
#[config(
id = 6,
key = "aiding.position_variance_m2",
unit = "m2",
finite,
range(min = 0.000_001, max = 10_000.0)
)]
pub(super) position_variance_m2: f32,
#[config(
id = 7,
key = "aiding.velocity_variance_m2ps2",
unit = "m2/s2",
finite,
range(min = 0.000_001, max = 10_000.0)
)]
pub(super) velocity_variance_m2_per_second_squared: f32,
}
#[derive(Clone, Copy, Config)]
pub(super) struct ControlCandidate {
/// Outer-loop controller policy.
#[derive(Clone, Copy, Debug, PartialEq, Config)]
#[config(post = Self::validate_control)]
pub struct ControlSettings {
#[config(
id = 8,
key = "control.position_gain_per_s2",
@@ -31,7 +13,7 @@ pub(super) struct ControlCandidate {
finite,
range(min = 0.0, max = 100.0)
)]
pub(super) position_gain_per_second_squared: f32,
pub position_gain_per_second_squared: f32,
#[config(
id = 9,
key = "control.velocity_gain_per_s",
@@ -39,7 +21,7 @@ pub(super) struct ControlCandidate {
finite,
range(min = 0.0, max = 100.0)
)]
pub(super) velocity_gain_per_second: f32,
pub velocity_gain_per_second: f32,
#[config(
id = 10,
key = "control.maximum_horizontal_acceleration_mps2",
@@ -47,7 +29,7 @@ pub(super) struct ControlCandidate {
finite,
range(min = 0.001, max = 100.0)
)]
pub(super) maximum_horizontal_acceleration_mps2: f32,
pub maximum_horizontal_acceleration_mps2: f32,
#[config(
id = 11,
key = "control.maximum_upward_acceleration_mps2",
@@ -55,7 +37,7 @@ pub(super) struct ControlCandidate {
finite,
range(min = 0.001, max = 100.0)
)]
pub(super) maximum_upward_acceleration_mps2: f32,
pub maximum_upward_acceleration_mps2: f32,
#[config(
id = 12,
key = "control.maximum_downward_acceleration_mps2",
@@ -63,11 +45,43 @@ pub(super) struct ControlCandidate {
finite,
range(min = 0.001, max = 100.0)
)]
pub(super) maximum_downward_acceleration_mps2: f32,
pub maximum_downward_acceleration_mps2: f32,
#[config(
id = 41,
key = "control.attitude_roll_pitch_gain_per_s",
unit = "1/s",
finite,
range(min = 0.0, max = 10.0)
)]
pub attitude_roll_pitch_gain_per_second: f32,
#[config(
id = 42,
key = "control.attitude_yaw_gain_per_s",
unit = "1/s",
finite,
range(min = 0.0, max = 10.0)
)]
pub attitude_yaw_gain_per_second: f32,
}
#[derive(Clone, Copy, Config)]
impl ControlSettings {
fn validate_control(&self) -> Result<(), ConfigViolation> {
// Positive multicopter thrust requires downward acceleration strictly
// below gravity. An inclusive scalar maximum cannot express this rule.
if self.maximum_downward_acceleration_mps2 >= kaptan_navigation::GRAVITY_MPS2 {
return Err(ConfigViolation::field(
ids::MAXIMUM_DOWNWARD_ACCELERATION_MPS2,
ValidationRule::Inconsistent,
));
}
Ok(())
}
}
/// Physical thrust envelope.
#[derive(Clone, Copy, Debug, PartialEq, Config)]
#[config(post = Self::validate_thrust)]
pub(super) struct ThrustCandidate {
pub struct ThrustSettings {
#[config(
id = 13,
key = "thrust.mass_kg",
@@ -75,7 +89,7 @@ pub(super) struct ThrustCandidate {
finite,
range(min = 0.01, max = 100.0)
)]
pub(super) mass_kg: f32,
pub mass_kg: f32,
#[config(
id = 14,
key = "thrust.maximum_collective_thrust_n",
@@ -83,7 +97,7 @@ pub(super) struct ThrustCandidate {
finite,
range(min = 0.1, max = 10_000.0)
)]
pub(super) maximum_collective_thrust_newtons: f32,
pub maximum_collective_thrust_newtons: f32,
#[config(
id = 15,
key = "thrust.maximum_tilt_tangent",
@@ -91,11 +105,16 @@ pub(super) struct ThrustCandidate {
finite,
range(min = 0.001, max = 10.0)
)]
pub(super) maximum_tilt_tangent: f32,
pub maximum_tilt_tangent: f32,
}
impl ThrustCandidate {
impl ThrustSettings {
fn validate_thrust(&self) -> Result<(), ConfigViolation> {
if self.maximum_collective_thrust_newtons <= self.mass_kg * 9.806_65 {
if self.maximum_collective_thrust_newtons <= self.mass_kg * kaptan_navigation::GRAVITY_MPS2
|| f64::from(self.maximum_collective_thrust_newtons)
> f64::from(self.mass_kg)
* (f64::from(kaptan_navigation::MAXIMUM_FREE_ACCELERATION_MPS2)
- f64::from(kaptan_navigation::GRAVITY_MPS2))
{
return Err(ConfigViolation::related(
ids::MAXIMUM_COLLECTIVE_THRUST_NEWTONS,
ids::MASS_KG,
@@ -106,9 +125,10 @@ impl ThrustCandidate {
}
}
#[derive(Clone, Copy, Config)]
/// Roll/pitch and yaw P/I gains with shared derivative and contribution limits.
#[derive(Clone, Copy, Debug, PartialEq, Config)]
#[config(post = Self::validate_pid)]
pub(super) struct PidCandidate {
pub struct PidSettings {
#[config(
id = 16,
key = "control.rate_pid.proportional",
@@ -116,7 +136,7 @@ pub(super) struct PidCandidate {
finite,
range(min = 0.0, max = 10.0)
)]
pub(super) proportional: f32,
pub proportional: f32,
#[config(
id = 17,
key = "control.rate_pid.integral",
@@ -124,7 +144,7 @@ pub(super) struct PidCandidate {
finite,
range(min = 0.0, max = 10.0)
)]
pub(super) integral: f32,
pub integral: f32,
#[config(
id = 18,
key = "control.rate_pid.derivative",
@@ -132,7 +152,7 @@ pub(super) struct PidCandidate {
finite,
range(min = 0.0, max = 10.0)
)]
pub(super) derivative: f32,
pub derivative: f32,
#[config(
id = 19,
key = "control.rate_pid.output_limit",
@@ -140,7 +160,7 @@ pub(super) struct PidCandidate {
finite,
range(min = 0.001, max = 1.0)
)]
pub(super) output_limit: f32,
pub output_limit: f32,
#[config(
id = 20,
key = "control.rate_pid.integral_limit",
@@ -148,9 +168,25 @@ pub(super) struct PidCandidate {
finite,
range(min = 0.0, max = 1.0)
)]
pub(super) integral_limit: f32,
pub integral_limit: f32,
#[config(
id = 43,
key = "control.yaw_rate_pid.proportional",
unit = "",
finite,
range(min = 0.0, max = 10.0)
)]
pub yaw_proportional: f32,
#[config(
id = 44,
key = "control.yaw_rate_pid.integral",
unit = "",
finite,
range(min = 0.0, max = 10.0)
)]
pub yaw_integral: f32,
}
impl PidCandidate {
impl PidSettings {
fn validate_pid(&self) -> Result<(), ConfigViolation> {
if self.integral_limit > self.output_limit {
return Err(ConfigViolation::related(
@@ -163,9 +199,10 @@ impl PidCandidate {
}
}
#[derive(Clone, Copy, Config)]
/// Actuator command bounds.
#[derive(Clone, Copy, Debug, PartialEq, Config)]
#[config(post = Self::validate_actuator_limits)]
pub(super) struct ActuatorLimitsCandidate {
pub struct ActuatorLimits {
#[config(
id = 21,
key = "actuator.minimum",
@@ -173,7 +210,7 @@ pub(super) struct ActuatorLimitsCandidate {
finite,
range(min = 0.0, max = 1.0)
)]
pub(super) minimum: f32,
pub minimum: f32,
#[config(
id = 22,
key = "actuator.maximum",
@@ -181,7 +218,7 @@ pub(super) struct ActuatorLimitsCandidate {
finite,
range(min = 0.0, max = 1.0)
)]
pub(super) maximum: f32,
pub maximum: f32,
#[config(
id = 23,
key = "actuator.slew_per_second",
@@ -189,9 +226,9 @@ pub(super) struct ActuatorLimitsCandidate {
finite,
range(min = 0.001, max = 100.0)
)]
pub(super) slew_per_second: f32,
pub slew_per_second: f32,
}
impl ActuatorLimitsCandidate {
impl ActuatorLimits {
fn validate_actuator_limits(&self) -> Result<(), ConfigViolation> {
if self.minimum >= self.maximum {
return Err(ConfigViolation::related(