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

+55
View File
@@ -0,0 +1,55 @@
use std::fmt;
use crate::{ParseError, err::{perr, ParseErrorKind::*}};
/// A bool literal: `true` or `false`. Also see [the reference][ref].
///
/// Notice that, strictly speaking, from Rust point of view "boolean literals" are not
/// actual literals but [keywords].
///
/// [ref]: https://doc.rust-lang.org/reference/expressions/literal-expr.html#boolean-literal-expressions
/// [keywords]: https://doc.rust-lang.org/reference/keywords.html#strict-keywords
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoolLit {
False,
True,
}
impl BoolLit {
/// Parses the input as a bool literal. Returns an error if the input is
/// invalid or represents a different kind of literal.
pub fn parse(s: &str) -> Result<Self, ParseError> {
match s {
"false" => Ok(Self::False),
"true" => Ok(Self::True),
_ => Err(perr(None, InvalidLiteral)),
}
}
/// Returns the actual Boolean value of this literal.
pub fn value(self) -> bool {
match self {
Self::False => false,
Self::True => true,
}
}
/// Returns the literal as string.
pub fn as_str(&self) -> &'static str {
match self {
Self::False => "false",
Self::True => "true",
}
}
}
impl fmt::Display for BoolLit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(self.as_str())
}
}
#[cfg(test)]
mod tests;
+48
View File
@@ -0,0 +1,48 @@
use crate::{
Literal, BoolLit,
test_util::assert_parse_ok_eq,
};
macro_rules! assert_bool_parse {
($input:literal, $expected:expr) => {
assert_parse_ok_eq(
$input, Literal::parse($input), Literal::Bool($expected), "Literal::parse");
assert_parse_ok_eq($input, BoolLit::parse($input), $expected, "BoolLit::parse");
};
}
#[test]
fn parse_ok() {
assert_bool_parse!("false", BoolLit::False);
assert_bool_parse!("true", BoolLit::True);
}
#[test]
fn parse_err() {
assert!(Literal::parse("fa").is_err());
assert!(Literal::parse("fal").is_err());
assert!(Literal::parse("fals").is_err());
assert!(Literal::parse(" false").is_err());
assert!(Literal::parse("false ").is_err());
assert!(Literal::parse("False").is_err());
assert!(Literal::parse("tr").is_err());
assert!(Literal::parse("tru").is_err());
assert!(Literal::parse(" true").is_err());
assert!(Literal::parse("true ").is_err());
assert!(Literal::parse("True").is_err());
}
#[test]
fn value() {
assert!(!BoolLit::False.value());
assert!(BoolLit::True.value());
}
#[test]
fn as_str() {
assert_eq!(BoolLit::False.as_str(), "false");
assert_eq!(BoolLit::True.as_str(), "true");
}