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

+114
View File
@@ -0,0 +1,114 @@
/// A literal UTF-8 string with a trailing null terminator.
#[macro_export]
macro_rules! s {
($s:literal) => {
::core::concat!($s, '\0').as_ptr()
};
}
/// A literal UTF-16 wide string with a trailing null terminator.
#[macro_export]
macro_rules! w {
($s:literal) => {{
const INPUT: &[u8] = $s.as_bytes();
const OUTPUT_LEN: usize = $crate::core::utf16_len(INPUT) + 1;
const OUTPUT: &[u16; OUTPUT_LEN] = {
let mut buffer = [0; OUTPUT_LEN];
let mut input_pos = 0;
let mut output_pos = 0;
while let Some((mut code_point, new_pos)) = $crate::core::decode_utf8_char(INPUT, input_pos) {
input_pos = new_pos;
if code_point <= 0xffff {
buffer[output_pos] = code_point as u16;
output_pos += 1;
} else {
code_point -= 0x10000;
buffer[output_pos] = 0xd800 + (code_point >> 10) as u16;
output_pos += 1;
buffer[output_pos] = 0xdc00 + (code_point & 0x3ff) as u16;
output_pos += 1;
}
}
&{ buffer }
};
OUTPUT.as_ptr()
}};
}
pub use s;
pub use w;
#[doc(hidden)]
pub const fn decode_utf8_char(bytes: &[u8], mut pos: usize) -> Option<(u32, usize)> {
if bytes.len() == pos {
return None;
}
let ch = bytes[pos] as u32;
pos += 1;
if ch <= 0x7f {
return Some((ch, pos));
}
if (ch & 0xe0) == 0xc0 {
if bytes.len() - pos < 1 {
return None;
}
let ch2 = bytes[pos] as u32;
pos += 1;
if (ch2 & 0xc0) != 0x80 {
return None;
}
let result: u32 = ((ch & 0x1f) << 6) | (ch2 & 0x3f);
if result <= 0x7f {
return None;
}
return Some((result, pos));
}
if (ch & 0xf0) == 0xe0 {
if bytes.len() - pos < 2 {
return None;
}
let ch2 = bytes[pos] as u32;
pos += 1;
let ch3 = bytes[pos] as u32;
pos += 1;
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 {
return None;
}
let result = ((ch & 0x0f) << 12) | ((ch2 & 0x3f) << 6) | (ch3 & 0x3f);
if result <= 0x7ff || (0xd800 <= result && result <= 0xdfff) {
return None;
}
return Some((result, pos));
}
if (ch & 0xf8) == 0xf0 {
if bytes.len() - pos < 3 {
return None;
}
let ch2 = bytes[pos] as u32;
pos += 1;
let ch3 = bytes[pos] as u32;
pos += 1;
let ch4 = bytes[pos] as u32;
pos += 1;
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 || (ch4 & 0xc0) != 0x80 {
return None;
}
let result = ((ch & 0x07) << 18) | ((ch2 & 0x3f) << 12) | ((ch3 & 0x3f) << 6) | (ch4 & 0x3f);
if result <= 0xffff || 0x10ffff < result {
return None;
}
return Some((result, pos));
}
None
}
#[doc(hidden)]
pub const fn utf16_len(bytes: &[u8]) -> usize {
let mut pos = 0;
let mut len = 0;
while let Some((code_point, new_pos)) = decode_utf8_char(bytes, pos) {
pos = new_pos;
len += if code_point <= 0xffff { 1 } else { 2 };
}
len
}
+47
View File
@@ -0,0 +1,47 @@
mod literals;
#[doc(hidden)]
pub use literals::*;
pub type BOOL = i32;
pub type HRESULT = i32;
pub type PSTR = *mut u8;
pub type PWSTR = *mut u16;
pub type PCSTR = *const u8;
pub type PCWSTR = *const u16;
pub type BSTR = *const u16;
pub type HSTRING = *mut core::ffi::c_void;
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct GUID {
pub data1: u32,
pub data2: u16,
pub data3: u16,
pub data4: [u8; 8],
}
impl GUID {
pub const fn from_u128(uuid: u128) -> Self {
Self { data1: (uuid >> 96) as u32, data2: (uuid >> 80 & 0xffff) as u16, data3: (uuid >> 64 & 0xffff) as u16, data4: (uuid as u64).to_be_bytes() }
}
}
pub const IID_IUnknown: GUID = GUID::from_u128(0x00000000_0000_0000_c000_000000000046);
#[repr(C)]
pub struct IUnknown_Vtbl {
pub QueryInterface: unsafe extern "system" fn(this: *mut core::ffi::c_void, iid: *const GUID, interface: *mut *mut core::ffi::c_void) -> HRESULT,
pub AddRef: unsafe extern "system" fn(this: *mut core::ffi::c_void) -> u32,
pub Release: unsafe extern "system" fn(this: *mut core::ffi::c_void) -> u32,
}
pub const IID_IInspectable: GUID = GUID::from_u128(0xaf86e2e0_b12d_4c6a_9c5a_d7aa65101e90);
#[repr(C)]
pub struct IInspectable_Vtbl {
pub base: IUnknown_Vtbl,
pub GetIids: unsafe extern "system" fn(this: *mut core::ffi::c_void, count: *mut u32, values: *mut *mut GUID) -> HRESULT,
pub GetRuntimeClassName: unsafe extern "system" fn(this: *mut core::ffi::c_void, value: *mut *mut core::ffi::c_void) -> HRESULT,
pub GetTrustLevel: unsafe extern "system" fn(this: *mut core::ffi::c_void, value: *mut i32) -> HRESULT,
}