refactor: TUI multi-feed monitor with binary parsers
- Add ratatui/crossterm TUI replacing stdout data dump - Add binary parsers for KSUDP RTCarInfo (port 9996) and Telemetry Tool (port 10101) - Add shared binary_utils module for safe packet reading - Add ring-buffer frequency calculation and feed health state machine - Add parse_first_driver() to avoid unnecessary multi-driver allocation - Add 27 unit tests (14 feed_state + 6 ksudp + 7 telemetry_tool) - Move parsing outside mutex lock to minimize TUI contention - Replace expect() on env var with graceful error handling - Remove unused serde dependency - Delete obsolete stats.rs and telemetry.rs
This commit is contained in:
parent
1731495197
commit
7a1c37fc86
11 changed files with 2308 additions and 307 deletions
97
src/binary_utils.rs
Normal file
97
src/binary_utils.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
//! Shared binary reading helpers for UDP packet parsers.
|
||||
//!
|
||||
//! All helpers use `get()` for safe bounds checking — they return a zero/empty
|
||||
//! fallback instead of panicking when the offset is out of bounds.
|
||||
|
||||
use crate::telemetry_frame::NUM_WHEELS;
|
||||
|
||||
/// Read a `bool` (1 byte) from the data at the given offset.
|
||||
/// Returns `false` if the offset is out of bounds.
|
||||
pub fn read_bool(data: &[u8], offset: usize) -> bool {
|
||||
data.get(offset).map(|&b| b != 0).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Read a little-endian `u32` from the data at the given offset.
|
||||
/// Returns 0 if the offset is out of bounds (safe fallback for malformed packets).
|
||||
pub fn read_u32(data: &[u8], offset: usize) -> u32 {
|
||||
let buf: [u8; 4] = data
|
||||
.get(offset..offset + 4)
|
||||
.and_then(|s| s.try_into().ok())
|
||||
.unwrap_or([0u8; 4]);
|
||||
u32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
/// Read a little-endian `i32` from the data at the given offset.
|
||||
/// Returns 0 if the offset is out of bounds (safe fallback for malformed packets).
|
||||
pub fn read_i32(data: &[u8], offset: usize) -> i32 {
|
||||
let buf: [u8; 4] = data
|
||||
.get(offset..offset + 4)
|
||||
.and_then(|s| s.try_into().ok())
|
||||
.unwrap_or([0u8; 4]);
|
||||
i32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
/// Read a little-endian `i16` from the data at the given offset.
|
||||
/// Returns 0 if the offset is out of bounds (safe fallback for malformed packets).
|
||||
pub fn read_i16(data: &[u8], offset: usize) -> i16 {
|
||||
let buf: [u8; 2] = data
|
||||
.get(offset..offset + 2)
|
||||
.and_then(|s| s.try_into().ok())
|
||||
.unwrap_or([0u8; 2]);
|
||||
i16::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
/// Read a little-endian `f32` from the data at the given offset.
|
||||
/// Returns 0.0 if the offset is out of bounds (safe fallback for malformed packets).
|
||||
pub fn read_f32(data: &[u8], offset: usize) -> f32 {
|
||||
let buf: [u8; 4] = data
|
||||
.get(offset..offset + 4)
|
||||
.and_then(|s| s.try_into().ok())
|
||||
.unwrap_or([0u8; 4]);
|
||||
f32::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
/// Read a little-endian `f64` from the data at the given offset.
|
||||
/// Returns 0.0 if the offset is out of bounds (safe fallback for malformed packets).
|
||||
pub fn read_f64(data: &[u8], offset: usize) -> f64 {
|
||||
let buf: [u8; 8] = data
|
||||
.get(offset..offset + 8)
|
||||
.and_then(|s| s.try_into().ok())
|
||||
.unwrap_or([0u8; 8]);
|
||||
f64::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
/// Read an array of 4 little-endian `f32` from the data at the given offset.
|
||||
pub fn read_f32_4(data: &[u8], offset: usize) -> [f32; NUM_WHEELS] {
|
||||
let mut arr = [0.0f32; NUM_WHEELS];
|
||||
for i in 0..NUM_WHEELS {
|
||||
arr[i] = read_f32(data, offset + i * 4);
|
||||
}
|
||||
arr
|
||||
}
|
||||
|
||||
/// Read an array of 2 little-endian `f32` from the data at the given offset.
|
||||
pub fn read_f32_2(data: &[u8], offset: usize) -> [f32; 2] {
|
||||
[read_f32(data, offset), read_f32(data, offset + 4)]
|
||||
}
|
||||
|
||||
/// Read an array of 3 little-endian `f32` from the data at the given offset.
|
||||
pub fn read_f32_3(data: &[u8], offset: usize) -> [f32; 3] {
|
||||
[read_f32(data, offset), read_f32(data, offset + 4), read_f32(data, offset + 8)]
|
||||
}
|
||||
|
||||
/// Read an array of 3 little-endian `i32` from the data at the given offset.
|
||||
pub fn read_i32_3(data: &[u8], offset: usize) -> [i32; 3] {
|
||||
[read_i32(data, offset), read_i32(data, offset + 4), read_i32(data, offset + 8)]
|
||||
}
|
||||
|
||||
/// Read a null-terminated/padded ASCII string.
|
||||
/// Returns an empty string if the offset is out of bounds.
|
||||
pub fn read_string(data: &[u8], offset: usize, len: usize) -> String {
|
||||
let slice = match data.get(offset..offset + len) {
|
||||
Some(s) => s,
|
||||
None => return String::new(),
|
||||
};
|
||||
let end = slice.iter().position(|&b| b == 0).unwrap_or(len);
|
||||
String::from_utf8_lossy(&slice[..end]).to_string()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue