rf24ble/
services.rs

1//! A module to contain all compatible BLE services.
2
3use crate::{
4    data_manipulation::{crc24_ble, reverse_bits, whiten},
5    BleChannels,
6};
7
8#[cfg(feature = "std")]
9extern crate std;
10
11/// The Temperature Service UUID number
12const TEMPERATURE_UUID: u16 = 0x1809;
13/// The Battery Service UUID number
14const BATTERY_UUID: u16 = 0x180F;
15/// The Eddystone Service UUID number
16const EDDYSTONE_UUID: u16 = 0xFEAA;
17
18/// Some common traits related to BLE service data structs.
19pub mod prelude {
20    /// A trait to define the factory method of constructing BLE service data from a buffer.
21    pub(super) trait FromBuffer {
22        fn from_buffer(buf: &[u8]) -> Self;
23    }
24
25    /// A trait to define the buffer extraction of BLE services.
26    pub trait AsBuffer {
27        fn buffer(&self) -> &[u8];
28    }
29
30    /// A trait to define the setter and getter of data for BLE services.
31    pub trait ServiceData<T> {
32        fn set_data(&mut self, value: T);
33        fn data(&self) -> T;
34    }
35}
36
37/// A data service that broadcasts a temperature (in Celsius)
38///
39/// Conforms to the Health Thermometer Measurement format as defined in
40/// [GATT Specifications Supplement](https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=502132&vId=542989).
41#[derive(Debug, Clone, Copy)]
42pub struct TemperatureService {
43    buf: [u8; 8],
44}
45
46impl Default for TemperatureService {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl TemperatureService {
53    /// Create an instance of [`TemperatureService`]
54    pub fn new() -> Self {
55        let mut data = [0u8; 8];
56        data[0] = 7; // chunk length (including type)
57        data[1] = 0x16; // chunk type. 0x16 means format is defined in BLE specs.
58        data[2..4].copy_from_slice(&TEMPERATURE_UUID.to_le_bytes());
59        data[7] = 0xFE;
60        Self { buf: data }
61    }
62}
63
64impl prelude::ServiceData<f32> for TemperatureService {
65    /// Set the temperature measurement (in Celsius) data.
66    fn set_data(&mut self, value: f32) {
67        let buf = ((value * 100.0) as u32 & 0xFFFFFF).to_le_bytes();
68        self.buf[4..7].copy_from_slice(&buf[0..3]);
69    }
70
71    /// Get the temperature measurement (in Celsius) data.
72    fn data(&self) -> f32 {
73        let mut buf = [0u8; 4];
74        buf[0..3].copy_from_slice(&self.buf[4..7]);
75        let value = u32::from_le_bytes(buf);
76        value as f32 / 100.0
77    }
78}
79
80impl prelude::AsBuffer for TemperatureService {
81    /// Transform the service data into a BLE compliant buffer that is ready for broadcasting.
82    fn buffer(&self) -> &[u8] {
83        &self.buf
84    }
85}
86
87impl prelude::FromBuffer for TemperatureService {
88    fn from_buffer(buf: &[u8]) -> Self {
89        let mut self_buf = [0u8; 8];
90        self_buf.copy_from_slice(&buf[0..buf.len().min(8)]);
91        Self { buf: self_buf }
92    }
93}
94
95/// A data service for broadcasting a battery's remaining charge (as a percentage).
96///
97/// Conforms to Battery Level format as defined in
98/// [GATT Specifications Supplement](https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=502132&vId=542989).
99#[derive(Debug, Clone, Copy)]
100pub struct BatteryService {
101    buf: [u8; 5],
102}
103
104impl Default for BatteryService {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110impl BatteryService {
111    /// Create an instance of [`BatteryService`].
112    pub fn new() -> Self {
113        let mut data = [0u8; 5];
114        data[0] = 4; // chunk length (including type)
115        data[1] = 0x16; // chunk type. 0x16 means format is defined in BLE specs.
116        data[2..4].copy_from_slice(&BATTERY_UUID.to_le_bytes());
117        Self { buf: data }
118    }
119}
120
121impl prelude::ServiceData<u8> for BatteryService {
122    /// Set the battery charge level (as integer percentage) data.
123    fn set_data(&mut self, value: u8) {
124        self.buf[4] = value;
125    }
126
127    /// Get the battery charge level (as integer percentage) data.
128    fn data(&self) -> u8 {
129        self.buf[4]
130    }
131}
132
133impl prelude::AsBuffer for BatteryService {
134    /// Transform the service data into a BLE compliant buffer that is ready for broadcasting.
135    fn buffer(&self) -> &[u8] {
136        &self.buf
137    }
138}
139
140impl prelude::FromBuffer for BatteryService {
141    fn from_buffer(buf: &[u8]) -> Self {
142        let mut self_buf = [0u8; 5];
143        self_buf.copy_from_slice(&buf[0..buf.len().min(5)]);
144        Self { buf: self_buf }
145    }
146}
147
148/// A data service for broadcasting a URL.
149///
150/// Conforms to specifications defined by [Google's EddyStone][eddystone] data format.
151///
152/// [eddystone]: https://github.com/google/eddystone
153#[derive(Debug, Clone, Copy)]
154pub struct UrlService {
155    buf: [u8; 18],
156}
157
158impl Default for UrlService {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164impl UrlService {
165    const CODEX_PREFIX: [&str; 4] = ["http://www.", "https://www.", "http://", "https://"];
166    const CODEX_SUFFIX: [&str; 14] = [
167        ".com/", ".org/", ".edu/", ".net/", ".info/", ".biz/", ".gov/", ".com", ".org", ".edu",
168        ".net", ".info", ".biz", ".gov",
169    ];
170
171    /// Create an instance of [`UrlService`].
172    pub fn new() -> Self {
173        let mut data = [0u8; 18];
174        data[1] = 0x16; // chunk type 0x16 format is defined by BLE specs
175        data[2..4].copy_from_slice(&EDDYSTONE_UUID.to_le_bytes());
176        data[4] = 0x10; // header for embedded PA level value
177        data[5] = -25i8 as u8;
178        Self { buf: data }
179    }
180
181    /// Set the predicted PA (Power Amplitude) level at 1 meter radius.
182    pub fn set_pa_level(&mut self, level: i8) {
183        self.buf[5] = level as u8;
184    }
185
186    /// Get the predicted PA (Power Amplitude) level at 1 meter radius.
187    pub fn pa_level(&self) -> i8 {
188        self.buf[5] as i8
189    }
190
191    /// Set the URL to be broadcasted.
192    pub fn set_data(&mut self, value: &str) {
193        let mut index = 6; // index of self.buf
194        let max_len = self.buf.len();
195        let mut pos = 0; // position in str `value`
196        let len = value.len();
197        for (j, pre) in Self::CODEX_PREFIX.iter().enumerate() {
198            if value[0..len].starts_with(*pre) {
199                self.buf[index] = j as u8;
200                pos += pre.len();
201                index += 1;
202                break;
203            }
204        }
205        for (i, ch) in value.char_indices() {
206            if index >= max_len {
207                break;
208            }
209            if i < pos {
210                continue;
211            }
212            for (j, post) in Self::CODEX_SUFFIX.iter().enumerate() {
213                if value[i..len].starts_with(*post) {
214                    self.buf[index] = j as u8;
215                    pos += post.len();
216                    index += 1;
217                    break;
218                }
219            }
220            if i < pos {
221                continue;
222            }
223            self.buf[index] = ch as u8;
224            index += 1;
225            pos += 1;
226        }
227        self.buf[0] = index as u8 - 1;
228    }
229
230    /// Get the URL to be broadcasted.
231    #[cfg(feature = "std")]
232    pub fn data(&self) -> std::string::String {
233        let mut result = std::string::String::new();
234        let mut index = 0; // index of self.buf
235        let max_len = self.buf[0] - 5;
236        for (j, pre) in Self::CODEX_PREFIX.iter().enumerate() {
237            if j as u8 == self.buf[6] {
238                result.push_str(pre);
239                index += 1;
240                break;
241            }
242        }
243        for (i, byte) in self.buf[6..6 + max_len as usize].iter().enumerate() {
244            if index > i {
245                continue;
246            }
247            for (j, post) in Self::CODEX_SUFFIX.iter().enumerate() {
248                if j as u8 == *byte {
249                    result.push_str(post);
250                    index += 1;
251                    break;
252                }
253            }
254            if index > i {
255                continue;
256            }
257            result.push(*byte as char);
258            index += 1;
259        }
260        result
261    }
262}
263
264impl prelude::AsBuffer for UrlService {
265    /// Transform the service data into a BLE compliant buffer that is ready for broadcasting.
266    fn buffer(&self) -> &[u8] {
267        let len = self.buf[0] + 1;
268        &self.buf[0..len as usize]
269    }
270}
271
272impl prelude::FromBuffer for UrlService {
273    fn from_buffer(buf: &[u8]) -> Self {
274        let max_len = buf.len().min(18);
275        let mut self_buf = [0u8; 18];
276        self_buf[0..max_len].copy_from_slice(&buf[0..max_len]);
277        Self { buf: self_buf }
278    }
279}
280
281/// A structure to represent received BLE data.
282pub struct BlePayload {
283    pub mac_address: [u8; 6],
284    pub short_name: Option<[u8; 10]>,
285    pub tx_power: Option<i8>,
286    pub battery_charge: Option<BatteryService>,
287    pub url: Option<UrlService>,
288    pub temperature: Option<TemperatureService>,
289}
290
291impl BlePayload {
292    pub(crate) const MAX_BLE_PAYLOAD_SIZE: u8 = 27;
293
294    pub fn from_bytes(buf: &mut [u8], channel: u8) -> Option<Self> {
295        use prelude::FromBuffer;
296
297        reverse_bits(buf);
298        let coefficient = (BleChannels::index_of(channel).unwrap_or_default() as u8 + 37) | 0x40;
299        whiten(buf, coefficient);
300
301        let len = buf[1];
302        if len > Self::MAX_BLE_PAYLOAD_SIZE {
303            return None;
304        }
305        let len = len as usize + 2;
306
307        let mut crc = [0u8; 3];
308        crc.copy_from_slice(&buf[len..len + 3]);
309        let expected = crc24_ble(&buf[0..len]);
310        if crc != expected {
311            return None;
312        }
313
314        let mut mac_address = [0u8; 6];
315        mac_address.copy_from_slice(&buf[2..8]);
316
317        let mut tx_power = None;
318        let mut short_name = None;
319        let mut battery_charge = None;
320        let mut temperature = None;
321        let mut url = None;
322
323        let mut index = 8_usize;
324        while index < len {
325            let chunk_len = (buf[index] - 1) as usize;
326            let chunk_type = buf[index + 1];
327            let start = index + 2;
328            let end = index + chunk_len + 2;
329            match chunk_type {
330                0x08 | 0x09 => {
331                    let mut name = [0u8; 10];
332                    let name_len = (end - start).min(10);
333                    name[0..name_len].copy_from_slice(&buf[start..start + name_len]);
334                    short_name = Some(name);
335                }
336                0x0A => {
337                    tx_power = Some(buf[start] as i8);
338                }
339                0x16 => {
340                    let mut tmp = [0u8; 2];
341                    tmp.copy_from_slice(&buf[start..start + 2]);
342                    let service_id = u16::from_le_bytes(tmp);
343                    match service_id {
344                        BATTERY_UUID => {
345                            let batt = BatteryService::from_buffer(&buf[index..end]);
346                            battery_charge = Some(batt);
347                        }
348                        TEMPERATURE_UUID => {
349                            let temp = TemperatureService::from_buffer(&buf[index..end]);
350                            temperature = Some(temp);
351                        }
352                        EDDYSTONE_UUID => {
353                            let eddystone = UrlService::from_buffer(&buf[index..end]);
354                            url = Some(eddystone);
355                        }
356                        _ => {}
357                    }
358                }
359                _ => {
360                    // unsupported chunk type
361                    // TODO: save arbitrary data from chunk as a buffer
362                }
363            }
364            index = end;
365        }
366        Some(Self {
367            mac_address,
368            short_name,
369            tx_power,
370            battery_charge,
371            url,
372            temperature,
373        })
374    }
375}
376
377#[cfg(test)]
378mod test {
379    use rf24::PaLevel;
380
381    use super::{
382        prelude::{AsBuffer, ServiceData},
383        BatteryService, BlePayload, TemperatureService, UrlService,
384    };
385    use crate::data_manipulation::{reverse_bits, whiten};
386    use crate::{BleChannels, FakeBle, BLE_CHANNEL};
387
388    #[test]
389    fn battery_service() {
390        let mut battery = BatteryService::default();
391        battery.set_data(85);
392        assert_eq!(battery.data(), 85);
393        assert_eq!([0x04, 0x16, 0x0F, 0x18, 0x55], *battery.buffer());
394    }
395
396    #[test]
397    fn temperature_service() {
398        let mut temp = TemperatureService::default();
399        temp.set_data(45.5);
400        assert_eq!(temp.data(), 45.5);
401        assert_eq!(
402            [0x07, 0x16, 0x09, 0x18, 0xC6, 0x11, 0x00, 0xFE],
403            *temp.buffer()
404        );
405    }
406
407    #[test]
408    fn url_service() {
409        let mut url = UrlService::default();
410        url.set_data("https://www.foo.com/bar/bazz");
411        url.set_pa_level(-20);
412        assert_eq!(url.pa_level(), -20);
413        assert_eq!(
414            [
415                0x11, 0x16, 0xAA, 0xFE, 0x10, 0xEC, 0x01, 0x66, 0x6F, 0x6F, 0x00, 0x62, 0x61, 0x72,
416                0x2F, 0x62, 0x61, 0x7A
417            ],
418            *(url.buffer())
419        );
420    }
421
422    #[test]
423    fn rx_battery() {
424        let mut service = BatteryService::default();
425        service.set_data(85);
426
427        let mut ble = FakeBle::default();
428        ble.set_name("nRF24L01");
429        let channel = BLE_CHANNEL[0];
430        let mut payload = ble
431            .make_payload(service.buffer(), Some(PaLevel::Low), channel)
432            .unwrap();
433
434        let ble_payload = BlePayload::from_bytes(&mut payload, channel).unwrap();
435        assert_eq!(&ble.mac_address, &ble_payload.mac_address);
436        assert_eq!(&ble_payload.short_name.unwrap(), &ble.name[2..]);
437        assert_eq!(ble_payload.tx_power.unwrap(), -12);
438        assert_eq!(
439            ble_payload.battery_charge.unwrap().buffer(),
440            service.buffer()
441        );
442    }
443
444    #[test]
445    fn rx_temperature() {
446        let mut service = TemperatureService::default();
447        service.set_data(45.5);
448
449        let ble = FakeBle::default();
450        let channel = BLE_CHANNEL[0];
451        let mut payload = ble.make_payload(service.buffer(), None, channel).unwrap();
452
453        let ble_payload = BlePayload::from_bytes(&mut payload, channel).unwrap();
454        assert_eq!(&ble.mac_address, &ble_payload.mac_address);
455        assert_eq!(ble_payload.temperature.unwrap().buffer(), service.buffer());
456    }
457
458    #[test]
459    fn rx_url() {
460        let mut service = UrlService::default();
461        service.set_data("https://www.google.com");
462        let buffer = service.buffer();
463
464        let ble = FakeBle::default();
465        let channel = BLE_CHANNEL[0];
466        let mut payload = ble.make_payload(buffer, None, channel).unwrap();
467
468        let ble_payload = BlePayload::from_bytes(&mut payload, channel).unwrap();
469        assert_eq!(&ble.mac_address, &ble_payload.mac_address);
470        for (i, byte) in ble_payload.url.unwrap().buffer().iter().enumerate() {
471            assert_eq!(buffer[i], *byte);
472        }
473    }
474
475    #[test]
476    fn rx_too_big() {
477        let channel = BLE_CHANNEL[0];
478        let coefficient = (BleChannels::index_of(channel).unwrap() as u8 + 37) | 0x40;
479        let mut payload = [0u8; 32];
480        payload[1] = 29;
481        whiten(&mut payload, coefficient);
482        reverse_bits(&mut payload);
483        assert!(BlePayload::from_bytes(&mut payload, channel).is_none());
484    }
485
486    #[test]
487    fn rx_bad_crc() {
488        let ble = FakeBle::default();
489        let channel = BLE_CHANNEL[0];
490        let coefficient = (BleChannels::index_of(channel).unwrap() as u8 + 37) | 0x40;
491
492        // bad CRC
493        let mut payload = ble.make_payload(&[17u8; 18], None, channel).unwrap();
494        reverse_bits(&mut payload[29..32]);
495        assert!(BlePayload::from_bytes(&mut payload, coefficient).is_none());
496    }
497
498    #[test]
499    fn rx_unsupported_service() {
500        let buffer = [4u8, 0x16, 0xFF, 0x0F, 0xFF];
501
502        let ble = FakeBle::default();
503        let channel = BLE_CHANNEL[0];
504        let mut payload = ble
505            .make_payload(&buffer, Some(PaLevel::Min), channel)
506            .unwrap();
507
508        let ble_payload = BlePayload::from_bytes(
509            &mut payload,
510            (BleChannels::index_of(channel).unwrap() as u8 + 37) | 0x40,
511        )
512        .unwrap();
513        assert_eq!(&ble.mac_address, &ble_payload.mac_address);
514        // TODO decode custom data
515    }
516}