feat: redesign active display layout with mode box and title box

New layout:
- Mode box (left): RX/TX indicator with vertical separator
- Title box (right): 'DongLoRa v{ver}' + radio settings (freq/bw/sf/cr)
- Centered info rows: packet counters + RSSI/SNR with labels
- Sparkline bar graph fills remaining height
- Idle state returns to splash screen (active screen only for RX/TX)
- Layout positions derived from display dimensions (h/w agnostic)
- RSSI history buffer doubled to 128 slots (~2 min at 1s/slot)
- Smart compact_count() formatting for large RX/TX counters (k/M)
This commit is contained in:
swaits committed 2026-03-30 15:32:45 -06:00
1 parent 466acfc0b6
commit b11fcac121
3 files changed
+336 -180

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
+---------------------+
|RXTX 910.525MHz 22dBM|
|62.5kHz SF7 CR4/5 |
|RX:4 TX:0 |
|0dBm SNR:14dB |
|---------------------|
| <BAR GRAPH HERE> |
+---------------------+
+---------------------+
|RX|DongLoRa v99.99.99| <= title (centered in title box)
|TX| 950.000/500/12/8 | <= radio settings (centered in title box)
|---------------------| <= thin separator line
|RX:99999999 TX:999999| <= rx/tx counts (centered, see note below)
|RSSI:-120dBm SNR:20dB| <= last receive info (cenetered)
|---------------------| <= thin separator line
|RX/TX BAR GRAPH HERE | <= bar graph, can be shorter than current
+---------------------+
* note the vertical bar to the right of the RX/TX indicators (which work the
same as before, alternating based on which mode we're in, and being inverted) -- that vertical bar puts them in their own little box now which I'm calling the "mode box" (get rid of "IDLE" mode, we never need to show that since we just go back to the idle/splash screen), and that gives way to a new box to the right I'm calling the title box
* note on RX/TX counts.. if these exceed what we have space for, I guess figure out something smart to do?
* note on display size -- all of this has to be done in a h/w agnostic way because different boards might have different display sizes!
* I believe I've used max values for all this stuff, check that and correct if needed
* Also, I want things to stay laid out nicely,
+261 -169
View File
@@ -12,15 +12,15 @@ use heapless::String;
use crate::channel::{RadioState, RadioStatus}; use crate::channel::{RadioState, RadioStatus};
use crate::protocol::Bandwidth; use crate::protocol::Bandwidth;
/// RSSI ring buffer length — one bar per 2px across 128px. /// RSSI ring buffer length — supports displays up to 256px wide (2px per bar).
pub const RSSI_HISTORY_LEN: usize = 64; pub const RSSI_HISTORY_LEN: usize = 128;
// Layout constants // Font metrics (FONT_6X10)
const W: i32 = 128; const CHAR_W: i32 = 6;
const FONT_H: i32 = 10; const FONT_H: i32 = 10;
const SEP_Y: i32 = 43;
const SPARK_TOP: i32 = 45; // Mode box: 2 chars + 1px padding each side
const SPARK_H: i32 = 19; // y=45..63 const MODE_BOX_W: i32 = 2 * CHAR_W + 2; // 14px
// RSSI mapping range (dBm) // RSSI mapping range (dBm)
const RSSI_MIN: i16 = -120; const RSSI_MIN: i16 = -120;
@@ -33,7 +33,7 @@ pub struct BoardInfo<'a> {
pub mac: &'a str, pub mac: &'a str,
} }
/// Render the main status dashboard. /// Render the active dashboard (shown when radio is in RX or TX mode).
pub fn dashboard( pub fn dashboard(
target: &mut impl DrawTarget<Color = BinaryColor>, target: &mut impl DrawTarget<Color = BinaryColor>,
status: &RadioStatus, status: &RadioStatus,
@@ -44,163 +44,251 @@ pub fn dashboard(
current_slot_tx: bool, current_slot_tx: bool,
board: &BoardInfo, board: &BoardInfo,
) { ) {
let bb = target.bounding_box();
let w = bb.size.width as i32;
let h = bb.size.height as i32;
let title_x = MODE_BOX_W + 1;
let title_w = w - title_x;
let header_h = 2 * FONT_H;
let sep1_y = header_h + 3; // 3px gap below header text (clears descenders)
let info_y = sep1_y + 1; // 1px line, info text tight below separator
let sep2_y = info_y + 2 * FONT_H + 3; // 3px gap below info text (clears descenders)
let spark_top = sep2_y + 2; // 1px line + 1px gap above graph
let spark_h = h - spark_top;
let visible_bars = ((w / 2) as usize).min(RSSI_HISTORY_LEN);
let _ = target.clear(BinaryColor::Off); let _ = target.clear(BinaryColor::Off);
let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On); let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let mut buf: String<32> = String::new(); let mut buf: String<32> = String::new();
match status.config { // ── Mode box (left column, 2 rows tall) ─────────────────────────
Some(cfg) => { let fill = PrimitiveStyle::with_fill(BinaryColor::On);
let freq_mhz = cfg.freq_hz / 1_000_000; let mut inv_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::Off);
let freq_khz = (cfg.freq_hz % 1_000_000) / 1_000; inv_style.set_background_color(Some(BinaryColor::On));
let _ = write!(buf, " {}.{:03}MHz", freq_mhz, freq_khz);
Text::new(&buf, Point::new(0, FONT_H - 1), style) match status.state {
RadioState::Receiving => {
// "RX" inverted on row 0
Rectangle::new(Point::new(0, 0), Size::new(MODE_BOX_W as u32, FONT_H as u32))
.into_styled(fill)
.draw(target) .draw(target)
.ok(); .ok();
buf.clear(); Text::new("RX", Point::new(1, FONT_H - 1), inv_style)
let _ = write!(buf, "{}dBm", cfg.tx_power_dbm);
Text::with_alignment(&buf, Point::new(W - 1, FONT_H - 1), style, Alignment::Right)
.draw(target) .draw(target)
.ok(); .ok();
// State indicator: inverted "RX" or "TX", plain "IDLE"
match status.state {
RadioState::Idle => {
Text::new("IDLE", Point::new(0, FONT_H - 1), style)
.draw(target)
.ok();
}
RadioState::Receiving => {
let x = 0;
let char_w = 6;
let text_w = 2 * char_w; // "RX" = 2 chars
let mut inv_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::Off);
inv_style.set_background_color(Some(BinaryColor::On));
Rectangle::new(Point::new(x, 0), Size::new(text_w as u32, FONT_H as u32))
.into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
.draw(target)
.ok();
Text::new("RX", Point::new(x, FONT_H - 1), inv_style)
.draw(target)
.ok();
}
RadioState::Transmitting => {
let char_w = 6;
let x = 2 * char_w; // start at column 2 (third character position)
let text_w = 2 * char_w; // "TX" = 2 chars
let mut inv_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::Off);
inv_style.set_background_color(Some(BinaryColor::On));
Rectangle::new(Point::new(x, 0), Size::new(text_w as u32, FONT_H as u32))
.into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
.draw(target)
.ok();
Text::new("TX", Point::new(x, FONT_H - 1), inv_style)
.draw(target)
.ok();
}
}
// Row 1: BW (full) SF CR
buf.clear();
let bw_str = match cfg.bw {
Bandwidth::Khz7 => "7.8kHz",
Bandwidth::Khz10 => "10.4kHz",
Bandwidth::Khz15 => "15.6kHz",
Bandwidth::Khz20 => "20.8kHz",
Bandwidth::Khz31 => "31.2kHz",
Bandwidth::Khz41 => "41.7kHz",
Bandwidth::Khz62 => "62.5kHz",
Bandwidth::Khz125 => "125kHz",
Bandwidth::Khz250 => "250kHz",
Bandwidth::Khz500 => "500kHz",
};
let _ = write!(buf, "{} SF{} CR4/{}", bw_str, cfg.sf, cfg.cr);
Text::new(&buf, Point::new(0, FONT_H * 2 - 1), style)
.draw(target)
.ok();
// Row 2: packet counters
buf.clear();
let _ = write!(buf, "RX:{} TX:{}", status.rx_count, status.tx_count);
Text::new(&buf, Point::new(0, FONT_H * 3 - 1), style)
.draw(target)
.ok();
// Row 3: RSSI + SNR (compact to fit 21 chars)
buf.clear();
match (status.last_rssi, status.last_snr) {
(Some(rssi), Some(snr)) => {
let _ = write!(buf, "{}dBm SNR:{}dB", rssi, snr);
}
(Some(rssi), None) => {
let _ = write!(buf, "{}dBm", rssi);
}
_ => {
let _ = write!(buf, "No signal");
}
}
Text::new(&buf, Point::new(0, FONT_H * 4 - 1), style)
.draw(target)
.ok();
// Separator line (moved down to give RSSI/SNR breathing room)
Line::new(Point::new(0, SEP_Y), Point::new(W - 1, SEP_Y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(target)
.ok();
// RSSI sparkline (TX slots shown as dotted bars)
rssi_sparkline(
target,
rssi_history,
tx_history,
rssi_count,
current_slot_rssi,
current_slot_tx,
);
} }
None => { RadioState::Transmitting => {
// Row 1: "DongLoRa" bold left, version small right // "TX" inverted on row 1
let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On); Rectangle::new(
Text::new("DongLoRa", Point::new(4, 15), title_style) Point::new(0, FONT_H),
.draw(target) Size::new(MODE_BOX_W as u32, FONT_H as u32),
.ok();
buf.clear();
let _ = write!(buf, "v{}", board.version);
Text::with_alignment(&buf, Point::new(W - 2, 15), style, Alignment::Right)
.draw(target)
.ok();
// Row 2: board name
Text::with_alignment(board.name, Point::new(W / 2, 28), style, Alignment::Center)
.draw(target)
.ok();
// Row 3: MAC address
Text::with_alignment(board.mac, Point::new(W / 2, 41), style, Alignment::Center)
.draw(target)
.ok();
// Row 4: status
Text::with_alignment(
"Waiting for host...",
Point::new(W / 2, 54),
style,
Alignment::Center,
) )
.into_styled(fill)
.draw(target) .draw(target)
.ok(); .ok();
Text::new("TX", Point::new(1, 2 * FONT_H - 1), inv_style)
.draw(target)
.ok();
}
RadioState::Idle => {} // Not shown on active screen
}
// Vertical separator between mode box and title box
Line::new(Point::new(MODE_BOX_W, 0), Point::new(MODE_BOX_W, sep1_y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(target)
.ok();
// ── Title box (right of mode box) ───────────────────────────────
let title_center_x = title_x + title_w / 2;
// Row 0: "DongLoRa v{version}"
buf.clear();
let _ = write!(buf, "DongLoRa v{}", board.version);
Text::with_alignment(
&buf,
Point::new(title_center_x, FONT_H - 1),
style,
Alignment::Center,
)
.draw(target)
.ok();
// Row 1: radio settings "{freq}/{bw}/{sf}/{cr}"
if let Some(cfg) = status.config {
buf.clear();
let freq_mhz = cfg.freq_hz / 1_000_000;
let freq_khz = (cfg.freq_hz % 1_000_000) / 1_000;
let bw_str = match cfg.bw {
Bandwidth::Khz7 => "7.8",
Bandwidth::Khz10 => "10.4",
Bandwidth::Khz15 => "15.6",
Bandwidth::Khz20 => "20.8",
Bandwidth::Khz31 => "31.2",
Bandwidth::Khz41 => "41.7",
Bandwidth::Khz62 => "62.5",
Bandwidth::Khz125 => "125",
Bandwidth::Khz250 => "250",
Bandwidth::Khz500 => "500",
};
let _ = write!(
buf,
"{}.{:03}/{}/{}/{}",
freq_mhz, freq_khz, bw_str, cfg.sf, cfg.cr
);
Text::with_alignment(
&buf,
Point::new(title_center_x, 2 * FONT_H - 1),
style,
Alignment::Center,
)
.draw(target)
.ok();
}
// ── First separator line ────────────────────────────────────────
Line::new(Point::new(0, sep1_y), Point::new(w - 1, sep1_y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(target)
.ok();
// ── Info rows (centered) ────────────────────────────────────────
let center_x = w / 2;
// Row 2: packet counters
buf.clear();
let _ = write!(
buf,
"RX:{} TX:{}",
compact_count(status.rx_count),
compact_count(status.tx_count)
);
Text::with_alignment(
&buf,
Point::new(center_x, info_y + FONT_H - 1),
style,
Alignment::Center,
)
.draw(target)
.ok();
// Row 3: RSSI + SNR
buf.clear();
match (status.last_rssi, status.last_snr) {
(Some(rssi), Some(snr)) => {
let _ = write!(buf, "RSSI:{}dBm SNR:{}dB", rssi, snr);
}
(Some(rssi), None) => {
let _ = write!(buf, "RSSI:{}dBm", rssi);
}
_ => {
let _ = write!(buf, "No signal");
} }
} }
Text::with_alignment(
&buf,
Point::new(center_x, info_y + 2 * FONT_H - 1),
style,
Alignment::Center,
)
.draw(target)
.ok();
// ── Second separator line ───────────────────────────────────────
Line::new(Point::new(0, sep2_y), Point::new(w - 1, sep2_y))
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(target)
.ok();
// ── Sparkline bar graph ─────────────────────────────────────────
if spark_h > 0 {
rssi_sparkline(
target,
rssi_history,
tx_history,
rssi_count,
current_slot_rssi,
current_slot_tx,
spark_top,
spark_h,
visible_bars,
);
}
}
/// Render the splash/waiting screen (shown when idle or no config).
pub fn splash(
target: &mut impl DrawTarget<Color = BinaryColor>,
board: &BoardInfo,
) {
let bb = target.bounding_box();
let w = bb.size.width as i32;
let _ = target.clear(BinaryColor::Off);
let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On);
let mut buf: String<32> = String::new();
let center_x = w / 2;
// Row 1: "DongLoRa" bold left, version small right
Text::new("DongLoRa", Point::new(4, 15), title_style)
.draw(target)
.ok();
buf.clear();
let _ = write!(buf, "v{}", board.version);
Text::with_alignment(&buf, Point::new(w - 2, 15), style, Alignment::Right)
.draw(target)
.ok();
// Row 2: board name
Text::with_alignment(
board.name,
Point::new(center_x, 28),
style,
Alignment::Center,
)
.draw(target)
.ok();
// Row 3: MAC address
Text::with_alignment(
board.mac,
Point::new(center_x, 41),
style,
Alignment::Center,
)
.draw(target)
.ok();
// Row 4: status
Text::with_alignment(
"Waiting for host...",
Point::new(center_x, 54),
style,
Alignment::Center,
)
.draw(target)
.ok();
}
/// Format a u32 count compactly for display.
fn compact_count(n: u32) -> String<10> {
let mut s: String<10> = String::new();
if n > 9_999_999 {
let _ = write!(s, "{}M", n / 1_000_000);
} else if n > 999_999 {
let _ = write!(s, "{}k", n / 1_000);
} else {
let _ = write!(s, "{}", n);
}
s
} }
/// Render the RSSI history as a bar-chart sparkline. /// Render the RSSI history as a bar-chart sparkline.
/// ///
/// Each bar represents one time slot. The display task advances the
/// slot index on a fixed timer, so each bar covers a constant duration
/// regardless of packet rate. RX slots draw solid bars; TX slots draw
/// dotted bars (alternating pixel rows). TX takes precedence if both
/// occurred in the same slot.
///
/// The current (uncommitted) slot is rendered at the rightmost position /// The current (uncommitted) slot is rendered at the rightmost position
/// so that incoming packets appear on the graph immediately, before the /// so that incoming packets appear on the graph immediately.
/// slot timer commits them to history.
fn rssi_sparkline( fn rssi_sparkline(
target: &mut impl DrawTarget<Color = BinaryColor>, target: &mut impl DrawTarget<Color = BinaryColor>,
history: &[i16; RSSI_HISTORY_LEN], history: &[i16; RSSI_HISTORY_LEN],
@@ -208,15 +296,16 @@ fn rssi_sparkline(
count: usize, count: usize,
current_rssi: i16, current_rssi: i16,
current_tx: bool, current_tx: bool,
spark_top: i32,
spark_h: i32,
visible_bars: usize,
) { ) {
let live = current_rssi > RSSI_MIN || current_tx; let live = current_rssi > RSSI_MIN || current_tx;
// Total bars: committed history + optional live bar
let committed = count.min(RSSI_HISTORY_LEN); let committed = count.min(RSSI_HISTORY_LEN);
// If live bar is active, reserve the rightmost slot for it
let hist_slots = if live { let hist_slots = if live {
committed.min(RSSI_HISTORY_LEN - 1) committed.min(visible_bars.saturating_sub(1))
} else { } else {
committed committed.min(visible_bars)
}; };
let total = hist_slots + if live { 1 } else { 0 }; let total = hist_slots + if live { 1 } else { 0 };
@@ -226,51 +315,53 @@ fn rssi_sparkline(
let fill = PrimitiveStyle::with_fill(BinaryColor::On); let fill = PrimitiveStyle::with_fill(BinaryColor::On);
// Draw committed history (shifted left by 1 when live bar is present) // Draw committed history
for i in 0..hist_slots { for i in 0..hist_slots {
let idx = if count <= RSSI_HISTORY_LEN { let idx = if count <= RSSI_HISTORY_LEN {
// Buffer hasn't wrapped yet — if live bar is active, skip if live && committed >= visible_bars {
// the oldest slot if we've hit the display limit. let skip = committed - (visible_bars - 1);
if live && committed == RSSI_HISTORY_LEN { i + skip
// Full buffer + live: drop oldest committed slot
i + 1
} else { } else {
i i
} }
} else { } else {
let start = count - RSSI_HISTORY_LEN; let start = count - RSSI_HISTORY_LEN;
let start = if live { start + 1 } else { start }; let skip = RSSI_HISTORY_LEN.saturating_sub(if live {
(start + i) % RSSI_HISTORY_LEN visible_bars - 1
} else {
visible_bars
});
(start + skip + i) % RSSI_HISTORY_LEN
}; };
let is_tx = tx_history[idx]; let is_tx = tx_history[idx];
let rssi = history[idx]; let rssi = history[idx];
if let Some(bar_h) = bar_height(rssi, is_tx) { if let Some(bar_h) = bar_height(rssi, is_tx, spark_h) {
let x = (RSSI_HISTORY_LEN - total + i) as i32 * 2; let x = (visible_bars - total + i) as i32 * 2;
draw_bar(target, x, bar_h, is_tx, &fill); draw_bar(target, x, bar_h, is_tx, spark_top, spark_h, &fill);
} }
} }
// Draw live (current) bar at the rightmost position // Draw live (current) bar at the rightmost position
if live { if live {
if let Some(bar_h) = bar_height(current_rssi, current_tx) { if let Some(bar_h) = bar_height(current_rssi, current_tx, spark_h) {
let x = (RSSI_HISTORY_LEN - 1) as i32 * 2; let x = (visible_bars - 1) as i32 * 2;
draw_bar(target, x, bar_h, current_tx, &fill); draw_bar(target, x, bar_h, current_tx, spark_top, spark_h, &fill);
} }
} }
} }
/// Compute the pixel height for a sparkline bar, or None to skip. /// Compute the pixel height for a sparkline bar, or None to skip.
fn bar_height(rssi: i16, is_tx: bool) -> Option<i32> { fn bar_height(rssi: i16, is_tx: bool, spark_h: i32) -> Option<i32> {
let h = if rssi <= RSSI_MIN { let h = if rssi <= RSSI_MIN {
if is_tx { if is_tx {
SPARK_H / 3 spark_h / 3
} else { } else {
return None; return None;
} }
} else { } else {
let clamped = rssi.clamp(RSSI_MIN, RSSI_MAX); let clamped = rssi.clamp(RSSI_MIN, RSSI_MAX);
((clamped - RSSI_MIN) as i32 * SPARK_H) / (RSSI_MAX - RSSI_MIN) as i32 ((clamped - RSSI_MIN) as i32 * spark_h) / (RSSI_MAX - RSSI_MIN) as i32
}; };
if h == 0 { None } else { Some(h) } if h == 0 { None } else { Some(h) }
} }
@@ -281,9 +372,11 @@ fn draw_bar(
x: i32, x: i32,
bar_h: i32, bar_h: i32,
is_tx: bool, is_tx: bool,
spark_top: i32,
spark_h: i32,
fill: &PrimitiveStyle<BinaryColor>, fill: &PrimitiveStyle<BinaryColor>,
) { ) {
let y = SPARK_TOP + SPARK_H - bar_h; let y = spark_top + spark_h - bar_h;
if is_tx { if is_tx {
for row in 0..bar_h { for row in 0..bar_h {
if row % 2 == 0 { if row % 2 == 0 {
@@ -305,4 +398,3 @@ fn draw_bar(
pub fn blank(target: &mut impl DrawTarget<Color = BinaryColor>) { pub fn blank(target: &mut impl DrawTarget<Color = BinaryColor>) {
let _ = target.clear(BinaryColor::Off); let _ = target.clear(BinaryColor::Off);
} }
+49 -11
View File
@@ -7,7 +7,9 @@ use ssd1306::size::DisplaySize128x64;
use ssd1306::{I2CDisplayInterface, Ssd1306Async}; use ssd1306::{I2CDisplayInterface, Ssd1306Async};
use crate::board::DisplayParts; use crate::board::DisplayParts;
use crate::channel::{DisplayCommand, DisplayCommandChannel, RadioStatus, StatusWatch}; use crate::channel::{
DisplayCommand, DisplayCommandChannel, RadioState, RadioStatus, StatusWatch,
};
use crate::board::{Board, LoRaBoard}; use crate::board::{Board, LoRaBoard};
@@ -15,7 +17,7 @@ use super::render::{self, RSSI_HISTORY_LEN};
const BOARD_NAME: &str = Board::NAME; const BOARD_NAME: &str = Board::NAME;
/// Duration per sparkline slot. 64 slots * 1s = ~1 minute of history. /// Duration per sparkline slot. 128 slots * 1s = ~2 minutes of history.
const SPARK_SLOT_MS: u64 = 1000; const SPARK_SLOT_MS: u64 = 1000;
/// Sentinel: no packet received in this slot. Below SX1262 sensitivity /// Sentinel: no packet received in this slot. Below SX1262 sensitivity
@@ -68,6 +70,14 @@ impl DisplayState {
self.current_slot_rssi = NO_SIGNAL; self.current_slot_rssi = NO_SIGNAL;
self.current_slot_tx = false; self.current_slot_tx = false;
} }
/// Whether the active dashboard should be shown (RX or TX mode).
fn is_active(&self) -> bool {
matches!(
self.last_status.state,
RadioState::Receiving | RadioState::Transmitting
)
}
} }
#[task] #[task]
@@ -115,8 +125,8 @@ pub async fn display_task(
return; return;
}; };
// Show splash/waiting screen (dashboard with no config renders it) // Show splash/waiting screen
render_and_flush(&mut display, &state, &board_info).await; render_splash(&mut display, &board_info).await;
loop { loop {
match select3( match select3(
@@ -141,7 +151,7 @@ pub async fn display_task(
state.last_status = radio_status; state.last_status = radio_status;
if state.display_on { if state.display_on {
render_and_flush(&mut display, &state, &board_info).await; render_current(&mut display, &state, &board_info).await;
} }
} }
Either3::Second(cmd) => match cmd { Either3::Second(cmd) => match cmd {
@@ -157,18 +167,18 @@ pub async fn display_task(
if let Some(s) = receiver.try_get() { if let Some(s) = receiver.try_get() {
state.last_status = s; state.last_status = s;
} }
render_and_flush(&mut display, &state, &board_info).await; render_current(&mut display, &state, &board_info).await;
} }
DisplayCommand::Reset => { DisplayCommand::Reset => {
state = DisplayState::new(); state = DisplayState::new();
render_and_flush(&mut display, &state, &board_info).await; render_splash(&mut display, &board_info).await;
} }
}, },
Either3::Third(()) => { Either3::Third(()) => {
// Timer tick: advance sparkline slot // Timer tick: advance sparkline slot
state.advance_slot(); state.advance_slot();
if state.display_on { if state.display_on && state.is_active() {
render_and_flush(&mut display, &state, &board_info).await; render_dashboard(&mut display, &state, &board_info).await;
} }
} }
} }
@@ -183,8 +193,27 @@ type Display<I> = ssd1306::Ssd1306Async<
ssd1306::mode::BufferedGraphicsModeAsync<DisplaySize128x64>, ssd1306::mode::BufferedGraphicsModeAsync<DisplaySize128x64>,
>; >;
async fn render_and_flush<I>(display: &mut Display<I>, state: &DisplayState, board: &render::BoardInfo<'_>) /// Render the appropriate screen (active dashboard or splash) and flush.
where async fn render_current<I>(
display: &mut Display<I>,
state: &DisplayState,
board: &render::BoardInfo<'_>,
) where
I: display_interface::AsyncWriteOnlyDataCommand,
{
if state.is_active() {
render_dashboard(display, state, board).await;
} else {
render_splash(display, board).await;
}
}
/// Render the active dashboard and flush.
async fn render_dashboard<I>(
display: &mut Display<I>,
state: &DisplayState,
board: &render::BoardInfo<'_>,
) where
I: display_interface::AsyncWriteOnlyDataCommand, I: display_interface::AsyncWriteOnlyDataCommand,
{ {
render::dashboard( render::dashboard(
@@ -199,3 +228,12 @@ where
); );
let _ = display.flush().await; let _ = display.flush().await;
} }
/// Render the splash/waiting screen and flush.
async fn render_splash<I>(display: &mut Display<I>, board: &render::BoardInfo<'_>)
where
I: display_interface::AsyncWriteOnlyDataCommand,
{
render::splash(display, board);
let _ = display.flush().await;
}