lora_phy/
lorawan_radio.rs

1#![allow(missing_docs)]
2
3use super::mod_params::{PacketParams, RadioError};
4use super::mod_traits::RadioKind;
5use super::{DelayNs, LoRa, RxMode};
6
7use lora_modulation::BaseBandModulationParams;
8use lorawan_device::async_device::{
9    radio::{PhyRxTx, RxConfig, RxMode as LorawanRxMode, RxQuality, RxStatus, TxConfig},
10    Timings,
11};
12
13const DEFAULT_RX_WINDOW_LEAD_TIME: u32 = 50;
14
15/// LoRa radio using the physical layer API in the external lora-phy crate.
16///
17/// The const generic P is the max power the radio may be instructed to transmit at. The const
18/// generic G is the antenna gain and board loss in dBi.
19pub struct LorawanRadio<RK, DLY, const P: u8, const G: i8 = 0>
20where
21    RK: RadioKind,
22    DLY: DelayNs,
23{
24    pub(crate) lora: LoRa<RK, DLY>,
25    rx_pkt_params: Option<PacketParams>,
26    rx_window_lead_time: u32,
27    rx_window_buffer: u32,
28}
29
30impl<RK, DLY, const P: u8, const G: i8> From<LoRa<RK, DLY>> for LorawanRadio<RK, DLY, P, G>
31where
32    RK: RadioKind,
33    DLY: DelayNs,
34{
35    fn from(lora: LoRa<RK, DLY>) -> Self {
36        Self {
37            lora,
38            rx_pkt_params: None,
39            rx_window_lead_time: DEFAULT_RX_WINDOW_LEAD_TIME,
40            rx_window_buffer: DEFAULT_RX_WINDOW_LEAD_TIME,
41        }
42    }
43}
44
45impl<RK, DLY, const P: u8, const G: i8> LorawanRadio<RK, DLY, P, G>
46where
47    RK: RadioKind,
48    DLY: DelayNs,
49{
50    pub fn set_rx_window_lead_time(&mut self, lt: u32) {
51        self.rx_window_lead_time = lt;
52    }
53    pub fn set_rx_window_buffer(&mut self, buffer: u32) {
54        self.rx_window_buffer = buffer;
55    }
56}
57
58/// Provide the timing values for boards supported by the external lora-phy crate
59impl<RK, DLY, const P: u8, const G: i8> Timings for LorawanRadio<RK, DLY, P, G>
60where
61    RK: RadioKind,
62    DLY: DelayNs,
63{
64    fn get_rx_window_buffer(&self) -> u32 {
65        self.rx_window_lead_time
66    }
67
68    fn get_rx_window_lead_time_ms(&self) -> u32 {
69        self.rx_window_lead_time
70    }
71}
72
73#[derive(Debug, defmt::Format)]
74pub enum Error {
75    Radio(RadioError),
76    NoRxParams,
77}
78
79impl From<RadioError> for Error {
80    fn from(err: RadioError) -> Self {
81        Error::Radio(err)
82    }
83}
84
85/// Provide the LoRa physical layer rx/tx interface for boards supported by the external lora-phy
86/// crate
87impl<RK, DLY, const P: u8, const G: i8> PhyRxTx for LorawanRadio<RK, DLY, P, G>
88where
89    RK: RadioKind,
90    DLY: DelayNs,
91{
92    type PhyError = Error;
93
94    const ANTENNA_GAIN: i8 = G;
95
96    const MAX_RADIO_POWER: u8 = P;
97
98    async fn tx(&mut self, config: TxConfig, buffer: &[u8]) -> Result<u32, Self::PhyError> {
99        let mdltn_params = self.lora.create_modulation_params(
100            config.rf.bb.sf,
101            config.rf.bb.bw,
102            config.rf.bb.cr,
103            config.rf.frequency,
104        )?;
105        let mut tx_pkt_params = self
106            .lora
107            .create_tx_packet_params(8, false, true, false, &mdltn_params)?;
108
109        self.lora
110            .prepare_for_tx(&mdltn_params, &mut tx_pkt_params, config.pw.into(), buffer)
111            .await?;
112        self.lora.tx().await?;
113        Ok(0)
114    }
115
116    async fn setup_rx(&mut self, config: RxConfig) -> Result<(), Self::PhyError> {
117        let mdltn_params = self.lora.create_modulation_params(
118            config.rf.bb.sf,
119            config.rf.bb.bw,
120            config.rf.bb.cr,
121            config.rf.frequency,
122        )?;
123        let rx_pkt_params = self
124            .lora
125            .create_rx_packet_params(8, false, 255, true, true, &mdltn_params)?;
126        self.lora
127            .prepare_for_rx(RxMode::from(config.mode, config.rf.bb), &mdltn_params, &rx_pkt_params)
128            .await?;
129        self.rx_pkt_params = Some(rx_pkt_params);
130        Ok(())
131    }
132
133    async fn rx_single(&mut self, buf: &mut [u8]) -> Result<RxStatus, Self::PhyError> {
134        if let Some(rx_params) = &self.rx_pkt_params {
135            match self.lora.rx(rx_params, buf).await {
136                Ok((len, q)) => Ok(RxStatus::Rx(len as usize, RxQuality::new(q.rssi, q.snr as i8))),
137                Err(RadioError::ReceiveTimeout) => Ok(RxStatus::RxTimeout),
138                Err(err) => Err(err.into()),
139            }
140        } else {
141            Err(Error::NoRxParams)
142        }
143    }
144    async fn rx_continuous(&mut self, receiving_buffer: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> {
145        if let Some(rx_params) = &self.rx_pkt_params {
146            match self.lora.rx(rx_params, receiving_buffer).await {
147                Ok((received_len, rx_pkt_status)) => {
148                    Ok((
149                        received_len as usize,
150                        RxQuality::new(rx_pkt_status.rssi, rx_pkt_status.snr as i8), // downcast snr
151                    ))
152                }
153                Err(err) => Err(err.into()),
154            }
155        } else {
156            Err(Error::NoRxParams)
157        }
158    }
159    async fn low_power(&mut self) -> Result<(), Self::PhyError> {
160        self.lora.sleep(false).await.map_err(|e| e.into())
161    }
162}
163
164impl RxMode {
165    fn from(mode: LorawanRxMode, bb: BaseBandModulationParams) -> Self {
166        match mode {
167            LorawanRxMode::Continuous => RxMode::Continuous,
168            LorawanRxMode::Single { ms } => {
169                // Since both sx126x and sx127x have a preamble-based timeout, we translate
170                // the additional millisecond delay into symbols and add it to the amount of preamble symbols.
171                const PREAMBLE_SYMBOLS: u16 = 13; // 12.25
172                let num_symbols = PREAMBLE_SYMBOLS + bb.delay_in_symbols(ms);
173                RxMode::Single(num_symbols)
174            }
175        }
176    }
177}