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
+326
@@ -0,0 +1,326 @@
|
||||
//! Low level definition of a Mutex.
|
||||
//!
|
||||
//! This crate provides:
|
||||
//!
|
||||
//! - A `Mutex` trait that is to be used as the foundation of exclusive access to the data
|
||||
//! contained within it.
|
||||
//! - Helper traits and implementations which allows for multiple locks to be taken at once.
|
||||
//!
|
||||
//! RFC that added this trait: [RFC #377](https://github.com/rust-embedded/wg/blob/master/rfcs/0377-mutex-trait.md)
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use mutex_trait::prelude::*;
|
||||
//!
|
||||
//! // A function taking 2 mutexes
|
||||
//! fn normal_lock(
|
||||
//! a: &mut impl Mutex<Data = i32>,
|
||||
//! b: &mut impl Mutex<Data = i32>,
|
||||
//! ) {
|
||||
//! // Taking each lock separately
|
||||
//! a.lock(|a| {
|
||||
//! b.lock(|b| {
|
||||
//! *a += 1;
|
||||
//! *b += 1;
|
||||
//! });
|
||||
//! });
|
||||
//!
|
||||
//! // Or both at once
|
||||
//! (a, b).lock(|a, b| {
|
||||
//! *a += 1;
|
||||
//! *b += 1;
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # Minimum Supported Rust Version (MSRV)
|
||||
//!
|
||||
//! This crate is guaranteed to compile on stable Rust 1.31 and up. It *might*
|
||||
//! compile with older versions but that may change in any new patch release.
|
||||
|
||||
#![no_std]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use core::cell::RefCell;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
/// Makes locks work on N-tuples, locks the mutexes from left-to-right in the tuple. These are
|
||||
/// used to reduce rightward drift in code and to help make intentions clearer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use mutex_trait::prelude::*;
|
||||
///
|
||||
/// fn normal_lock(
|
||||
/// a: &mut impl Mutex<Data = i32>,
|
||||
/// b: &mut impl Mutex<Data = i32>,
|
||||
/// c: &mut impl Mutex<Data = i32>
|
||||
/// ) {
|
||||
/// // A lot of rightward drift...
|
||||
/// a.lock(|a| {
|
||||
/// b.lock(|b| {
|
||||
/// c.lock(|c| {
|
||||
/// *a += 1;
|
||||
/// *b += 1;
|
||||
/// *c += 1;
|
||||
/// });
|
||||
/// });
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Has a shorthand as:
|
||||
///
|
||||
/// ```
|
||||
/// use mutex_trait::prelude::*;
|
||||
///
|
||||
/// fn tuple_lock(
|
||||
/// a: &mut impl Mutex<Data = i32>,
|
||||
/// b: &mut impl Mutex<Data = i32>,
|
||||
/// c: &mut impl Mutex<Data = i32>
|
||||
/// ) {
|
||||
/// // Look! Single indent and less to write
|
||||
/// (a, b, c).lock(|a, b, c| {
|
||||
/// *a += 1;
|
||||
/// *b += 1;
|
||||
/// *c += 1;
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub mod prelude {
|
||||
pub use crate::Mutex;
|
||||
|
||||
macro_rules! lock {
|
||||
($e:ident, $fun:block) => {
|
||||
$e.lock(|$e| $fun )
|
||||
};
|
||||
($e:ident, $($es:ident),+, $fun:block) => {
|
||||
$e.lock(|$e| lock!($($es),*, $fun))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! make_tuple_impl {
|
||||
($name:ident, $($es:ident),+) => {
|
||||
/// Auto-generated tuple implementation, see [`Mutex`](../trait.Mutex.html) for details.
|
||||
pub trait $name {
|
||||
$(
|
||||
/// Data protected by the mutex.
|
||||
type $es;
|
||||
)*
|
||||
|
||||
/// Creates a critical section and grants temporary access to the protected data.
|
||||
fn lock<R>(&mut self, f: impl FnOnce($(&mut Self::$es),*) -> R) -> R;
|
||||
}
|
||||
|
||||
impl<$($es),+> $name for ($($es,)+)
|
||||
where
|
||||
$($es: crate::Mutex),*
|
||||
{
|
||||
$(
|
||||
type $es = $es::Data;
|
||||
)*
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn lock<R>(&mut self, f: impl FnOnce($(&mut Self::$es),*) -> R) -> R {
|
||||
let ($(
|
||||
$es,
|
||||
)*) = self;
|
||||
|
||||
lock!($($es),*, { f($($es),*) })
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Generate tuple lock impls
|
||||
make_tuple_impl!(TupleExt01, T1);
|
||||
make_tuple_impl!(TupleExt02, T1, T2);
|
||||
make_tuple_impl!(TupleExt03, T1, T2, T3);
|
||||
make_tuple_impl!(TupleExt04, T1, T2, T3, T4);
|
||||
make_tuple_impl!(TupleExt05, T1, T2, T3, T4, T5);
|
||||
make_tuple_impl!(TupleExt06, T1, T2, T3, T4, T5, T6);
|
||||
make_tuple_impl!(TupleExt07, T1, T2, T3, T4, T5, T6, T7);
|
||||
make_tuple_impl!(TupleExt08, T1, T2, T3, T4, T5, T6, T7, T8);
|
||||
make_tuple_impl!(TupleExt09, T1, T2, T3, T4, T5, T6, T7, T8, T9);
|
||||
make_tuple_impl!(TupleExt10, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
|
||||
make_tuple_impl!(TupleExt11, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
|
||||
make_tuple_impl!(TupleExt12, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
|
||||
}
|
||||
|
||||
/// Any object implementing this trait guarantees exclusive access to the data contained
|
||||
/// within the mutex for the duration of the lock.
|
||||
pub trait Mutex {
|
||||
/// Data protected by the mutex.
|
||||
type Data;
|
||||
|
||||
/// Creates a critical section and grants temporary access to the protected data.
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R;
|
||||
}
|
||||
|
||||
// `lock` will now work on any mutable reference to a lock
|
||||
impl<L> Mutex for &'_ mut L
|
||||
where
|
||||
L: Mutex,
|
||||
{
|
||||
type Data = L::Data;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
|
||||
L::lock(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
// A RefCell is a lock in single threaded applications
|
||||
impl<T> Mutex for &'_ RefCell<T> {
|
||||
type Data = T;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut T) -> R) -> R {
|
||||
f(&mut self.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `T` and provides exclusive access via a `Mutex` impl.
|
||||
///
|
||||
/// This provides an no-op `Mutex` implementation for data that does not need a real mutex.
|
||||
#[derive(Debug)]
|
||||
pub struct Exclusive<'a, T>(&'a mut T);
|
||||
|
||||
impl<'a, T> Exclusive<'a, T> {
|
||||
/// Creates a new `Exclusive` object wrapping `data`.
|
||||
pub fn new(data: &'a mut T) -> Self {
|
||||
Exclusive(data)
|
||||
}
|
||||
|
||||
/// Consumes this `Exclusive` instance and returns the wrapped value.
|
||||
pub fn into_inner(self) -> &'a mut T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<&'a mut T> for Exclusive<'a, T> {
|
||||
fn from(data: &'a mut T) -> Self {
|
||||
Exclusive(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Exclusive<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for Exclusive<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Mutex for Exclusive<'a, T> {
|
||||
type Data = T;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut T) -> R) -> R {
|
||||
f(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
mod tests {
|
||||
use crate::prelude::*;
|
||||
use crate::Exclusive;
|
||||
|
||||
fn compile_test_single_move(mut a: impl Mutex<Data = i32>) {
|
||||
a.lock(|a| {
|
||||
*a += 1;
|
||||
});
|
||||
}
|
||||
|
||||
fn compile_test_single_reference(a: &mut impl Mutex<Data = i32>) {
|
||||
a.lock(|a| {
|
||||
*a += 1;
|
||||
});
|
||||
}
|
||||
|
||||
fn compile_test_double_move(mut a: impl Mutex<Data = i32>, mut b: impl Mutex<Data = i32>) {
|
||||
a.lock(|a| {
|
||||
*a += 1;
|
||||
});
|
||||
|
||||
b.lock(|b| {
|
||||
*b += 1;
|
||||
});
|
||||
|
||||
(a, b).lock(|a, b| {
|
||||
*a += 1;
|
||||
*b += 1;
|
||||
});
|
||||
}
|
||||
|
||||
fn compile_test_double_reference(
|
||||
a: &mut impl Mutex<Data = i32>,
|
||||
b: &mut impl Mutex<Data = i32>,
|
||||
) {
|
||||
a.lock(|a| {
|
||||
*a += 1;
|
||||
});
|
||||
|
||||
b.lock(|b| {
|
||||
*b += 1;
|
||||
});
|
||||
|
||||
(a, b).lock(|a, b| {
|
||||
*a += 1;
|
||||
*b += 1;
|
||||
});
|
||||
}
|
||||
|
||||
fn compile_test_move_and_reference(
|
||||
mut a: impl Mutex<Data = i32>,
|
||||
b: &mut impl Mutex<Data = i32>,
|
||||
) {
|
||||
a.lock(|a| {
|
||||
*a += 1;
|
||||
});
|
||||
|
||||
b.lock(|b| {
|
||||
*b += 1;
|
||||
});
|
||||
|
||||
(a, b).lock(|a, b| {
|
||||
*a += 1;
|
||||
*b += 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refcell_lock() {
|
||||
let a = core::cell::RefCell::new(0);
|
||||
let b = core::cell::RefCell::new(0);
|
||||
|
||||
(&a).lock(|a| {
|
||||
*a += 1;
|
||||
});
|
||||
|
||||
(&b).lock(|b| {
|
||||
*b += 1;
|
||||
});
|
||||
|
||||
(&a, &b).lock(|a, b| {
|
||||
*a += 1;
|
||||
*b += 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclusive() {
|
||||
let mut var = 0;
|
||||
let mut excl = Exclusive(&mut var);
|
||||
|
||||
excl.lock(|val| *val += 1);
|
||||
|
||||
assert_eq!(*excl.into_inner(), 1);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user