refactor: LoRaBoard trait with full associated types + BoardParts

- LoRaBoard trait now has associated types: RadioParts, CommParts, DisplayParts, DisplayDriver
- into_parts() is on the trait (enforced by compiler, not implicit convention)
- BoardParts<R, C, D> struct replaces tuple return from into_parts()
- mac_address() stays on trait, MAC also in BoardParts for convenience
- Removed mac field from DisplayParts (display task calls Board::mac_address())
- DisplayDriver associated type requires DrawTarget<Color = BinaryColor>
This commit is contained in:
swaits committed 2026-04-07 09:50:50 -06:00
1 parent ef2b574ae2
commit 4760615fc9
8 files changed
+134 -98

No files matched your search

-1
View File
@@ -44,7 +44,6 @@ pub struct UartParts {
pub struct DisplayParts { pub struct DisplayParts {
pub i2c: DisplayI2c, pub i2c: DisplayI2c,
pub mac: [u8; 6],
} }
// ── Display driver ────────────────────────────────────────────────── // ── Display driver ──────────────────────────────────────────────────
+18 -13
View File
@@ -1,17 +1,15 @@
use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::gpio::{Level, Output, OutputConfig};
use super::esp32s3; use super::esp32s3;
use super::traits::LoRaBoard; use super::traits::{BoardParts, LoRaBoard};
use crate::hal::esp32s3 as mcu; use crate::hal::esp32s3 as mcu;
#[allow(unused_imports)] // Re-exported for other modules (display, radio, uart tasks) #[allow(unused_imports)] // Re-exported for other modules (display, radio, host tasks)
pub use super::esp32s3::{ pub use super::esp32s3::{
create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UartDriver, create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UartDriver,
UartParts, UartParts,
}; };
// ── Board init ──────────────────────────────────────────────────────
pub struct Board { pub struct Board {
p: esp_hal::peripherals::Peripherals, p: esp_hal::peripherals::Peripherals,
} }
@@ -20,6 +18,11 @@ impl LoRaBoard for Board {
const NAME: &'static str = "Heltec V3"; const NAME: &'static str = "Heltec V3";
const TX_POWER_RANGE: (i8, i8) = (-9, 22); const TX_POWER_RANGE: (i8, i8) = (-9, 22);
type RadioParts = RadioParts;
type CommParts = UartParts;
type DisplayParts = DisplayParts;
type DisplayDriver = DisplayDriver;
fn init() -> Self { fn init() -> Self {
let p = esp_hal::init(esp_hal::Config::default()); let p = esp_hal::init(esp_hal::Config::default());
Self { p } Self { p }
@@ -28,21 +31,19 @@ impl LoRaBoard for Board {
fn mac_address() -> [u8; 6] { fn mac_address() -> [u8; 6] {
mcu::mac_address() mcu::mac_address()
} }
}
impl Board { fn into_parts(self) -> BoardParts<RadioParts, UartParts, DisplayParts> {
pub fn into_parts(self) -> (RadioParts, UartParts, Option<DisplayParts>) {
let p = self.p; let p = self.p;
mcu::start_timer(p.TIMG0); mcu::start_timer(p.TIMG0);
// Vext power: GPIO36, active LOW to enable peripherals // Vext power: GPIO36, active LOW to enable peripherals
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default()); let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
core::mem::forget(vext); // hold pin low permanently; drop would reset it core::mem::forget(vext);
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11); let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13); let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
let uart = UartParts { let host = UartParts {
driver: mcu::init_uart(p.UART0, p.GPIO43, p.GPIO44), driver: mcu::init_uart(p.UART0, p.GPIO43, p.GPIO44),
}; };
@@ -51,12 +52,16 @@ impl Board {
esp_hal::delay::Delay::new().delay_millis(10); esp_hal::delay::Delay::new().delay_millis(10);
display_rst.set_high(); display_rst.set_high();
esp_hal::delay::Delay::new().delay_millis(10); esp_hal::delay::Delay::new().delay_millis(10);
core::mem::forget(display_rst); // hold reset high permanently core::mem::forget(display_rst);
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18); let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
let mac = Self::mac_address(); let display = Some(DisplayParts { i2c });
let display = Some(DisplayParts { i2c, mac });
(radio, uart, display) BoardParts {
radio,
host,
display,
mac: Self::mac_address(),
}
} }
} }
+18 -14
View File
@@ -1,17 +1,15 @@
use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::gpio::{Level, Output, OutputConfig};
use super::esp32s3; use super::esp32s3;
use super::traits::LoRaBoard; use super::traits::{BoardParts, LoRaBoard};
use crate::hal::esp32s3 as mcu; use crate::hal::esp32s3 as mcu;
#[allow(unused_imports)] // Re-exported for other modules (display, radio, usb tasks) #[allow(unused_imports)] // Re-exported for other modules (display, radio, host tasks)
pub use super::esp32s3::{ pub use super::esp32s3::{
create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UsbDriver, create_display, DisplayDriver, DisplayI2c, DisplayParts, RadioDriver, RadioParts, UsbDriver,
UsbParts, UsbParts,
}; };
// ── Board implementation ────────────────────────────────────────────
pub struct Board { pub struct Board {
p: esp_hal::peripherals::Peripherals, p: esp_hal::peripherals::Peripherals,
} }
@@ -20,6 +18,11 @@ impl LoRaBoard for Board {
const NAME: &'static str = "Heltec V4"; const NAME: &'static str = "Heltec V4";
const TX_POWER_RANGE: (i8, i8) = (-9, 22); const TX_POWER_RANGE: (i8, i8) = (-9, 22);
type RadioParts = RadioParts;
type CommParts = UsbParts;
type DisplayParts = DisplayParts;
type DisplayDriver = DisplayDriver;
fn init() -> Self { fn init() -> Self {
let p = esp_hal::init(esp_hal::Config::default()); let p = esp_hal::init(esp_hal::Config::default());
Self { p } Self { p }
@@ -28,24 +31,21 @@ impl LoRaBoard for Board {
fn mac_address() -> [u8; 6] { fn mac_address() -> [u8; 6] {
mcu::mac_address() mcu::mac_address()
} }
}
impl Board { fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts> {
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
let p = self.p; let p = self.p;
mcu::start_timer(p.TIMG0); mcu::start_timer(p.TIMG0);
// Vext power: GPIO36, active LOW to enable peripherals // Vext power: GPIO36, active LOW to enable peripherals
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default()); let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
core::mem::forget(vext); // hold pin low permanently; drop would reset it core::mem::forget(vext);
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11); let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13); let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
// Note: switches internal USB PHY from Serial-JTAG to OTG. // Note: switches internal USB PHY from Serial-JTAG to OTG.
// espflash --monitor will stop working after this point. let host = UsbParts {
let usb = UsbParts {
driver: mcu::init_usb(p.USB0, p.GPIO20, p.GPIO19), driver: mcu::init_usb(p.USB0, p.GPIO20, p.GPIO19),
}; };
@@ -54,12 +54,16 @@ impl Board {
esp_hal::delay::Delay::new().delay_millis(10); esp_hal::delay::Delay::new().delay_millis(10);
display_rst.set_high(); display_rst.set_high();
esp_hal::delay::Delay::new().delay_millis(10); esp_hal::delay::Delay::new().delay_millis(10);
core::mem::forget(display_rst); // hold reset high permanently core::mem::forget(display_rst);
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18); let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
let mac = Self::mac_address(); let display = Some(DisplayParts { i2c });
let display = Some(DisplayParts { i2c, mac });
(radio, usb, display) BoardParts {
radio,
host,
display,
mac: Self::mac_address(),
}
} }
} }
+32 -25
View File
@@ -9,7 +9,7 @@ use embassy_time::Delay;
use lora_phy::iv::GenericSx126xInterfaceVariant; use lora_phy::iv::GenericSx126xInterfaceVariant;
use lora_phy::sx126x::{self, Sx1262, Sx126x}; use lora_phy::sx126x::{self, Sx1262, Sx126x};
use super::traits::LoRaBoard; use super::traits::{BoardParts, LoRaBoard};
use crate::hal::nrf52840 as mcu; use crate::hal::nrf52840 as mcu;
// ── Concrete peripheral types ─────────────────────────────────────── // ── Concrete peripheral types ───────────────────────────────────────
@@ -27,6 +27,23 @@ pub type DisplayDriver = ssd1306::Ssd1306Async<
ssd1306::mode::BufferedGraphicsModeAsync<ssd1306::size::DisplaySize128x64>, ssd1306::mode::BufferedGraphicsModeAsync<ssd1306::size::DisplaySize128x64>,
>; >;
// ── Peripheral bundles ──────────────────────────────────────────────
pub struct RadioParts {
pub driver: RadioDriver,
pub delay: Delay,
}
pub struct UsbParts {
pub driver: UsbDriver,
}
pub struct DisplayParts {
pub i2c: DisplayI2c,
}
// ── Display init ────────────────────────────────────────────────────
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> { pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
use ssd1306::mode::DisplayConfigAsync; use ssd1306::mode::DisplayConfigAsync;
use ssd1306::prelude::{Brightness, DisplayRotation}; use ssd1306::prelude::{Brightness, DisplayRotation};
@@ -46,22 +63,6 @@ pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
Some(display) Some(display)
} }
// ── Peripheral bundles ──────────────────────────────────────────────
pub struct RadioParts {
pub driver: RadioDriver,
pub delay: Delay,
}
pub struct UsbParts {
pub driver: UsbDriver,
}
pub struct DisplayParts {
pub i2c: DisplayI2c,
pub mac: [u8; 6],
}
// ── Board init ────────────────────────────────────────────────────── // ── Board init ──────────────────────────────────────────────────────
pub struct Board { pub struct Board {
@@ -72,6 +73,11 @@ impl LoRaBoard for Board {
const NAME: &'static str = "RAK WisBlock 4631"; const NAME: &'static str = "RAK WisBlock 4631";
const TX_POWER_RANGE: (i8, i8) = (-9, 22); const TX_POWER_RANGE: (i8, i8) = (-9, 22);
type RadioParts = RadioParts;
type CommParts = UsbParts;
type DisplayParts = DisplayParts;
type DisplayDriver = DisplayDriver;
fn init() -> Self { fn init() -> Self {
let p = embassy_nrf::init(Default::default()); let p = embassy_nrf::init(Default::default());
Self { p } Self { p }
@@ -80,10 +86,8 @@ impl LoRaBoard for Board {
fn mac_address() -> [u8; 6] { fn mac_address() -> [u8; 6] {
mcu::mac_address() mcu::mac_address()
} }
}
impl Board { fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts> {
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
let p = self.p; let p = self.p;
// ── SPI bus for SX1262 ────────────────────────────────── // ── SPI bus for SX1262 ──────────────────────────────────
@@ -95,7 +99,6 @@ impl Board {
let nss = Output::new(p.P1_10, Level::High, OutputDrive::Standard); let nss = Output::new(p.P1_10, Level::High, OutputDrive::Standard);
let spi_device = SpiDevice::new(spi_bus, nss); let spi_device = SpiDevice::new(spi_bus, nss);
// ── SX1262 control pins ─────────────────────────────────
let reset = Output::new(p.P1_06, Level::High, OutputDrive::Standard); let reset = Output::new(p.P1_06, Level::High, OutputDrive::Standard);
let dio1 = Input::new(p.P1_15, Pull::Down); let dio1 = Input::new(p.P1_15, Pull::Down);
let busy = Input::new(p.P1_14, Pull::Down); let busy = Input::new(p.P1_14, Pull::Down);
@@ -117,7 +120,7 @@ impl Board {
// ── USB ───────────────────────────────────────────────── // ── USB ─────────────────────────────────────────────────
let vbus = mcu::alloc_vbus_detect(true, false); let vbus = mcu::alloc_vbus_detect(true, false);
let usb = UsbParts { let host = UsbParts {
driver: Driver::new(p.USBD, mcu::Irqs, vbus), driver: Driver::new(p.USBD, mcu::Irqs, vbus),
}; };
@@ -126,9 +129,13 @@ impl Board {
let i2c = embassy_nrf::twim::Twim::new( let i2c = embassy_nrf::twim::Twim::new(
p.TWISPI0, mcu::Irqs, p.P0_13, p.P0_14, twim::Config::default(), twim_buf, p.TWISPI0, mcu::Irqs, p.P0_13, p.P0_14, twim::Config::default(), twim_buf,
); );
let mac = Self::mac_address(); let display = Some(DisplayParts { i2c });
let display = Some(DisplayParts { i2c, mac });
(radio, usb, display) BoardParts {
radio,
host,
display,
mac: Self::mac_address(),
}
} }
} }
+30 -13
View File
@@ -1,31 +1,48 @@
//! Board abstraction trait. //! Board abstraction trait.
//! //!
//! Every board must implement [`LoRaBoard`]. The compiler enforces completeness — //! Every board must implement [`LoRaBoard`] with concrete associated types
//! missing constants or methods are compile errors, not runtime surprises. //! for its radio, host communication, and display peripherals. The compiler
//! //! enforces completeness — missing types or methods are compile errors.
//! Board modules also export concrete types (`RadioParts`, `UsbParts`,
//! `DisplayParts`, `RadioDriver`, `UsbDriver`, `DisplayI2c`) that the tasks
//! use directly. The trait verifies metadata and initialization; the types
//! verify hardware compatibility.
//! //!
//! See `src/board/PORTING.md` for a step-by-step guide to adding a new board. //! See `src/board/PORTING.md` for a step-by-step guide to adding a new board.
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::pixelcolor::BinaryColor;
/// Compile-time contract for a DongLoRa board. /// Compile-time contract for a DongLoRa board.
/// pub trait LoRaBoard: Sized {
/// Every board's `Board` struct must implement this trait. It verifies that
/// the board provides all required metadata and initialization methods.
/// The tasks use the board's concrete types directly (not the trait's
/// associated types) because Embassy tasks must be concrete.
pub trait LoRaBoard {
/// Human-readable board name (shown on display splash screen). /// Human-readable board name (shown on display splash screen).
const NAME: &'static str; const NAME: &'static str;
/// TX power range in dBm (min, max) for this board's radio + PA. /// TX power range in dBm (min, max) for this board's radio + PA.
const TX_POWER_RANGE: (i8, i8); const TX_POWER_RANGE: (i8, i8);
/// Radio peripheral bundle (driver + delay).
type RadioParts;
/// Host communication peripheral bundle (USB or UART driver).
type CommParts;
/// Display peripheral bundle (I2C bus for display init).
type DisplayParts;
/// Concrete display driver type (must implement DrawTarget for rendering).
type DisplayDriver: DrawTarget<Color = BinaryColor>;
/// Initialize the board hardware. /// Initialize the board hardware.
fn init() -> Self; fn init() -> Self;
/// Read the board's unique hardware address (MAC, device ID, etc.). /// Read the board's unique hardware address (MAC, device ID, etc.).
fn mac_address() -> [u8; 6]; fn mac_address() -> [u8; 6];
/// Decompose initialized board into peripheral bundles for each task.
fn into_parts(self) -> BoardParts<Self::RadioParts, Self::CommParts, Self::DisplayParts>;
}
/// Peripheral bundles for each firmware task, produced by [`LoRaBoard::into_parts`].
pub struct BoardParts<R, C, D> {
pub radio: R,
pub host: C,
pub display: Option<D>,
pub mac: [u8; 6],
} }
+29 -22
View File
@@ -9,7 +9,7 @@ use embassy_time::Delay;
use lora_phy::iv::GenericSx126xInterfaceVariant; use lora_phy::iv::GenericSx126xInterfaceVariant;
use lora_phy::sx126x::{self, Sx1262, Sx126x}; use lora_phy::sx126x::{self, Sx1262, Sx126x};
use super::traits::LoRaBoard; use super::traits::{BoardParts, LoRaBoard};
use crate::hal::nrf52840 as mcu; use crate::hal::nrf52840 as mcu;
// ── Concrete peripheral types ─────────────────────────────────────── // ── Concrete peripheral types ───────────────────────────────────────
@@ -23,18 +23,6 @@ pub type DisplayI2c = mcu::I2cBus;
pub type DisplayDriver = crate::driver::sh1106::Sh1106<DisplayI2c>; pub type DisplayDriver = crate::driver::sh1106::Sh1106<DisplayI2c>;
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
let mut display = crate::driver::sh1106::Sh1106::new(i2c, 0x3D);
embassy_time::Timer::after_millis(100).await;
if display.init().await.is_err() {
defmt::error!("SH1106 display init failed");
return None;
}
let _ = display.set_brightness(0xFF).await;
Some(display)
}
// ── Peripheral bundles ────────────────────────────────────────────── // ── Peripheral bundles ──────────────────────────────────────────────
pub struct RadioParts { pub struct RadioParts {
@@ -48,7 +36,20 @@ pub struct UsbParts {
pub struct DisplayParts { pub struct DisplayParts {
pub i2c: DisplayI2c, pub i2c: DisplayI2c,
pub mac: [u8; 6], }
// ── Display init ────────────────────────────────────────────────────
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
let mut display = crate::driver::sh1106::Sh1106::new(i2c, 0x3D);
embassy_time::Timer::after_millis(100).await;
if display.init().await.is_err() {
defmt::error!("SH1106 display init failed");
return None;
}
let _ = display.set_brightness(0xFF).await;
Some(display)
} }
// ── Board init ────────────────────────────────────────────────────── // ── Board init ──────────────────────────────────────────────────────
@@ -61,6 +62,11 @@ impl LoRaBoard for Board {
const NAME: &'static str = "Wio Tracker L1"; const NAME: &'static str = "Wio Tracker L1";
const TX_POWER_RANGE: (i8, i8) = (-9, 22); const TX_POWER_RANGE: (i8, i8) = (-9, 22);
type RadioParts = RadioParts;
type CommParts = UsbParts;
type DisplayParts = DisplayParts;
type DisplayDriver = DisplayDriver;
fn init() -> Self { fn init() -> Self {
let mut config = embassy_nrf::config::Config::default(); let mut config = embassy_nrf::config::Config::default();
config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal; config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal;
@@ -72,10 +78,8 @@ impl LoRaBoard for Board {
fn mac_address() -> [u8; 6] { fn mac_address() -> [u8; 6] {
mcu::mac_address() mcu::mac_address()
} }
}
impl Board { fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts> {
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
let p = self.p; let p = self.p;
// ── SPI bus for SX1262 ────────────────────────────────── // ── SPI bus for SX1262 ──────────────────────────────────
@@ -87,7 +91,6 @@ impl Board {
let nss = Output::new(p.P1_14, Level::High, OutputDrive::Standard); let nss = Output::new(p.P1_14, Level::High, OutputDrive::Standard);
let spi_device = SpiDevice::new(spi_bus, nss); let spi_device = SpiDevice::new(spi_bus, nss);
// ── SX1262 control pins ─────────────────────────────────
let reset = Output::new(p.P1_07, Level::High, OutputDrive::Standard); let reset = Output::new(p.P1_07, Level::High, OutputDrive::Standard);
let dio1 = Input::new(p.P0_07, Pull::Down); let dio1 = Input::new(p.P0_07, Pull::Down);
let busy = Input::new(p.P1_10, Pull::Down); let busy = Input::new(p.P1_10, Pull::Down);
@@ -111,7 +114,7 @@ impl Board {
// ── USB ───────────────────────────────────────────────── // ── USB ─────────────────────────────────────────────────
let vbus = mcu::alloc_vbus_detect(true, true); let vbus = mcu::alloc_vbus_detect(true, true);
let usb = UsbParts { let host = UsbParts {
driver: Driver::new(p.USBD, mcu::Irqs, vbus), driver: Driver::new(p.USBD, mcu::Irqs, vbus),
}; };
@@ -124,9 +127,13 @@ impl Board {
let i2c = embassy_nrf::twim::Twim::new( let i2c = embassy_nrf::twim::Twim::new(
p.TWISPI0, mcu::Irqs, p.P0_06, p.P0_05, twim::Config::default(), twim_buf, p.TWISPI0, mcu::Irqs, p.P0_06, p.P0_05, twim::Config::default(), twim_buf,
); );
let mac = Self::mac_address(); let display = Some(DisplayParts { i2c });
let display = Some(DisplayParts { i2c, mac });
(radio, usb, display) BoardParts {
radio,
host,
display,
mac: Self::mac_address(),
}
} }
} }
+1 -1
View File
@@ -108,7 +108,7 @@ pub async fn display_task(
) { ) {
// Format MAC address as "XX:XX:XX:XX:XX:XX" // Format MAC address as "XX:XX:XX:XX:XX:XX"
let mut mac_str: heapless::String<18> = heapless::String::new(); let mut mac_str: heapless::String<18> = heapless::String::new();
let m = parts.mac; let m = Board::mac_address();
let _ = core::fmt::Write::write_fmt( let _ = core::fmt::Write::write_fmt(
&mut mac_str, &mut mac_str,
format_args!( format_args!(
+6 -9
View File
@@ -77,27 +77,24 @@ cfg_if::cfg_if! {
#[cfg(not(test))] #[cfg(not(test))]
async fn run(spawner: Spawner) { async fn run(spawner: Spawner) {
let board = <board::Board as LoRaBoard>::init(); let board = <board::Board as LoRaBoard>::init();
let mac = board::Board::mac_address(); let parts = board.into_parts();
let (radio, comm, display) = board.into_parts();
let has_display = display.is_some();
spawner spawner
.spawn(radio::radio_task(radio, &COMMANDS, &RESPONSES, &STATUS)) .spawn(radio::radio_task(parts.radio, &COMMANDS, &RESPONSES, &STATUS))
.expect("spawn radio_task"); .expect("spawn radio_task");
spawner spawner
.spawn(host::host_task( .spawn(host::host_task(
comm, parts.host,
&COMMANDS, &COMMANDS,
&RESPONSES, &RESPONSES,
&DISPLAY_COMMANDS, &DISPLAY_COMMANDS,
has_display, parts.display.is_some(),
mac, parts.mac,
)) ))
.expect("spawn host_task"); .expect("spawn host_task");
if let Some(dp) = display { if let Some(dp) = parts.display {
spawner spawner
.spawn(display::display_task(dp, &STATUS, &DISPLAY_COMMANDS)) .spawn(display::display_task(dp, &STATUS, &DISPLAY_COMMANDS))
.expect("spawn display_task"); .expect("spawn display_task");