lora_phy/
lib.rs

1#![no_std]
2#![allow(async_fn_in_trait)]
3#![warn(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc = include_str!("../README.md")]
6
7#[cfg(feature = "lorawan-radio")]
8#[cfg_attr(docsrs, doc(cfg(feature = "lorawan-radio")))]
9/// Provides an implementation of the async LoRaWAN device trait.
10pub mod lorawan_radio;
11
12/// The read/write interface between an embedded framework/MCU combination and a LoRa chip
13pub(crate) mod interface;
14/// InterfaceVariant implementations using `embedded-hal`.
15pub mod iv;
16/// Parameters used across the lora-phy crate to support various use cases
17pub mod mod_params;
18/// Traits implemented externally or internally to support control of LoRa chips
19pub mod mod_traits;
20/// Specific implementation to support Semtech Sx126x chips
21pub mod sx126x;
22/// Specific implementation to support Semtech Sx127x chips
23pub mod sx127x;
24
25pub use crate::mod_params::RxMode;
26
27pub use embedded_hal_async::delay::DelayNs;
28use interface::*;
29use mod_params::*;
30use mod_traits::*;
31
32/// Provides the physical layer API to support LoRa chips
33pub struct LoRa<RK, DLY>
34where
35    RK: RadioKind,
36    DLY: DelayNs,
37{
38    radio_kind: RK,
39    delay: DLY,
40    radio_mode: RadioMode,
41    enable_public_network: bool,
42    cold_start: bool,
43    calibrate_image: bool,
44}
45
46impl<RK, DLY> LoRa<RK, DLY>
47where
48    RK: RadioKind,
49    DLY: DelayNs,
50{
51    /// Build and return a new instance of the LoRa physical layer API to control an initialized LoRa radio
52    pub async fn new(radio_kind: RK, enable_public_network: bool, delay: DLY) -> Result<Self, RadioError> {
53        let mut lora = Self {
54            radio_kind,
55            delay,
56            radio_mode: RadioMode::Sleep,
57            enable_public_network,
58            cold_start: true,
59            calibrate_image: true,
60        };
61        lora.init().await?;
62
63        Ok(lora)
64    }
65
66    /// Wait for an IRQ event to occur
67    pub async fn wait_for_irq(&mut self) -> Result<(), RadioError> {
68        self.radio_kind.await_irq().await
69    }
70
71    /// Process an IRQ event and return the new state of the radio
72    pub async fn process_irq_event(&mut self) -> Result<Option<IrqState>, RadioError> {
73        self.radio_kind.process_irq_event(self.radio_mode, None, false).await
74    }
75
76    /// Create modulation parameters for a communication channel
77    pub fn create_modulation_params(
78        &mut self,
79        spreading_factor: SpreadingFactor,
80        bandwidth: Bandwidth,
81        coding_rate: CodingRate,
82        frequency_in_hz: u32,
83    ) -> Result<ModulationParams, RadioError> {
84        self.radio_kind
85            .create_modulation_params(spreading_factor, bandwidth, coding_rate, frequency_in_hz)
86    }
87
88    /// Create packet parameters for a send operation on a communication channel
89    pub fn create_tx_packet_params(
90        &mut self,
91        preamble_length: u16,
92        implicit_header: bool,
93        crc_on: bool,
94        iq_inverted: bool,
95        modulation_params: &ModulationParams,
96    ) -> Result<PacketParams, RadioError> {
97        self.radio_kind.create_packet_params(
98            preamble_length,
99            implicit_header,
100            0,
101            crc_on,
102            iq_inverted,
103            modulation_params,
104        )
105    }
106
107    /// Create packet parameters for a receive operation on a communication channel
108    pub fn create_rx_packet_params(
109        &mut self,
110        preamble_length: u16,
111        implicit_header: bool,
112        max_payload_length: u8,
113        crc_on: bool,
114        iq_inverted: bool,
115        modulation_params: &ModulationParams,
116    ) -> Result<PacketParams, RadioError> {
117        self.radio_kind.create_packet_params(
118            preamble_length,
119            implicit_header,
120            max_payload_length,
121            crc_on,
122            iq_inverted,
123            modulation_params,
124        )
125    }
126
127    /// Initialize a Semtech chip as the radio for LoRa physical layer communications
128    pub async fn init(&mut self) -> Result<(), RadioError> {
129        self.cold_start = true;
130        self.radio_kind.reset(&mut self.delay).await?;
131        self.radio_kind.ensure_ready(self.radio_mode).await?;
132        self.radio_kind.set_standby().await?;
133        self.radio_mode = RadioMode::Standby;
134        self.do_cold_start().await
135    }
136
137    async fn do_cold_start(&mut self) -> Result<(), RadioError> {
138        self.radio_kind.init_lora(self.enable_public_network).await?;
139        self.radio_kind.set_tx_power_and_ramp_time(0, None, false).await?;
140        self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
141        self.cold_start = false;
142        self.calibrate_image = true;
143        Ok(())
144    }
145
146    /// Place the LoRa physical layer in standby mode
147    pub async fn enter_standby(&mut self) -> Result<(), RadioError> {
148        self.radio_kind.set_standby().await
149    }
150
151    /// Place the LoRa physical layer in low power mode, specifying cold or warm start (if the Semtech chip supports it)
152    pub async fn sleep(&mut self, warm_start_if_possible: bool) -> Result<(), RadioError> {
153        if self.radio_mode != RadioMode::Sleep {
154            self.radio_kind.ensure_ready(self.radio_mode).await?;
155            self.radio_kind
156                .set_sleep(warm_start_if_possible, &mut self.delay)
157                .await?;
158            if !warm_start_if_possible {
159                self.cold_start = true;
160            }
161            self.radio_mode = RadioMode::Sleep;
162        }
163        Ok(())
164    }
165
166    /// Prepare the Semtech chip for a send operation
167    pub async fn prepare_for_tx(
168        &mut self,
169        mdltn_params: &ModulationParams,
170        tx_pkt_params: &mut PacketParams,
171        output_power: i32,
172        buffer: &[u8],
173    ) -> Result<(), RadioError> {
174        self.prepare_modem(mdltn_params).await?;
175
176        self.radio_kind.set_modulation_params(mdltn_params).await?;
177        self.radio_kind
178            .set_tx_power_and_ramp_time(output_power, Some(mdltn_params), true)
179            .await?;
180        self.radio_kind.ensure_ready(self.radio_mode).await?;
181        if self.radio_mode != RadioMode::Standby {
182            self.radio_kind.set_standby().await?;
183            self.radio_mode = RadioMode::Standby;
184        }
185
186        tx_pkt_params.set_payload_length(buffer.len())?;
187        self.radio_kind.set_packet_params(tx_pkt_params).await?;
188        self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
189        self.radio_kind.set_payload(buffer).await?;
190        self.radio_mode = RadioMode::Transmit;
191        self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
192        Ok(())
193    }
194
195    /// Execute a send operation
196    pub async fn tx(&mut self) -> Result<(), RadioError> {
197        if let RadioMode::Transmit = self.radio_mode {
198            self.radio_kind.do_tx().await?;
199            loop {
200                self.wait_for_irq().await?;
201                match self.radio_kind.process_irq_event(self.radio_mode, None, true).await {
202                    Ok(Some(IrqState::Done | IrqState::PreambleReceived)) => {
203                        self.radio_mode = RadioMode::Standby;
204                        return Ok(());
205                    }
206                    Ok(None) => continue,
207                    Err(err) => {
208                        self.radio_kind.ensure_ready(self.radio_mode).await?;
209                        self.radio_kind.set_standby().await?;
210                        self.radio_mode = RadioMode::Standby;
211                        return Err(err);
212                    }
213                }
214            }
215        } else {
216            Err(RadioError::InvalidRadioMode)
217        }
218    }
219
220    /// Prepare radio to receive a frame in either single or continuous packet mode.
221    /// Notes:
222    /// * sx126x SetRx(0 < timeout < MAX) will listen util LoRa packet header is detected,
223    /// therefore we only use 0 (Single Mode) and MAX (continuous) values.
224    /// TODO: Find a way to express timeout for sx126x, allowing waiting for packet upto 262s
225    /// TODO: Allow DutyCycle as well?
226    pub async fn prepare_for_rx(
227        &mut self,
228        listen_mode: RxMode,
229        mdltn_params: &ModulationParams,
230        rx_pkt_params: &PacketParams,
231    ) -> Result<(), RadioError> {
232        defmt::trace!("RX mode: {}", listen_mode);
233        self.prepare_modem(mdltn_params).await?;
234
235        self.radio_kind.set_modulation_params(mdltn_params).await?;
236        self.radio_kind.set_packet_params(rx_pkt_params).await?;
237        self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
238        self.radio_mode = listen_mode.into();
239        self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
240        Ok(())
241    }
242
243    /// Start receiving and wait for result
244    pub async fn rx(
245        &mut self,
246        packet_params: &PacketParams,
247        receiving_buffer: &mut [u8],
248    ) -> Result<(u8, PacketStatus), RadioError> {
249        if let RadioMode::Receive(listen_mode) = self.radio_mode {
250            self.radio_kind.do_rx(listen_mode).await?;
251            loop {
252                self.wait_for_irq().await?;
253                match self.radio_kind.process_irq_event(self.radio_mode, None, true).await {
254                    Ok(Some(actual_state)) => match actual_state {
255                        IrqState::PreambleReceived => continue,
256                        IrqState::Done => {
257                            let received_len = self.radio_kind.get_rx_payload(packet_params, receiving_buffer).await?;
258                            let rx_pkt_status = self.radio_kind.get_rx_packet_status().await?;
259                            return Ok((received_len, rx_pkt_status));
260                        }
261                    },
262                    Ok(None) => continue,
263                    Err(err) => {
264                        // if in rx continuous mode, allow the caller to determine whether to keep receiving
265                        if self.radio_mode != RadioMode::Receive(RxMode::Continuous) {
266                            self.radio_kind.ensure_ready(self.radio_mode).await?;
267                            self.radio_kind.set_standby().await?;
268                            self.radio_mode = RadioMode::Standby;
269                        }
270                        return Err(err);
271                    }
272                }
273            }
274        } else {
275            Err(RadioError::InvalidRadioMode)
276        }
277    }
278
279    /// Prepare the Semtech chip for a channel activity detection operation
280    pub async fn prepare_for_cad(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
281        self.prepare_modem(mdltn_params).await?;
282
283        self.radio_kind.set_modulation_params(mdltn_params).await?;
284        self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
285        self.radio_mode = RadioMode::ChannelActivityDetection;
286        self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
287        Ok(())
288    }
289
290    /// Start channel activity detection operation and return the result
291    pub async fn cad(&mut self, mdltn_params: &ModulationParams) -> Result<bool, RadioError> {
292        if self.radio_mode == RadioMode::ChannelActivityDetection {
293            self.radio_kind.do_cad(mdltn_params).await?;
294            self.wait_for_irq().await?;
295            let mut cad_activity_detected = false;
296            match self
297                .radio_kind
298                .process_irq_event(self.radio_mode, Some(&mut cad_activity_detected), true)
299                .await
300            {
301                Ok(Some(IrqState::Done)) => Ok(cad_activity_detected),
302                Err(err) => {
303                    self.radio_kind.ensure_ready(self.radio_mode).await?;
304                    self.radio_kind.set_standby().await?;
305                    self.radio_mode = RadioMode::Standby;
306                    Err(err)
307                }
308                Ok(_) => unreachable!(),
309            }
310        } else {
311            Err(RadioError::InvalidRadioMode)
312        }
313    }
314
315    /// Place radio in continuous wave mode, generally for regulatory testing
316    ///
317    /// SemTech app note AN1200.26 “Semtech LoRa FCC 15.247 Guidance” covers usage.
318    ///
319    /// Presumes that init() is called before this function
320    pub async fn continuous_wave(
321        &mut self,
322        mdltn_params: &ModulationParams,
323        output_power: i32,
324    ) -> Result<(), RadioError> {
325        self.prepare_modem(mdltn_params).await?;
326
327        let tx_pkt_params = self
328            .radio_kind
329            .create_packet_params(0, false, 16, false, false, mdltn_params)?;
330        self.radio_kind.set_packet_params(&tx_pkt_params).await?;
331        self.radio_kind.set_modulation_params(mdltn_params).await?;
332        self.radio_kind
333            .set_tx_power_and_ramp_time(output_power, Some(mdltn_params), true)
334            .await?;
335
336        self.radio_kind.ensure_ready(self.radio_mode).await?;
337        if self.radio_mode != RadioMode::Standby {
338            self.radio_kind.set_standby().await?;
339            self.radio_mode = RadioMode::Standby;
340        }
341        self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
342        self.radio_mode = RadioMode::Transmit;
343        self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
344        self.radio_kind.set_tx_continuous_wave_mode().await
345    }
346
347    async fn prepare_modem(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
348        self.radio_kind.ensure_ready(self.radio_mode).await?;
349        if self.radio_mode != RadioMode::Standby {
350            self.radio_kind.set_standby().await?;
351            self.radio_mode = RadioMode::Standby;
352        }
353
354        if self.cold_start {
355            self.do_cold_start().await?;
356        }
357
358        if self.calibrate_image {
359            self.radio_kind.calibrate_image(mdltn_params.frequency_in_hz).await?;
360            self.calibrate_image = false;
361        }
362
363        Ok(())
364    }
365}