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:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
use crate::chain::Chain;
|
||||
use crate::error::ErrorImpl;
|
||||
use crate::ptr::Ref;
|
||||
use core::fmt::{self, Debug, Write};
|
||||
|
||||
impl ErrorImpl {
|
||||
pub(crate) unsafe fn display(this: Ref<Self>, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", unsafe { Self::error(this) })?;
|
||||
|
||||
if f.alternate() {
|
||||
let chain = unsafe { Self::chain(this) };
|
||||
for cause in chain.skip(1) {
|
||||
write!(f, ": {}", cause)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn debug(this: Ref<Self>, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let error = unsafe { Self::error(this) };
|
||||
|
||||
if f.alternate() {
|
||||
return Debug::fmt(error, f);
|
||||
}
|
||||
|
||||
write!(f, "{}", error)?;
|
||||
|
||||
if let Some(cause) = error.source() {
|
||||
write!(f, "\n\nCaused by:")?;
|
||||
let multiple = cause.source().is_some();
|
||||
for (n, error) in Chain::new(cause).enumerate() {
|
||||
writeln!(f)?;
|
||||
let mut indented = Indented {
|
||||
inner: f,
|
||||
number: if multiple { Some(n) } else { None },
|
||||
started: false,
|
||||
};
|
||||
write!(indented, "{}", error)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
use alloc::string::ToString;
|
||||
use std::backtrace::BacktraceStatus;
|
||||
|
||||
let backtrace = unsafe { Self::backtrace(this) };
|
||||
if let BacktraceStatus::Captured = backtrace.status() {
|
||||
let mut backtrace = backtrace.to_string();
|
||||
write!(f, "\n\n")?;
|
||||
if backtrace.starts_with("stack backtrace:") {
|
||||
// Capitalize to match "Caused by:"
|
||||
backtrace.replace_range(0..1, "S");
|
||||
} else {
|
||||
// "stack backtrace:" prefix was removed in
|
||||
// https://github.com/rust-lang/backtrace-rs/pull/286
|
||||
writeln!(f, "Stack backtrace:")?;
|
||||
}
|
||||
backtrace.truncate(backtrace.trim_end().len());
|
||||
write!(f, "{}", backtrace)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Indented<'a, D> {
|
||||
inner: &'a mut D,
|
||||
number: Option<usize>,
|
||||
started: bool,
|
||||
}
|
||||
|
||||
impl<T> Write for Indented<'_, T>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for (i, line) in s.split('\n').enumerate() {
|
||||
if !self.started {
|
||||
self.started = true;
|
||||
match self.number {
|
||||
Some(number) => write!(self.inner, "{: >5}: ", number)?,
|
||||
None => self.inner.write_str(" ")?,
|
||||
}
|
||||
} else if i > 0 {
|
||||
self.inner.write_char('\n')?;
|
||||
if self.number.is_some() {
|
||||
self.inner.write_str(" ")?;
|
||||
} else {
|
||||
self.inner.write_str(" ")?;
|
||||
}
|
||||
}
|
||||
|
||||
self.inner.write_str(line)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::string::String;
|
||||
|
||||
#[test]
|
||||
fn one_digit() {
|
||||
let input = "verify\nthis";
|
||||
let expected = " 2: verify\n this";
|
||||
let mut output = String::new();
|
||||
|
||||
Indented {
|
||||
inner: &mut output,
|
||||
number: Some(2),
|
||||
started: false,
|
||||
}
|
||||
.write_str(input)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(expected, output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_digits() {
|
||||
let input = "verify\nthis";
|
||||
let expected = " 12: verify\n this";
|
||||
let mut output = String::new();
|
||||
|
||||
Indented {
|
||||
inner: &mut output,
|
||||
number: Some(12),
|
||||
started: false,
|
||||
}
|
||||
.write_str(input)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(expected, output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_digits() {
|
||||
let input = "verify\nthis";
|
||||
let expected = " verify\n this";
|
||||
let mut output = String::new();
|
||||
|
||||
Indented {
|
||||
inner: &mut output,
|
||||
number: None,
|
||||
started: false,
|
||||
}
|
||||
.write_str(input)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(expected, output);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user