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
+25
@@ -0,0 +1,25 @@
|
||||
//! Collections of `Send`-able things are `Send`
|
||||
|
||||
use heapless::{
|
||||
spsc::{Consumer, Producer, Queue},
|
||||
HistoryBuffer, Vec,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn send() {
|
||||
struct IsSend;
|
||||
|
||||
unsafe impl Send for IsSend {}
|
||||
|
||||
fn is_send<T>()
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
}
|
||||
|
||||
is_send::<Consumer<IsSend, 4>>();
|
||||
is_send::<Producer<IsSend, 4>>();
|
||||
is_send::<Queue<IsSend, 4>>();
|
||||
is_send::<Vec<IsSend, 4>>();
|
||||
is_send::<HistoryBuffer<IsSend, 4>>();
|
||||
}
|
||||
Vendored
+239
@@ -0,0 +1,239 @@
|
||||
#![deny(rust_2018_compatibility)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::thread;
|
||||
|
||||
use heapless::spsc;
|
||||
|
||||
#[test]
|
||||
fn once() {
|
||||
static mut RB: spsc::Queue<i32, 4> = spsc::Queue::new();
|
||||
|
||||
let rb = unsafe { &mut RB };
|
||||
|
||||
rb.enqueue(0).unwrap();
|
||||
|
||||
let (mut p, mut c) = rb.split();
|
||||
|
||||
p.enqueue(1).unwrap();
|
||||
|
||||
thread::spawn(move || {
|
||||
p.enqueue(1).unwrap();
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
c.dequeue().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twice() {
|
||||
static mut RB: spsc::Queue<i32, 5> = spsc::Queue::new();
|
||||
|
||||
let rb = unsafe { &mut RB };
|
||||
|
||||
rb.enqueue(0).unwrap();
|
||||
rb.enqueue(1).unwrap();
|
||||
|
||||
let (mut p, mut c) = rb.split();
|
||||
|
||||
thread::spawn(move || {
|
||||
p.enqueue(2).unwrap();
|
||||
p.enqueue(3).unwrap();
|
||||
});
|
||||
|
||||
thread::spawn(move || {
|
||||
c.dequeue().unwrap();
|
||||
c.dequeue().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scoped() {
|
||||
let mut rb: spsc::Queue<i32, 5> = spsc::Queue::new();
|
||||
|
||||
rb.enqueue(0).unwrap();
|
||||
|
||||
{
|
||||
let (mut p, mut c) = rb.split();
|
||||
|
||||
thread::scope(move |scope| {
|
||||
scope.spawn(move || {
|
||||
p.enqueue(1).unwrap();
|
||||
});
|
||||
|
||||
scope.spawn(move || {
|
||||
c.dequeue().unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
rb.dequeue().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)] // too slow
|
||||
fn contention() {
|
||||
const N: usize = 1024;
|
||||
|
||||
let mut rb: spsc::Queue<u8, 4> = spsc::Queue::new();
|
||||
|
||||
{
|
||||
let (mut p, mut c) = rb.split();
|
||||
|
||||
thread::scope(move |scope| {
|
||||
scope.spawn(move || {
|
||||
let mut sum: u32 = 0;
|
||||
|
||||
for i in 0..(2 * N) {
|
||||
sum = sum.wrapping_add(i as u32);
|
||||
while let Err(_) = p.enqueue(i as u8) {}
|
||||
}
|
||||
|
||||
println!("producer: {}", sum);
|
||||
});
|
||||
|
||||
scope.spawn(move || {
|
||||
let mut sum: u32 = 0;
|
||||
|
||||
for _ in 0..(2 * N) {
|
||||
loop {
|
||||
match c.dequeue() {
|
||||
Some(v) => {
|
||||
sum = sum.wrapping_add(v as u32);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("consumer: {}", sum);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
assert!(rb.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)] // too slow
|
||||
fn mpmc_contention() {
|
||||
use std::sync::mpsc;
|
||||
|
||||
use heapless::mpmc::Q64;
|
||||
|
||||
const N: u32 = 64;
|
||||
|
||||
static Q: Q64<u32> = Q64::new();
|
||||
|
||||
let (s, r) = mpsc::channel();
|
||||
thread::scope(|scope| {
|
||||
let s1 = s.clone();
|
||||
scope.spawn(move || {
|
||||
let mut sum: u32 = 0;
|
||||
|
||||
for i in 0..(16 * N) {
|
||||
sum = sum.wrapping_add(i);
|
||||
println!("enqueue {}", i);
|
||||
while let Err(_) = Q.enqueue(i) {}
|
||||
}
|
||||
|
||||
s1.send(sum).unwrap();
|
||||
});
|
||||
|
||||
let s2 = s.clone();
|
||||
scope.spawn(move || {
|
||||
let mut sum: u32 = 0;
|
||||
|
||||
for _ in 0..(16 * N) {
|
||||
loop {
|
||||
match Q.dequeue() {
|
||||
Some(v) => {
|
||||
sum = sum.wrapping_add(v);
|
||||
println!("dequeue {}", v);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s2.send(sum).unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
assert_eq!(r.recv().unwrap(), r.recv().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)] // too slow
|
||||
fn unchecked() {
|
||||
const N: usize = 1024;
|
||||
|
||||
let mut rb: spsc::Queue<u8, N> = spsc::Queue::new();
|
||||
|
||||
for _ in 0..N / 2 - 1 {
|
||||
rb.enqueue(1).unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let (mut p, mut c) = rb.split();
|
||||
|
||||
thread::scope(move |scope| {
|
||||
scope.spawn(move || {
|
||||
for _ in 0..N / 2 - 1 {
|
||||
p.enqueue(2).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
scope.spawn(move || {
|
||||
let mut sum: usize = 0;
|
||||
|
||||
for _ in 0..N / 2 - 1 {
|
||||
sum = sum.wrapping_add(usize::from(c.dequeue().unwrap()));
|
||||
}
|
||||
|
||||
assert_eq!(sum, N / 2 - 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
assert_eq!(rb.len(), N / 2 - 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn len_properly_wraps() {
|
||||
const N: usize = 4;
|
||||
let mut rb: spsc::Queue<u8, N> = spsc::Queue::new();
|
||||
|
||||
rb.enqueue(1).unwrap();
|
||||
assert_eq!(rb.len(), 1);
|
||||
rb.dequeue();
|
||||
assert_eq!(rb.len(), 0);
|
||||
rb.enqueue(2).unwrap();
|
||||
assert_eq!(rb.len(), 1);
|
||||
rb.enqueue(3).unwrap();
|
||||
assert_eq!(rb.len(), 2);
|
||||
rb.enqueue(4).unwrap();
|
||||
assert_eq!(rb.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_properly_wraps() {
|
||||
const N: usize = 4;
|
||||
let mut rb: spsc::Queue<u8, N> = spsc::Queue::new();
|
||||
|
||||
rb.enqueue(1).unwrap();
|
||||
rb.dequeue();
|
||||
rb.enqueue(2).unwrap();
|
||||
rb.enqueue(3).unwrap();
|
||||
rb.enqueue(4).unwrap();
|
||||
let expected = [2, 3, 4];
|
||||
let mut actual = [0, 0, 0];
|
||||
for (idx, el) in rb.iter().enumerate() {
|
||||
actual[idx] = *el;
|
||||
}
|
||||
assert_eq!(expected, actual)
|
||||
}
|
||||
Reference in new issue
Block a user