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
@@ -1,45 +1,48 @@
|
||||
//! Central, transport-neutral flight-runtime composition.
|
||||
//!
|
||||
//! This crate owns the one configuration manager and one bounded log hub for
|
||||
//! an image. MAVLink adapters receive mutable borrows for bounded dispatch or
|
||||
//! draining, but neither can own configuration lifecycle or link state.
|
||||
//! This crate owns the one configuration manager and the bounded queue for the
|
||||
//! image's MAVLink diagnostic sink. MAVLink adapters receive mutable borrows
|
||||
//! for bounded dispatch or draining, but cannot own configuration lifecycle or
|
||||
//! link state.
|
||||
|
||||
#![no_std]
|
||||
|
||||
use kaptan_config::{ConfigManager, ProfileStore, StartupError};
|
||||
use kaptan_config::{ConfigManager, ConfigStore, StartupError};
|
||||
use kaptan_flight_config::{CandidateProfile, VehicleCapabilities};
|
||||
use kaptan_log::{EventHub, LogFlags, LogRecord, LogText, PublishOutcome, Severity};
|
||||
use kaptan_log::{LogRecord, LogSinkQueue};
|
||||
|
||||
/// Default bounded allocation for central runtime text diagnostics. Error
|
||||
/// records are partitioned from optional detail.
|
||||
pub type RuntimeLogHub = EventHub<8, 16>;
|
||||
/// Default bounded allocation for the MAVLink diagnostic sink. Error records
|
||||
/// are partitioned from optional detail.
|
||||
pub type RuntimeMavlinkLogQueue = LogSinkQueue<8, 16>;
|
||||
|
||||
/// One boot-active vehicle runtime. It deliberately has no MAVLink dependency.
|
||||
pub struct FlightRuntime<S> {
|
||||
configuration: ConfigManager<CandidateProfile, S>,
|
||||
logs: RuntimeLogHub,
|
||||
mavlink_logs: RuntimeMavlinkLogQueue,
|
||||
}
|
||||
|
||||
impl<S> FlightRuntime<S>
|
||||
where
|
||||
S: ProfileStore<CandidateProfile>,
|
||||
S: ConfigStore<CandidateProfile>,
|
||||
{
|
||||
/// Recovers the central manager, validates its boot-active snapshot, and
|
||||
/// copies the restart-only observability policy into the bounded hub.
|
||||
/// copies the restart-only MAVLink sink policy into its bounded queue.
|
||||
pub fn recover(
|
||||
factory: CandidateProfile,
|
||||
capabilities: VehicleCapabilities,
|
||||
store: S,
|
||||
) -> Result<Self, StartupError<S::Error>> {
|
||||
let configuration =
|
||||
ConfigManager::recover(CandidateProfile::default(), capabilities, store)?;
|
||||
let maximum_log_level = configuration.active().observability().maximum_log_level();
|
||||
let logs = RuntimeLogHub::new(kaptan_log::LogFilter::new(
|
||||
Severity::try_from_wire(maximum_log_level as u8)
|
||||
.expect("validated flight profile has a MAVLink log severity"),
|
||||
));
|
||||
let configuration = ConfigManager::recover(factory, capabilities, store)?;
|
||||
let logging = configuration.active().mavlink_logging();
|
||||
let filter = if logging.enabled() {
|
||||
kaptan_log::LogFilter::up_to(logging.maximum_severity())
|
||||
} else {
|
||||
kaptan_log::LogFilter::disabled()
|
||||
};
|
||||
let mavlink_logs = RuntimeMavlinkLogQueue::new(filter);
|
||||
Ok(Self {
|
||||
configuration,
|
||||
logs,
|
||||
mavlink_logs,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,82 +51,108 @@ where
|
||||
&self.configuration
|
||||
}
|
||||
|
||||
/// Borrows the manager and hub together for a state-only protocol adapter.
|
||||
/// This makes adapter ownership inversion unrepresentable in composition.
|
||||
pub fn configuration_and_logs_mut(
|
||||
&mut self,
|
||||
) -> (&mut ConfigManager<CandidateProfile, S>, &mut RuntimeLogHub) {
|
||||
(&mut self.configuration, &mut self.logs)
|
||||
/// Mutably borrows the sole configuration manager for protocol dispatch.
|
||||
pub fn configuration_mut(&mut self) -> &mut ConfigManager<CandidateProfile, S> {
|
||||
&mut self.configuration
|
||||
}
|
||||
|
||||
/// Publishes one preformatted bounded task record through the sole hub.
|
||||
pub fn log(&mut self, severity: Severity, flags: LogFlags, message: LogText) -> PublishOutcome {
|
||||
self.logs.publish_text(severity, flags, message)
|
||||
/// Composition-owned fan-out boundary for one preformatted event.
|
||||
/// A future blackbox adds its independent sink admission here.
|
||||
pub fn publish_log_event(&mut self, record: LogRecord) {
|
||||
let _ = self.mavlink_logs.publish_record(record);
|
||||
}
|
||||
|
||||
/// Peeks the highest-priority pending text log for the sole telemetry link.
|
||||
pub fn pending_report(&self) -> Option<LogRecord> {
|
||||
self.logs.peek()
|
||||
pub fn pending_mavlink_report(&self) -> Option<LogRecord> {
|
||||
self.mavlink_logs.peek()
|
||||
}
|
||||
|
||||
/// Submits the next record and removes it only after transport admission.
|
||||
pub fn submit_next<R>(
|
||||
pub fn submit_next_mavlink<R>(
|
||||
&mut self,
|
||||
submit: impl FnOnce(&LogRecord) -> (kaptan_log::TransportAdmission, R),
|
||||
) -> Option<R> {
|
||||
self.logs.submit_next(submit)
|
||||
self.mavlink_logs.submit_next(submit)
|
||||
}
|
||||
|
||||
/// Immutable boot-active log policy copied from the validated profile.
|
||||
pub const fn log_filter(&self) -> kaptan_log::LogFilter {
|
||||
self.logs.filter()
|
||||
/// Immutable boot-active MAVLink log policy copied from the validated profile.
|
||||
pub const fn mavlink_log_filter(&self) -> kaptan_log::LogFilter {
|
||||
self.mavlink_logs.filter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kaptan_config::{Generation, ProfileStore};
|
||||
use kaptan_log::{LogFlags, LogText, Severity};
|
||||
use kaptan_config::{ConfigModel, ConfigStore, ConfigValue};
|
||||
use kaptan_log::{LogFlags, LogRecord, LogText, Severity};
|
||||
|
||||
use super::FlightRuntime;
|
||||
|
||||
struct Store;
|
||||
impl ProfileStore<kaptan_flight_config::CandidateProfile> for Store {
|
||||
impl ConfigStore<kaptan_flight_config::CandidateProfile> for Store {
|
||||
type Error = ();
|
||||
|
||||
fn load(
|
||||
&mut self,
|
||||
) -> Result<Option<(Generation, kaptan_flight_config::CandidateProfile)>, Self::Error>
|
||||
{
|
||||
fn load(&mut self) -> Result<Option<kaptan_flight_config::CandidateProfile>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
_: &kaptan_flight_config::CandidateProfile,
|
||||
) -> Result<Generation, Self::Error> {
|
||||
Ok(Generation::new(1))
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn central_runtime_uses_the_active_profile_filter_and_owns_one_hub() {
|
||||
let mut runtime =
|
||||
FlightRuntime::recover(kaptan_flight_config::VehicleCapabilities::quad_x(), Store)
|
||||
.expect("factory profile validates");
|
||||
fn runtime_uses_the_active_mavlink_policy_and_owns_one_sink_queue() {
|
||||
let mut runtime = FlightRuntime::recover(
|
||||
kaptan_flight_config::CandidateProfile::factory(),
|
||||
kaptan_flight_config::VehicleCapabilities::quad_x(),
|
||||
Store,
|
||||
)
|
||||
.expect("factory configuration validates");
|
||||
assert_eq!(
|
||||
runtime
|
||||
.configuration()
|
||||
.active()
|
||||
.observability()
|
||||
.maximum_log_level(),
|
||||
runtime.log_filter().maximum_severity() as u32
|
||||
.mavlink_logging()
|
||||
.maximum_severity(),
|
||||
runtime
|
||||
.mavlink_log_filter()
|
||||
.maximum_severity()
|
||||
.expect("factory enables MAVLink logs")
|
||||
);
|
||||
let _ = runtime.log(
|
||||
runtime.publish_log_event(LogRecord::new(
|
||||
Severity::Info,
|
||||
LogFlags::NONE,
|
||||
LogText::from_message("runtime ready"),
|
||||
);
|
||||
assert!(runtime.pending_report().is_some());
|
||||
));
|
||||
assert!(runtime.pending_mavlink_report().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_mavlink_sink_filters_every_severity() {
|
||||
let mut factory = kaptan_flight_config::CandidateProfile::factory();
|
||||
let enabled = kaptan_flight_config::CandidateProfile::descriptors()
|
||||
.find(|descriptor| descriptor.display_key == "mavlink.logging.enabled")
|
||||
.expect("MAVLink logging enable descriptor");
|
||||
factory
|
||||
.set_field(enabled.id, ConfigValue::Bool(false))
|
||||
.unwrap();
|
||||
let mut runtime = FlightRuntime::recover(
|
||||
factory,
|
||||
kaptan_flight_config::VehicleCapabilities::quad_x(),
|
||||
Store,
|
||||
)
|
||||
.expect("disabled MAVLink sink is valid");
|
||||
|
||||
assert_eq!(runtime.mavlink_log_filter().maximum_severity(), None);
|
||||
runtime.publish_log_event(LogRecord::new(
|
||||
Severity::Error,
|
||||
LogFlags::NONE,
|
||||
LogText::from_message("not routed to MAVLink"),
|
||||
));
|
||||
assert!(runtime.pending_mavlink_report().is_none());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user