feat: show MAC address on splash screen
Splash/waiting screen now shows: Row 1: "DongLoRa" (bold) Row 2: "Board Name vX.Y.Z" Row 3: MAC address (XX:XX:XX:XX:XX:XX) Row 4: "Waiting for host..." Each board reads its MAC at init: - ESP32-S3: efuse MAC via esp_hal::efuse::Efuse::mac_address() - nRF52840: FICR device address registers BoardInfo struct bundles name/version/mac to keep dashboard() under clippy's argument limit.
This commit is contained in:
5 files changed
+67
-19
No files matched your search
@@ -41,6 +41,7 @@ pub struct UsbParts {
|
|||||||
|
|
||||||
pub struct DisplayParts {
|
pub struct DisplayParts {
|
||||||
pub i2c: DisplayI2c,
|
pub i2c: DisplayI2c,
|
||||||
|
pub mac: [u8; 6],
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Board init ───────────────────────────────────────────────────────
|
// ── Board init ───────────────────────────────────────────────────────
|
||||||
@@ -130,7 +131,8 @@ impl Board {
|
|||||||
.with_sda(p.GPIO17)
|
.with_sda(p.GPIO17)
|
||||||
.with_scl(p.GPIO18)
|
.with_scl(p.GPIO18)
|
||||||
.into_async();
|
.into_async();
|
||||||
let display = Some(DisplayParts { i2c });
|
let mac = esp_hal::efuse::Efuse::mac_address();
|
||||||
|
let display = Some(DisplayParts { i2c, mac });
|
||||||
|
|
||||||
(radio, usb, display)
|
(radio, usb, display)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ pub struct UsbParts {
|
|||||||
|
|
||||||
pub struct DisplayParts {
|
pub struct DisplayParts {
|
||||||
pub i2c: DisplayI2c,
|
pub i2c: DisplayI2c,
|
||||||
|
pub mac: [u8; 6],
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Board init ───────────────────────────────────────────────────────
|
// ── Board init ───────────────────────────────────────────────────────
|
||||||
@@ -141,7 +142,8 @@ impl Board {
|
|||||||
.with_sda(p.GPIO17)
|
.with_sda(p.GPIO17)
|
||||||
.with_scl(p.GPIO18)
|
.with_scl(p.GPIO18)
|
||||||
.into_async();
|
.into_async();
|
||||||
let display = Some(DisplayParts { i2c });
|
let mac = esp_hal::efuse::Efuse::mac_address();
|
||||||
|
let display = Some(DisplayParts { i2c, mac });
|
||||||
|
|
||||||
(radio, usb, display)
|
(radio, usb, display)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ pub struct UsbParts {
|
|||||||
|
|
||||||
pub struct DisplayParts {
|
pub struct DisplayParts {
|
||||||
pub i2c: DisplayI2c,
|
pub i2c: DisplayI2c,
|
||||||
|
pub mac: [u8; 6],
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Board init ───────────────────────────────────────────────────────
|
// ── Board init ───────────────────────────────────────────────────────
|
||||||
@@ -110,7 +111,23 @@ impl Board {
|
|||||||
// via SSD1306 init (fails gracefully if no display attached).
|
// via SSD1306 init (fails gracefully if no display attached).
|
||||||
let i2c_cfg = twim::Config::default();
|
let i2c_cfg = twim::Config::default();
|
||||||
let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_13, p.P0_14, i2c_cfg);
|
let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_13, p.P0_14, i2c_cfg);
|
||||||
let display = Some(DisplayParts { i2c });
|
// Read device address from FICR (factory info registers).
|
||||||
|
// FICR is at fixed address 0x10000000 on nRF52840.
|
||||||
|
let mac = unsafe {
|
||||||
|
let ficr = 0x10000000 as *const u32;
|
||||||
|
// DEVICEADDR[0] at offset 0x0A4, DEVICEADDR[1] at offset 0x0A8
|
||||||
|
let addr0 = core::ptr::read_volatile(ficr.byte_add(0x0A4));
|
||||||
|
let addr1 = core::ptr::read_volatile(ficr.byte_add(0x0A8));
|
||||||
|
[
|
||||||
|
addr0 as u8,
|
||||||
|
(addr0 >> 8) as u8,
|
||||||
|
(addr0 >> 16) as u8,
|
||||||
|
(addr0 >> 24) as u8,
|
||||||
|
addr1 as u8,
|
||||||
|
(addr1 >> 8) as u8,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let display = Some(DisplayParts { i2c, mac });
|
||||||
|
|
||||||
(radio, usb, display)
|
(radio, usb, display)
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-8
@@ -26,6 +26,13 @@ const SPARK_H: i32 = 19; // y=45..63
|
|||||||
const RSSI_MIN: i16 = -120;
|
const RSSI_MIN: i16 = -120;
|
||||||
const RSSI_MAX: i16 = 0;
|
const RSSI_MAX: i16 = 0;
|
||||||
|
|
||||||
|
/// Static board identity info for display rendering.
|
||||||
|
pub struct BoardInfo<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub version: &'a str,
|
||||||
|
pub mac: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the main status dashboard.
|
/// Render the main status dashboard.
|
||||||
pub fn dashboard(
|
pub fn dashboard(
|
||||||
target: &mut impl DrawTarget<Color = BinaryColor>,
|
target: &mut impl DrawTarget<Color = BinaryColor>,
|
||||||
@@ -33,16 +40,14 @@ pub fn dashboard(
|
|||||||
rssi_history: &[i16; RSSI_HISTORY_LEN],
|
rssi_history: &[i16; RSSI_HISTORY_LEN],
|
||||||
tx_history: &[bool; RSSI_HISTORY_LEN],
|
tx_history: &[bool; RSSI_HISTORY_LEN],
|
||||||
rssi_count: usize,
|
rssi_count: usize,
|
||||||
board_name: &str,
|
board: &BoardInfo,
|
||||||
version: &str,
|
|
||||||
) {
|
) {
|
||||||
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();
|
||||||
|
|
||||||
match status.config {
|
match status.config {
|
||||||
Some(cfg) => {
|
Some(cfg) => {
|
||||||
// Row 0: state + frequency, TX power right-justified
|
|
||||||
let mut buf: String<32> = String::new();
|
|
||||||
let freq_mhz = cfg.freq_hz / 1_000_000;
|
let freq_mhz = cfg.freq_hz / 1_000_000;
|
||||||
let freq_khz = (cfg.freq_hz % 1_000_000) / 1_000;
|
let freq_khz = (cfg.freq_hz % 1_000_000) / 1_000;
|
||||||
let _ = write!(buf, " {}.{:03}MHz", freq_mhz, freq_khz);
|
let _ = write!(buf, " {}.{:03}MHz", freq_mhz, freq_khz);
|
||||||
@@ -145,24 +150,30 @@ pub fn dashboard(
|
|||||||
rssi_sparkline(target, rssi_history, tx_history, rssi_count);
|
rssi_sparkline(target, rssi_history, tx_history, rssi_count);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Row 1: "DongLoRa" in bold
|
||||||
let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On);
|
let title_style = MonoTextStyle::new(&FONT_9X15_BOLD, BinaryColor::On);
|
||||||
Text::with_alignment(
|
Text::with_alignment(
|
||||||
"DongLoRa",
|
"DongLoRa",
|
||||||
Point::new(W / 2, 24),
|
Point::new(W / 2, 15),
|
||||||
title_style,
|
title_style,
|
||||||
Alignment::Center,
|
Alignment::Center,
|
||||||
)
|
)
|
||||||
.draw(target)
|
.draw(target)
|
||||||
.ok();
|
.ok();
|
||||||
Text::with_alignment(version, Point::new(W / 2, 38), style, Alignment::Center)
|
// Row 2: "Board Name v0.1.0"
|
||||||
|
buf.clear();
|
||||||
|
let _ = write!(buf, "{} v{}", board.name, board.version);
|
||||||
|
Text::with_alignment(&buf, Point::new(W / 2, 28), style, Alignment::Center)
|
||||||
.draw(target)
|
.draw(target)
|
||||||
.ok();
|
.ok();
|
||||||
Text::with_alignment(board_name, Point::new(W / 2, 50), style, Alignment::Center)
|
// Row 3: MAC address
|
||||||
|
Text::with_alignment(board.mac, Point::new(W / 2, 41), style, Alignment::Center)
|
||||||
.draw(target)
|
.draw(target)
|
||||||
.ok();
|
.ok();
|
||||||
|
// Row 4: status
|
||||||
Text::with_alignment(
|
Text::with_alignment(
|
||||||
"Waiting for host...",
|
"Waiting for host...",
|
||||||
Point::new(W / 2, 63),
|
Point::new(W / 2, 54),
|
||||||
style,
|
style,
|
||||||
Alignment::Center,
|
Alignment::Center,
|
||||||
)
|
)
|
||||||
|
|||||||
+24
-8
@@ -81,6 +81,23 @@ pub async fn display_task(
|
|||||||
status: &'static StatusWatch,
|
status: &'static StatusWatch,
|
||||||
display_commands: &'static DisplayCommandChannel,
|
display_commands: &'static DisplayCommandChannel,
|
||||||
) {
|
) {
|
||||||
|
// Format MAC address as "XX:XX:XX:XX:XX:XX"
|
||||||
|
let mut mac_str: heapless::String<18> = heapless::String::new();
|
||||||
|
let m = parts.mac;
|
||||||
|
let _ = core::fmt::Write::write_fmt(
|
||||||
|
&mut mac_str,
|
||||||
|
format_args!(
|
||||||
|
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
|
||||||
|
m[0], m[1], m[2], m[3], m[4], m[5]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let board_info = render::BoardInfo {
|
||||||
|
name: BOARD_NAME,
|
||||||
|
version: env!("CARGO_PKG_VERSION"),
|
||||||
|
mac: &mac_str,
|
||||||
|
};
|
||||||
|
|
||||||
let interface = I2CDisplayInterface::new(parts.i2c);
|
let interface = I2CDisplayInterface::new(parts.i2c);
|
||||||
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||||
.into_buffered_graphics_mode();
|
.into_buffered_graphics_mode();
|
||||||
@@ -104,7 +121,7 @@ pub async fn display_task(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Show splash/waiting screen (dashboard with no config renders it)
|
// Show splash/waiting screen (dashboard with no config renders it)
|
||||||
render_and_flush(&mut display, &state).await;
|
render_and_flush(&mut display, &state, &board_info).await;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match select3(
|
match select3(
|
||||||
@@ -129,7 +146,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).await;
|
render_and_flush(&mut display, &state, &board_info).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Either3::Second(cmd) => match cmd {
|
Either3::Second(cmd) => match cmd {
|
||||||
@@ -145,18 +162,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).await;
|
render_and_flush(&mut display, &state, &board_info).await;
|
||||||
}
|
}
|
||||||
DisplayCommand::Reset => {
|
DisplayCommand::Reset => {
|
||||||
state = DisplayState::new();
|
state = DisplayState::new();
|
||||||
render_and_flush(&mut display, &state).await;
|
render_and_flush(&mut display, &state, &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 {
|
||||||
render_and_flush(&mut display, &state).await;
|
render_and_flush(&mut display, &state, &board_info).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +188,7 @@ 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)
|
async fn render_and_flush<I>(display: &mut Display<I>, state: &DisplayState, board: &render::BoardInfo<'_>)
|
||||||
where
|
where
|
||||||
I: display_interface::AsyncWriteOnlyDataCommand,
|
I: display_interface::AsyncWriteOnlyDataCommand,
|
||||||
{
|
{
|
||||||
@@ -181,8 +198,7 @@ where
|
|||||||
&state.rssi_history,
|
&state.rssi_history,
|
||||||
&state.tx_history,
|
&state.tx_history,
|
||||||
state.rssi_count,
|
state.rssi_count,
|
||||||
BOARD_NAME,
|
board,
|
||||||
env!("CARGO_PKG_VERSION"),
|
|
||||||
);
|
);
|
||||||
let _ = display.flush().await;
|
let _ = display.flush().await;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user