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>
134 lines
4.1 KiB
Rust
134 lines
4.1 KiB
Rust
// Copyright © 2018–2025 Trevor Spiteri
|
||
|
||
// Copying and distribution of this file, with or without
|
||
// modification, are permitted in any medium without royalty provided
|
||
// the copyright notice and this notice are preserved. This file is
|
||
// offered as-is, without any warranty.
|
||
|
||
#![allow(dead_code)]
|
||
#![allow(unused_variables)]
|
||
#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
|
||
|
||
use std::env;
|
||
use std::ffi::OsString;
|
||
use std::fs;
|
||
use std::fs::File;
|
||
use std::io::{Result as IoResult, Write};
|
||
use std::path::{Path, PathBuf};
|
||
use std::process::Command;
|
||
|
||
struct Environment {
|
||
out_dir: PathBuf,
|
||
rustc: OsString,
|
||
}
|
||
|
||
fn main() {
|
||
let env = Environment {
|
||
out_dir: PathBuf::from(cargo_env("OUT_DIR")),
|
||
rustc: cargo_env("RUSTC"),
|
||
};
|
||
}
|
||
|
||
#[derive(Clone, Copy, PartialEq)]
|
||
struct Optional(bool);
|
||
|
||
impl Environment {
|
||
// 1. If optional feature is availble (both with and without flag), output:
|
||
// cargo::rustc-cfg=<name>
|
||
// 2. If feature is available with flag (both optional and not), output:
|
||
// cargo::rustc-cfg=nightly_<name>
|
||
// 3. If non-optional feature is not available, panic.
|
||
fn check_feature(
|
||
&self,
|
||
name: &str,
|
||
optional: Optional,
|
||
contents: &str,
|
||
nightly_features: Option<&str>,
|
||
) {
|
||
let try_dir = self.out_dir.join(format!("try_{name}"));
|
||
let filename = format!("try_{name}.rs");
|
||
create_dir_or_panic(&try_dir);
|
||
println!("$ cd {try_dir:?}");
|
||
|
||
#[derive(PartialEq)]
|
||
enum Iteration {
|
||
Stable,
|
||
Unstable,
|
||
}
|
||
let mut found = false;
|
||
for i in &[Iteration::Stable, Iteration::Unstable] {
|
||
let s;
|
||
let file_contents = match *i {
|
||
Iteration::Stable => contents,
|
||
Iteration::Unstable => match nightly_features {
|
||
Some(features) => {
|
||
s = format!("#![feature({features})]\n{contents}");
|
||
&s
|
||
}
|
||
None => continue,
|
||
},
|
||
};
|
||
create_file_or_panic(&try_dir.join(&filename), file_contents);
|
||
let mut cmd = Command::new(&self.rustc);
|
||
cmd.current_dir(&try_dir)
|
||
.args([&filename, "--emit=dep-info,metadata"]);
|
||
println!("$ {cmd:?}");
|
||
let status = cmd
|
||
.status()
|
||
.unwrap_or_else(|_| panic!("Unable to execute: {cmd:?}"));
|
||
println!("cargo::rustc-cfg=cfg({name})");
|
||
println!("cargo::rustc-cfg=cfg(nightly_{name})");
|
||
if status.success() {
|
||
if optional.0 {
|
||
println!("cargo::rustc-cfg={name}");
|
||
}
|
||
if *i == Iteration::Unstable {
|
||
println!("cargo::rustc-cfg=nightly_{name}");
|
||
}
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
remove_dir_or_panic(&try_dir);
|
||
assert!(
|
||
found || optional.0,
|
||
"essential feature not supported by compiler: {name}"
|
||
);
|
||
}
|
||
}
|
||
|
||
fn cargo_env(name: &str) -> OsString {
|
||
env::var_os(name)
|
||
.unwrap_or_else(|| panic!("environment variable not found: {name}, please use cargo"))
|
||
}
|
||
|
||
fn remove_dir(dir: &Path) -> IoResult<()> {
|
||
if !dir.exists() {
|
||
return Ok(());
|
||
}
|
||
assert!(dir.is_dir(), "Not a directory: {dir:?}");
|
||
println!("$ rm -r {dir:?}");
|
||
fs::remove_dir_all(dir)
|
||
}
|
||
|
||
fn remove_dir_or_panic(dir: &Path) {
|
||
remove_dir(dir).unwrap_or_else(|_| panic!("Unable to remove directory: {dir:?}"));
|
||
}
|
||
|
||
fn create_dir(dir: &Path) -> IoResult<()> {
|
||
println!("$ mkdir -p {dir:?}");
|
||
fs::create_dir_all(dir)
|
||
}
|
||
|
||
fn create_dir_or_panic(dir: &Path) {
|
||
create_dir(dir).unwrap_or_else(|_| panic!("Unable to create directory: {dir:?}"));
|
||
}
|
||
|
||
fn create_file_or_panic(filename: &Path, contents: &str) {
|
||
println!("$ printf '%s' {:?}... > {:?}", &contents[0..20], filename);
|
||
let mut file =
|
||
File::create(filename).unwrap_or_else(|_| panic!("Unable to create file: {filename:?}"));
|
||
file.write_all(contents.as_bytes())
|
||
.unwrap_or_else(|_| panic!("Unable to write to file: {filename:?}"));
|
||
}
|