feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
use postcard::accumulator::{CobsAccumulator, FeedResult};
use serde::{Deserialize, Serialize};
use std::io::Read;
// Read a "huge" serialized struct in 32 byte chunks into a 256 byte buffer and deserialize it.
#[test]
fn reader() {
let mut raw_buf = [0u8; 32];
let mut input_buf = [0u8; 256];
let mut cobs_buf: CobsAccumulator<256> = CobsAccumulator::new();
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Huge {
a: u32,
b: [u32; 32],
d: u64,
}
let expected = Huge {
a: 0xabcdef00,
b: [0x01234567; 32],
d: u64::MAX,
};
let input = postcard::to_slice_cobs(&expected, &mut input_buf).unwrap();
// TODO(https://github.com/rust-lang/rust-clippy/issues/12751): Remove once fixed.
#[allow(clippy::redundant_slicing)]
let mut input = &input[..];
// Magic number from serializing struct and printing length
assert_eq!(input.len(), 145);
let mut output = None;
while let Ok(ct) = input.read(&mut raw_buf) {
// Finished reading input
if ct == 0 {
break;
}
let buf = &raw_buf[..ct];
let mut window = buf;
'cobs: while !window.is_empty() {
window = match cobs_buf.feed::<Huge>(window) {
FeedResult::Consumed => break 'cobs,
FeedResult::OverFull(new_wind) => new_wind,
FeedResult::DeserError(new_wind) => new_wind,
FeedResult::Success { data, remaining } => {
output = Some(data);
remaining
}
};
}
}
assert_eq!(output.unwrap(), expected);
}