esp_radio/ieee802154/
mod.rs

1//! Low-level [IEEE 802.15.4] driver for the ESP32-C6 and ESP32-H2.
2//!
3//! Implements the PHY/MAC layers of the IEEE 802.15.4 protocol stack, and
4//! supports sending and receiving of raw frames.
5//!
6//! This module is intended to be used to implement support for higher-level
7//! communication protocols, for example [esp-openthread].
8//!
9//! Note that this module currently requires you to enable the `unstable` feature
10//! on `esp-hal`.
11//!
12//! NOTE: Coexistence with Wi-Fi or Bluetooth is currently not possible. If you do it anyway,
13//! things will break.
14//!
15//! [IEEE 802.15.4]: https://en.wikipedia.org/wiki/IEEE_802.15.4
16//! [esp-openthread]: https://github.com/esp-rs/esp-openthread
17
18use byte::{BytesExt, TryRead};
19use esp_hal::{clock::PhyClockGuard, peripherals::IEEE802154};
20use esp_phy::PhyInitGuard;
21use esp_sync::NonReentrantMutex;
22use ieee802154::mac::{self, FooterMode, FrameSerDesContext};
23
24use self::{
25    frame::FRAME_SIZE,
26    pib::{CONFIG_IEEE802154_CCA_THRESHOLD, IEEE802154_FRAME_EXT_ADDR_SIZE},
27    raw::*,
28};
29pub use self::{
30    frame::{Frame, ReceivedFrame},
31    pib::{CcaMode, PendingMode},
32    raw::RawReceived,
33};
34
35mod frame;
36mod hal;
37mod pib;
38mod raw;
39
40/// IEEE 802.15.4 errors
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Error {
43    /// The requested data is bigger than available range, and/or the offset is
44    /// invalid.
45    Incomplete,
46
47    /// The requested data content is invalid.
48    BadInput,
49}
50
51impl core::fmt::Display for Error {
52    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53        match self {
54            Error::Incomplete => write!(f, "Incomplete data."),
55            Error::BadInput => write!(f, "Bad input data."),
56        }
57    }
58}
59
60impl core::error::Error for Error {}
61
62impl From<byte::Error> for Error {
63    fn from(err: byte::Error) -> Self {
64        match err {
65            byte::Error::Incomplete | byte::Error::BadOffset(_) => Error::Incomplete,
66            byte::Error::BadInput { .. } => Error::BadInput,
67        }
68    }
69}
70
71/// IEEE 802.15.4 driver configuration
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Config {
74    pub auto_ack_tx: bool,
75    pub auto_ack_rx: bool,
76    pub enhance_ack_tx: bool,
77    pub promiscuous: bool,
78    pub coordinator: bool,
79    pub rx_when_idle: bool,
80    pub txpower: i8,
81    pub channel: u8,
82    pub cca_threshold: i8,
83    pub cca_mode: CcaMode,
84    pub pan_id: Option<u16>,
85    pub short_addr: Option<u16>,
86    pub ext_addr: Option<u64>,
87    pub rx_queue_size: usize,
88}
89
90impl Default for Config {
91    fn default() -> Self {
92        Self {
93            auto_ack_tx: Default::default(),
94            auto_ack_rx: Default::default(),
95            enhance_ack_tx: Default::default(),
96            promiscuous: Default::default(),
97            coordinator: Default::default(),
98            rx_when_idle: Default::default(),
99            txpower: 10,
100            channel: 15,
101            cca_threshold: CONFIG_IEEE802154_CCA_THRESHOLD,
102            cca_mode: CcaMode::Ed,
103            pan_id: None,
104            short_addr: None,
105            ext_addr: None,
106            rx_queue_size: 10,
107        }
108    }
109}
110
111/// IEEE 802.15.4 driver
112#[derive(Debug)]
113pub struct Ieee802154<'a> {
114    _align: u32,
115    transmit_buffer: [u8; FRAME_SIZE],
116    _phy_clock_guard: PhyClockGuard<'a>,
117    _phy_init_guard: PhyInitGuard<'a>,
118}
119
120impl<'a> Ieee802154<'a> {
121    /// Construct a new driver, enabling the IEEE 802.15.4 radio in the process
122    ///
123    /// NOTE: Coexistence with Wi-Fi or Bluetooth is currently not possible. If you do it anyway,
124    /// things will break.
125    pub fn new(radio: IEEE802154<'a>) -> Self {
126        let (_phy_clock_guard, _phy_init_guard) = esp_ieee802154_enable(radio);
127        Self {
128            _align: 0,
129            transmit_buffer: [0u8; FRAME_SIZE],
130            _phy_clock_guard,
131            _phy_init_guard,
132        }
133    }
134
135    /// Set the configuration for the driver
136    pub fn set_config(&mut self, cfg: Config) {
137        set_auto_ack_tx(cfg.auto_ack_tx);
138        set_auto_ack_rx(cfg.auto_ack_rx);
139        set_enhance_ack_tx(cfg.enhance_ack_tx);
140        set_promiscuous(cfg.promiscuous);
141        set_coordinator(cfg.coordinator);
142        set_rx_when_idle(cfg.rx_when_idle);
143        set_tx_power(cfg.txpower);
144        set_channel(cfg.channel);
145        set_cca_theshold(cfg.cca_threshold);
146        set_cca_mode(cfg.cca_mode);
147
148        if let Some(pan_id) = cfg.pan_id {
149            set_panid(0, pan_id);
150        }
151
152        if let Some(short_addr) = cfg.short_addr {
153            set_short_address(0, short_addr);
154        }
155
156        if let Some(ext_addr) = cfg.ext_addr {
157            let mut address = [0u8; IEEE802154_FRAME_EXT_ADDR_SIZE];
158            address.copy_from_slice(&ext_addr.to_be_bytes()); // LE or BE?
159
160            set_extended_address(0, address);
161        }
162
163        raw::set_queue_size(cfg.rx_queue_size);
164    }
165
166    /// Start receiving frames
167    pub fn start_receive(&mut self) {
168        ieee802154_receive();
169    }
170
171    /// Return the raw data of a received frame
172    pub fn raw_received(&mut self) -> Option<RawReceived> {
173        raw::ensure_receive_enabled();
174        ieee802154_poll()
175    }
176
177    /// Get a received frame, if available
178    pub fn received(&mut self) -> Option<Result<ReceivedFrame, Error>> {
179        raw::ensure_receive_enabled();
180        if let Some(raw) = ieee802154_poll() {
181            let maybe_decoded = if raw.data[0] as usize > raw.data.len() {
182                // try to decode up to data.len()
183                mac::Frame::try_read(&raw.data[1..][..raw.data.len()], FooterMode::Explicit)
184            } else {
185                mac::Frame::try_read(&raw.data[1..][..raw.data[0] as usize], FooterMode::Explicit)
186            };
187
188            let result = match maybe_decoded {
189                Ok((decoded, _)) => {
190                    // crc is not written to rx buffer
191                    let rssi = if (raw.data[0] as usize > raw.data.len()) || (raw.data[0] == 0) {
192                        raw.data[raw.data.len() - 1] as i8
193                    } else {
194                        raw.data[raw.data[0] as usize - 1] as i8
195                    };
196
197                    Ok(ReceivedFrame {
198                        frame: Frame {
199                            header: decoded.header,
200                            content: decoded.content,
201                            payload: decoded.payload.to_vec(),
202                            footer: decoded.footer,
203                        },
204                        channel: raw.channel,
205                        rssi,
206                        lqi: rssi_to_lqi(rssi),
207                    })
208                }
209                Err(err) => Err(err.into()),
210            };
211
212            Some(result)
213        } else {
214            None
215        }
216    }
217
218    /// Transmit a frame
219    pub fn transmit(&mut self, frame: &Frame) -> Result<(), Error> {
220        let frm = mac::Frame {
221            header: frame.header,
222            content: frame.content,
223            payload: &frame.payload,
224            footer: frame.footer,
225        };
226
227        let mut offset = 1usize;
228        self.transmit_buffer
229            .write_with(
230                &mut offset,
231                frm,
232                &mut FrameSerDesContext::no_security(FooterMode::Explicit),
233            )
234            .unwrap();
235        self.transmit_buffer[0] = (offset - 1) as u8;
236
237        ieee802154_transmit(self.transmit_buffer.as_ptr(), false); // what about CCA?
238
239        Ok(())
240    }
241
242    /// Transmit a raw frame
243    pub fn transmit_raw(&mut self, frame: &[u8]) -> Result<(), Error> {
244        self.transmit_buffer[1..][..frame.len()].copy_from_slice(frame);
245        self.transmit_buffer[0] = frame.len() as u8;
246
247        ieee802154_transmit(self.transmit_buffer.as_ptr(), false); // what about CCA?
248
249        Ok(())
250    }
251
252    /// Set the transmit done callback function.
253    pub fn set_tx_done_callback(&mut self, callback: &'a mut (dyn FnMut() + Send)) {
254        CALLBACKS.with(|cbs| {
255            let cb: &'static mut (dyn FnMut() + Send) = unsafe { core::mem::transmute(callback) };
256            cbs.tx_done = Some(cb);
257        });
258    }
259
260    /// Clear the transmit done callback function.
261    pub fn clear_tx_done_callback(&mut self) {
262        CALLBACKS.with(|cbs| cbs.tx_done = None);
263    }
264
265    /// Set the receive available callback function.
266    pub fn set_rx_available_callback(&mut self, callback: &'a mut (dyn FnMut() + Send)) {
267        CALLBACKS.with(|cbs| {
268            let cb: &'static mut (dyn FnMut() + Send) = unsafe { core::mem::transmute(callback) };
269            cbs.rx_available = Some(cb);
270        });
271    }
272
273    /// Clear the receive available callback function.
274    pub fn clear_rx_available_callback(&mut self) {
275        CALLBACKS.with(|cbs| cbs.rx_available = None);
276    }
277
278    /// Set the transmit done callback function.
279    pub fn set_tx_done_callback_fn(&mut self, callback: fn()) {
280        CALLBACKS.with(|cbs| cbs.tx_done_fn = Some(callback));
281    }
282
283    /// Clear the transmit done callback function.
284    pub fn clear_tx_done_callback_fn(&mut self) {
285        CALLBACKS.with(|cbs| cbs.tx_done_fn = None);
286    }
287
288    /// Set the receive available callback function.
289    pub fn set_rx_available_callback_fn(&mut self, callback: fn()) {
290        CALLBACKS.with(|cbs| cbs.rx_available_fn = Some(callback));
291    }
292
293    /// Clear the receive available callback function.
294    pub fn clear_rx_available_callback_fn(&mut self) {
295        CALLBACKS.with(|cbs| cbs.rx_available_fn = None);
296    }
297}
298
299impl Drop for Ieee802154<'_> {
300    fn drop(&mut self) {
301        self.clear_tx_done_callback();
302        self.clear_tx_done_callback_fn();
303        self.clear_rx_available_callback();
304        self.clear_rx_available_callback_fn();
305    }
306}
307
308/// Convert from RSSI (Received Signal Strength Indicator) to LQI (Link Quality
309/// Indication)
310///
311/// RSSI is a measure of incoherent (raw) RF power in a channel. LQI is a
312/// cumulative value used in multi-hop networks to assess the cost of a link.
313pub fn rssi_to_lqi(rssi: i8) -> u8 {
314    if rssi < -80 {
315        0
316    } else if rssi > -30 {
317        0xff
318    } else {
319        let lqi_convert = ((rssi as u32).wrapping_add(80)) * 255;
320        (lqi_convert / 50) as u8
321    }
322}
323
324struct Callbacks {
325    tx_done: Option<&'static mut (dyn FnMut() + Send)>,
326    rx_available: Option<&'static mut (dyn FnMut() + Send)>,
327    // TODO: remove these - Box<dyn FnMut> should be good enough
328    tx_done_fn: Option<fn()>,
329    rx_available_fn: Option<fn()>,
330}
331
332impl Callbacks {
333    fn call_tx_done(&mut self) {
334        if let Some(cb) = self.tx_done.as_mut() {
335            cb();
336        }
337        if let Some(cb) = self.tx_done_fn.as_mut() {
338            cb();
339        }
340    }
341
342    fn call_rx_available(&mut self) {
343        if let Some(cb) = self.rx_available.as_mut() {
344            cb();
345        }
346        if let Some(cb) = self.rx_available_fn.as_mut() {
347            cb();
348        }
349    }
350}
351
352static CALLBACKS: NonReentrantMutex<Callbacks> = NonReentrantMutex::new(Callbacks {
353    tx_done: None,
354    rx_available: None,
355    tx_done_fn: None,
356    rx_available_fn: None,
357});
358
359fn tx_done() {
360    trace!("tx_done callback");
361
362    CALLBACKS.with(|cbs| cbs.call_tx_done());
363}
364
365fn rx_available() {
366    trace!("rx available callback");
367
368    CALLBACKS.with(|cbs| cbs.call_rx_available());
369}