refactor: LoRaBoard trait for compile-time board completeness verification
Every board's Board struct now implements the LoRaBoard trait, which enforces that NAME, TX_POWER_RANGE, init(), and mac_address() are all provided. Missing any of these is a compile error — no "good intentions" needed. The trait verifies metadata and initialization. Tasks use the board's concrete types directly (Embassy tasks must be concrete). Board-specific type aliases (RadioDriver, UsbDriver, DisplayI2c) and struct definitions (RadioParts, UsbParts, DisplayParts) remain in each board file. - New: src/board/traits.rs (LoRaBoard trait definition) - Modified: all 3 board files (implement trait, remove standalone constants) - Modified: main.rs (import trait for init) - Modified: radio/task.rs (use Board::TX_POWER_RANGE via trait) - Modified: display/task.rs (use Board::NAME via trait) - Modified: build.rs (exclude traits.rs from board discovery) - Removed: embedded-hal-async direct dep (not needed for trait approach)
This commit is contained in:
11 files changed
+107
-55
No files matched your search
Generated
+1
@@ -341,6 +341,7 @@ dependencies = [
|
|||||||
"embassy-usb",
|
"embassy-usb",
|
||||||
"embassy-usb-driver",
|
"embassy-usb-driver",
|
||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
|
"embedded-hal-async",
|
||||||
"embedded-io-async",
|
"embedded-io-async",
|
||||||
"esp-backtrace",
|
"esp-backtrace",
|
||||||
"esp-hal",
|
"esp-hal",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ categories = ["embedded", "hardware-support", "no-std"]
|
|||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
critical-section = "1.2"
|
critical-section = "1.2"
|
||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
|
embedded-hal-async = "1.0"
|
||||||
embassy-embedded-hal = "0.2"
|
embassy-embedded-hal = "0.2"
|
||||||
embassy-executor = { version = "0.6", features = ["task-arena-size-32768"] }
|
embassy-executor = { version = "0.6", features = ["task-arena-size-32768"] }
|
||||||
embassy-sync = "0.6"
|
embassy-sync = "0.6"
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ fn main() {
|
|||||||
.expect("failed to read src/board directory")
|
.expect("failed to read src/board directory")
|
||||||
.filter_map(|e| {
|
.filter_map(|e| {
|
||||||
let p = e.ok()?.path();
|
let p = e.ok()?.path();
|
||||||
if p.is_file() && p.extension()? == "rs" && p.file_stem()? != "mod" {
|
let stem = p.file_stem()?.to_str()?;
|
||||||
|
if p.is_file() && p.extension()? == "rs" && stem != "mod" && stem != "traits" {
|
||||||
return Some(p.file_stem()?.to_str()?.to_string());
|
return Some(p.file_stem()?.to_str()?.to_string());
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
+12
-4
@@ -1,3 +1,4 @@
|
|||||||
|
use super::traits::LoRaBoard;
|
||||||
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
|
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
|
||||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
use embassy_time::Delay;
|
use embassy_time::Delay;
|
||||||
@@ -25,8 +26,6 @@ pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>;
|
|||||||
|
|
||||||
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
||||||
|
|
||||||
/// TX power range for this board's radio (SX1262).
|
|
||||||
pub const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
|
||||||
|
|
||||||
// ── Peripheral bundles ───────────────────────────────────────────────
|
// ── Peripheral bundles ───────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -50,12 +49,21 @@ pub struct Board {
|
|||||||
p: esp_hal::peripherals::Peripherals,
|
p: esp_hal::peripherals::Peripherals,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl LoRaBoard for Board {
|
||||||
pub fn init() -> Self {
|
const NAME: &'static str = "Heltec V3";
|
||||||
|
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||||
|
|
||||||
|
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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mac_address() -> [u8; 6] {
|
||||||
|
esp_hal::efuse::Efuse::mac_address()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||||
let p = self.p;
|
let p = self.p;
|
||||||
|
|
||||||
|
|||||||
+19
-13
@@ -13,6 +13,8 @@ use static_cell::StaticCell;
|
|||||||
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;
|
||||||
|
|
||||||
// ── Concrete peripheral types ────────────────────────────────────────
|
// ── Concrete peripheral types ────────────────────────────────────────
|
||||||
|
|
||||||
type SpiBus = SpiDmaBus<'static, esp_hal::Async>;
|
type SpiBus = SpiDmaBus<'static, esp_hal::Async>;
|
||||||
@@ -25,9 +27,6 @@ pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>;
|
|||||||
|
|
||||||
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
||||||
|
|
||||||
/// TX power range for this board's radio (SX1262).
|
|
||||||
pub const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
|
||||||
|
|
||||||
// ── Peripheral bundles ───────────────────────────────────────────────
|
// ── Peripheral bundles ───────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct RadioParts {
|
pub struct RadioParts {
|
||||||
@@ -44,18 +43,27 @@ pub struct DisplayParts {
|
|||||||
pub mac: [u8; 6],
|
pub mac: [u8; 6],
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Board init ───────────────────────────────────────────────────────
|
// ── Board implementation ────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
p: esp_hal::peripherals::Peripherals,
|
p: esp_hal::peripherals::Peripherals,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl LoRaBoard for Board {
|
||||||
pub fn init() -> Self {
|
const NAME: &'static str = "Heltec V4";
|
||||||
|
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||||
|
|
||||||
|
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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mac_address() -> [u8; 6] {
|
||||||
|
esp_hal::efuse::Efuse::mac_address()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||||
let p = self.p;
|
let p = self.p;
|
||||||
|
|
||||||
@@ -64,7 +72,6 @@ impl Board {
|
|||||||
esp_hal_embassy::init(timg0.timer0);
|
esp_hal_embassy::init(timg0.timer0);
|
||||||
|
|
||||||
// ── Vext power: GPIO36 ─────────────────────────────────────
|
// ── Vext power: GPIO36 ─────────────────────────────────────
|
||||||
// V4 datasheet says "pull low to enable Vext"
|
|
||||||
let vext = Output::new(p.GPIO36, Level::Low);
|
let vext = Output::new(p.GPIO36, Level::Low);
|
||||||
core::mem::forget(vext); // hold pin low permanently; drop would reset it
|
core::mem::forget(vext); // hold pin low permanently; drop would reset it
|
||||||
|
|
||||||
@@ -101,7 +108,8 @@ impl Board {
|
|||||||
let dio1 = Input::new(p.GPIO14, Pull::Down);
|
let dio1 = Input::new(p.GPIO14, Pull::Down);
|
||||||
let busy = Input::new(p.GPIO13, Pull::Down);
|
let busy = Input::new(p.GPIO13, Pull::Down);
|
||||||
|
|
||||||
let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None).expect("SX1262 interface init");
|
let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None)
|
||||||
|
.expect("SX1262 interface init");
|
||||||
|
|
||||||
let sx_config = sx126x::Config {
|
let sx_config = sx126x::Config {
|
||||||
chip: Sx1262,
|
chip: Sx1262,
|
||||||
@@ -115,9 +123,8 @@ impl Board {
|
|||||||
delay: Delay,
|
delay: Delay,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── USB Serial (via USB-Serial-JTAG, shares PHY safely) ───
|
// ── USB OTG CDC-ACM ────────────────────────────────────────
|
||||||
// ── USB OTG CDC-ACM driver ──────────────────────────────
|
// Note: switches internal USB PHY from Serial-JTAG to OTG.
|
||||||
// Note: this switches the internal USB PHY from Serial-JTAG to OTG.
|
|
||||||
// espflash --monitor will stop working after this point.
|
// espflash --monitor will stop working after this point.
|
||||||
let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19);
|
let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19);
|
||||||
static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new();
|
static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new();
|
||||||
@@ -131,7 +138,6 @@ impl Board {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ── Display (SSD1315 OLED on I2C, 0x3C) ───────────────────
|
// ── Display (SSD1315 OLED on I2C, 0x3C) ───────────────────
|
||||||
// Reset display controller
|
|
||||||
let mut display_rst = Output::new(p.GPIO21, Level::Low);
|
let mut display_rst = Output::new(p.GPIO21, Level::Low);
|
||||||
esp_hal::delay::Delay::new().delay_millis(10);
|
esp_hal::delay::Delay::new().delay_millis(10);
|
||||||
display_rst.set_high();
|
display_rst.set_high();
|
||||||
@@ -142,7 +148,7 @@ impl Board {
|
|||||||
.with_sda(p.GPIO17)
|
.with_sda(p.GPIO17)
|
||||||
.with_scl(p.GPIO18)
|
.with_scl(p.GPIO18)
|
||||||
.into_async();
|
.into_async();
|
||||||
let mac = esp_hal::efuse::Efuse::mac_address();
|
let mac = Self::mac_address();
|
||||||
let display = Some(DisplayParts { i2c, mac });
|
let display = Some(DisplayParts { i2c, mac });
|
||||||
|
|
||||||
(radio, usb, display)
|
(radio, usb, display)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
// @generated by build.rs — DO NOT EDIT
|
// @generated by build.rs — DO NOT EDIT
|
||||||
|
pub mod traits;
|
||||||
|
pub use traits::*;
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
{%- for board in boards %}
|
{%- for board in boards %}
|
||||||
{% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] {
|
{% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ use static_cell::StaticCell;
|
|||||||
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;
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
USBD => embassy_nrf::usb::InterruptHandler<USBD>;
|
USBD => embassy_nrf::usb::InterruptHandler<USBD>;
|
||||||
SPIM3 => embassy_nrf::spim::InterruptHandler<embassy_nrf::peripherals::SPI3>;
|
SPIM3 => embassy_nrf::spim::InterruptHandler<embassy_nrf::peripherals::SPI3>;
|
||||||
@@ -33,9 +35,6 @@ pub type UsbDriver = Driver<'static, USBD, &'static embassy_nrf::usb::vbus_detec
|
|||||||
/// I2C bus for an optional SSD1306 OLED.
|
/// I2C bus for an optional SSD1306 OLED.
|
||||||
pub type DisplayI2c = Twim<'static, embassy_nrf::peripherals::TWISPI0>;
|
pub type DisplayI2c = Twim<'static, embassy_nrf::peripherals::TWISPI0>;
|
||||||
|
|
||||||
/// TX power range for this board's radio (SX1262).
|
|
||||||
pub const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
|
||||||
|
|
||||||
// ── Peripheral bundles ───────────────────────────────────────────────
|
// ── Peripheral bundles ───────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct RadioParts {
|
pub struct RadioParts {
|
||||||
@@ -58,12 +57,34 @@ pub struct Board {
|
|||||||
p: Peripherals,
|
p: Peripherals,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl super::traits::LoRaBoard for Board {
|
||||||
pub fn init() -> Self {
|
const NAME: &'static str = "RAK WisBlock 4631";
|
||||||
|
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||||
|
|
||||||
|
fn init() -> Self {
|
||||||
let p = embassy_nrf::init(Default::default());
|
let p = embassy_nrf::init(Default::default());
|
||||||
Self { p }
|
Self { p }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mac_address() -> [u8; 6] {
|
||||||
|
unsafe {
|
||||||
|
let ficr = 0x10000000 as *const u32;
|
||||||
|
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,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
|
||||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||||
let p = self.p;
|
let p = self.p;
|
||||||
|
|
||||||
@@ -111,22 +132,7 @@ 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);
|
||||||
// Read device address from FICR (factory info registers).
|
let mac = Self::mac_address();
|
||||||
// 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 });
|
let display = Some(DisplayParts { i2c, mac });
|
||||||
|
|
||||||
(radio, usb, display)
|
(radio, usb, display)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//! Board abstraction trait.
|
||||||
|
//!
|
||||||
|
//! Every board must implement [`LoRaBoard`]. The compiler enforces completeness —
|
||||||
|
//! missing constants or methods are compile errors, not runtime surprises.
|
||||||
|
//!
|
||||||
|
//! 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.
|
||||||
|
|
||||||
|
/// Compile-time contract for a DongLoRa board.
|
||||||
|
///
|
||||||
|
/// 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).
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
|
/// TX power range in dBm (min, max) for this board's radio + PA.
|
||||||
|
const TX_POWER_RANGE: (i8, i8);
|
||||||
|
|
||||||
|
/// Initialize the board hardware.
|
||||||
|
fn init() -> Self;
|
||||||
|
|
||||||
|
/// Read the board's unique hardware address (MAC, device ID, etc.).
|
||||||
|
fn mac_address() -> [u8; 6];
|
||||||
|
}
|
||||||
+3
-9
@@ -9,17 +9,11 @@ use ssd1306::{I2CDisplayInterface, Ssd1306Async};
|
|||||||
use crate::board::DisplayParts;
|
use crate::board::DisplayParts;
|
||||||
use crate::channel::{DisplayCommand, DisplayCommandChannel, RadioStatus, StatusWatch};
|
use crate::channel::{DisplayCommand, DisplayCommandChannel, RadioStatus, StatusWatch};
|
||||||
|
|
||||||
|
use crate::board::{Board, LoRaBoard};
|
||||||
|
|
||||||
use super::render::{self, RSSI_HISTORY_LEN};
|
use super::render::{self, RSSI_HISTORY_LEN};
|
||||||
|
|
||||||
const BOARD_NAME: &str = if cfg!(feature = "rak_wisblock_4631") {
|
const BOARD_NAME: &str = Board::NAME;
|
||||||
"RAK WisBlock 4631"
|
|
||||||
} else if cfg!(feature = "heltec_v3") {
|
|
||||||
"Heltec v3"
|
|
||||||
} else if cfg!(feature = "heltec_v4") {
|
|
||||||
"Heltec v4"
|
|
||||||
} else {
|
|
||||||
"unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Duration per sparkline slot. 64 slots * 1s = ~1 minute of history.
|
/// Duration per sparkline slot. 64 slots * 1s = ~1 minute of history.
|
||||||
const SPARK_SLOT_MS: u64 = 1000;
|
const SPARK_SLOT_MS: u64 = 1000;
|
||||||
|
|||||||
+2
-1
@@ -24,6 +24,7 @@ mod usb;
|
|||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
|
||||||
|
use crate::board::LoRaBoard;
|
||||||
use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel, StatusWatch};
|
use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel, StatusWatch};
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
@@ -55,7 +56,7 @@ cfg_if::cfg_if! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run(spawner: Spawner) {
|
async fn run(spawner: Spawner) {
|
||||||
let board = board::Board::init();
|
let board = <board::Board as LoRaBoard>::init();
|
||||||
let (radio, usb, display) = board.into_parts();
|
let (radio, usb, display) = board.into_parts();
|
||||||
|
|
||||||
let has_display = display.is_some();
|
let has_display = display.is_some();
|
||||||
|
|||||||
+6
-6
@@ -24,12 +24,12 @@ use embassy_time::Delay;
|
|||||||
use lora_phy::mod_params::RadioError;
|
use lora_phy::mod_params::RadioError;
|
||||||
use lora_phy::{LoRa, RxMode};
|
use lora_phy::{LoRa, RxMode};
|
||||||
|
|
||||||
use crate::board::{RadioDriver, RadioParts, TX_POWER_RANGE};
|
use crate::board::{Board, LoRaBoard, RadioDriver, RadioParts};
|
||||||
use crate::channel::{CommandChannel, RadioState, RadioStatus, ResponseChannel, StatusWatch};
|
use crate::channel::{CommandChannel, RadioState, RadioStatus, ResponseChannel, StatusWatch};
|
||||||
use crate::protocol::{self, Command, ErrorCode, RadioConfig, Response};
|
use crate::protocol::{self, Command, ErrorCode, RadioConfig, Response};
|
||||||
|
|
||||||
const MAX_PAYLOAD: usize = protocol::MAX_PAYLOAD;
|
const MAX_PAYLOAD: usize = protocol::MAX_PAYLOAD;
|
||||||
const POWER_RANGE: (i8, i8) = TX_POWER_RANGE;
|
// TX power range comes from the board trait: Board::TX_POWER_RANGE
|
||||||
|
|
||||||
type Radio = LoRa<RadioDriver, Delay>;
|
type Radio = LoRa<RadioDriver, Delay>;
|
||||||
|
|
||||||
@@ -178,14 +178,14 @@ async fn handle_cmd(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::SetConfig(cfg) => {
|
Command::SetConfig(cfg) => {
|
||||||
if let Err(reason) = cfg.validate(POWER_RANGE) {
|
if let Err(reason) = cfg.validate(Board::TX_POWER_RANGE) {
|
||||||
warn!("SetConfig rejected: {}", reason);
|
warn!("SetConfig rejected: {}", reason);
|
||||||
responses
|
responses
|
||||||
.send(Response::Error(ErrorCode::InvalidConfig))
|
.send(Response::Error(ErrorCode::InvalidConfig))
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
// Resolve TX_POWER_MAX sentinel to board's actual max
|
// Resolve TX_POWER_MAX sentinel to board's actual max
|
||||||
state.config = Some(cfg.resolve_power(POWER_RANGE));
|
state.config = Some(cfg.resolve_power(Board::TX_POWER_RANGE));
|
||||||
status.sender().send(state.clone());
|
status.sender().send(state.clone());
|
||||||
responses.send(Response::Ok).await;
|
responses.send(Response::Ok).await;
|
||||||
}
|
}
|
||||||
@@ -221,9 +221,9 @@ async fn handle_cmd(
|
|||||||
}
|
}
|
||||||
Command::DisplayOn | Command::DisplayOff => {}
|
Command::DisplayOn | Command::DisplayOff => {}
|
||||||
Command::Transmit { config, payload } => {
|
Command::Transmit { config, payload } => {
|
||||||
let tx_config = config.map(|c| c.resolve_power(POWER_RANGE)).or(state.config);
|
let tx_config = config.map(|c| c.resolve_power(Board::TX_POWER_RANGE)).or(state.config);
|
||||||
if let Some(cfg) = tx_config {
|
if let Some(cfg) = tx_config {
|
||||||
if let Err(reason) = cfg.validate(POWER_RANGE) {
|
if let Err(reason) = cfg.validate(Board::TX_POWER_RANGE) {
|
||||||
warn!("TX config rejected: {}", reason);
|
warn!("TX config rejected: {}", reason);
|
||||||
responses
|
responses
|
||||||
.send(Response::Error(ErrorCode::InvalidConfig))
|
.send(Response::Error(ErrorCode::InvalidConfig))
|
||||||
|
|||||||
Reference in new issue
Block a user