rf24ble/
radio.rs

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