refactor(firmware): simplify configuration and diagnostics

This commit is contained in:
Onur Özkan committed 2026-08-23 00:40:36 +03:00
1 parent ba9c97ed3b
commit fdf82a5114
96 files changed
+1612 -2772

No files matched your search

+74 -66
View File
@@ -2,71 +2,42 @@
use std::{collections::VecDeque, time::SystemTime};
use kaptan_log::Level;
use serde::{Serialize, Serializer};
const MAX_LOG_ENTRIES: usize = 512;
/// Severity attached to one decoded diagnostic event.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LogSeverity {
Emergency,
Alert,
Critical,
Error,
Warning,
Notice,
Info,
Debug,
Trace,
Unknown(u8),
fn serialize_level<S>(level: &Level, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(level.as_str())
}
impl LogSeverity {
/// Decodes the stable MAV_SEVERITY numeric representation without
/// accepting values that the protocol has not defined.
pub const fn from_wire(level: u8, trace: bool) -> Self {
match level {
0 => Self::Emergency,
1 => Self::Alert,
2 => Self::Critical,
3 => Self::Error,
4 => Self::Warning,
5 => Self::Notice,
6 => Self::Info,
7 if trace => Self::Trace,
7 => Self::Debug,
value => Self::Unknown(value),
}
/// Decodes the exact Kaptan log-level subset from its MAVLink representation.
///
/// Kaptan emits Error, Warn, Info, and Debug on the conventional MAVLink
/// numbers 3, 4, 6, and 7. Trace is Debug plus the dedicated trace flag. The
/// other MAVLink severity numbers are not Kaptan log levels.
pub const fn level_from_wire(level: u8, trace: bool) -> Option<Level> {
if trace && level != 7 {
return None;
}
const fn known_label(self) -> Option<&'static str> {
match self {
Self::Emergency => Some("EMERGENCY"),
Self::Alert => Some("ALERT"),
Self::Critical => Some("CRITICAL"),
Self::Error => Some("ERROR"),
Self::Warning => Some("WARNING"),
Self::Notice => Some("NOTICE"),
Self::Info => Some("INFO"),
Self::Debug => Some("DEBUG"),
Self::Trace => Some("TRACE"),
Self::Unknown(_) => None,
}
match level {
3 => Some(Level::Error),
4 => Some(Level::Warn),
6 => Some(Level::Info),
7 if trace => Some(Level::Trace),
7 => Some(Level::Debug),
_ => None,
}
}
impl Serialize for LogSeverity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(label) = self.known_label() {
serializer.serialize_str(label)
} else if let Self::Unknown(value) = self {
serializer.serialize_str(&format!("UNKNOWN ({value})"))
} else {
unreachable!("every known severity has a static label")
}
/// Human-readable packet-view representation, including malformed values.
pub fn wire_level_label(level: u8, trace: bool) -> String {
match level_from_wire(level, trace) {
Some(level) => level.as_str().into(),
None => format!("UNKNOWN ({level})"),
}
}
@@ -75,7 +46,8 @@ impl Serialize for LogSeverity {
pub struct LogObservation {
pub system_id: u8,
pub component_id: u8,
pub severity: LogSeverity,
pub level: Level,
pub truncated: bool,
pub text: String,
}
@@ -94,7 +66,9 @@ pub struct LogView {
pub received_at_ms: u64,
pub system_id: u8,
pub component_id: u8,
pub severity: LogSeverity,
#[serde(serialize_with = "serialize_level")]
pub level: Level,
pub truncated: bool,
pub text: String,
}
@@ -131,7 +105,8 @@ impl LogMonitor {
received_at_ms,
system_id: observation.system_id,
component_id: observation.component_id,
severity: observation.severity,
level: observation.level,
truncated: observation.truncated,
text: observation.text,
});
self.next_id += 1;
@@ -164,7 +139,8 @@ mod tests {
LogObservation {
system_id: 1,
component_id: 2,
severity: LogSeverity::Info,
level: Level::Info,
truncated: false,
text: format!("event {index}"),
}
}
@@ -179,10 +155,15 @@ mod tests {
assert_eq!(snapshot.entries.len(), 2);
assert_eq!(snapshot.entries[0].id, 0);
assert_eq!(snapshot.entries[0].received_at_ms, 1_234);
assert!(!snapshot.entries[0].truncated);
assert_eq!(snapshot.entries[0].text, "event 0");
assert_eq!(snapshot.entries[1].id, 1);
assert_eq!(snapshot.entries[1].text, "event 1");
assert_eq!(snapshot.discarded, 0);
assert_eq!(
serde_json::to_value(&snapshot).unwrap()["entries"][0]["level"],
"INFO"
);
}
#[test]
@@ -213,14 +194,41 @@ mod tests {
}
#[test]
fn wire_severity_is_checked_and_unknown_values_remain_visible() {
assert_eq!(LogSeverity::from_wire(3, false), LogSeverity::Error);
assert_eq!(LogSeverity::from_wire(7, false), LogSeverity::Debug);
assert_eq!(LogSeverity::from_wire(7, true), LogSeverity::Trace);
assert_eq!(LogSeverity::from_wire(9, false), LogSeverity::Unknown(9));
fn wire_level_accepts_only_the_exact_kaptan_contract() {
for (wire, expected) in [
(0, None),
(1, None),
(2, None),
(3, Some(Level::Error)),
(4, Some(Level::Warn)),
(5, None),
(6, Some(Level::Info)),
(7, Some(Level::Debug)),
(8, None),
(u8::MAX, None),
] {
assert_eq!(level_from_wire(wire, false), expected, "wire {wire}");
}
assert_eq!(level_from_wire(7, true), Some(Level::Trace));
for invalid_trace_level in [0, 1, 2, 3, 4, 5, 6, 8, u8::MAX] {
assert_eq!(level_from_wire(invalid_trace_level, true), None);
}
assert_eq!(wire_level_label(2, false), "UNKNOWN (2)");
assert_eq!(wire_level_label(9, false), "UNKNOWN (9)");
}
#[test]
fn truncation_state_survives_the_browser_projection() {
let mut event = observation(0);
event.truncated = true;
let mut monitor = LogMonitor::default();
monitor.observe([event], SystemTime::UNIX_EPOCH);
let snapshot = monitor.snapshot();
assert!(snapshot.entries[0].truncated);
assert_eq!(
serde_json::to_value(LogSeverity::Unknown(9)).unwrap(),
"UNKNOWN (9)"
serde_json::to_value(&snapshot).unwrap()["entries"][0]["truncated"],
true
);
}