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
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"3ebc3f4994e606eb23003e9c891e4e4a2cfdddcd6d0b6c738deea4b38240acc0","Cargo.lock":"71b087cce63e17e9dd94f8dc18b227983f7b24c942672f506535b949df4d6f35","Cargo.toml":"830244f7c63478a57e214ef9ca2b76d6c7e6fd3d30bfe1cadcf58dc1942ff019","Cargo.toml.orig":"dd1441d18aa6b5d8f716f0f5b8a62cb79d52a6665923cdbb1bee12975e77b7ea","LICENSE":"3eab77734a181b2a2e8418af9004f960f715f04aac4d76a26cf774f63a7b13d1","README.md":"93b3f5c468e2d326f59c5882a758500ca96dcef81521f7c20fa7d6f89e2870f1","build.rs":"08bfcfa380ded6870ed262baa4b6e87ec54482306aebffabefbd19c83ad4b6ba","src/lib.rs":"9eb3f0288685ff7b7987a37aa48a6c50dc0f2016732d40a0cd8c7de96284ccca"},"package":"460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "ba7b86cea6ba89ccfc72ccb24cc4a4ac6d9c6272"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
authors = ["Eira Fransham <jackefransham@gmail.com>"]
|
||||
build = "build.rs"
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Crunchy unroller: deterministically unroll constant loops"
|
||||
homepage = "https://github.com/eira-fransham/crunchy"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/eira-fransham/crunchy"
|
||||
|
||||
[features]
|
||||
default = ["limit_128"]
|
||||
limit_1024 = []
|
||||
limit_128 = []
|
||||
limit_2048 = []
|
||||
limit_256 = []
|
||||
limit_512 = []
|
||||
limit_64 = []
|
||||
std = []
|
||||
|
||||
[lib]
|
||||
name = "crunchy"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2017-2023 Eira Fransham.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
# Crunchy
|
||||
|
||||
The crunchy unroller - deterministically unroll constant loops. For number
|
||||
"crunching".
|
||||
|
||||
The Rust optimizer will unroll constant loops that don't use the loop variable,
|
||||
like this:
|
||||
|
||||
```rust
|
||||
for _ in 0..100 {
|
||||
println!("Hello!");
|
||||
}
|
||||
```
|
||||
|
||||
However, using the loop variable will cause it to never unroll the loop. This is
|
||||
unfortunate because it means that you can't constant-fold the loop variable, and
|
||||
if you end up stomping on the registers it will have to do a load for each
|
||||
iteration. This crate ensures that your code is unrolled and const-folded. It
|
||||
only works on literals, unfortunately, but there's a work-around:
|
||||
|
||||
```rust
|
||||
debug_assert_eq!(MY_CONSTANT, 100);
|
||||
unroll! {
|
||||
for i in 0..100 {
|
||||
println!("Iteration {}", i);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This means that your tests will catch if you redefine the constant.
|
||||
|
||||
To default maximum number of loops to unroll is `128`, but that can be easily decreased or increased using the cargo features:
|
||||
|
||||
* `limit_64`
|
||||
* `limit_128`
|
||||
* `limit_256`
|
||||
* `limit_512`
|
||||
* `limit_1024`
|
||||
* `limit_2048`
|
||||
Vendored
+255
@@ -0,0 +1,255 @@
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
const LOWER_LIMIT: usize = 16;
|
||||
|
||||
fn main() {
|
||||
let limit = if cfg!(feature="limit_2048") {
|
||||
2048
|
||||
} else if cfg!(feature="limit_1024") {
|
||||
1024
|
||||
} else if cfg!(feature="limit_512") {
|
||||
512
|
||||
} else if cfg!(feature="limit_256") {
|
||||
256
|
||||
} else if cfg!(feature="limit_128") {
|
||||
128
|
||||
} else {
|
||||
64
|
||||
};
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("lib.rs");
|
||||
let mut f = File::create(&dest_path).unwrap();
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str(r#"
|
||||
/// Unroll the given for loop
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// unroll! {
|
||||
/// for i in 0..5 {
|
||||
/// println!("Iteration {}", i);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// will expand into:
|
||||
///
|
||||
/// ```ignore
|
||||
/// { println!("Iteration {}", 0); }
|
||||
/// { println!("Iteration {}", 1); }
|
||||
/// { println!("Iteration {}", 2); }
|
||||
/// { println!("Iteration {}", 3); }
|
||||
/// { println!("Iteration {}", 4); }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! unroll {
|
||||
(for $v:ident in 0..0 $c:block) => {};
|
||||
|
||||
(for $v:ident < $max:tt in ($start:tt..$end:tt).step_by($val:expr) {$($c:tt)*}) => {
|
||||
{
|
||||
let step = $val;
|
||||
let start = $start;
|
||||
let end = start + ($end - start) / step;
|
||||
unroll! {
|
||||
for val < $max in start..end {
|
||||
let $v: usize = ((val - start) * step) + start;
|
||||
|
||||
$($c)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(for $v:ident in ($start:tt..$end:tt).step_by($val:expr) {$($c:tt)*}) => {
|
||||
unroll! {
|
||||
for $v < $end in ($start..$end).step_by($val) {$($c)*}
|
||||
}
|
||||
};
|
||||
|
||||
(for $v:ident in ($start:tt..$end:tt) {$($c:tt)*}) => {
|
||||
unroll!{
|
||||
for $v in $start..$end {$($c)*}
|
||||
}
|
||||
};
|
||||
|
||||
(for $v:ident in $start:tt..$end:tt {$($c:tt)*}) => {
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(unused_comparisons)]
|
||||
{
|
||||
unroll!(@$v, 0, $end, {
|
||||
if $v >= $start {$($c)*}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
(for $v:ident < $max:tt in $start:tt..$end:tt $c:block) => {
|
||||
#[allow(non_upper_case_globals)]
|
||||
{
|
||||
let range = $start..$end;
|
||||
assert!(
|
||||
$max >= range.end,
|
||||
"`{}` out of range `{:?}`",
|
||||
stringify!($max),
|
||||
range,
|
||||
);
|
||||
unroll!(
|
||||
@$v,
|
||||
0,
|
||||
$max,
|
||||
{
|
||||
if $v >= range.start && $v < range.end {
|
||||
$c
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
(for $v:ident in 0..$end:tt {$($statement:tt)*}) => {
|
||||
#[allow(non_upper_case_globals)]
|
||||
{ unroll!(@$v, 0, $end, {$($statement)*}); }
|
||||
};
|
||||
|
||||
"#);
|
||||
|
||||
for i in 0..limit + 1 {
|
||||
output.push_str(format!(" (@$v:ident, $a:expr, {}, $c:block) => {{\n", i).as_str());
|
||||
|
||||
if i <= LOWER_LIMIT {
|
||||
output.push_str(format!(" {{ const $v: usize = $a; $c }}\n").as_str());
|
||||
|
||||
for a in 1..i {
|
||||
output.push_str(format!(" {{ const $v: usize = $a + {}; $c }}\n", a).as_str());
|
||||
}
|
||||
} else {
|
||||
let half = i / 2;
|
||||
|
||||
if i % 2 == 0 {
|
||||
output.push_str(format!(" unroll!(@$v, $a, {0}, $c);\n", half).as_str());
|
||||
output.push_str(format!(" unroll!(@$v, $a + {0}, {0}, $c);\n", half).as_str());
|
||||
} else {
|
||||
if half > 1 {
|
||||
output.push_str(format!(" unroll!(@$v, $a, {}, $c);\n", i - 1).as_str())
|
||||
}
|
||||
|
||||
output.push_str(format!(" {{ const $v: usize = $a + {}; $c }}\n", i - 1).as_str());
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(" };\n\n");
|
||||
}
|
||||
|
||||
output.push_str("}\n\n");
|
||||
|
||||
output.push_str(format!(r#"
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod tests {{
|
||||
#[test]
|
||||
fn invalid_range() {{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in (5..4) {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, vec![]);
|
||||
}}
|
||||
|
||||
#[test]
|
||||
fn start_at_one_with_step() {{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in (2..4).step_by(1) {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, vec![2, 3]);
|
||||
}}
|
||||
|
||||
#[test]
|
||||
fn start_at_one() {{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in 1..4 {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, vec![1, 2, 3]);
|
||||
}}
|
||||
|
||||
#[test]
|
||||
fn test_all() {{
|
||||
{{
|
||||
let a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in 0..0 {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, (0..0).collect::<Vec<usize>>());
|
||||
}}
|
||||
{{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in 0..1 {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, (0..1).collect::<Vec<usize>>());
|
||||
}}
|
||||
{{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in 0..{0} {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, (0..{0}).collect::<Vec<usize>>());
|
||||
}}
|
||||
{{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
let start = {0} / 4;
|
||||
let end = start * 3;
|
||||
unroll! {{
|
||||
for i < {0} in start..end {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, (start..end).collect::<Vec<usize>>());
|
||||
}}
|
||||
{{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
unroll! {{
|
||||
for i in (0..{0}).step_by(2) {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, (0..{0} / 2).map(|x| x * 2).collect::<Vec<usize>>());
|
||||
}}
|
||||
{{
|
||||
let mut a: Vec<usize> = vec![];
|
||||
let start = {0} / 4;
|
||||
let end = start * 3;
|
||||
unroll! {{
|
||||
for i < {0} in (start..end).step_by(2) {{
|
||||
a.push(i);
|
||||
}}
|
||||
}}
|
||||
assert_eq!(a, (start..end).filter(|x| x % 2 == 0).collect::<Vec<usize>>());
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"#, limit).as_str());
|
||||
|
||||
f.write_all(output.as_bytes()).unwrap();
|
||||
|
||||
println!("cargo:rustc-env=CRUNCHY_LIB_SUFFIX={}lib.rs", std::path::MAIN_SEPARATOR);
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
//! The crunchy unroller - deterministically unroll constant loops. For number "crunching".
|
||||
//!
|
||||
//! The Rust optimizer will unroll constant loops that don't use the loop variable, like this:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! for _ in 0..100 {
|
||||
//! println!("Hello!");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! However, using the loop variable will cause it to never unroll the loop. This is unfortunate because it means that you can't
|
||||
//! constant-fold the loop variable, and if you end up stomping on the registers it will have to do a load for each iteration.
|
||||
//! This crate ensures that your code is unrolled and const-folded. It only works on literals,
|
||||
//! unfortunately, but there's a work-around:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! debug_assert_eq!(MY_CONSTANT, 100);
|
||||
//! unroll! {
|
||||
//! for i in 0..100 {
|
||||
//! println!("Iteration {}", i);
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//! This means that your tests will catch if you redefine the constant.
|
||||
//!
|
||||
//! To default maximum number of loops to unroll is `64`, but that can be easily increased using the cargo features:
|
||||
//!
|
||||
//! * `limit_128`
|
||||
//! * `limit_256`
|
||||
//! * `limit_512`
|
||||
//! * `limit_1024`
|
||||
//! * `limit_2048`
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), env!("CRUNCHY_LIB_SUFFIX")));
|
||||
|
||||
Reference in new issue
Block a user