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:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

@@ -0,0 +1,13 @@
#[derive(darling::FromDeriveInput)]
#[darling(attributes(example), forward_attrs(example))]
pub struct Example {
ignored: String,
}
#[derive(darling::FromField)]
#[darling(attributes(example), forward_attrs(example))]
pub struct ExampleField {
ignored: String,
}
fn main() {}
@@ -0,0 +1,11 @@
error: attribute path `example` will not be forwarded because it is also listed in `attributes`
--> tests/compile-fail/attr_forwarded_and_parsed.rs:2:46
|
2 | #[darling(attributes(example), forward_attrs(example))]
| ^^^^^^^
error: attribute path `example` will not be forwarded because it is also listed in `attributes`
--> tests/compile-fail/attr_forwarded_and_parsed.rs:8:46
|
8 | #[darling(attributes(example), forward_attrs(example))]
| ^^^^^^^
+15
View File
@@ -0,0 +1,15 @@
use darling::FromDeriveInput;
use syn::Attribute;
fn bad_converter(attrs: Vec<Attribute>) -> Vec<Attribute> {
attrs
}
#[derive(FromDeriveInput)]
#[darling(forward_attrs)]
struct Receiver {
#[darling(with = bad_converter)]
attrs: Vec<Attribute>,
}
fn main() {}
@@ -0,0 +1,20 @@
error[E0308]: mismatched types
--> tests/compile-fail/attrs_with_bad_fn.rs:11:22
|
11 | #[darling(with = bad_converter)]
| ^^^^^^^^^^^^^
| |
| expected `Result<_, Error>`, found `Vec<Attribute>`
| arguments to this method are incorrect
|
= note: expected enum `Result<_, darling::Error>`
found struct `Vec<Attribute>`
note: method defined here
--> core/src/error/mod.rs
|
| pub fn handle<T>(&mut self, result: Result<T>) -> Option<T> {
| ^^^^^^
help: try wrapping the expression in `Ok`
|
11 | #[darling(with = Ok(bad_converter))]
| +++ +
@@ -0,0 +1,10 @@
use darling::FromDeriveInput;
use syn::{Attribute, Ident};
#[derive(FromDeriveInput)]
struct HelloArgs {
ident: Ident,
attrs: Vec<Attribute>,
}
fn main() {}
@@ -0,0 +1,5 @@
error: field will not be populated because `forward_attrs` is not set on the struct
--> tests/compile-fail/attrs_without_forward_attrs.rs:7:5
|
7 | attrs: Vec<Attribute>,
| ^^^^^
@@ -0,0 +1,12 @@
use darling::FromMeta;
#[derive(FromMeta)]
struct Receiver {
#[darling(default = "usize::default")]
not_u32: String,
#[darling(multiple, default = "usize::default")]
also_not_u32: Vec<String>,
}
fn main() {}
@@ -0,0 +1,16 @@
error[E0308]: mismatched types
--> tests/compile-fail/default_expr_wrong_type.rs:5:25
|
5 | #[darling(default = "usize::default")]
| ^^^^^^^^^^^^^^^^- help: try using a conversion method: `.to_string()`
| |
| expected `String`, found `usize`
error[E0308]: mismatched types
--> tests/compile-fail/default_expr_wrong_type.rs:8:35
|
8 | #[darling(multiple, default = "usize::default")]
| ^^^^^^^^^^^^^^^^ expected `Vec<String>`, found `usize`
|
= note: expected struct `Vec<String>`
found type `usize`
@@ -0,0 +1,12 @@
use darling::FromMeta;
#[derive(FromMeta)]
enum Choice {
#[darling(word)]
A,
#[darling(word)]
B,
C,
}
fn main() {}
@@ -0,0 +1,11 @@
error: `#[darling(word)]` can only be applied to one variant
--> tests/compile-fail/duplicate_word_across_variants.rs:5:15
|
5 | #[darling(word)]
| ^^^^
error: `#[darling(word)]` can only be applied to one variant
--> tests/compile-fail/duplicate_word_across_variants.rs:7:15
|
7 | #[darling(word)]
| ^^^^
@@ -0,0 +1,10 @@
use darling::FromMeta;
#[derive(FromMeta)]
enum Choice {
#[darling(word, word)]
A,
B,
}
fn main() {}
@@ -0,0 +1,5 @@
error: Duplicate field `word`
--> tests/compile-fail/duplicate_word_on_variant.rs:5:21
|
5 | #[darling(word, word)]
| ^^^^
@@ -0,0 +1,21 @@
use darling::FromMeta;
#[derive(FromMeta)]
struct Inner {
left: String,
right: String,
}
#[derive(FromMeta)]
struct Outer {
#[darling(flatten, multiple, with = demo, skip = true)]
field: Inner,
}
#[derive(FromMeta)]
struct ThisIsFine {
#[darling(flatten, multiple = false)]
field: Inner,
}
fn main() {}
@@ -0,0 +1,17 @@
error: `flatten` and `multiple` cannot be used together
--> tests/compile-fail/flatten_meta_conflicts.rs:11:24
|
11 | #[darling(flatten, multiple, with = demo, skip = true)]
| ^^^^^^^^
error: `flatten` and `with` cannot be used together
--> tests/compile-fail/flatten_meta_conflicts.rs:11:34
|
11 | #[darling(flatten, multiple, with = demo, skip = true)]
| ^^^^
error: `flatten` and `skip` cannot be used together
--> tests/compile-fail/flatten_meta_conflicts.rs:11:47
|
11 | #[darling(flatten, multiple, with = demo, skip = true)]
| ^^^^
@@ -0,0 +1,28 @@
//! Test that multiple fields cannot be marked `flatten` at once.
use darling::{FromDeriveInput, FromMeta};
#[derive(FromMeta)]
struct Inner {
left: String,
right: String,
}
#[derive(FromMeta)]
pub struct Example {
#[darling(flatten)]
first: Inner,
#[darling(flatten)]
last: Inner,
}
#[derive(FromDeriveInput)]
pub struct FdiExample {
ident: syn::Ident,
#[darling(flatten)]
first: Inner,
#[darling(flatten)]
last: Inner,
}
fn main() {}
@@ -0,0 +1,23 @@
error: `#[darling(flatten)]` can only be applied to one field
--> tests/compile-fail/flatten_multiple_fields.rs:13:15
|
13 | #[darling(flatten)]
| ^^^^^^^
error: `#[darling(flatten)]` can only be applied to one field
--> tests/compile-fail/flatten_multiple_fields.rs:15:15
|
15 | #[darling(flatten)]
| ^^^^^^^
error: `#[darling(flatten)]` can only be applied to one field
--> tests/compile-fail/flatten_multiple_fields.rs:22:15
|
22 | #[darling(flatten)]
| ^^^^^^^
error: `#[darling(flatten)]` can only be applied to one field
--> tests/compile-fail/flatten_multiple_fields.rs:24:15
|
24 | #[darling(flatten)]
| ^^^^^^^
+32
View File
@@ -0,0 +1,32 @@
use darling::FromMeta;
/// This usage of `from_expr` is VALID because there are no unit variants that should conflict with the
/// implementation.
#[derive(FromMeta)]
#[darling(from_expr = |expr| Ok(HasNoUnits::Variant2 { other: format!("{:?}", expr) }))]
enum HasNoUnits {
Variant1 { field: String },
Variant2 { other: String },
}
/// This usage of `from_expr` is invalid because unit variants already generate a from_expr
/// method, and we don't allow using the from_expr override when it conflicts with the macro's
/// "normal" operation.
#[derive(FromMeta)]
#[darling(from_expr = |expr| Ok(HasUnits::Variant2))]
enum HasUnits {
Variant1 { field: String },
Variant2,
}
fn newtype_from_expr(_expr: &syn::Expr) -> darling::Result<Newtype> {
Ok(Newtype(true))
}
// This usage of `from_expr` is invalid because newtype structs call the inner type's `from_meta`
// directly from their `from_meta`, so the custom `from_expr` will never be called in normal usage.
#[derive(FromMeta)]
#[darling(from_expr = newtype_from_expr)]
struct Newtype(bool);
fn main() {}
+11
View File
@@ -0,0 +1,11 @@
error: `from_expr` cannot be used on enums with non-skipped unit variants because it conflicts with the generated impl
--> tests/compile-fail/from_expr.rs:16:23
|
16 | #[darling(from_expr = |expr| Ok(HasUnits::Variant2))]
| ^
error: `from_expr` cannot be used on newtype structs because the implementation is entirely delegated to the inner type
--> tests/compile-fail/from_expr.rs:29:23
|
29 | #[darling(from_expr = newtype_from_expr)]
| ^^^^^^^^^^^^^^^^^
+30
View File
@@ -0,0 +1,30 @@
use darling::FromMeta;
// This usage of `from_word` is invalid because unit structs already generate a from_word
// method, and we don't allow using the from_word override when it conflicts with the macro's
// "normal" operation.
#[derive(FromMeta)]
#[darling(from_word = || Ok(Unit))]
struct Unit;
fn newtype_from_word() -> darling::Result<Newtype> {
Ok(Newtype(true))
}
// This usage of `from_word` is invalid because newtype structs call the inner type's `from_meta`
// directly from their `from_meta`, so the custom `from_word` will never be called in normal usage.
#[derive(FromMeta)]
#[darling(from_word = newtype_from_word)]
struct Newtype(bool);
#[derive(FromMeta)]
#[darling(from_word = || Ok(Wordy::Options { thing: "Hello".to_string() }))]
enum Wordy {
#[darling(word)]
Normal,
Options {
thing: String,
},
}
fn main() {}
+17
View File
@@ -0,0 +1,17 @@
error: `from_word` cannot be used on unit structs because it conflicts with the generated impl
--> tests/compile-fail/from_word.rs:7:23
|
7 | #[darling(from_word = || Ok(Unit))]
| ^
error: `from_word` cannot be used on newtype structs because the implementation is entirely delegated to the inner type
--> tests/compile-fail/from_word.rs:17:23
|
17 | #[darling(from_word = newtype_from_word)]
| ^^^^^^^^^^^^^^^^^
error: `from_word` cannot be used with an enum that also uses `word`
--> tests/compile-fail/from_word.rs:21:23
|
21 | #[darling(from_word = || Ok(Wordy::Options { thing: "Hello".to_string() }))]
| ^
+16
View File
@@ -0,0 +1,16 @@
use darling::FromMeta;
struct NotImplFm;
#[derive(FromMeta)]
struct OuterFm {
inner: NotImplFm,
}
#[derive(darling::FromDeriveInput)]
#[darling(attributes(hello))]
struct OuterFdi {
inner: NotImplFm,
}
fn main() {}
@@ -0,0 +1,33 @@
error[E0277]: the trait bound `NotImplFm: FromMeta` is not satisfied
--> tests/compile-fail/not_impl_from_meta.rs:7:12
|
7 | inner: NotImplFm,
| ^^^^^^^^^ the trait `FromMeta` is not implemented for `NotImplFm`
|
= help: the following other types implement trait `FromMeta`:
bool
char
isize
i8
i16
i32
i64
i128
and $N others
error[E0277]: the trait bound `NotImplFm: FromMeta` is not satisfied
--> tests/compile-fail/not_impl_from_meta.rs:13:12
|
13 | inner: NotImplFm,
| ^^^^^^^^^ the trait `FromMeta` is not implemented for `NotImplFm`
|
= help: the following other types implement trait `FromMeta`:
bool
char
isize
i8
i16
i32
i64
i128
and $N others
@@ -0,0 +1,18 @@
use darling::FromMeta;
#[derive(FromMeta)]
struct NoDefault(String);
#[derive(FromMeta)]
struct Recevier {
#[darling(skip)]
skipped: NoDefault,
#[darling(skip = true)]
explicitly_skipped: NoDefault,
#[darling(skip = false)]
not_skipped_no_problem: NoDefault,
}
fn main() {}
@@ -0,0 +1,23 @@
error[E0277]: the trait bound `NoDefault: std::default::Default` is not satisfied
--> tests/compile-fail/skip_field_not_impl_default.rs:8:15
|
8 | #[darling(skip)]
| ^^^^ the trait `std::default::Default` is not implemented for `NoDefault`
|
help: consider annotating `NoDefault` with `#[derive(Default)]`
|
4 + #[derive(Default)]
5 | struct NoDefault(String);
|
error[E0277]: the trait bound `NoDefault: std::default::Default` is not satisfied
--> tests/compile-fail/skip_field_not_impl_default.rs:11:22
|
11 | #[darling(skip = true)]
| ^^^^ the trait `std::default::Default` is not implemented for `NoDefault`
|
help: consider annotating `NoDefault` with `#[derive(Default)]`
|
4 + #[derive(Default)]
5 | struct NoDefault(String);
|
@@ -0,0 +1,21 @@
use darling::{FromDeriveInput, FromMeta};
#[derive(FromDeriveInput)]
#[darling(attributes(demo))]
pub struct Receiver {
example1: String,
#[darling(
// This should fail because `example1` is a local that's been captured
// from the `FromDeriveInput` impl. That's disallowed because exposing
// those internals would make any change to the derived method body a
// potentially-breaking change.
with = |m| Ok(
String::from_meta(m)?.to_uppercase()
+ example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
),
default
)]
example2: String,
}
fn main() {}
@@ -0,0 +1,30 @@
error[E0308]: mismatched types
--> tests/compile-fail/with_closure_capture.rs:12:16
|
12 | with = |m| Ok(
| ^ arguments to this function are incorrect
| ________________|
| |
13 | | String::from_meta(m)?.to_uppercase()
14 | | + example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
15 | | ),
| |_________^ expected fn pointer, found closure
|
= note: expected fn pointer `for<'a> fn(&'a syn::attr::Meta) -> Result<String, darling::Error>`
found closure `{closure@$DIR/tests/compile-fail/with_closure_capture.rs:12:16: 12:19}`
note: closures can only be coerced to `fn` types if they do not capture any variables
--> tests/compile-fail/with_closure_capture.rs:14:15
|
14 | + example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
| ^^^^^^^^ `example1` captured here
help: the return type of this call is `{closure@$DIR/tests/compile-fail/with_closure_capture.rs:12:16: 12:19}` due to the type of the argument passed
--> tests/compile-fail/with_closure_capture.rs:12:16
|
12 | with = |m| Ok(
| ________________^
13 | | String::from_meta(m)?.to_uppercase()
14 | | + example1.1.as_ref().map(|s| s.as_str()).unwrap_or("")
15 | | ),
| |_________- this argument influences the return type of `{{root}}`
note: function defined here
--> $RUST/core/src/convert/mod.rs
@@ -0,0 +1,10 @@
use darling::FromMeta;
#[derive(FromMeta)]
enum Meta {
Unit,
#[darling(word)]
NotUnit(String)
}
fn main() {}
@@ -0,0 +1,5 @@
error: Unexpected field: `word`. `#[darling(word)]` can only be applied to a unit variant
--> tests/compile-fail/word_on_wrong_variant_type.rs:6:15
|
6 | #[darling(word)]
| ^^^^