130 lines
4.4 KiB
Rust
130 lines
4.4 KiB
Rust
//! 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.
|
|
|
|
#![no_std]
|
|
|
|
use kaptan_config::{ConfigManager, ProfileStore, StartupError};
|
|
use kaptan_flight_config::{CandidateProfile, VehicleCapabilities};
|
|
use kaptan_log::{EventHub, LogFlags, LogRecord, LogText, PublishOutcome, Severity};
|
|
|
|
/// Default bounded allocation for central runtime text diagnostics. Error
|
|
/// records are partitioned from optional detail.
|
|
pub type RuntimeLogHub = EventHub<8, 16>;
|
|
|
|
/// One boot-active vehicle runtime. It deliberately has no MAVLink dependency.
|
|
pub struct FlightRuntime<S> {
|
|
configuration: ConfigManager<CandidateProfile, S>,
|
|
logs: RuntimeLogHub,
|
|
}
|
|
|
|
impl<S> FlightRuntime<S>
|
|
where
|
|
S: ProfileStore<CandidateProfile>,
|
|
{
|
|
/// Recovers the central manager, validates its boot-active snapshot, and
|
|
/// copies the restart-only observability policy into the bounded hub.
|
|
pub fn recover(
|
|
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"),
|
|
));
|
|
Ok(Self {
|
|
configuration,
|
|
logs,
|
|
})
|
|
}
|
|
|
|
/// The sole central configuration manager.
|
|
pub const fn configuration(&self) -> &ConfigManager<CandidateProfile, S> {
|
|
&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)
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/// Peeks the highest-priority pending text log for the sole telemetry link.
|
|
pub fn pending_report(&self) -> Option<LogRecord> {
|
|
self.logs.peek()
|
|
}
|
|
|
|
/// Submits the next record and removes it only after transport admission.
|
|
pub fn submit_next<R>(
|
|
&mut self,
|
|
submit: impl FnOnce(&LogRecord) -> (kaptan_log::TransportAdmission, R),
|
|
) -> Option<R> {
|
|
self.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()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use kaptan_config::{Generation, ProfileStore};
|
|
use kaptan_log::{LogFlags, LogText, Severity};
|
|
|
|
use super::FlightRuntime;
|
|
|
|
struct Store;
|
|
impl ProfileStore<kaptan_flight_config::CandidateProfile> for Store {
|
|
type Error = ();
|
|
|
|
fn load(
|
|
&mut self,
|
|
) -> Result<Option<(Generation, kaptan_flight_config::CandidateProfile)>, Self::Error>
|
|
{
|
|
Ok(None)
|
|
}
|
|
|
|
fn persist(
|
|
&mut self,
|
|
_: &kaptan_flight_config::CandidateProfile,
|
|
) -> Result<Generation, Self::Error> {
|
|
Ok(Generation::new(1))
|
|
}
|
|
}
|
|
|
|
#[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");
|
|
assert_eq!(
|
|
runtime
|
|
.configuration()
|
|
.active()
|
|
.observability()
|
|
.maximum_log_level(),
|
|
runtime.log_filter().maximum_severity() as u32
|
|
);
|
|
let _ = runtime.log(
|
|
Severity::Info,
|
|
LogFlags::NONE,
|
|
LogText::from_message("runtime ready"),
|
|
);
|
|
assert!(runtime.pending_report().is_some());
|
|
}
|
|
}
|