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
+114
@@ -0,0 +1,114 @@
|
||||
#![allow(
|
||||
clippy::elidable_lifetime_names,
|
||||
clippy::enum_glob_use,
|
||||
clippy::must_use_candidate,
|
||||
clippy::single_match_else
|
||||
)]
|
||||
|
||||
mod rustc;
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::fs;
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
use std::process::{self, Command};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build/build.rs");
|
||||
|
||||
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
|
||||
let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty());
|
||||
let wrapped_rustc = rustc_wrapper.iter().chain(iter::once(&rustc));
|
||||
|
||||
let mut is_clippy_driver = false;
|
||||
let mut is_mirai = false;
|
||||
let version = loop {
|
||||
let mut command;
|
||||
if is_mirai {
|
||||
command = Command::new(&rustc);
|
||||
} else {
|
||||
let mut wrapped_rustc = wrapped_rustc.clone();
|
||||
command = Command::new(wrapped_rustc.next().unwrap());
|
||||
command.args(wrapped_rustc);
|
||||
}
|
||||
if is_clippy_driver {
|
||||
command.arg("--rustc");
|
||||
}
|
||||
command.arg("--version");
|
||||
|
||||
let output = match command.output() {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
let rustc = rustc.to_string_lossy();
|
||||
eprintln!("Error: failed to run `{} --version`: {}", rustc, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let string = match String::from_utf8(output.stdout) {
|
||||
Ok(string) => string,
|
||||
Err(e) => {
|
||||
let rustc = rustc.to_string_lossy();
|
||||
eprintln!(
|
||||
"Error: failed to parse output of `{} --version`: {}",
|
||||
rustc, e,
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
break match rustc::parse(&string) {
|
||||
rustc::ParseResult::Success(version) => version,
|
||||
rustc::ParseResult::OopsClippy if !is_clippy_driver => {
|
||||
is_clippy_driver = true;
|
||||
continue;
|
||||
}
|
||||
rustc::ParseResult::OopsMirai if !is_mirai && rustc_wrapper.is_some() => {
|
||||
is_mirai = true;
|
||||
continue;
|
||||
}
|
||||
rustc::ParseResult::Unrecognized
|
||||
| rustc::ParseResult::OopsClippy
|
||||
| rustc::ParseResult::OopsMirai => {
|
||||
eprintln!(
|
||||
"Error: unexpected output from `rustc --version`: {:?}\n\n\
|
||||
Please file an issue in https://github.com/dtolnay/rustversion",
|
||||
string
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if version.minor < 38 {
|
||||
// Prior to 1.38, a #[proc_macro] is not allowed to be named `cfg`.
|
||||
println!("cargo:rustc-cfg=cfg_macro_not_allowed");
|
||||
}
|
||||
|
||||
if version.minor >= 80 {
|
||||
println!("cargo:rustc-check-cfg=cfg(cfg_macro_not_allowed)");
|
||||
println!("cargo:rustc-check-cfg=cfg(host_os, values(\"windows\"))");
|
||||
}
|
||||
|
||||
let version = format!("{:#}\n", Render(&version));
|
||||
let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set");
|
||||
let out_file = Path::new(&out_dir).join("version.expr");
|
||||
fs::write(out_file, version).expect("failed to write version.expr");
|
||||
|
||||
let host = env::var_os("HOST").expect("HOST not set");
|
||||
if let Some("windows") = host.to_str().unwrap().split('-').nth(2) {
|
||||
println!("cargo:rustc-cfg=host_os=\"windows\"");
|
||||
}
|
||||
}
|
||||
|
||||
// Shim Version's {:?} format into a {} format, because {:?} is unusable in
|
||||
// format strings when building with `-Zfmt-debug=none`.
|
||||
struct Render<'a>(&'a rustc::Version);
|
||||
|
||||
impl<'a> Display for Render<'a> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
Debug::fmt(self.0, formatter)
|
||||
}
|
||||
}
|
||||
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
use self::Channel::*;
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
pub enum ParseResult {
|
||||
Success(Version),
|
||||
OopsClippy,
|
||||
OopsMirai,
|
||||
Unrecognized,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Version {
|
||||
pub minor: u16,
|
||||
pub patch: u16,
|
||||
pub channel: Channel,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum Channel {
|
||||
Stable,
|
||||
Beta,
|
||||
Nightly(Date),
|
||||
Dev,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Date {
|
||||
pub year: u16,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
}
|
||||
|
||||
pub fn parse(string: &str) -> ParseResult {
|
||||
let last_line = string.lines().last().unwrap_or(string);
|
||||
let mut words = last_line.trim().split(' ');
|
||||
|
||||
match words.next() {
|
||||
Some("rustc") => {}
|
||||
Some(word) if word.starts_with("clippy") => return ParseResult::OopsClippy,
|
||||
Some("mirai") => return ParseResult::OopsMirai,
|
||||
Some(_) | None => return ParseResult::Unrecognized,
|
||||
}
|
||||
|
||||
parse_words(&mut words).map_or(ParseResult::Unrecognized, ParseResult::Success)
|
||||
}
|
||||
|
||||
fn parse_words(words: &mut dyn Iterator<Item = &str>) -> Option<Version> {
|
||||
let mut version_channel = words.next()?.split('-');
|
||||
let version = version_channel.next()?;
|
||||
let channel = version_channel.next();
|
||||
|
||||
let mut digits = version.split('.');
|
||||
let major = digits.next()?;
|
||||
if major != "1" {
|
||||
return None;
|
||||
}
|
||||
let minor = digits.next()?.parse().ok()?;
|
||||
let patch = digits.next().unwrap_or("0").parse().ok()?;
|
||||
|
||||
let channel = match channel {
|
||||
None => Stable,
|
||||
Some("dev") => Dev,
|
||||
Some(channel) if channel.starts_with("beta") => Beta,
|
||||
Some("nightly") => match words.next() {
|
||||
Some(hash) if hash.starts_with('(') => match words.next() {
|
||||
None if hash.ends_with(')') => Dev,
|
||||
Some(date) if date.ends_with(')') => {
|
||||
let mut date = date[..date.len() - 1].split('-');
|
||||
let year = date.next()?.parse().ok()?;
|
||||
let month = date.next()?.parse().ok()?;
|
||||
let day = date.next()?.parse().ok()?;
|
||||
match date.next() {
|
||||
None => Nightly(Date { year, month, day }),
|
||||
Some(_) => return None,
|
||||
}
|
||||
}
|
||||
None | Some(_) => return None,
|
||||
},
|
||||
Some(_) => return None,
|
||||
None => Dev,
|
||||
},
|
||||
Some(_) => return None,
|
||||
};
|
||||
|
||||
Some(Version {
|
||||
minor,
|
||||
patch,
|
||||
channel,
|
||||
})
|
||||
}
|
||||
|
||||
impl Debug for Version {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("crate::version::Version")
|
||||
.field("minor", &self.minor)
|
||||
.field("patch", &self.patch)
|
||||
.field("channel", &self.channel)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Channel {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Channel::Stable => formatter.write_str("crate::version::Channel::Stable"),
|
||||
Channel::Beta => formatter.write_str("crate::version::Channel::Beta"),
|
||||
Channel::Nightly(date) => formatter
|
||||
.debug_tuple("crate::version::Channel::Nightly")
|
||||
.field(date)
|
||||
.finish(),
|
||||
Channel::Dev => formatter.write_str("crate::version::Channel::Dev"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Date {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("crate::date::Date")
|
||||
.field("year", &self.year)
|
||||
.field("month", &self.month)
|
||||
.field("day", &self.day)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user