Files
kaptan/firmware/libs/kaptan-flight-config/src/profile/control.rs
T
Onur Özkan f9a61a086f 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.
2026-11-07 18:12:11 +03:00

243 lines
6.4 KiB
Rust

use kaptan_config::{Config, ConfigViolation, ValidationRule};
use super::ids;
/// 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",
unit = "1/s2",
finite,
range(min = 0.0, max = 100.0)
)]
pub position_gain_per_second_squared: f32,
#[config(
id = 9,
key = "control.velocity_gain_per_s",
unit = "1/s",
finite,
range(min = 0.0, max = 100.0)
)]
pub velocity_gain_per_second: f32,
#[config(
id = 10,
key = "control.maximum_horizontal_acceleration_mps2",
unit = "m/s2",
finite,
range(min = 0.001, max = 100.0)
)]
pub maximum_horizontal_acceleration_mps2: f32,
#[config(
id = 11,
key = "control.maximum_upward_acceleration_mps2",
unit = "m/s2",
finite,
range(min = 0.001, max = 100.0)
)]
pub maximum_upward_acceleration_mps2: f32,
#[config(
id = 12,
key = "control.maximum_downward_acceleration_mps2",
unit = "m/s2",
finite,
range(min = 0.001, max = 100.0)
)]
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,
}
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 struct ThrustSettings {
#[config(
id = 13,
key = "thrust.mass_kg",
unit = "kg",
finite,
range(min = 0.01, max = 100.0)
)]
pub mass_kg: f32,
#[config(
id = 14,
key = "thrust.maximum_collective_thrust_n",
unit = "N",
finite,
range(min = 0.1, max = 10_000.0)
)]
pub maximum_collective_thrust_newtons: f32,
#[config(
id = 15,
key = "thrust.maximum_tilt_tangent",
unit = "",
finite,
range(min = 0.001, max = 10.0)
)]
pub maximum_tilt_tangent: f32,
}
impl ThrustSettings {
fn validate_thrust(&self) -> Result<(), ConfigViolation> {
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,
ValidationRule::Inconsistent,
));
}
Ok(())
}
}
/// 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 struct PidSettings {
#[config(
id = 16,
key = "control.rate_pid.proportional",
unit = "",
finite,
range(min = 0.0, max = 10.0)
)]
pub proportional: f32,
#[config(
id = 17,
key = "control.rate_pid.integral",
unit = "",
finite,
range(min = 0.0, max = 10.0)
)]
pub integral: f32,
#[config(
id = 18,
key = "control.rate_pid.derivative",
unit = "",
finite,
range(min = 0.0, max = 10.0)
)]
pub derivative: f32,
#[config(
id = 19,
key = "control.rate_pid.output_limit",
unit = "",
finite,
range(min = 0.001, max = 1.0)
)]
pub output_limit: f32,
#[config(
id = 20,
key = "control.rate_pid.integral_limit",
unit = "",
finite,
range(min = 0.0, max = 1.0)
)]
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 PidSettings {
fn validate_pid(&self) -> Result<(), ConfigViolation> {
if self.integral_limit > self.output_limit {
return Err(ConfigViolation::related(
ids::RATE_PID_INTEGRAL_LIMIT,
ids::RATE_PID_OUTPUT_LIMIT,
ValidationRule::Inconsistent,
));
}
Ok(())
}
}
/// Actuator command bounds.
#[derive(Clone, Copy, Debug, PartialEq, Config)]
#[config(post = Self::validate_actuator_limits)]
pub struct ActuatorLimits {
#[config(
id = 21,
key = "actuator.minimum",
unit = "",
finite,
range(min = 0.0, max = 1.0)
)]
pub minimum: f32,
#[config(
id = 22,
key = "actuator.maximum",
unit = "",
finite,
range(min = 0.0, max = 1.0)
)]
pub maximum: f32,
#[config(
id = 23,
key = "actuator.slew_per_second",
unit = "1/s",
finite,
range(min = 0.001, max = 100.0)
)]
pub slew_per_second: f32,
}
impl ActuatorLimits {
fn validate_actuator_limits(&self) -> Result<(), ConfigViolation> {
if self.minimum >= self.maximum {
return Err(ConfigViolation::related(
ids::ACTUATOR_MINIMUM,
ids::ACTUATOR_MAXIMUM,
ValidationRule::Inconsistent,
));
}
Ok(())
}
}