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
+257
@@ -0,0 +1,257 @@
|
||||
// Copyright 2024 The Fuchsia Authors
|
||||
//
|
||||
// Licensed under the 2-Clause BSD License <LICENSE-BSD or
|
||||
// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0
|
||||
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
|
||||
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
|
||||
// This file may not be copied, modified, or distributed except according to
|
||||
// those terms.
|
||||
|
||||
// Sometimes we want to use lints which were added after our MSRV.
|
||||
// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
|
||||
// this attribute, any unknown lint would cause a CI failure when testing with
|
||||
// our MSRV.
|
||||
#![allow(unknown_lints)]
|
||||
#![deny(renamed_and_removed_lints)]
|
||||
#![deny(
|
||||
anonymous_parameters,
|
||||
deprecated_in_future,
|
||||
late_bound_lifetime_arguments,
|
||||
missing_copy_implementations,
|
||||
missing_debug_implementations,
|
||||
path_statements,
|
||||
patterns_in_fns_without_body,
|
||||
rust_2018_idioms,
|
||||
trivial_numeric_casts,
|
||||
unreachable_pub,
|
||||
unsafe_op_in_unsafe_fn,
|
||||
unused_extern_crates,
|
||||
variant_size_differences
|
||||
)]
|
||||
#![deny(
|
||||
clippy::all,
|
||||
clippy::alloc_instead_of_core,
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::as_underscore,
|
||||
clippy::assertions_on_result_states,
|
||||
clippy::as_conversions,
|
||||
clippy::correctness,
|
||||
clippy::dbg_macro,
|
||||
clippy::decimal_literal_representation,
|
||||
clippy::get_unwrap,
|
||||
clippy::indexing_slicing,
|
||||
clippy::missing_inline_in_public_items,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::obfuscated_if_else,
|
||||
clippy::perf,
|
||||
clippy::print_stdout,
|
||||
clippy::style,
|
||||
clippy::suspicious,
|
||||
clippy::todo,
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unwrap_used,
|
||||
clippy::use_debug
|
||||
)]
|
||||
|
||||
use std::{env, fs, process::Command, str};
|
||||
|
||||
fn main() {
|
||||
// Avoid unnecessary re-building.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
// This is necessary because changes to the list of detected Rust toolchain
|
||||
// versions will affect what `--cfg`s this script emits. Without this,
|
||||
// changes to that list have no effect on the build without running `cargo
|
||||
// clean` or similar.
|
||||
println!("cargo:rerun-if-changed=Cargo.toml");
|
||||
|
||||
let version_cfgs = parse_version_cfgs_from_cargo_toml();
|
||||
let rustc_version = rustc_version();
|
||||
|
||||
if rustc_version >= (Version { major: 1, minor: 77, patch: 0 }) {
|
||||
for version_cfg in &version_cfgs {
|
||||
// This tells the `unexpected_cfgs` lint to expect to see all of
|
||||
// these `cfg`s. The `cargo::` syntax was only added in 1.77, so we
|
||||
// don't emit these on earlier toolchain versions.
|
||||
println!("cargo:rustc-check-cfg=cfg({})", version_cfg.cfg_name);
|
||||
|
||||
// This tells the `unexpected_cfgs` lint to expect to see `cfg`s of
|
||||
// the form `rust = "1.2.3"`. These aren't real `cfg`s, but we use
|
||||
// them in `cfg_attr(doc_cfg, doc(cfg(rust = "1.2.3")))` on items
|
||||
// that are version-gated so that the rendered Rustdoc shows which
|
||||
// Rust toolchain versions those items are available on.
|
||||
let Version { major, minor, patch } = version_cfg.version;
|
||||
println!("cargo:rustc-check-cfg=cfg(rust, values(\"{}.{}.{}\"))", major, minor, patch);
|
||||
}
|
||||
// FIXME(https://github.com/rust-lang/rust/issues/124816): Remove these
|
||||
// once they're no longer needed.
|
||||
println!("cargo:rustc-check-cfg=cfg(doc_cfg)");
|
||||
println!("cargo:rustc-check-cfg=cfg(kani)");
|
||||
println!(
|
||||
"cargo:rustc-check-cfg=cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)"
|
||||
);
|
||||
println!("cargo:rustc-check-cfg=cfg(__ZEROCOPY_INTERNAL_USE_ONLY_DEV_MODE)");
|
||||
println!("cargo:rustc-check-cfg=cfg(coverage_nightly)");
|
||||
}
|
||||
|
||||
for version_cfg in version_cfgs {
|
||||
if rustc_version < version_cfg.version {
|
||||
println!("cargo:rustc-cfg={}", version_cfg.cfg_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialEq, PartialOrd, Eq)]
|
||||
struct Version {
|
||||
major: usize,
|
||||
minor: usize,
|
||||
patch: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VersionCfg {
|
||||
version: Version,
|
||||
cfg_name: String,
|
||||
}
|
||||
|
||||
const ITER_FIRST_NEXT_EXPECT_MSG: &str = "unreachable: a string split cannot produce 0 items";
|
||||
|
||||
fn parse_version_cfgs_from_cargo_toml() -> Vec<VersionCfg> {
|
||||
let cargo_toml = fs::read_to_string("Cargo.toml").expect("failed to read Cargo.toml");
|
||||
|
||||
// Expect a Cargo.toml with the following format:
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// [package.metadata.build-rs]
|
||||
// # Comments...
|
||||
// zerocopy-panic-in-const-fn = "1.57.0"
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// [...]
|
||||
//
|
||||
// In other words, the following sections, in order:
|
||||
// - Arbitrary content
|
||||
// - The literal header `[package.metadata.build-rs]`
|
||||
// - Any number of:
|
||||
// - Comments
|
||||
// - Key/value pairs
|
||||
// - A TOML table, indicating the end of the section we care about
|
||||
|
||||
const TABLE_HEADER: &str = "[package.metadata.build-rs]";
|
||||
|
||||
if !cargo_toml.contains(TABLE_HEADER) {
|
||||
panic!("{}", format!("Cargo.toml does not contain `{}`", TABLE_HEADER));
|
||||
}
|
||||
|
||||
// Now that we know there's at least one instance of `TABLE_HEADER`, we
|
||||
// consume the iterator until we find the text following that first
|
||||
// instance. This isn't terribly bullet-proof, but we also authored
|
||||
// `Cargo.toml`, and we'd have to mess up pretty badly to accidentally put
|
||||
// two copies of the same table header in that file.
|
||||
let mut iter = cargo_toml.split(TABLE_HEADER);
|
||||
let _prefix = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
|
||||
let rest = iter.next().expect("unreachable: we already confirmed that there's a table header");
|
||||
|
||||
// Scan until we find the next table section, which should start with a `[`
|
||||
// character at the beginning of a line.
|
||||
let mut iter = rest.split("\n[");
|
||||
let section = iter.next().expect("unreachable: a string split cannot produce 0 items");
|
||||
|
||||
section
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
// Parse lines of one of the following forms:
|
||||
//
|
||||
// # Comment
|
||||
//
|
||||
// name-of-key = "1.2.3" # Comment
|
||||
//
|
||||
// Comments on their own line are ignored, and comments after a
|
||||
// key/value pair will be stripped before further processing.
|
||||
|
||||
// We don't need to handle the case where the `#` character isn't a
|
||||
// comment (which can happen if it's inside a string) since we authored
|
||||
// `Cargo.toml` and, in this section, we only put Rust version numbers
|
||||
// in strings.
|
||||
let before_comment = line.split('#').next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
|
||||
let before_comment_without_whitespace = before_comment.trim_start();
|
||||
if before_comment_without_whitespace.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// At this point, assuming Cargo.toml is correctly formatted according
|
||||
// to the format expected by this function, we know that
|
||||
// `before_comment_without_whitespace` is of the form:
|
||||
//
|
||||
// name-of-key = "1.2.3" # Comment
|
||||
//
|
||||
// ...with no leading whitespace, and where the trailing comment is
|
||||
// optional.
|
||||
|
||||
let mut iter = before_comment_without_whitespace.split_whitespace();
|
||||
let name = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
|
||||
const EXPECT_MSG: &str =
|
||||
"expected lines of the format `name-of-key = \"1.2.3\" # Comment`";
|
||||
let equals_sign = iter.next().expect(EXPECT_MSG);
|
||||
let value = iter.next().expect(EXPECT_MSG);
|
||||
|
||||
assert_eq!(equals_sign, "=", "{}", EXPECT_MSG);
|
||||
|
||||
// Replace dashes with underscores.
|
||||
let name = name.replace('-', "_");
|
||||
|
||||
// Strip the quotation marks.
|
||||
let value = value.trim_start_matches('"').trim_end_matches('"');
|
||||
|
||||
let mut iter = value.split('.');
|
||||
let major = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
|
||||
let minor = iter.next().expect(EXPECT_MSG);
|
||||
let patch = iter.next().expect(EXPECT_MSG);
|
||||
|
||||
assert_eq!(iter.next(), None, "{}", EXPECT_MSG);
|
||||
|
||||
let major: usize = major.parse().expect(EXPECT_MSG);
|
||||
let minor: usize = minor.parse().expect(EXPECT_MSG);
|
||||
let patch: usize = patch.parse().expect(EXPECT_MSG);
|
||||
|
||||
Some(VersionCfg { version: Version { major, minor, patch }, cfg_name: name })
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn rustc_version() -> Version {
|
||||
let rustc_cmd_name = env::var_os("RUSTC").expect("could not get rustc command name");
|
||||
let version =
|
||||
Command::new(rustc_cmd_name).arg("--version").output().expect("could not invoke rustc");
|
||||
if !version.status.success() {
|
||||
panic!(
|
||||
"rustc failed with status: {}\nrustc output: {}",
|
||||
version.status,
|
||||
String::from_utf8_lossy(version.stderr.as_slice())
|
||||
);
|
||||
}
|
||||
|
||||
const RUSTC_EXPECT_MSG: &str = "could not parse rustc version output";
|
||||
let version = str::from_utf8(version.stdout.as_slice()).expect(RUSTC_EXPECT_MSG);
|
||||
let version = version.trim_start_matches("rustc ");
|
||||
// The version string is sometimes followed by other information such as the
|
||||
// string `-nightly` or other build information. We don't care about any of
|
||||
// that.
|
||||
let version = version
|
||||
.split(|c: char| c != '.' && !c.is_ascii_digit())
|
||||
.next()
|
||||
.expect(ITER_FIRST_NEXT_EXPECT_MSG);
|
||||
let mut iter = version.split('.');
|
||||
let major = iter.next().expect(ITER_FIRST_NEXT_EXPECT_MSG);
|
||||
let minor = iter.next().expect(RUSTC_EXPECT_MSG);
|
||||
let patch = iter.next().expect(RUSTC_EXPECT_MSG);
|
||||
|
||||
let major: usize = major.parse().expect(RUSTC_EXPECT_MSG);
|
||||
let minor: usize = minor.parse().expect(RUSTC_EXPECT_MSG);
|
||||
let patch: usize = patch.parse().expect(RUSTC_EXPECT_MSG);
|
||||
|
||||
Version { major, minor, patch }
|
||||
}
|
||||
Reference in new issue
Block a user