feat(mavlink): adopt the kaptan-native dialect
This commit is contained in:
1 parent
1c90870634
commit
5f91465e66
87 files changed
+7733
-6850
No files matched your search
@@ -12,12 +12,9 @@ workspace = true
|
||||
kaptan-err-reportable = { path = "../../libs/kaptan-err-reportable" }
|
||||
kaptan-control = { path = "../../libs/kaptan-control" }
|
||||
kaptan-config = { path = "../../libs/kaptan-config" }
|
||||
kaptan-config-mavlink = { path = "../../libs/kaptan-config-mavlink" }
|
||||
kaptan-flight-config = { path = "../../libs/kaptan-flight-config" }
|
||||
kaptan-flight-runtime = { path = "../../libs/kaptan-flight-runtime" }
|
||||
kaptan-log = { path = "../../libs/kaptan-log" }
|
||||
kaptan-diagnostics-mavlink = { path = "../../libs/kaptan-diagnostics-mavlink" }
|
||||
kaptan-mavlink = { path = "../../libs/kaptan-mavlink" }
|
||||
kaptan-math = { path = "../../libs/kaptan-math" }
|
||||
kaptan-navigation = { path = "../../libs/kaptan-navigation", features = ["sitl"] }
|
||||
kaptan-safety = { path = "../../libs/kaptan-safety" }
|
||||
@@ -25,4 +22,5 @@ kaptan-time = { path = "../../libs/kaptan-time" }
|
||||
minicbor = "0.25"
|
||||
|
||||
[dev-dependencies]
|
||||
mavlink = { version = "0.18", default-features = false, features = ["embedded"] }
|
||||
kaptan-diagnostics-mavlink = { path = "../../libs/kaptan-diagnostics-mavlink" }
|
||||
kaptan-mavlink = { path = "../../libs/kaptan-mavlink", default-features = false }
|
||||
@@ -4,6 +4,7 @@
|
||||
//! need a reviewed A/B journal and authority path; SITL uses a same-directory
|
||||
//! file replacement to make startup and configuration tests reproducible.
|
||||
|
||||
#[cfg(test)]
|
||||
mod logging;
|
||||
mod runtime;
|
||||
mod store;
|
||||
@@ -11,7 +12,7 @@ mod store;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use runtime::{CAPABILITIES, SitlConfigRuntime, configuration_service};
|
||||
pub(super) use runtime::{SitlConfigRuntime, configuration_service};
|
||||
pub use store::{
|
||||
FileConfigStore, FileStoreError, default_profile, load_profile, write_default_profile,
|
||||
};
|
||||
@@ -1,49 +1,27 @@
|
||||
//! Central SITL configuration and protocol composition.
|
||||
//! Central SITL configuration composition.
|
||||
|
||||
use kaptan_config::ConfigManager;
|
||||
use kaptan_config_mavlink::{ConfigCommand, ConfigProtocol, Principal, ServiceResponse, Target};
|
||||
#[cfg(test)]
|
||||
use kaptan_diagnostics_mavlink::encode_log;
|
||||
use kaptan_flight_config::{CandidateProfile, VehicleCapabilities, VehicleProfile};
|
||||
use kaptan_flight_runtime::FlightRuntime;
|
||||
#[cfg(test)]
|
||||
use kaptan_log::TransportAdmission;
|
||||
use kaptan_mavlink::message::kaptan::KAPTAN_LOG_DATA;
|
||||
#[cfg(test)]
|
||||
use kaptan_mavlink::generated::kaptan::LOG_DATA;
|
||||
|
||||
use super::{FileConfigStore, logging};
|
||||
use super::FileConfigStore;
|
||||
#[cfg(test)]
|
||||
use super::logging;
|
||||
|
||||
/// SITL's immutable compiled capability contract.
|
||||
pub const CAPABILITIES: VehicleCapabilities = VehicleCapabilities::quad_x();
|
||||
pub(super) const CAPABILITIES: VehicleCapabilities = VehicleCapabilities::quad_x();
|
||||
|
||||
/// SITL composition root retaining the one profile manager and one protocol
|
||||
/// lease state machine. The MAVLink adapter never owns either resource.
|
||||
/// SITL composition root retaining the one profile manager.
|
||||
pub struct SitlConfigRuntime {
|
||||
runtime: FlightRuntime<FileConfigStore>,
|
||||
protocol: ConfigProtocol,
|
||||
}
|
||||
|
||||
impl SitlConfigRuntime {
|
||||
/// Dispatches one command for a caller-owned lease principal.
|
||||
pub fn dispatch(&mut self, input: (Principal, ConfigCommand)) -> Vec<ServiceResponse> {
|
||||
let (principal, command) = input;
|
||||
let mut responses = Vec::new();
|
||||
self.protocol
|
||||
.dispatch_from_composition(
|
||||
self.runtime.configuration_mut(),
|
||||
principal,
|
||||
command,
|
||||
|response| {
|
||||
responses.push(response);
|
||||
Ok::<(), core::convert::Infallible>(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
responses
|
||||
}
|
||||
|
||||
/// Returns the central manager for SITL state inspection only.
|
||||
pub const fn manager(&self) -> &ConfigManager<CandidateProfile, FileConfigStore> {
|
||||
self.runtime.configuration()
|
||||
}
|
||||
|
||||
/// Returns the immutable profile selected by the central manager at boot.
|
||||
pub const fn active_profile(&self) -> VehicleProfile {
|
||||
*self.runtime.configuration().active()
|
||||
@@ -52,7 +30,8 @@ impl SitlConfigRuntime {
|
||||
/// Drains one standard `log` facade record and exposes the generated
|
||||
/// diagnostic payload for the host's sole MAVLink link owner. This
|
||||
/// host-only harness accepts the bounded handoff synchronously.
|
||||
pub fn pending_log_payload(&mut self) -> Option<KAPTAN_LOG_DATA> {
|
||||
#[cfg(test)]
|
||||
pub(super) fn pending_log_payload(&mut self) -> Option<LOG_DATA> {
|
||||
let runtime = &mut self.runtime;
|
||||
let _ = logging::drain_one(|record| runtime.publish_log_event(*record));
|
||||
self.runtime
|
||||
@@ -60,14 +39,9 @@ impl SitlConfigRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the host-only central configuration composition used by forthcoming
|
||||
/// SITL/GCS integration. Flight targets must supply authenticated authority
|
||||
/// and a reviewed journal/activation coordinator before constructing one.
|
||||
/// Builds the host-only central configuration composition.
|
||||
pub fn configuration_service(store: FileConfigStore) -> Result<SitlConfigRuntime, String> {
|
||||
let runtime = FlightRuntime::recover(CandidateProfile::factory(), CAPABILITIES, store)
|
||||
.map_err(|error| format!("configuration recovery failed: {error:?}"))?;
|
||||
Ok(SitlConfigRuntime {
|
||||
runtime,
|
||||
protocol: ConfigProtocol::new(Target::new(1, 1)),
|
||||
})
|
||||
Ok(SitlConfigRuntime { runtime })
|
||||
}
|
||||
@@ -13,7 +13,7 @@ use kaptan_flight_config::{
|
||||
decode_candidate, encode_candidate,
|
||||
};
|
||||
|
||||
use super::{CAPABILITIES, configuration_service};
|
||||
use super::{configuration_service, runtime::CAPABILITIES};
|
||||
|
||||
/// Host file-store error. It intentionally makes no flash power-loss claim.
|
||||
#[derive(Debug, KaptanError)]
|
||||
@@ -55,7 +55,7 @@ impl FileConfigStore {
|
||||
impl ConfigStore<CandidateProfile> for FileConfigStore {
|
||||
type Error = FileStoreError;
|
||||
|
||||
fn load(&mut self) -> Result<Option<CandidateProfile>, Self::Error> {
|
||||
fn load(&mut self, _: &CandidateProfile) -> Result<Option<CandidateProfile>, Self::Error> {
|
||||
let Some(path) = self.path.as_deref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -3,11 +3,21 @@ use std::fs;
|
||||
use kaptan_config::{ConfigModel, ConfigStore};
|
||||
use kaptan_flight_config::{CandidateProfile, decode_candidate};
|
||||
use kaptan_mavlink::{
|
||||
MavlinkFrame,
|
||||
encoder::MavlinkEncoder,
|
||||
message::{Message, kaptan::MavMessage},
|
||||
generated::kaptan::{LogLevel, MavMessage},
|
||||
receiver::IntegrityMavlinkReceiver,
|
||||
};
|
||||
|
||||
use super::{CAPABILITIES, FileConfigStore, configuration_service};
|
||||
use super::{FileConfigStore, configuration_service, runtime::CAPABILITIES};
|
||||
|
||||
fn parse_test_message(frame: &MavlinkFrame) -> Option<MavMessage> {
|
||||
let mut receiver = IntegrityMavlinkReceiver::<1>::new();
|
||||
for byte in frame.raw_bytes() {
|
||||
receiver.receive_byte(*byte);
|
||||
}
|
||||
receiver.take_received()?.decode()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_store_round_trips_a_complete_validated_snapshot() {
|
||||
@@ -20,20 +30,21 @@ fn host_store_round_trips_a_complete_validated_snapshot() {
|
||||
let _ = fs::remove_file(&path);
|
||||
let _ = fs::remove_file(&temporary);
|
||||
|
||||
let factory = CandidateProfile::factory();
|
||||
let mut store = FileConfigStore::new(&path);
|
||||
assert!(store.load().unwrap().is_none());
|
||||
store.persist(&CandidateProfile::factory()).unwrap();
|
||||
assert!(store.load(&factory).unwrap().is_none());
|
||||
store.persist(&factory).unwrap();
|
||||
let snapshot = fs::read(&path).unwrap();
|
||||
let decoded = decode_candidate(&snapshot, &CAPABILITIES).unwrap();
|
||||
assert!(decoded.validate(&CAPABILITIES).is_ok());
|
||||
let candidate = store.load().unwrap().expect("persisted candidate");
|
||||
let candidate = store.load(&factory).unwrap().expect("persisted candidate");
|
||||
assert!(candidate.validate(&CAPABILITIES).is_ok());
|
||||
|
||||
store.persist(&CandidateProfile::factory()).unwrap();
|
||||
store.persist(&factory).unwrap();
|
||||
let mut reopened = FileConfigStore::new(&path);
|
||||
assert!(reopened.load().unwrap().is_some());
|
||||
assert!(reopened.load(&factory).unwrap().is_some());
|
||||
let mut fresh = FileConfigStore::new(&path);
|
||||
fresh.persist(&CandidateProfile::factory()).unwrap();
|
||||
fresh.persist(&factory).unwrap();
|
||||
|
||||
fs::remove_file(&path).unwrap();
|
||||
}
|
||||
@@ -51,21 +62,17 @@ fn standard_log_macro_reaches_the_generated_mavlink_log_payload() {
|
||||
kaptan_log::info!("SITL configuration service ready");
|
||||
let payload = service
|
||||
.pending_log_payload()
|
||||
.expect("info facade record must reach KAPTAN_LOG");
|
||||
assert_eq!(payload.level, 6);
|
||||
.expect("info facade record must reach LOG");
|
||||
assert_eq!(payload.level, LogLevel::LOG_LEVEL_INFO);
|
||||
let message = std::str::from_utf8(&payload.message[..usize::from(payload.message_length)])
|
||||
.expect("facade text remains UTF-8");
|
||||
assert_eq!(message, "SITL configuration service ready");
|
||||
let mut encoder = MavlinkEncoder::new(1, 1);
|
||||
let frame = encoder.custom(&payload);
|
||||
let frame = encoder.encode(&payload);
|
||||
assert_eq!(frame.message_id(), 51_020);
|
||||
assert!(matches!(
|
||||
MavMessage::parse(
|
||||
mavlink::MavlinkVersion::V2,
|
||||
frame.message_id(),
|
||||
frame.payload(),
|
||||
),
|
||||
Ok(MavMessage::KAPTAN_LOG(data))
|
||||
parse_test_message(&frame),
|
||||
Some(MavMessage::LOG(data))
|
||||
if data.level == payload.level
|
||||
&& data.message_length == payload.message_length
|
||||
&& data.message == payload.message
|
||||
|
||||
Reference in new issue
Block a user