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

+78
View File
@@ -0,0 +1,78 @@
use std::fmt::Write;
use crate::cargo;
pub(crate) fn mangled(defmt_tag: &str, data: &str) -> String {
Symbol::new(defmt_tag, data).mangle()
}
struct Symbol<'a> {
/// Name of the Cargo package in which the symbol is being instantiated. Used for avoiding
/// symbol name collisions.
package: String,
/// Unique identifier that disambiguates otherwise equivalent invocations in the same crate.
disambiguator: u64,
/// Symbol categorization. Known values:
/// * `defmt_prim` for primitive formatting strings that are placed at the start of the `.defmt`
/// section.
/// * `defmt_fmt`, `defmt_str` for interned format strings and string literals.
/// * `defmt_println` for logging messages that are always displayed.
/// * `defmt_trace`, `defmt_debug`, `defmt_info`, `defmt_warn`, `defmt_error` for logging
/// messages used at the different log levels.
/// * `defmt_bitflags` indicates that a format string was generated by a `defmt::bitflags!`
/// invocation, and that the decoder should look up possible flags in the binary.
/// The data string is of the format `NAME@REPR#NUM`, where `NAME` is the name of the bitflags
/// struct, `REPR` is the raw integer type representing the bitflags value (and also the
/// wire format), and `NUM` is the number of defined bitflag values.
/// * `defmt_bitflags_value` marks a `static` that holds the value of a bitflags `const`, its
/// data field is `STRUCT_NAME::FLAG_NAME`.
/// * Anything starting with `defmt_` is reserved for use by defmt, other prefixes are free for
/// use by third-party apps (but they all should use a prefix!).
tag: String,
/// Symbol data for use by the host tooling. Interpretation depends on `tag`.
data: &'a str,
/// Crate name obtained via CARGO_CRATE_NAME (added since a Cargo package can contain many crates).
crate_name: String,
}
impl<'a> Symbol<'a> {
fn new(tag: &'a str, data: &'a str) -> Self {
Self {
// `CARGO_PKG_NAME` is set to the invoking package's name.
package: cargo::package_name(),
disambiguator: super::crate_local_disambiguator(),
tag: format!("defmt_{tag}"),
data,
crate_name: cargo::crate_name(),
}
}
fn mangle(&self) -> String {
format!(
r#"{{"package":"{}","tag":"{}","data":"{}","disambiguator":"{}","crate_name":"{}"}}"#,
json_escape(&self.package),
json_escape(&self.tag),
json_escape(self.data),
self.disambiguator,
json_escape(&self.crate_name),
)
}
}
fn json_escape(string: &str) -> String {
let mut escaped = String::new();
for c in string.chars() {
match c {
'\\' => escaped.push_str("\\\\"),
'\"' => escaped.push_str("\\\""),
'\n' => escaped.push_str("\\n"),
c if c.is_control() || c == '@' => write!(escaped, "\\u{:04x}", c as u32).unwrap(),
c => escaped.push(c),
}
}
escaped
}