Skip to main content

btleplug/bluez/
peripheral.rs

1use async_trait::async_trait;
2use bluez_async::{
3    BluetoothEvent, BluetoothSession, CharacteristicEvent, CharacteristicFlags, CharacteristicId,
4    CharacteristicInfo, DescriptorInfo, DeviceId, DeviceInfo, MacAddress, ServiceInfo,
5    WriteOptions,
6};
7use futures::future::{join_all, ready};
8use futures::stream::{Stream, StreamExt};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11#[cfg(feature = "serde")]
12use serde_cr as serde;
13use std::collections::{BTreeSet, HashMap};
14use std::fmt::{self, Display, Formatter};
15use std::pin::Pin;
16use std::sync::{Arc, Mutex};
17use uuid::Uuid;
18
19use crate::api::{
20    self, AddressType, BDAddr, CharPropFlags, Characteristic, Descriptor, PeripheralProperties,
21    Service, ValueNotification, WriteType,
22};
23use crate::{Error, Result};
24
25#[derive(Clone, Debug)]
26struct CharacteristicInternal {
27    info: CharacteristicInfo,
28    descriptors: HashMap<Uuid, DescriptorInfo>,
29}
30
31impl CharacteristicInternal {
32    fn new(info: CharacteristicInfo, descriptors: HashMap<Uuid, DescriptorInfo>) -> Self {
33        Self { info, descriptors }
34    }
35}
36
37#[derive(Clone, Debug)]
38struct ServiceInternal {
39    info: ServiceInfo,
40    characteristics: HashMap<Uuid, CharacteristicInternal>,
41}
42
43#[cfg_attr(
44    feature = "serde",
45    derive(Serialize, Deserialize),
46    serde(crate = "serde_cr")
47)]
48#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
49pub struct PeripheralId(pub(crate) DeviceId);
50
51impl Display for PeripheralId {
52    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
53        self.0.fmt(f)
54    }
55}
56
57/// Implementation of [api::Peripheral](crate::api::Peripheral).
58#[derive(Clone, Debug)]
59pub struct Peripheral {
60    session: BluetoothSession,
61    device: DeviceId,
62    mac_address: BDAddr,
63    services: Arc<Mutex<HashMap<Uuid, ServiceInternal>>>,
64}
65
66fn get_characteristic<'a>(
67    services: &'a HashMap<Uuid, ServiceInternal>,
68    service_uuid: &Uuid,
69    characteristic_uuid: &Uuid,
70) -> Result<&'a CharacteristicInternal> {
71    services
72        .get(service_uuid)
73        .ok_or_else(|| {
74            Error::Other(format!("Service with UUID {} not found.", service_uuid).into())
75        })?
76        .characteristics
77        .get(characteristic_uuid)
78        .ok_or_else(|| {
79            Error::Other(
80                format!(
81                    "Characteristic with UUID {} not found.",
82                    characteristic_uuid
83                )
84                .into(),
85            )
86        })
87}
88
89impl Peripheral {
90    pub(crate) fn new(session: BluetoothSession, device: DeviceInfo) -> Self {
91        Peripheral {
92            session,
93            device: device.id,
94            mac_address: device.mac_address.into(),
95            services: Arc::new(Mutex::new(HashMap::new())),
96        }
97    }
98
99    fn characteristic_info(&self, characteristic: &Characteristic) -> Result<CharacteristicInfo> {
100        let services = self.services.lock().map_err(Into::<Error>::into)?;
101        get_characteristic(
102            &services,
103            &characteristic.service_uuid,
104            &characteristic.uuid,
105        )
106        .map(|c| &c.info)
107        .cloned()
108    }
109
110    fn descriptor_info(&self, descriptor: &Descriptor) -> Result<DescriptorInfo> {
111        let services = self.services.lock().map_err(Into::<Error>::into)?;
112        let characteristic = get_characteristic(
113            &services,
114            &descriptor.service_uuid,
115            &descriptor.characteristic_uuid,
116        )?;
117        characteristic
118            .descriptors
119            .get(&descriptor.uuid)
120            .ok_or_else(|| {
121                Error::Other(format!("Descriptor with UUID {} not found.", descriptor.uuid).into())
122            })
123            .cloned()
124    }
125
126    async fn device_info(&self) -> Result<DeviceInfo> {
127        Ok(self.session.get_device_info(&self.device).await?)
128    }
129}
130
131#[async_trait]
132impl api::Peripheral for Peripheral {
133    fn id(&self) -> PeripheralId {
134        PeripheralId(self.device.to_owned())
135    }
136
137    fn address(&self) -> BDAddr {
138        self.mac_address
139    }
140
141    fn mtu(&self) -> u16 {
142        let services = self.services.lock().unwrap();
143        for (_, service) in services.iter() {
144            for (_, characteristic) in service.characteristics.iter() {
145                return characteristic.info.mtu.unwrap();
146            }
147        }
148
149        api::DEFAULT_MTU_SIZE
150    }
151
152    async fn properties(&self) -> Result<Option<PeripheralProperties>> {
153        let device_info = self.device_info().await?;
154        Ok(Some(PeripheralProperties {
155            address: device_info.mac_address.into(),
156            address_type: Some(device_info.address_type.into()),
157            local_name: device_info.alias.or(device_info.name.clone()),
158            advertisement_name: device_info.name,
159            tx_power_level: device_info.tx_power,
160            rssi: device_info.rssi,
161            manufacturer_data: device_info.manufacturer_data,
162            service_data: device_info.service_data,
163            services: device_info.services,
164            class: device_info.class,
165        }))
166    }
167
168    fn services(&self) -> BTreeSet<Service> {
169        self.services
170            .lock()
171            .unwrap()
172            .values()
173            .map(|service| service.into())
174            .collect()
175    }
176
177    async fn is_connected(&self) -> Result<bool> {
178        let device_info = self.device_info().await?;
179        Ok(device_info.connected)
180    }
181
182    async fn connect(&self) -> Result<()> {
183        self.session.connect(&self.device).await?;
184        Ok(())
185    }
186
187    async fn disconnect(&self) -> Result<()> {
188        self.session.disconnect(&self.device).await?;
189        Ok(())
190    }
191
192    async fn discover_services(&self) -> Result<()> {
193        let mut services_internal = HashMap::new();
194        let services = self.session.get_services(&self.device).await?;
195        for service in services {
196            let characteristics = self.session.get_characteristics(&service.id).await?;
197            let characteristics = join_all(
198                characteristics
199                    .into_iter()
200                    .fold(
201                        // Only consider the first characteristic of each UUID
202                        // This "should" be unique, but of course it's not enforced
203                        HashMap::<Uuid, CharacteristicInfo>::new(),
204                        |mut map, characteristic| {
205                            if !map.contains_key(&characteristic.uuid) {
206                                map.insert(characteristic.uuid, characteristic);
207                            }
208                            map
209                        },
210                    )
211                    .into_iter()
212                    .map(|mapped_characteristic| async {
213                        let characteristic = mapped_characteristic.1;
214                        let descriptors = self
215                            .session
216                            .get_descriptors(&characteristic.id)
217                            .await
218                            .unwrap_or(Vec::new())
219                            .into_iter()
220                            .map(|descriptor| (descriptor.uuid, descriptor))
221                            .collect();
222                        CharacteristicInternal::new(characteristic, descriptors)
223                    }),
224            )
225            .await;
226            services_internal.insert(
227                service.uuid,
228                ServiceInternal {
229                    info: service,
230                    characteristics: characteristics
231                        .into_iter()
232                        .map(|characteristic| (characteristic.info.uuid, characteristic))
233                        .collect(),
234                },
235            );
236        }
237        *(self.services.lock().map_err(Into::<Error>::into)?) = services_internal;
238        Ok(())
239    }
240
241    async fn write(
242        &self,
243        characteristic: &Characteristic,
244        data: &[u8],
245        write_type: WriteType,
246    ) -> Result<()> {
247        let characteristic_info = self.characteristic_info(characteristic)?;
248        let options = WriteOptions {
249            write_type: Some(write_type.into()),
250            ..Default::default()
251        };
252        Ok(self
253            .session
254            .write_characteristic_value_with_options(&characteristic_info.id, data, options)
255            .await?)
256    }
257
258    async fn read(&self, characteristic: &Characteristic) -> Result<Vec<u8>> {
259        let characteristic_info = self.characteristic_info(characteristic)?;
260        Ok(self
261            .session
262            .read_characteristic_value(&characteristic_info.id)
263            .await?)
264    }
265
266    async fn subscribe(&self, characteristic: &Characteristic) -> Result<()> {
267        let characteristic_info = self.characteristic_info(characteristic)?;
268        Ok(self.session.start_notify(&characteristic_info.id).await?)
269    }
270
271    async fn unsubscribe(&self, characteristic: &Characteristic) -> Result<()> {
272        let characteristic_info = self.characteristic_info(characteristic)?;
273        Ok(self.session.stop_notify(&characteristic_info.id).await?)
274    }
275
276    async fn notifications(&self) -> Result<Pin<Box<dyn Stream<Item = ValueNotification> + Send>>> {
277        let device_id = self.device.clone();
278        let events = self.session.device_event_stream(&device_id).await?;
279        let services = self.services.clone();
280        Ok(Box::pin(events.filter_map(move |event| {
281            ready(value_notification(event, &device_id, services.clone()))
282        })))
283    }
284
285    async fn read_rssi(&self) -> Result<i16> {
286        let device_info = self.device_info().await?;
287        device_info.rssi.ok_or(Error::NotConnected)
288    }
289
290    async fn write_descriptor(&self, descriptor: &Descriptor, data: &[u8]) -> Result<()> {
291        let descriptor_info = self.descriptor_info(descriptor)?;
292        Ok(self
293            .session
294            .write_descriptor_value(&descriptor_info.id, data)
295            .await?)
296    }
297
298    async fn read_descriptor(&self, descriptor: &Descriptor) -> Result<Vec<u8>> {
299        let descriptor_info = self.descriptor_info(descriptor)?;
300        Ok(self
301            .session
302            .read_descriptor_value(&descriptor_info.id)
303            .await?)
304    }
305}
306
307fn value_notification(
308    event: BluetoothEvent,
309    device_id: &DeviceId,
310    services: Arc<Mutex<HashMap<Uuid, ServiceInternal>>>,
311) -> Option<ValueNotification> {
312    match event {
313        BluetoothEvent::Characteristic {
314            id,
315            event: CharacteristicEvent::Value { value },
316        } if id.service().device() == *device_id => {
317            let services = services.lock().unwrap();
318            let (charac, service) = find_characteristic_by_id(&services, id.clone())?;
319            Some(ValueNotification {
320                uuid: charac.uuid,
321                service_uuid: service.uuid,
322                value,
323            })
324        }
325        _ => None,
326    }
327}
328
329fn find_characteristic_by_id(
330    services: &HashMap<Uuid, ServiceInternal>,
331    characteristic_id: CharacteristicId,
332) -> Option<(&CharacteristicInfo, &ServiceInfo)> {
333    for service in services.values() {
334        for characteristic in service.characteristics.values() {
335            if characteristic.info.id == characteristic_id {
336                return Some((&characteristic.info, &service.info));
337            }
338        }
339    }
340    None
341}
342
343impl From<WriteType> for bluez_async::WriteType {
344    fn from(write_type: WriteType) -> Self {
345        match write_type {
346            WriteType::WithoutResponse => bluez_async::WriteType::WithoutResponse,
347            WriteType::WithResponse => bluez_async::WriteType::WithResponse,
348        }
349    }
350}
351
352impl From<MacAddress> for BDAddr {
353    fn from(mac_address: MacAddress) -> Self {
354        <[u8; 6]>::into(mac_address.into())
355    }
356}
357
358impl From<DeviceId> for PeripheralId {
359    fn from(device_id: DeviceId) -> Self {
360        PeripheralId(device_id)
361    }
362}
363
364impl From<bluez_async::AddressType> for AddressType {
365    fn from(address_type: bluez_async::AddressType) -> Self {
366        match address_type {
367            bluez_async::AddressType::Public => AddressType::Public,
368            bluez_async::AddressType::Random => AddressType::Random,
369        }
370    }
371}
372
373fn make_descriptor(
374    info: &DescriptorInfo,
375    characteristic_uuid: Uuid,
376    service_uuid: Uuid,
377) -> Descriptor {
378    Descriptor {
379        uuid: info.uuid,
380        characteristic_uuid,
381        service_uuid,
382    }
383}
384
385fn make_characteristic(
386    characteristic: &CharacteristicInternal,
387    service_uuid: Uuid,
388) -> Characteristic {
389    let CharacteristicInternal { info, descriptors } = characteristic;
390    Characteristic {
391        uuid: info.uuid,
392        properties: info.flags.into(),
393        descriptors: descriptors
394            .iter()
395            .map(|(_, descriptor)| make_descriptor(descriptor, info.uuid, service_uuid))
396            .collect(),
397        service_uuid,
398    }
399}
400
401impl From<&ServiceInternal> for Service {
402    fn from(service: &ServiceInternal) -> Self {
403        Service {
404            uuid: service.info.uuid,
405            primary: service.info.primary,
406            characteristics: service
407                .characteristics
408                .values()
409                .map(|characteristic| make_characteristic(characteristic, service.info.uuid))
410                .collect(),
411        }
412    }
413}
414
415impl From<CharacteristicFlags> for CharPropFlags {
416    fn from(flags: CharacteristicFlags) -> Self {
417        let mut result = CharPropFlags::default();
418        if flags.contains(CharacteristicFlags::BROADCAST) {
419            result.insert(CharPropFlags::BROADCAST);
420        }
421        if flags.contains(CharacteristicFlags::READ) {
422            result.insert(CharPropFlags::READ);
423        }
424        if flags.contains(CharacteristicFlags::WRITE_WITHOUT_RESPONSE) {
425            result.insert(CharPropFlags::WRITE_WITHOUT_RESPONSE);
426        }
427        if flags.contains(CharacteristicFlags::WRITE) {
428            result.insert(CharPropFlags::WRITE);
429        }
430        if flags.contains(CharacteristicFlags::NOTIFY) {
431            result.insert(CharPropFlags::NOTIFY);
432        }
433        if flags.contains(CharacteristicFlags::INDICATE) {
434            result.insert(CharPropFlags::INDICATE);
435        }
436        if flags.contains(CharacteristicFlags::SIGNED_WRITE) {
437            result.insert(CharPropFlags::AUTHENTICATED_SIGNED_WRITES);
438        }
439        if flags.contains(CharacteristicFlags::EXTENDED_PROPERTIES) {
440            result.insert(CharPropFlags::EXTENDED_PROPERTIES);
441        }
442        result
443    }
444}