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
+247
@@ -0,0 +1,247 @@
|
||||
//! Metadata for Espressif devices, primarily intended for use in build scripts.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
macro_rules! include_toml {
|
||||
($type:ty, $file:expr) => {{
|
||||
static LOADED_TOML: OnceLock<$type> = OnceLock::new();
|
||||
LOADED_TOML.get_or_init(|| basic_toml::from_str(include_str!($file)).unwrap())
|
||||
}};
|
||||
}
|
||||
|
||||
/// Supported device architectures.
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumIter,
|
||||
strum::EnumString,
|
||||
strum::AsRefStr,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum Arch {
|
||||
/// RISC-V architecture
|
||||
RiscV,
|
||||
/// Xtensa architecture
|
||||
Xtensa,
|
||||
}
|
||||
|
||||
/// Device core count.
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumIter,
|
||||
strum::EnumString,
|
||||
strum::AsRefStr,
|
||||
)]
|
||||
pub enum Cores {
|
||||
/// Single CPU core
|
||||
#[serde(rename = "single_core")]
|
||||
#[strum(serialize = "single_core")]
|
||||
Single,
|
||||
/// Two or more CPU cores
|
||||
#[serde(rename = "multi_core")]
|
||||
#[strum(serialize = "multi_core")]
|
||||
Multi,
|
||||
}
|
||||
|
||||
/// Supported devices.
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumIter,
|
||||
strum::EnumString,
|
||||
strum::AsRefStr,
|
||||
)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum Chip {
|
||||
/// ESP32
|
||||
Esp32,
|
||||
/// ESP32-C2, ESP8684
|
||||
Esp32c2,
|
||||
/// ESP32-C3, ESP8685
|
||||
Esp32c3,
|
||||
/// ESP32-C6
|
||||
Esp32c6,
|
||||
/// ESP32-H2
|
||||
Esp32h2,
|
||||
/// ESP32-S2
|
||||
Esp32s2,
|
||||
/// ESP32-S3
|
||||
Esp32s3,
|
||||
}
|
||||
|
||||
impl Chip {
|
||||
pub fn target(&self) -> &str {
|
||||
use Chip::*;
|
||||
|
||||
match self {
|
||||
Esp32 => "xtensa-esp32-none-elf",
|
||||
Esp32c2 | Esp32c3 => "riscv32imc-unknown-none-elf",
|
||||
Esp32c6 | Esp32h2 => "riscv32imac-unknown-none-elf",
|
||||
Esp32s2 => "xtensa-esp32s2-none-elf",
|
||||
Esp32s3 => "xtensa-esp32s3-none-elf",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_lp_core(&self) -> bool {
|
||||
use Chip::*;
|
||||
|
||||
matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
|
||||
}
|
||||
|
||||
pub fn lp_target(&self) -> Result<&str> {
|
||||
use Chip::*;
|
||||
|
||||
match self {
|
||||
Esp32c6 => Ok("riscv32imac-unknown-none-elf"),
|
||||
Esp32s2 | Esp32s3 => Ok("riscv32imc-unknown-none-elf"),
|
||||
_ => bail!("Chip does not contain an LP core: '{}'", self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pretty_name(&self) -> &str {
|
||||
match self {
|
||||
Chip::Esp32 => "ESP32",
|
||||
Chip::Esp32c2 => "ESP32-C2",
|
||||
Chip::Esp32c3 => "ESP32-C3",
|
||||
Chip::Esp32c6 => "ESP32-C6",
|
||||
Chip::Esp32h2 => "ESP32-H2",
|
||||
Chip::Esp32s2 => "ESP32-S2",
|
||||
Chip::Esp32s3 => "ESP32-S3",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_xtensa(&self) -> bool {
|
||||
matches!(self, Chip::Esp32 | Chip::Esp32s2 | Chip::Esp32s3)
|
||||
}
|
||||
|
||||
pub fn is_riscv(&self) -> bool {
|
||||
!self.is_xtensa()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct Device {
|
||||
pub name: String,
|
||||
pub arch: Arch,
|
||||
pub cores: Cores,
|
||||
pub peripherals: Vec<String>,
|
||||
pub symbols: Vec<String>,
|
||||
}
|
||||
|
||||
/// Device configuration file format.
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Config {
|
||||
device: Device,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// The configuration for the specified chip.
|
||||
pub fn for_chip(chip: &Chip) -> &Self {
|
||||
match chip {
|
||||
Chip::Esp32 => include_toml!(Config, "../devices/esp32.toml"),
|
||||
Chip::Esp32c2 => include_toml!(Config, "../devices/esp32c2.toml"),
|
||||
Chip::Esp32c3 => include_toml!(Config, "../devices/esp32c3.toml"),
|
||||
Chip::Esp32c6 => include_toml!(Config, "../devices/esp32c6.toml"),
|
||||
Chip::Esp32h2 => include_toml!(Config, "../devices/esp32h2.toml"),
|
||||
Chip::Esp32s2 => include_toml!(Config, "../devices/esp32s2.toml"),
|
||||
Chip::Esp32s3 => include_toml!(Config, "../devices/esp32s3.toml"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the device.
|
||||
pub fn name(&self) -> String {
|
||||
self.device.name.clone()
|
||||
}
|
||||
|
||||
/// The CPU architecture of the device.
|
||||
pub fn arch(&self) -> Arch {
|
||||
self.device.arch
|
||||
}
|
||||
|
||||
/// The core count of the device.
|
||||
pub fn cores(&self) -> Cores {
|
||||
self.device.cores
|
||||
}
|
||||
|
||||
/// The peripherals of the device.
|
||||
pub fn peripherals(&self) -> &[String] {
|
||||
&self.device.peripherals
|
||||
}
|
||||
|
||||
/// User-defined symbols for the device.
|
||||
pub fn symbols(&self) -> &[String] {
|
||||
&self.device.symbols
|
||||
}
|
||||
|
||||
/// All configuration values for the device.
|
||||
pub fn all(&self) -> impl Iterator<Item = &str> + '_ {
|
||||
[
|
||||
self.device.name.as_str(),
|
||||
self.device.arch.as_ref(),
|
||||
self.device.cores.as_ref(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(self.device.peripherals.iter().map(|s| s.as_str()))
|
||||
.chain(self.device.symbols.iter().map(|s| s.as_str()))
|
||||
}
|
||||
|
||||
/// Does the configuration contain `item`?
|
||||
pub fn contains(&self, item: &str) -> bool {
|
||||
self.all().any(|i| i == item)
|
||||
}
|
||||
|
||||
/// Define all symbols for a given configuration.
|
||||
pub fn define_symbols(&self) {
|
||||
define_all_possible_symbols();
|
||||
// Define all necessary configuration symbols for the configured device:
|
||||
for symbol in self.all() {
|
||||
println!("cargo:rustc-cfg={symbol}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines all possible symbols that _could_ be output from this crate
|
||||
/// regardless of the chosen configuration.
|
||||
///
|
||||
/// This is required to avoid triggering the unexpected-cfgs lint.
|
||||
fn define_all_possible_symbols() {
|
||||
for chip in Chip::iter() {
|
||||
let config = Config::for_chip(&chip);
|
||||
for symbol in config.all() {
|
||||
// https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-check-cfg
|
||||
println!("cargo:rustc-check-cfg=cfg({})", symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user