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
@@ -209,6 +209,8 @@ mod tests {
|
||||
assert!(INDEX.contains("id=\"packet-rows\""));
|
||||
assert!(APP.contains("api('/api/logs')"));
|
||||
assert!(APP.contains("newEntries.map(createLogEntry)"));
|
||||
assert!(APP.contains("entry.truncated"));
|
||||
assert!(APP.contains("truncated.textContent = 'TRUNCATED'"));
|
||||
assert!(INDEX.contains("id=\"log-entries\""));
|
||||
}
|
||||
}
|
||||
@@ -1540,15 +1540,15 @@ mod tests {
|
||||
|
||||
let descriptor = response(MavMessage::KAPTAN_CONFIG_DESCRIPTOR(
|
||||
KAPTAN_CONFIG_DESCRIPTOR_DATA {
|
||||
minimum_bits: 5,
|
||||
maximum_bits: 6,
|
||||
default_bits: 6,
|
||||
minimum_bits: 3,
|
||||
maximum_bits: 4,
|
||||
default_bits: 3,
|
||||
request_id: 2,
|
||||
descriptor_index: 0,
|
||||
field_id: 108,
|
||||
enum_option_count: 2,
|
||||
display_key: "future.logging.severity".into(),
|
||||
unit: "MAV_SEVERITY".into(),
|
||||
display_key: "future.logging.level_filter".into(),
|
||||
unit: "log_level".into(),
|
||||
value_kind: KaptanConfigValueKind::KAPTAN_CONFIG_VALUE_KIND_U32,
|
||||
flags: DESCRIPTOR_HAS_MINIMUM | DESCRIPTOR_HAS_MAXIMUM,
|
||||
},
|
||||
@@ -1568,12 +1568,12 @@ mod tests {
|
||||
|
||||
let first_option = response(MavMessage::KAPTAN_CONFIG_ENUM_OPTION(
|
||||
KAPTAN_CONFIG_ENUM_OPTION_DATA {
|
||||
value: 5,
|
||||
value: 3,
|
||||
request_id: first.request_id,
|
||||
descriptor_index: 0,
|
||||
field_id: 108,
|
||||
option_index: 0,
|
||||
label: "Notice".into(),
|
||||
label: "Info".into(),
|
||||
},
|
||||
));
|
||||
let (event, second_request) = client.observe(&first_option, &mut sequence);
|
||||
@@ -1591,12 +1591,12 @@ mod tests {
|
||||
|
||||
let second_option = response(MavMessage::KAPTAN_CONFIG_ENUM_OPTION(
|
||||
KAPTAN_CONFIG_ENUM_OPTION_DATA {
|
||||
value: 6,
|
||||
value: 4,
|
||||
request_id: second.request_id,
|
||||
descriptor_index: 0,
|
||||
field_id: 108,
|
||||
option_index: 1,
|
||||
label: "Info".into(),
|
||||
label: "Debug".into(),
|
||||
},
|
||||
));
|
||||
let (event, value_request) = client.observe(&second_option, &mut sequence);
|
||||
@@ -1613,7 +1613,7 @@ mod tests {
|
||||
_ => panic!("enum discovery must advance to VALUE_REQUEST"),
|
||||
};
|
||||
let value = response(MavMessage::KAPTAN_CONFIG_VALUE(KAPTAN_CONFIG_VALUE_DATA {
|
||||
value_bits: 6,
|
||||
value_bits: 3,
|
||||
request_id: value_request_id,
|
||||
session_id: 0,
|
||||
revision: 0,
|
||||
@@ -1626,9 +1626,9 @@ mod tests {
|
||||
panic!("complete enum discovery must publish the configuration");
|
||||
};
|
||||
assert_eq!(configuration.fields[0].options.len(), 2);
|
||||
assert_eq!(configuration.fields[0].options[0].label, "Notice");
|
||||
assert_eq!(configuration.fields[0].options[0].value, Scalar::U32(5));
|
||||
assert_eq!(configuration.fields[0].options[1].label, "Info");
|
||||
assert_eq!(configuration.fields[0].options[0].label, "Info");
|
||||
assert_eq!(configuration.fields[0].options[0].value, Scalar::U32(3));
|
||||
assert_eq!(configuration.fields[0].options[1].label, "Debug");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
config_client::{ClientEvent, ConfigClient},
|
||||
log_monitor::{LogObservation, LogSeverity},
|
||||
log_monitor::{LogObservation, level_from_wire, wire_level_label},
|
||||
packet_monitor::PacketObservation,
|
||||
state::{SaveRequest, SharedState},
|
||||
};
|
||||
use kaptan_log::Level;
|
||||
use kaptan_mavlink::{
|
||||
MavlinkFrame,
|
||||
message::{self, Message},
|
||||
@@ -321,36 +322,34 @@ fn log_observation(
|
||||
frame: &MavlinkFrame,
|
||||
message: &message::kaptan::MavMessage,
|
||||
) -> Option<LogObservation> {
|
||||
let (severity, text) = match message {
|
||||
let (level, truncated, text) = match message {
|
||||
message::kaptan::MavMessage::KAPTAN_LOG(log) => {
|
||||
let trace = log
|
||||
.flags
|
||||
.contains(message::kaptan::KaptanLogFlags::KAPTAN_LOG_FLAG_TRACE);
|
||||
(
|
||||
LogSeverity::from_wire(log.level, trace),
|
||||
bounded_text(&log.message, log.message_length),
|
||||
level_from_wire(log.level, trace)?,
|
||||
log.flags
|
||||
.contains(message::kaptan::KaptanLogFlags::KAPTAN_LOG_FLAG_TRUNCATED),
|
||||
bounded_text(&log.message, log.message_length)?,
|
||||
)
|
||||
}
|
||||
message::kaptan::MavMessage::KAPTAN_ERROR(error) => {
|
||||
let path = bounded_text(&error.path, error.path_length);
|
||||
let information = bounded_text(
|
||||
&error.additional_information,
|
||||
error.additional_information_length,
|
||||
);
|
||||
let text = match (path.is_empty(), information.is_empty()) {
|
||||
(false, false) => format!("{path}: {information}"),
|
||||
(false, true) => path,
|
||||
(true, false) => information,
|
||||
(true, true) => "Unspecified Kaptan error".into(),
|
||||
let raised_at = zero_padded_text(&error.raised_at)?;
|
||||
let text = if raised_at.is_empty() {
|
||||
format!("Kaptan error {}", error.error_id)
|
||||
} else {
|
||||
format!("Kaptan error {} at {raised_at}", error.error_id)
|
||||
};
|
||||
(LogSeverity::from_wire(error.level, false), text)
|
||||
(Level::Error, false, text)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(LogObservation {
|
||||
system_id: frame.system_id(),
|
||||
component_id: frame.component_id(),
|
||||
severity,
|
||||
level,
|
||||
truncated,
|
||||
text,
|
||||
})
|
||||
}
|
||||
@@ -358,23 +357,17 @@ fn log_observation(
|
||||
fn kaptan_message_values(message: &message::kaptan::MavMessage) -> serde_json::Value {
|
||||
match message {
|
||||
message::kaptan::MavMessage::KAPTAN_ERROR(error) => serde_json::json!({
|
||||
"level": LogSeverity::from_wire(error.level, false),
|
||||
"flags": format!("{:?}", error.flags),
|
||||
"flags_bits": error.flags.bits(),
|
||||
"path": bounded_text(&error.path, error.path_length),
|
||||
"additional_information": bounded_text(
|
||||
&error.additional_information,
|
||||
error.additional_information_length,
|
||||
),
|
||||
"error_id": error.error_id,
|
||||
"raised_at": packet_zero_padded_text(&error.raised_at),
|
||||
}),
|
||||
message::kaptan::MavMessage::KAPTAN_LOG(log) => serde_json::json!({
|
||||
"level": LogSeverity::from_wire(
|
||||
"level": wire_level_label(
|
||||
log.level,
|
||||
log.flags.contains(message::kaptan::KaptanLogFlags::KAPTAN_LOG_FLAG_TRACE),
|
||||
),
|
||||
"flags": format!("{:?}", log.flags),
|
||||
"flags_bits": log.flags.bits(),
|
||||
"message": bounded_text(&log.message, log.message_length),
|
||||
"message": packet_text(&log.message, log.message_length),
|
||||
}),
|
||||
_ => decoded_fields(message),
|
||||
}
|
||||
@@ -384,9 +377,22 @@ fn decoded_fields(message: &impl core::fmt::Debug) -> serde_json::Value {
|
||||
format!("{message:#?}").into()
|
||||
}
|
||||
|
||||
fn bounded_text<const N: usize>(bytes: &[u8; N], length: u8) -> String {
|
||||
let end = usize::from(length).min(N);
|
||||
String::from_utf8_lossy(&bytes[..end]).into_owned()
|
||||
fn bounded_text<const N: usize>(bytes: &[u8; N], length: u8) -> Option<String> {
|
||||
let text = core::str::from_utf8(bytes.get(..usize::from(length))?).ok()?;
|
||||
Some(text.into())
|
||||
}
|
||||
|
||||
fn packet_text<const N: usize>(bytes: &[u8; N], length: u8) -> String {
|
||||
bounded_text(bytes, length).unwrap_or_else(|| format!("INVALID TEXT (length {length})"))
|
||||
}
|
||||
|
||||
fn zero_padded_text<const N: usize>(bytes: &[u8; N]) -> Option<String> {
|
||||
let length = bytes.iter().position(|byte| *byte == 0).unwrap_or(N);
|
||||
Some(core::str::from_utf8(&bytes[..length]).ok()?.into())
|
||||
}
|
||||
|
||||
fn packet_zero_padded_text<const N: usize>(bytes: &[u8; N]) -> String {
|
||||
zero_padded_text(bytes).unwrap_or_else(|| "INVALID TEXT".into())
|
||||
}
|
||||
|
||||
fn hex_bytes(bytes: &[u8]) -> String {
|
||||
@@ -533,13 +539,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_monitor_decodes_kaptan_error_text() {
|
||||
let path_text = b"driver::Icm42688::Transfer";
|
||||
let information_text = b"SPI timeout";
|
||||
let mut path = [0_u8; 96];
|
||||
let mut additional_information = [0_u8; 64];
|
||||
path[..path_text.len()].copy_from_slice(path_text);
|
||||
additional_information[..information_text.len()].copy_from_slice(information_text);
|
||||
fn packet_and_history_decode_a_typed_kaptan_error() {
|
||||
let raised_at_text = "platforms/stm32f7/src/f722/board.rs:572";
|
||||
let mut raised_at = [0; 96];
|
||||
raised_at[..raised_at_text.len()].copy_from_slice(raised_at_text.as_bytes());
|
||||
let mut frame = MavlinkFrame::new();
|
||||
frame.serialize_message_data(
|
||||
MavHeader {
|
||||
@@ -548,27 +551,28 @@ mod tests {
|
||||
component_id: 7,
|
||||
},
|
||||
&message::kaptan::KAPTAN_ERROR_DATA {
|
||||
level: 2,
|
||||
flags: message::kaptan::KaptanErrorFlags::empty(),
|
||||
path_length: path_text.len() as u8,
|
||||
additional_information_length: information_text.len() as u8,
|
||||
path,
|
||||
additional_information,
|
||||
error_id: 1234,
|
||||
raised_at,
|
||||
},
|
||||
);
|
||||
|
||||
let (observation, log) = frame_observations(&frame);
|
||||
assert_eq!(observation.name, "KAPTAN_ERROR");
|
||||
assert_eq!(observation.values["level"], "CRITICAL");
|
||||
assert_eq!(observation.values["path"], "driver::Icm42688::Transfer");
|
||||
assert_eq!(observation.values["additional_information"], "SPI timeout");
|
||||
assert!(observation.values.get("level").is_none());
|
||||
assert_eq!(observation.values["error_id"], 1234);
|
||||
assert_eq!(observation.values["raised_at"], raised_at_text);
|
||||
assert!(observation.values.get("flags").is_none());
|
||||
assert!(observation.values.get("flags_bits").is_none());
|
||||
assert!(observation.values.get("path").is_none());
|
||||
assert!(observation.values.get("additional_information").is_none());
|
||||
assert_eq!(
|
||||
log,
|
||||
Some(LogObservation {
|
||||
system_id: 42,
|
||||
component_id: 7,
|
||||
severity: LogSeverity::Critical,
|
||||
text: "driver::Icm42688::Transfer: SPI timeout".into(),
|
||||
level: kaptan_log::Level::Error,
|
||||
truncated: false,
|
||||
text: format!("Kaptan error 1234 at {raised_at_text}"),
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -601,12 +605,104 @@ mod tests {
|
||||
Some(LogObservation {
|
||||
system_id: 42,
|
||||
component_id: 7,
|
||||
severity: LogSeverity::Info,
|
||||
level: kaptan_log::Level::Info,
|
||||
truncated: false,
|
||||
text: "estimator initialized".into(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_level_runtime_log_remains_kaptan_log() {
|
||||
let message_bytes = [b'x'; 160];
|
||||
let message_text = "x".repeat(message_bytes.len());
|
||||
let mut frame = MavlinkFrame::new();
|
||||
frame.serialize_message_data(
|
||||
MavHeader {
|
||||
sequence: 1,
|
||||
system_id: 42,
|
||||
component_id: 7,
|
||||
},
|
||||
&message::kaptan::KAPTAN_LOG_DATA {
|
||||
level: 3,
|
||||
flags: message::kaptan::KaptanLogFlags::KAPTAN_LOG_FLAG_TRUNCATED,
|
||||
message_length: message_bytes.len() as u8,
|
||||
message: message_bytes,
|
||||
},
|
||||
);
|
||||
|
||||
let (packet, log) = frame_observations(&frame);
|
||||
assert_eq!(packet.name, "KAPTAN_LOG");
|
||||
assert_eq!(
|
||||
log,
|
||||
Some(LogObservation {
|
||||
system_id: 42,
|
||||
component_id: 7,
|
||||
level: Level::Error,
|
||||
truncated: true,
|
||||
text: message_text,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_log_text_or_level_is_not_added_to_history() {
|
||||
let mut invalid_utf8 = [0_u8; 160];
|
||||
invalid_utf8[0] = 0xff;
|
||||
for (level, flags, length, message) in [
|
||||
(
|
||||
6,
|
||||
message::kaptan::KaptanLogFlags::KAPTAN_LOG_FLAG_TRACE,
|
||||
0,
|
||||
[0_u8; 160],
|
||||
),
|
||||
(
|
||||
6,
|
||||
message::kaptan::KaptanLogFlags::empty(),
|
||||
161,
|
||||
[0_u8; 160],
|
||||
),
|
||||
(6, message::kaptan::KaptanLogFlags::empty(), 1, invalid_utf8),
|
||||
] {
|
||||
let mut frame = MavlinkFrame::new();
|
||||
frame.serialize_message_data(
|
||||
MavHeader {
|
||||
sequence: 1,
|
||||
system_id: 42,
|
||||
component_id: 7,
|
||||
},
|
||||
&message::kaptan::KAPTAN_LOG_DATA {
|
||||
level,
|
||||
flags,
|
||||
message_length: length,
|
||||
message,
|
||||
},
|
||||
);
|
||||
let (_, log) = frame_observations(&frame);
|
||||
assert_eq!(log, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_typed_error_location_is_not_added_to_history() {
|
||||
let mut raised_at = [0_u8; 96];
|
||||
raised_at[0] = 0xff;
|
||||
let mut frame = MavlinkFrame::new();
|
||||
frame.serialize_message_data(
|
||||
MavHeader {
|
||||
sequence: 1,
|
||||
system_id: 42,
|
||||
component_id: 7,
|
||||
},
|
||||
&message::kaptan::KAPTAN_ERROR_DATA {
|
||||
error_id: 1,
|
||||
raised_at,
|
||||
},
|
||||
);
|
||||
let (_, log) = frame_observations(&frame);
|
||||
assert_eq!(log, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serial_baud_accepts_only_rates_with_an_exact_termios_mapping() {
|
||||
for rate in [
|
||||
|
||||
Reference in new issue
Block a user