rf24ble/
radio.rs

1use crate::{
2    data_manipulation::{crc24_ble, reverse_bits, whiten},
3    services::BlePayload,
4};
5use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiDevice};
6use rf24::{
7    radio::{
8        prelude::{EsbChannel, EsbPaLevel, EsbRadio},
9        Nrf24Error, RadioConfig, RF24,
10    },
11    CrcLength, PaLevel,
12};
13
14/// The supported channels used amongst BLE devices.
15pub const BLE_CHANNEL: [u8; 3] = [2, 26, 80];
16
17/// A namespace of methods to manage the supported range of BLE channels.
18pub struct BleChannels;
19
20impl BleChannels {
21    /// Get the index of [`BLE_CHANNEL`] for the given `channel`.
22    ///
23    /// Returns [`None`] if the given current `channel` is not in [`BLE_CHANNEL`].
24    pub fn index_of(channel: u8) -> Option<usize> {
25        for (index, ch) in BLE_CHANNEL.iter().enumerate() {
26            if *ch == channel {
27                return Some(index);
28            }
29        }
30        None
31    }
32
33    /// Get the next BLE channel given the `current` radio channel.
34    ///
35    /// Returns [`None`] if the given `current` channel is not in [`BLE_CHANNEL`].
36    pub fn increment(current: u8) -> Option<u8> {
37        if let Some(index) = Self::index_of(current) {
38            if index < (BLE_CHANNEL.len() - 1) {
39                return Some(BLE_CHANNEL[index + 1]);
40            } else {
41                return Some(BLE_CHANNEL[0]);
42            }
43        }
44        None
45    }
46}
47
48/// The only address usable in BLE context.
49const BLE_ADDRESS: [u8; 4] = [0x71, 0x91, 0x7d, 0x6b];
50
51/// Returns a [`RadioConfig`] object tailored for OTA compatibility with
52/// BLE specifications.
53///
54/// This configuration complies with inherent [Limitations](index.html#limitations).
55pub fn ble_config() -> RadioConfig {
56    RadioConfig::default()
57        .with_channel(BLE_CHANNEL[0])
58        .with_crc_length(CrcLength::Disabled)
59        .with_auto_ack(0)
60        .with_auto_retries(0, 0)
61        .with_address_length(4)
62        .with_rx_address(1, &BLE_ADDRESS)
63        .with_tx_address(&BLE_ADDRESS)
64}
65
66/// A struct that implements BLE functionality.
67///
68/// This implementation is subject to [Limitations](index.html#limitations).
69///
70/// Use [`ble_config()`] to properly configure the radio for BLE compatibility.
71///
72/// ```ignore
73/// use rf24::radio::{prelude::*, RF24};
74/// use rf24ble::{ble_config, radio::FakeBle};
75///
76/// let mut radio = RF24::new(ce_pin, spi_device, delay_impl);
77/// radio.init()?;
78/// radio.withConfig(&ble_config())?;
79/// let mut ble = FakeBle::new();
80///
81/// radio.print_details()?;
82/// ```
83pub struct FakeBle {
84    pub(crate) name: [u8; 12],
85    /// Enable or disable the inclusion of the radio's PA level in advertisements.
86    ///
87    /// Enabling this feature occupies 3 bytes of the 18 available bytes in
88    /// advertised payloads.
89    pub show_pa_level: bool,
90    /// Set or get the BLE device's MAC address.
91    ///
92    /// A MAC address is required by BLE specifications.
93    /// Use this attribute to uniquely identify the BLE device.
94    pub mac_address: [u8; 6],
95}
96
97impl Default for FakeBle {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl FakeBle {
104    const DEVICE_FLAGS: u8 = 0x42;
105    const PROFILE_FLAGS: [u8; 3] = [2, 1, 5];
106
107    /// Instantiate a BLE device using a given instance of [`RF24`].
108    ///
109    /// The `radio` object is consumed because altering the radio's setting will
110    /// instigate unexpected behavior.
111    pub fn new() -> Self {
112        let mut mac_address = [0u8; 6];
113
114        // TODO: randomize this default MAC address.
115        mac_address.copy_from_slice(b"nRF24L");
116
117        Self {
118            name: [0u8; 12],
119            show_pa_level: false,
120            mac_address,
121        }
122    }
123
124    /// Set the BLE device's name for inclusion in advertisements.
125    ///
126    /// Setting a BLE device name will occupy more bytes from the
127    /// 18 available bytes in advertisements. The exact number of bytes occupied
128    /// is the length of the given `name` buffer plus 2.
129    ///
130    /// The maximum supported name length is 10 bytes.
131    /// So, up to 12 bytes (10 + 2) will be used in the advertising payload.
132    pub fn set_name(&mut self, name: &str) {
133        let len = name.len().min(10);
134        self.name[2..len + 2].copy_from_slice(&name.as_bytes()[0..len]);
135        self.name[0] = len as u8 + 1;
136        self.name[1] = 0x08;
137    }
138
139    /// Get the current BLE device name included in advertisements.
140    ///
141    /// If no name is set (with [`FakeBle::set_name()`]), then this function
142    /// does nothing.
143    /// If a BLE device name has been set, then this function
144    /// stores the bytes of the name in the given `name` buffer.
145    ///
146    /// This function returns the number of bytes changed in the given `name` buffer.
147    pub fn get_name(&self, name: &mut [u8]) -> u8 {
148        let len = self.name[0];
149        if len > 1 {
150            let len = (len as usize - 1).min(name.len());
151            name[0..len].copy_from_slice(&self.name[2..len + 2]);
152            return len as u8;
153        }
154        0
155    }
156
157    /// How many bytes are available in an advertisement payload?
158    ///
159    /// The `hypothetical` parameter shall be the same `buf` value passed to [`FakeBle::send()`].
160    ///
161    /// In addition to the given `hypothetical` payload length, this function also
162    /// accounts for the current state of [`FakeBle::get_name()`] and
163    /// [`FakeBle::show_pa_level`].
164    ///
165    /// If the returned value is less than `0`, then the `hypothetical` payload will not
166    /// be broadcasted.
167    pub fn len_available(&self, hypothetical: &[u8]) -> i8 {
168        let mut result = 18 - hypothetical.len() as i8;
169        let name_len = self.name[0];
170        if name_len > 1 {
171            result -= name_len as i8 + 1;
172        }
173        if self.show_pa_level {
174            result -= 3;
175        }
176        result
177    }
178
179    /// Hop the radio's current channel to the next BLE compliant frequency.
180    ///
181    /// Use this function after [`FakeBle::send()`] to comply with BLE specifications.
182    /// This is not required, but it is recommended to avoid bandwidth pollution.
183    pub fn hop_channel<SPI, DO, DELAY>(
184        &self,
185        radio: &mut RF24<SPI, DO, DELAY>,
186    ) -> Result<(), Nrf24Error<SPI::Error, DO::Error>>
187    where
188        SPI: SpiDevice,
189        DO: OutputPin,
190        DELAY: DelayNs,
191    {
192        let channel = radio.get_channel()?;
193        if let Some(channel) = BleChannels::increment(channel) {
194            radio.set_channel(channel)?;
195        }
196        // if the current channel is not a BLE_CHANNEL, then do nothing
197        Ok(())
198    }
199
200    /// Create a buffer to be used as a BLE advertisement payload.
201    ///
202    /// This is a helper method to [`FakeBle::send()`], but it is publicly exposed for
203    /// advanced usage only (eg. FFI binding).
204    ///
205    /// If the resulting payload length is larger than 32 bytes, then [`None`] is returned.
206    pub fn make_payload(
207        &self,
208        buf: &[u8],
209        pa_level: Option<PaLevel>,
210        channel: u8,
211    ) -> Option<[u8; 32]> {
212        let mut payload_length = buf.len() + 9;
213
214        let mut tx_queue = [0; 32];
215        // tx_queue[11..29] is available for user data.
216        tx_queue[0] = Self::DEVICE_FLAGS;
217        // tx_queue[1] is for the total payload size excluding the following data:
218        // - GATT profile flags (tx_queue[0]) at beginning
219        // - payload size at tx_queue[1]
220        // - CRC24 at the end
221        tx_queue[2..8].copy_from_slice(&self.mac_address);
222        // flags for declaring device capabilities
223        tx_queue[8..11].copy_from_slice(&Self::PROFILE_FLAGS);
224        let mut offset = 11;
225
226        if let Some(pa_level) = pa_level {
227            let pa_level: i8 = match pa_level {
228                rf24::PaLevel::Min => -18,
229                rf24::PaLevel::Low => -12,
230                rf24::PaLevel::High => -6,
231                rf24::PaLevel::Max => 0,
232            };
233            payload_length += 3;
234            offset += 3;
235            tx_queue[11..offset].copy_from_slice(&[2, 0x0A, pa_level as u8]);
236        }
237
238        if self.name[0] > 1 {
239            let len = self.name[0] as usize + 1;
240            payload_length += len;
241            tx_queue[offset..offset + len].copy_from_slice(&self.name[0..len]);
242            offset += len;
243        }
244
245        if payload_length > BlePayload::MAX_BLE_PAYLOAD_SIZE as usize {
246            return None;
247        }
248
249        tx_queue[1] = payload_length as u8;
250        for byte in buf {
251            tx_queue[offset] = *byte;
252            offset += 1;
253        }
254        let crc = crc24_ble(&tx_queue[0..offset]);
255        tx_queue[offset..offset + 3].copy_from_slice(&crc);
256        offset += 3;
257
258        let coefficient = (BleChannels::index_of(channel).unwrap_or_default() + 37) | 0x40;
259        whiten(&mut tx_queue[0..offset], coefficient as u8);
260
261        reverse_bits(&mut tx_queue[0..offset]);
262        Some(tx_queue)
263    }
264
265    /// Send a BLE advertisement
266    ///
267    /// The `buf` parameter takes a buffer that has been already formatted for
268    /// BLE specifications.
269    ///
270    /// See our convenient API to
271    /// - advertise a Battery's remaining change level: [`BatteryService`](struct@crate::services::BatteryService)
272    /// - advertise a Temperature measurement: [`TemperatureService`](struct@crate::services::TemperatureService)
273    /// - advertise a URL: [`UrlService`](struct@crate::services::UrlService)
274    ///
275    /// For a custom/proprietary BLE service, the given `buf` must adopt compliance with BLE specifications.
276    /// For example, a buffer of `n` bytes shall be formed as follows:
277    ///
278    /// | index | value |
279    /// |:------|:------|
280    /// | `0` | `n - 1` |
281    /// | `1` | `0xFF`  |
282    /// | `2 ... n - 1` | custom data |
283    pub fn send<SPI, DO, DELAY>(
284        &self,
285        radio: &mut RF24<SPI, DO, DELAY>,
286        buf: &[u8],
287    ) -> Result<bool, Nrf24Error<SPI::Error, DO::Error>>
288    where
289        SPI: SpiDevice,
290        DO: OutputPin,
291        DELAY: DelayNs,
292    {
293        if let Some(tx_queue) = self.make_payload(
294            buf,
295            if self.show_pa_level {
296                Some(radio.get_pa_level()?)
297            } else {
298                None
299            },
300            radio.get_channel()?,
301        ) {
302            // Disregarding any hardware error, `RF24::send()` should
303            // always return `Ok(true)` because auto-ack is off.
304            return radio.send(&tx_queue, false);
305        }
306        Ok(false)
307    }
308
309    /// Read the first available payload from the radio's RX FIFO
310    /// and decode it into a [`BlePayload`].
311    ///
312    /// <div class="warning">
313    ///
314    /// The payload must be decoded while the radio is on
315    /// the same channel that it received the data.
316    /// Otherwise, the decoding process will fail.
317    ///
318    /// </div>
319    ///
320    /// Use [`RF24::available`](fn@rf24::radio::prelude::EsbFifo::available) to
321    /// check if there is data in the radio's RX FIFO.
322    ///
323    /// If the payload was somehow malformed or incomplete,
324    /// then this function returns a [`None`] value.
325    pub fn read<SPI, DO, DELAY>(
326        &self,
327        radio: &mut RF24<SPI, DO, DELAY>,
328    ) -> Result<Option<BlePayload>, Nrf24Error<SPI::Error, DO::Error>>
329    where
330        SPI: SpiDevice,
331        DO: OutputPin,
332        DELAY: DelayNs,
333    {
334        let mut buf = [0u8; 32];
335        radio.read(&mut buf, Some(32))?;
336        let channel = radio.get_channel()?;
337        Ok(BlePayload::from_bytes(&mut buf, channel))
338    }
339}
340
341/////////////////////////////////////////////////////////////////////////////////
342/// unit tests
343#[cfg(test)]
344mod test {
345    extern crate std;
346    use super::{ble_config, FakeBle, BLE_ADDRESS, BLE_CHANNEL};
347    use crate::{spi_test_expects, test::mk_radio};
348    use embedded_hal_mock::eh1::{
349        digital::{State, Transaction as PinTransaction},
350        spi::Transaction as SpiTransaction,
351    };
352    use rf24::{CrcLength, PaLevel};
353    use std::vec;
354
355    #[test]
356    fn name() {
357        let mut ble = FakeBle::default();
358        let mut expected = [0u8; 10];
359        assert_eq!(0, ble.get_name(&mut expected));
360        ble.set_name("nRF24L");
361        assert_eq!(6, ble.get_name(&mut expected));
362        assert!(expected.starts_with(b"nRF24L"));
363        assert_eq!(ble.len_available(b""), 10);
364    }
365
366    #[test]
367    fn mac() {
368        let mut mac = [0u8; 6];
369        mac.copy_from_slice(b"nRF24L");
370        let mut ble = FakeBle::default();
371        ble.mac_address.copy_from_slice(&mac);
372        assert_eq!(ble.len_available(b""), 18);
373    }
374
375    #[test]
376    fn pa_level() {
377        let mut ble = FakeBle::default();
378        assert_eq!(ble.len_available(b""), 18);
379        ble.show_pa_level = true;
380        assert_eq!(ble.len_available(b""), 15);
381    }
382
383    #[test]
384    fn config() {
385        let config = ble_config();
386        assert_eq!(config.channel(), BLE_CHANNEL[0]);
387        assert_eq!(config.crc_length(), CrcLength::Disabled);
388        assert_eq!(config.auto_ack(), 0);
389        assert_eq!(config.auto_retry_count(), 0);
390        assert_eq!(config.auto_retry_delay(), 0);
391        assert_eq!(config.address_length(), 4);
392        let mut address = [0u8; 4];
393        config.tx_address(&mut address);
394        assert_eq!(address, BLE_ADDRESS);
395        config.rx_address(1, &mut address);
396        assert_eq!(address, BLE_ADDRESS);
397        for pipe in 0..5 {
398            let enabled = config.is_rx_pipe_enabled(pipe);
399            assert_eq!(enabled, pipe == 1);
400        }
401    }
402
403    /// radio's register to control the channel
404    const RF_CH: u8 = 5;
405    /// mnemonic to write to a register
406    const W_REGISTER: u8 = 0x20;
407
408    #[test]
409    fn channel_hop() {
410        let expectations = spi_test_expects![
411            (vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[0]]),
412            (vec![RF_CH | W_REGISTER, BLE_CHANNEL[1]], vec![0xEu8, 0]),
413            (vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[1]]),
414            (vec![RF_CH | W_REGISTER, BLE_CHANNEL[2]], vec![0xEu8, 0]),
415            (vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[2]]),
416            (vec![RF_CH | W_REGISTER, BLE_CHANNEL[0]], vec![0xEu8, 0]),
417            (vec![RF_CH, 0], vec![0xEu8, 0]),
418        ];
419        let mocks = mk_radio(&[], &expectations);
420        let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
421        let ble = FakeBle::default();
422        for _ in 0..4 {
423            ble.hop_channel(&mut radio).unwrap();
424        }
425        spi.done();
426        ce_pin.done();
427    }
428
429    const R_RX_PAYLOAD: u8 = 0x61;
430    const STATUS: u8 = 7;
431    const MASK_RX_DR: u8 = 1 << 6;
432
433    #[test]
434    fn read() {
435        let ble = FakeBle::default();
436        let channel = BLE_CHANNEL[0];
437        let payload = ble.make_payload(&[], None, channel).unwrap();
438        let mut buf = [0; 33];
439        buf[1..].copy_from_slice(&payload);
440        buf[0] = 0xE;
441        let mut expected = [0; 33];
442        expected[0] = R_RX_PAYLOAD;
443
444        let spi_expectations = spi_test_expects![
445            (expected.to_vec(), buf.to_vec()),
446            (vec![STATUS | W_REGISTER, MASK_RX_DR], vec![0xEu8, 0]),
447            (vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[0]]),
448        ];
449        let mocks = mk_radio(&[], &spi_expectations);
450        let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
451
452        let ble_payload = ble.read(&mut radio).unwrap().unwrap();
453        assert_eq!(&ble.mac_address, &ble_payload.mac_address);
454        spi.done();
455        ce_pin.done();
456    }
457
458    const MASK_TX_DS: u8 = 1 << 5;
459    const MASK_MAX_RT: u8 = 1 << 4;
460    const W_TX_PAYLOAD: u8 = 0xA0;
461    const FLUSH_TX: u8 = 0xE1;
462    const NOP: u8 = 0xFF;
463    const RF_SETUP: u8 = 0x06;
464
465    fn send_ce_expects() -> vec::Vec<PinTransaction> {
466        vec![
467            PinTransaction::set(State::Low),
468            PinTransaction::set(State::High),
469        ]
470    }
471
472    fn send_spi_expects(
473        ble: &FakeBle,
474        pa_level: Option<PaLevel>,
475        big_buf: bool,
476    ) -> vec::Vec<SpiTransaction<u8>> {
477        let channel = BLE_CHANNEL[0];
478        let payload = ble.make_payload(&[], pa_level, channel).unwrap();
479        let mut buf = [0; 33];
480        buf[0] = 0xE;
481        let mut expected = [0; 33];
482        expected[0] = W_TX_PAYLOAD;
483        expected[1..].copy_from_slice(&payload);
484
485        let mut expectations = vec![];
486        let bin_pa_level = pa_level.map(|lvl| match lvl {
487            // PaLevel::Min => 0,
488            // PaLevel::Low => 2,
489            // these tests only use Max and High variants
490            PaLevel::High => 4,
491            /* PaLevel::Max */ _ => 6,
492        });
493        if let Some(lvl) = bin_pa_level {
494            expectations
495                .append(&mut spi_test_expects![(vec![RF_SETUP, 0], vec![0xEu8, lvl]),].to_vec());
496        }
497        expectations.append(
498            &mut spi_test_expects![(
499                vec![RF_CH, bin_pa_level.unwrap_or_default()],
500                vec![0xEu8, BLE_CHANNEL[0]]
501            ),]
502            .to_vec(),
503        );
504        if !big_buf {
505            expectations.append(
506                &mut spi_test_expects![
507                    (vec![FLUSH_TX], vec![0xEu8]),
508                    (
509                        vec![STATUS | W_REGISTER, MASK_TX_DS | MASK_MAX_RT],
510                        vec![0xEu8, 0]
511                    ),
512                    (expected.to_vec(), buf.to_vec()),
513                    (vec![NOP], vec![0xE | MASK_TX_DS]),
514                ]
515                .to_vec(),
516            );
517        }
518        expectations
519    }
520
521    #[test]
522    fn send() {
523        let ble = FakeBle::default();
524
525        let spi_expectations = send_spi_expects(&ble, None, false);
526        let ce_expectations = send_ce_expects();
527        let mocks = mk_radio(&ce_expectations, &spi_expectations);
528        let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
529
530        assert!(ble.send(&mut radio, &[]).unwrap());
531        spi.done();
532        ce_pin.done();
533    }
534
535    #[test]
536    fn send_pa_level() {
537        let mut ble = FakeBle::new();
538        ble.show_pa_level = true;
539
540        let spi_expectations = send_spi_expects(&ble, Some(PaLevel::Max), false);
541        let ce_expectations = send_ce_expects();
542        let mocks = mk_radio(&ce_expectations, &spi_expectations);
543        let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
544
545        assert!(ble.send(&mut radio, &[]).unwrap());
546        spi.done();
547        ce_pin.done();
548    }
549
550    #[test]
551    fn send_big_buf() {
552        let mut ble = FakeBle::new();
553        ble.show_pa_level = true;
554
555        let spi_expectations = send_spi_expects(&ble, Some(PaLevel::High), true);
556        let ce_expectations = [];
557        let mocks = mk_radio(&ce_expectations, &spi_expectations);
558        let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
559
560        assert!(!ble.send(&mut radio, &[0u8; 20]).unwrap());
561        spi.done();
562        ce_pin.done();
563    }
564}