refactor(firmware): centralize configuration and diagnostics

This commit is contained in:
Onur Özkan committed 2026-08-22 18:37:18 +03:00
1 parent 7048ed89d0
commit 387fdaad23
140 files changed
+6891 -4541

No files matched your search

+68 -47
View File
@@ -9,18 +9,21 @@
#[cfg(test)]
extern crate std;
use kaptan_log::{LogRecord, LogText, RecordChannel, ShutdownReason, ShutdownReport};
use kaptan_log::{
LogRecord, MAXIMUM_ERROR_ADDITIONAL_INFORMATION_BYTES, MAXIMUM_ERROR_PATH_BYTES,
MAXIMUM_LOG_TEXT_BYTES, RecordChannel, ShutdownReason, ShutdownReport,
};
use kaptan_mavlink::message::kaptan::{
KAPTAN_ERROR_DATA, KAPTAN_LOG_DATA, KAPTAN_SHUTDOWN_DATA, KaptanErrorFlags, KaptanLogFlags,
KaptanShutdownReason,
};
/// Exact capacity of the fixed text field in [`KAPTAN_LOG_DATA`].
pub const LOG_MESSAGE_BYTES: usize = 160;
pub const LOG_MESSAGE_BYTES: usize = MAXIMUM_LOG_TEXT_BYTES;
/// Exact capacity of the fixed error identity path field.
pub const ERROR_PATH_BYTES: usize = 96;
pub const ERROR_PATH_BYTES: usize = MAXIMUM_ERROR_PATH_BYTES;
/// Exact capacity of the fixed error additional-information field.
pub const ERROR_ADDITIONAL_INFORMATION_BYTES: usize = 64;
pub const ERROR_ADDITIONAL_INFORMATION_BYTES: usize = MAXIMUM_ERROR_ADDITIONAL_INFORMATION_BYTES;
/// One generated diagnostic payload. A platform-owned link remains the sole
/// MAVLink framing, admission, DMA, and retry owner.
@@ -45,8 +48,8 @@ pub fn encode_diagnostic(record: &LogRecord) -> DiagnosticPayload {
.expect("log channel must contain ordinary message text");
debug_assert!(usize::from(message.len()) <= LOG_MESSAGE_BYTES);
DiagnosticPayload::Log(KAPTAN_LOG_DATA {
level: record.severity() as u8,
flags: KaptanLogFlags::from_bits_retain(record.flags().bits()),
level: record.severity().wire_value(),
flags: KaptanLogFlags::from_bits_retain(record.flags().log_bits()),
message_length: message.len(),
message: *message.bytes(),
})
@@ -58,47 +61,29 @@ pub fn encode_diagnostic(record: &LogRecord) -> DiagnosticPayload {
let additional_information = record
.additional_information()
.expect("error channel must contain additional-information storage");
let (path, path_length, path_clipped) = project_text::<ERROR_PATH_BYTES>(path);
let (additional_information, additional_information_length, information_clipped) =
project_text::<ERROR_ADDITIONAL_INFORMATION_BYTES>(additional_information);
let mut flags = record.flags().error_bits();
if path_clipped || information_clipped {
flags |= KaptanErrorFlags::KAPTAN_ERROR_FLAG_TRUNCATED.bits();
}
debug_assert_eq!(path.bytes().len(), ERROR_PATH_BYTES);
debug_assert_eq!(
additional_information.bytes().len(),
ERROR_ADDITIONAL_INFORMATION_BYTES
);
DiagnosticPayload::Error(KAPTAN_ERROR_DATA {
flags: KaptanErrorFlags::from_bits_retain(flags),
path_length,
additional_information_length,
path,
additional_information,
level: record.severity().wire_value(),
flags: KaptanErrorFlags::from_bits_retain(record.flags().error_bits()),
path_length: path.len(),
additional_information_length: additional_information.len(),
path: *path.bytes(),
additional_information: *additional_information.bytes(),
})
}
}
}
fn project_text<const N: usize>(text: &LogText) -> ([u8; N], u8, bool) {
let source = text.as_str();
let retained = source
.char_indices()
.take_while(|(index, character)| index + character.len_utf8() <= N)
.map(|(index, character)| index + character.len_utf8())
.last()
.unwrap_or(0);
let mut bytes = [0; N];
bytes[..retained].copy_from_slice(&source.as_bytes()[..retained]);
(
bytes,
retained as u8,
text.is_truncated() || retained != source.len(),
)
}
/// Encodes one typed shutdown event. The caller owns when a shutdown is safe
/// to report; this pure adapter cannot run a shutdown state machine.
pub const fn encode_shutdown(report: ShutdownReport) -> KAPTAN_SHUTDOWN_DATA {
KAPTAN_SHUTDOWN_DATA {
reason: match report.reason() {
ShutdownReason::Operator => KaptanShutdownReason::KAPTAN_SHUTDOWN_REASON_OPERATOR,
ShutdownReason::User => KaptanShutdownReason::KAPTAN_SHUTDOWN_REASON_USER,
ShutdownReason::Safety => KaptanShutdownReason::KAPTAN_SHUTDOWN_REASON_SAFETY,
},
}
@@ -113,8 +98,8 @@ mod tests {
encode_diagnostic, encode_shutdown,
};
use kaptan_log::{
ErrorAdditionalInformation, ErrorPath, EventHub, FacadeIngress, GlobalLogger, LogFilter,
LogFlags, LogRecord, LogText, Severity, ShutdownReason, ShutdownReport,
ErrorAdditionalInformation, ErrorPath, FacadeIngress, GlobalLogger, LogFlags, LogRecord,
LogText, Severity, ShutdownReason, ShutdownReport,
};
use kaptan_mavlink::{
encoder::MavlinkEncoder,
@@ -124,7 +109,7 @@ mod tests {
},
};
static ERROR_INGRESS: FacadeIngress<1> = FacadeIngress::new(LogFilter::new(Severity::Info));
static ERROR_INGRESS: FacadeIngress<1> = FacadeIngress::new();
static ERROR_LOGGER: GlobalLogger<1> = GlobalLogger::new(&ERROR_INGRESS, never_forbidden);
static INSTALL_ERROR_LOGGER: Once = Once::new();
@@ -136,13 +121,13 @@ mod tests {
fn ordinary_record_preserves_level_flags_and_bounded_text() {
let record = LogRecord::new(
Severity::Info,
LogFlags::TRUNCATED,
LogFlags::TRUNCATED.with(LogFlags::COALESCED),
LogText::from_message("imu unavailable"),
);
let DiagnosticPayload::Log(data) = encode_diagnostic(&record) else {
panic!("info record must use KAPTAN_LOG");
};
assert_eq!(data.level, Severity::Info as u8);
assert_eq!(data.level, Severity::Info.wire_value());
assert_eq!(data.message_length, 15);
assert_eq!(&data.message[..15], b"imu unavailable");
assert!(data.message[15..].iter().all(|byte| *byte == 0));
@@ -161,9 +146,9 @@ mod tests {
kaptan_log::log::info!("optional ingress is occupied");
kaptan_log::log::error!("critical error survives");
let mut hub = EventHub::<1, 1>::new(LogFilter::default());
assert!(ERROR_INGRESS.drain_one_into(&mut hub).is_some());
let record = hub.peek().expect("critical macro record retained");
let record = ERROR_INGRESS
.take()
.expect("critical macro record retained");
let DiagnosticPayload::Error(payload) = encode_diagnostic(&record) else {
panic!("log::error! must not produce KAPTAN_LOG");
};
@@ -211,6 +196,7 @@ mod tests {
panic!("error record must use KAPTAN_ERROR");
};
assert_eq!(error_data.flags.bits(), LogFlags::COALESCED.bits());
assert_eq!(error_data.level, Severity::Error.wire_value());
assert_eq!(error_data.path_length, 23);
assert_eq!(error_data.additional_information_length, 0);
assert_eq!(&error_data.path[..23], b"driver::Error::Transfer");
@@ -220,10 +206,10 @@ mod tests {
.iter()
.all(|byte| *byte == 0)
);
assert_eq!(KAPTAN_ERROR_DATA::ENCODED_LEN, 163);
assert_eq!(KAPTAN_ERROR_DATA::ENCODED_LEN, 164);
let error_frame = encoder.custom(&error_data);
assert_eq!(error_frame.message_id(), 51_021);
assert!(error_frame.raw_bytes().len() <= 175);
assert!(error_frame.raw_bytes().len() <= 176);
assert!(matches!(
MavMessage::parse(
mavlink::MavlinkVersion::V2,
@@ -235,7 +221,7 @@ mod tests {
}
#[test]
fn error_wire_projection_clips_each_component_independently() {
fn error_record_bounds_each_component_before_wire_projection() {
let error = LogRecord::error(
LogFlags::NONE,
ErrorPath::from_message(&"p".repeat(LOG_MESSAGE_BYTES)),
@@ -257,6 +243,41 @@ mod tests {
);
}
#[test]
fn ordinary_error_constructor_preserves_narrowing_as_truncation() {
let error = LogRecord::new(
Severity::Error,
LogFlags::NONE,
LogText::from_message(&"p".repeat(ERROR_PATH_BYTES + 1)),
);
let DiagnosticPayload::Error(payload) = encode_diagnostic(&error) else {
panic!("error record must use KAPTAN_ERROR");
};
assert_eq!(usize::from(payload.path_length), ERROR_PATH_BYTES);
assert_eq!(&payload.path, &[b'p'; ERROR_PATH_BYTES]);
assert_eq!(payload.flags.bits(), LogFlags::TRUNCATED.bits());
}
#[test]
fn error_wire_preserves_every_error_severity() {
for severity in [
Severity::Emergency,
Severity::Alert,
Severity::Critical,
Severity::Error,
] {
let error = LogRecord::new(
severity,
LogFlags::NONE,
LogText::from_message("flight fault"),
);
let DiagnosticPayload::Error(payload) = encode_diagnostic(&error) else {
panic!("error severity must use KAPTAN_ERROR");
};
assert_eq!(payload.level, severity.wire_value());
}
}
#[test]
fn error_path_remains_decodable_when_only_additional_information_clips() {
let error = LogRecord::error(