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

+24 -3
View File
@@ -1,23 +1,44 @@
use embassy_executor::task;
use crate::board::UsbParts;
use crate::channel::{CommandChannel, ResponseChannel};
use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel};
#[task]
pub async fn usb_task(
parts: UsbParts,
commands: &'static CommandChannel,
responses: &'static ResponseChannel,
display_commands: &'static DisplayCommandChannel,
has_display: bool,
) {
let _driver = parts.driver;
let _ = commands;
let _ = responses;
let _ = display_commands;
let _ = has_display;
// TODO: build embassy_usb CDC-ACM class from driver
// TODO: COBS-framed postcard ser/de loop:
// select! {
// bytes = cdc_read() => deserialize Command → commands.send()
// resp = responses.receive() => serialize Response → cdc_write()
// bytes = cdc_read() => {
// let cmd: Command = deserialize(bytes);
// match cmd {
// Command::DisplayOn => route_display(DisplayCommand::On),
// Command::DisplayOff => route_display(DisplayCommand::Off),
// other => commands.send(other).await,
// }
// }
// resp = responses.receive() => serialize Response → cdc_write()
// }
//
// Display command routing:
// async fn route_display(cmd, display_commands, has_display, responses) {
// if has_display {
// display_commands.send(cmd).await;
// responses.send(Response::Ok).await;
// } else {
// responses.send(Response::Error(ErrorCode::NoDisplay)).await;
// }
// }
loop {