refactor(firmware): simplify configuration and diagnostics
This commit is contained in:
1 parent
ba9c97ed3b
commit
fdf82a5114
96 files changed
+1612
-2772
No files matched your search
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "kaptan-diagnostics-mavlink"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
kaptan-err-propagate = { path = "../kaptan-err-propagate" }
|
||||
kaptan-log = { path = "../kaptan-log" }
|
||||
kaptan-mavlink = { path = "../kaptan-mavlink", default-features = false, features = ["dialect-kaptan"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mavlink = { version = "0.18", default-features = false, features = ["embedded"] }
|
||||
@@ -0,0 +1,202 @@
|
||||
//! MAVLink projection for Kaptan logs and typed firmware errors.
|
||||
//!
|
||||
//! This adapter creates generated payload data only. A platform-owned link is
|
||||
//! solely responsible for MAVLink framing, bounded admission, DMA, and retry
|
||||
//! policy.
|
||||
|
||||
#![no_std]
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate std;
|
||||
|
||||
use kaptan_err_propagate::ErrorRecord;
|
||||
use kaptan_log::{Level, LogRecord, MAXIMUM_LOG_TEXT_BYTES};
|
||||
use kaptan_mavlink::message::kaptan::{KAPTAN_ERROR_DATA, KAPTAN_LOG_DATA, KaptanLogFlags};
|
||||
|
||||
/// Exact capacity of the fixed log text field in [`KAPTAN_LOG_DATA`].
|
||||
pub const LOG_MESSAGE_BYTES: usize = MAXIMUM_LOG_TEXT_BYTES;
|
||||
const ERROR_RAISED_AT_BYTES: usize = 96;
|
||||
|
||||
/// Encodes one ordinary log record as its sole wire message.
|
||||
pub fn encode_log(record: &LogRecord) -> KAPTAN_LOG_DATA {
|
||||
let mut flags = KaptanLogFlags::from_bits_retain(record.flags().bits());
|
||||
if record.level() == Level::Trace {
|
||||
flags.insert(KaptanLogFlags::KAPTAN_LOG_FLAG_TRACE);
|
||||
}
|
||||
|
||||
KAPTAN_LOG_DATA {
|
||||
level: mavlink_severity(record.level()),
|
||||
flags,
|
||||
message_length: record.message().len(),
|
||||
message: *record.message().bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes one typed firmware error as its sole wire message.
|
||||
pub fn encode_error(record: &ErrorRecord) -> KAPTAN_ERROR_DATA {
|
||||
let source = record.raised_at().as_bytes();
|
||||
let mut length = source.len().min(ERROR_RAISED_AT_BYTES);
|
||||
while !record.raised_at().is_char_boundary(length) {
|
||||
length -= 1;
|
||||
}
|
||||
let mut raised_at = [0; ERROR_RAISED_AT_BYTES];
|
||||
raised_at[..length].copy_from_slice(&source[..length]);
|
||||
|
||||
KAPTAN_ERROR_DATA {
|
||||
error_id: record.id(),
|
||||
raised_at,
|
||||
}
|
||||
}
|
||||
|
||||
/// Projects the five conventional levels into MAVLink's wider severity
|
||||
/// number space. This transport adapter is the only place that needs that
|
||||
/// protocol-specific representation.
|
||||
const fn mavlink_severity(level: Level) -> u8 {
|
||||
match level {
|
||||
Level::Error => 3,
|
||||
Level::Warn => 4,
|
||||
Level::Info => 6,
|
||||
Level::Debug | Level::Trace => 7,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{LOG_MESSAGE_BYTES, encode_error, encode_log};
|
||||
use kaptan_err_propagate::KaptanError;
|
||||
use kaptan_log::{Level, LogFlags, LogRecord, LogText};
|
||||
use kaptan_mavlink::{
|
||||
MavlinkVersion,
|
||||
encoder::MavlinkEncoder,
|
||||
message::{
|
||||
Message, MessageData,
|
||||
kaptan::{KAPTAN_ERROR_DATA, KAPTAN_LOG_DATA, KaptanLogFlags, MavMessage},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)]
|
||||
enum TestError {
|
||||
#[kaptan_error(id = 65301)]
|
||||
Transfer,
|
||||
}
|
||||
|
||||
#[kaptan_err_propagate::propagate]
|
||||
fn report_typed_error() -> Result<(), TestError> {
|
||||
Err(TestError::Transfer)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_record_preserves_level_flags_and_bounded_text() {
|
||||
let record = LogRecord::new(
|
||||
Level::Info,
|
||||
LogFlags::TRUNCATED,
|
||||
LogText::from_message("imu unavailable"),
|
||||
);
|
||||
let data = encode_log(&record);
|
||||
assert_eq!(data.level, 6);
|
||||
assert_eq!(data.message_length, 15);
|
||||
assert_eq!(&data.message[..15], b"imu unavailable");
|
||||
assert!(data.message[15..].iter().all(|byte| *byte == 0));
|
||||
assert_eq!(data.flags.bits(), LogFlags::TRUNCATED.bits());
|
||||
assert_eq!(KAPTAN_LOG_DATA::ENCODED_LEN, 163);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_level_owns_its_mavlink_projection() {
|
||||
let record = LogRecord::new(
|
||||
Level::Trace,
|
||||
LogFlags::NONE,
|
||||
LogText::from_message("scheduler tick"),
|
||||
);
|
||||
let data = encode_log(&record);
|
||||
assert_eq!(data.level, 7);
|
||||
assert!(data.flags.contains(KaptanLogFlags::KAPTAN_LOG_FLAG_TRACE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_macro_remains_an_error_level_log() {
|
||||
while kaptan_log::drain_one(|_| ()).is_some() {}
|
||||
kaptan_log::error!("flight fault");
|
||||
|
||||
let payload = kaptan_log::drain_one(encode_log).expect("error-level log retained");
|
||||
assert_eq!(payload.level, 3);
|
||||
assert_eq!(
|
||||
&payload.message[..usize::from(payload.message_length)],
|
||||
b"flight fault"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_log_and_error_messages_round_trip() {
|
||||
let record = LogRecord::new(
|
||||
Level::Info,
|
||||
LogFlags::NONE,
|
||||
LogText::from_message(&"x".repeat(LOG_MESSAGE_BYTES)),
|
||||
);
|
||||
let data = encode_log(&record);
|
||||
let mut encoder = MavlinkEncoder::new(1, 1);
|
||||
let frame = encoder.custom(&data);
|
||||
assert!(frame.has_valid_crc::<MavMessage>());
|
||||
assert_eq!(frame.message_id(), 51_020);
|
||||
assert_eq!(frame.raw_bytes().len(), 175);
|
||||
assert!(matches!(
|
||||
MavMessage::parse(
|
||||
mavlink::MavlinkVersion::V2,
|
||||
frame.message_id(),
|
||||
frame.payload(),
|
||||
),
|
||||
Ok(MavMessage::KAPTAN_LOG(decoded)) if decoded == data
|
||||
));
|
||||
|
||||
while kaptan_err_propagate::take().is_some() {}
|
||||
assert_eq!(report_typed_error(), Err(TestError::Transfer));
|
||||
let error = kaptan_err_propagate::take().expect("typed error retained");
|
||||
let error_data = encode_error(&error);
|
||||
assert_eq!(error_data.error_id, error.id());
|
||||
assert_eq!(KAPTAN_ERROR_DATA::ENCODED_LEN, 98);
|
||||
let raised_at_length = error_data
|
||||
.raised_at
|
||||
.iter()
|
||||
.position(|byte| *byte == 0)
|
||||
.unwrap_or(error_data.raised_at.len());
|
||||
assert_eq!(
|
||||
&error_data.raised_at[..raised_at_length],
|
||||
error.raised_at().as_bytes()
|
||||
);
|
||||
assert!(
|
||||
error
|
||||
.raised_at()
|
||||
.contains("kaptan-diagnostics-mavlink/src/lib.rs:")
|
||||
);
|
||||
let error_frame = encoder.custom(&error_data);
|
||||
assert_eq!(error_frame.message_id(), 51_021);
|
||||
assert!(error_frame.raw_bytes().len() <= 110);
|
||||
assert!(matches!(
|
||||
MavMessage::parse(
|
||||
mavlink::MavlinkVersion::V2,
|
||||
error_frame.message_id(),
|
||||
error_frame.payload(),
|
||||
),
|
||||
Ok(MavMessage::KAPTAN_ERROR(decoded)) if decoded == error_data
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kaptan_error_wire_contract_is_stable() {
|
||||
assert_eq!(KAPTAN_ERROR_DATA::ID, 51_021);
|
||||
assert_eq!(KAPTAN_ERROR_DATA::EXTRA_CRC, 46);
|
||||
assert_eq!(KAPTAN_ERROR_DATA::ENCODED_LEN, 98);
|
||||
|
||||
let data = KAPTAN_ERROR_DATA {
|
||||
error_id: 1234,
|
||||
raised_at: [b'A'; 96],
|
||||
};
|
||||
assert_eq!(data.raised_at.len(), 96);
|
||||
|
||||
let mut payload = [0_u8; KAPTAN_ERROR_DATA::ENCODED_LEN];
|
||||
let length = data.ser(MavlinkVersion::V2, &mut payload);
|
||||
assert_eq!(length, KAPTAN_ERROR_DATA::ENCODED_LEN);
|
||||
assert_eq!(&payload[..2], &[210, 4]);
|
||||
assert_eq!(&payload[2..], &[b'A'; 96]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user