Skip to main content

bluez_async/
lib.rs

1//! An async wrapper around the D-Bus interface of BlueZ, the Linux Bluetooth daemon. This provides
2//! type-safe interfaces to a subset of the Bluetooth client (i.e. central, in Bluetooth
3//! terminology) interfaces exposed by BlueZ, focussing on the Generic Attribute Profile (GATT) of
4//! Bluetooth Low Energy (BLE).
5//!
6//! Start by creating a [`BluetoothSession`].
7//!
8//! [`BluetoothSession']: struct.BluetoothSession.html
9
10mod adapter;
11mod bleuuid;
12mod characteristic;
13mod descriptor;
14mod device;
15mod events;
16mod introspect;
17mod macaddress;
18mod messagestream;
19mod modalias;
20mod serde_path;
21mod service;
22
23pub use self::adapter::{AdapterId, AdapterInfo};
24pub use self::bleuuid::{BleUuid, uuid_from_u16, uuid_from_u32};
25pub use self::characteristic::{CharacteristicFlags, CharacteristicId, CharacteristicInfo};
26pub use self::descriptor::{DescriptorId, DescriptorInfo};
27pub use self::device::{AddressType, DeviceId, DeviceInfo};
28pub use self::events::{AdapterEvent, BluetoothEvent, CharacteristicEvent, DeviceEvent};
29use self::introspect::IntrospectParse;
30pub use self::macaddress::{MacAddress, ParseMacAddressError};
31use self::messagestream::MessageStream;
32pub use self::modalias::{Modalias, ModaliasSubtype, ParseModaliasError};
33pub use self::service::{ServiceId, ServiceInfo};
34use bluez_generated::{
35    ORG_BLUEZ_ADAPTER1_NAME, ORG_BLUEZ_DEVICE1_NAME, ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME,
36    OrgBluezAdapter1, OrgBluezAdapter1Properties, OrgBluezDevice1, OrgBluezDevice1Properties,
37    OrgBluezGattCharacteristic1, OrgBluezGattCharacteristic1Properties, OrgBluezGattDescriptor1,
38    OrgBluezGattService1,
39};
40use dbus::Path;
41use dbus::arg::{PropMap, Variant};
42use dbus::nonblock::stdintf::org_freedesktop_dbus::{Introspectable, ObjectManager, Properties};
43use dbus::nonblock::{Proxy, SyncConnection};
44use dbus_tokio::connection::IOResourceError;
45use futures::stream::{self, StreamExt, select_all};
46use futures::{FutureExt, Stream};
47use std::collections::HashMap;
48use std::fmt::{self, Debug, Display, Formatter};
49use std::future::Future;
50use std::sync::Arc;
51use std::time::Duration;
52use thiserror::Error;
53use tokio::task::JoinError;
54use tokio::time::timeout;
55use uuid::Uuid;
56
57const DBUS_METHOD_CALL_TIMEOUT: Duration = Duration::from_secs(30);
58// in dbus C lib the max value is #define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff)
59// 0x7fffffff (the largest 32-bit signed integer) or INT32_MAX
60const DBUS_METHOD_CALL_MAX_TIMEOUT: Duration = Duration::from_secs(i32::MAX as u64);
61const SERVICE_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(30);
62
63/// An error carrying out a Bluetooth operation.
64#[derive(Debug, Error)]
65pub enum BluetoothError {
66    /// No Bluetooth adapters were found on the system.
67    #[error("No Bluetooth adapters found.")]
68    NoBluetoothAdapters,
69    /// There was an error talking to the BlueZ daemon over D-Bus.
70    #[error(transparent)]
71    DbusError(#[from] dbus::Error),
72    /// Error parsing XML for introspection.
73    #[error("Error parsing XML for introspection: {0}")]
74    XmlParseError(#[from] serde_xml_rs::Error),
75    /// No service or characteristic was found for some UUID.
76    #[error("Service or characteristic UUID {uuid} not found.")]
77    UuidNotFound { uuid: Uuid },
78    /// Error parsing a UUID from a string.
79    #[error("Error parsing UUID string: {0}")]
80    UuidParseError(#[from] uuid::Error),
81    /// Error parsing a characteristic flag from a string.
82    #[error("Invalid characteristic flag {0:?}")]
83    FlagParseError(String),
84    /// Error parsing an `AddressType` from a string.
85    #[error("Invalid address type {0}")]
86    AddressTypeParseError(String),
87    /// A required property of some device or other object was not found.
88    #[error("Required property {0} missing.")]
89    RequiredPropertyMissing(&'static str),
90    /// Service discovery didn't happen within the time limit.
91    #[error("Service discovery timed out")]
92    ServiceDiscoveryTimedOut,
93    /// Error parsing a `MacAddress` from a string.
94    #[error(transparent)]
95    MacAddressParseError(#[from] ParseMacAddressError),
96    /// Error parsing a `Modalias` from a string.
97    #[error(transparent)]
98    ModaliasParseError(#[from] ParseModaliasError),
99    /// Error parsing DeviceId from a string.
100    #[error("Error parsing DeviceId string: {0}")]
101    DeviceIdParseError(String),
102}
103
104/// Error type for futures representing tasks spawned by this crate.
105#[derive(Debug, Error)]
106pub enum SpawnError {
107    #[error("D-Bus connection lost: {0}")]
108    DbusConnectionLost(#[source] IOResourceError),
109    #[error("Task failed: {0}")]
110    Join(#[from] JoinError),
111}
112
113/// The type of transport to use for a scan.
114#[derive(Copy, Clone, Debug, Eq, PartialEq)]
115pub enum Transport {
116    /// Interleaved scan, both BLE and Bluetooth Classic (if they are both enabled on the adapter).
117    Auto,
118    /// BR/EDR inquiry, i.e. Bluetooth Classic.
119    BrEdr,
120    /// LE scan only.
121    Le,
122}
123
124impl Transport {
125    fn as_str(&self) -> &'static str {
126        match self {
127            Self::Auto => "auto",
128            Self::BrEdr => "bredr",
129            Self::Le => "le",
130        }
131    }
132}
133
134impl Display for Transport {
135    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
136        f.write_str(self.as_str())
137    }
138}
139
140/// A set of filter parameters for discovery. Parameters may be set to `None` to use the BlueZ
141/// defaults.
142///
143/// If no parameters are set then there is a default filter on the RSSI values, where only values
144/// which have changed more than a certain amount will be reported.
145#[derive(Clone, Debug, Default, Eq, PartialEq)]
146pub struct DiscoveryFilter {
147    /// If non-empty, only report devices which advertise at least one of these service UUIDs.
148    pub service_uuids: Vec<Uuid>,
149    /// Only report devices with RSSI values greater than the given threshold.
150    pub rssi_threshold: Option<i16>,
151    pub pathloss_threshold: Option<u16>,
152    /// The type of scan.
153    pub transport: Option<Transport>,
154    /// Whether to include duplicate advertisements. If this is set to true then there will be an
155    /// event whenever an advertisement containing manufacturer-specific data for a device is
156    /// received.
157    pub duplicate_data: Option<bool>,
158    /// Whether to make the adapter discoverable while discovering.
159    pub discoverable: Option<bool>,
160    /// Only report devices whose address or name starts with the given pattern.
161    pub pattern: Option<String>,
162}
163
164impl From<&DiscoveryFilter> for PropMap {
165    fn from(filter: &DiscoveryFilter) -> Self {
166        let mut map: PropMap = HashMap::new();
167        if !filter.service_uuids.is_empty() {
168            let uuids: Vec<String> = filter.service_uuids.iter().map(Uuid::to_string).collect();
169            map.insert("UUIDs".to_string(), Variant(Box::new(uuids)));
170        }
171        if let Some(rssi_threshold) = filter.rssi_threshold {
172            map.insert("RSSI".to_string(), Variant(Box::new(rssi_threshold)));
173        }
174        if let Some(pathloss_threshold) = filter.pathloss_threshold {
175            map.insert(
176                "Pathloss".to_string(),
177                Variant(Box::new(pathloss_threshold)),
178            );
179        }
180        if let Some(transport) = filter.transport {
181            map.insert(
182                "Transport".to_string(),
183                Variant(Box::new(transport.to_string())),
184            );
185        }
186        if let Some(duplicate_data) = filter.duplicate_data {
187            map.insert(
188                "DuplicateData".to_string(),
189                Variant(Box::new(duplicate_data)),
190            );
191        }
192        if let Some(discoverable) = filter.discoverable {
193            map.insert("Discoverable".to_string(), Variant(Box::new(discoverable)));
194        }
195        if let Some(pattern) = &filter.pattern {
196            map.insert("Pattern".to_string(), Variant(Box::new(pattern.to_owned())));
197        }
198        map
199    }
200}
201
202/// The type of write operation to use.
203#[derive(Clone, Copy, Debug, Eq, PartialEq)]
204pub enum WriteType {
205    /// A write operation where the device is expected to respond with a confirmation or error. Also
206    /// known as a request. This corresponds to
207    /// [`CharacteristicFlags`](struct.CharacteristicFlags.html)`::WRITE`.
208    WithResponse,
209    /// A write-without-response, also known as a command. This corresponds to
210    /// [`CharacteristicFlags`](struct.CharacteristicFlags.html)`::WRITE_WITHOUT_RESPONSE`.
211    WithoutResponse,
212    /// A reliable write. This corresponds to
213    /// [`CharacteristicFlags`](struct.CharacteristicFlags.html)`::RELIABLE_WRITE`.
214    Reliable,
215}
216
217impl WriteType {
218    fn as_str(&self) -> &'static str {
219        match self {
220            Self::WithResponse => "request",
221            Self::WithoutResponse => "command",
222            Self::Reliable => "reliable",
223        }
224    }
225}
226
227impl Display for WriteType {
228    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
229        f.write_str(self.as_str())
230    }
231}
232
233/// A set of options for a characteristic write operation.
234#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
235pub struct WriteOptions {
236    /// The starting offset of the write operation.
237    pub offset: usize,
238    /// The type of write operation to use.
239    ///
240    /// Note that a given characteristic may not support all different types of write operations;
241    /// you can check with
242    /// [`CharacteristicInfo.flags`](struct.CharacteristicInfo.html#structfield.flags). The type of
243    /// write operation will also affect the maximum possible length of data which can be written in
244    /// a single operation.
245    pub write_type: Option<WriteType>,
246}
247
248impl From<WriteOptions> for PropMap {
249    fn from(options: WriteOptions) -> Self {
250        let mut map: PropMap = HashMap::new();
251        if options.offset != 0 {
252            map.insert(
253                "offset".to_string(),
254                Variant(Box::new(options.offset as u64)),
255            );
256        }
257        if let Some(write_type) = options.write_type {
258            map.insert(
259                "type".to_string(),
260                Variant(Box::new(write_type.to_string())),
261            );
262        }
263        map
264    }
265}
266
267/// A connection to the Bluetooth daemon. This can be cheaply cloned and passed around to be used
268/// from different places. It is the main entry point to the library.
269#[derive(Clone)]
270pub struct BluetoothSession {
271    connection: Arc<SyncConnection>,
272}
273
274impl Debug for BluetoothSession {
275    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
276        write!(f, "BluetoothSession")
277    }
278}
279
280impl BluetoothSession {
281    /// Establish a new D-Bus connection to communicate with BlueZ.
282    ///
283    /// Returns a tuple of (join handle, Self).
284    /// If the join handle ever completes then you're in trouble and should
285    /// probably restart the process.
286    pub async fn new()
287    -> Result<(impl Future<Output = Result<(), SpawnError>>, Self), BluetoothError> {
288        // Connect to the D-Bus system bus (this is blocking, unfortunately).
289        let (dbus_resource, connection) = dbus_tokio::connection::new_system_sync()?;
290        // Configure the connection to send signal messages to all matching `MsgMatch`es, as we may
291        // have streams with overlapping match rules.
292        connection.set_signal_match_mode(true);
293        // The resource is a task that should be spawned onto a tokio compatible
294        // reactor ASAP. If the resource ever finishes, you lost connection to D-Bus.
295        let dbus_handle = tokio::spawn(async {
296            let err = dbus_resource.await;
297            Err(SpawnError::DbusConnectionLost(err))
298        });
299        Ok((dbus_handle.map(|res| res?), BluetoothSession { connection }))
300    }
301
302    /// Powers the given adapter on or off.
303    pub async fn set_powered(
304        &self,
305        adapter_id: &AdapterId,
306        powered: bool,
307    ) -> Result<(), BluetoothError> {
308        self.adapter(adapter_id).set_powered(powered).await?;
309        Ok(())
310    }
311
312    /// Power on all Bluetooth adapters, remove any discovery filter, and then start scanning for
313    /// devices.
314    ///
315    /// This is equivalent to calling `start_discovery_with_filter(&DiscoveryFilter::default())`.
316    pub async fn start_discovery(&self) -> Result<(), BluetoothError> {
317        self.start_discovery_with_filter(&DiscoveryFilter::default())
318            .await
319    }
320
321    /// Power on the given Bluetooth adapter, remove any discovery filter, and then start scanning
322    /// for devices.
323    ///
324    /// This is equivalent to calling
325    /// `start_discovery_on_adapter_with_filter(adapter, &DiscoveryFilter::default())`.
326    pub async fn start_discovery_on_adapter(
327        &self,
328        adapter: &AdapterId,
329    ) -> Result<(), BluetoothError> {
330        self.start_discovery_on_adapter_with_filter(adapter, &DiscoveryFilter::default())
331            .await
332    }
333
334    /// Power on all Bluetooth adapters, set the given discovery filter, and then start scanning for
335    /// devices.
336    ///
337    /// Note that BlueZ combines discovery filters from all clients and sends events matching any
338    /// filter to all clients, so you may receive unexpected discovery events if there are other
339    /// clients on the system using Bluetooth as well.
340    ///
341    /// In most common cases, `DiscoveryFilter::default()` is fine.
342    pub async fn start_discovery_with_filter(
343        &self,
344        discovery_filter: &DiscoveryFilter,
345    ) -> Result<(), BluetoothError> {
346        let adapters = self.get_adapters().await?;
347        if adapters.is_empty() {
348            return Err(BluetoothError::NoBluetoothAdapters);
349        }
350
351        for adapter in adapters {
352            log::trace!("Starting discovery on adapter {}", adapter.id);
353            self.start_discovery_on_adapter_with_filter(&adapter.id, discovery_filter)
354                .await
355                .unwrap_or_else(|err| log::error!("starting discovery failed {:?}", err));
356        }
357        Ok(())
358    }
359
360    /// Power on the given Bluetooth adapter, set the given discovery filter, and then start
361    /// scanning for devices.
362    ///
363    /// Note that BlueZ combines discovery filters from all clients and sends events matching any
364    /// filter to all clients, so you may receive unexpected discovery events if there are other
365    /// clients on the system using Bluetooth as well.
366    ///
367    /// In most common cases, `DiscoveryFilter::default()` is fine.
368    pub async fn start_discovery_on_adapter_with_filter(
369        &self,
370        adapter_id: &AdapterId,
371        discovery_filter: &DiscoveryFilter,
372    ) -> Result<(), BluetoothError> {
373        let adapter = self.adapter(adapter_id);
374        adapter.set_powered(true).await?;
375        adapter
376            .set_discovery_filter(discovery_filter.into())
377            .await?;
378        adapter.start_discovery().await?;
379        Ok(())
380    }
381
382    /// Stop scanning for devices on all Bluetooth adapters.
383    pub async fn stop_discovery(&self) -> Result<(), BluetoothError> {
384        let adapters = self.get_adapters().await?;
385        if adapters.is_empty() {
386            return Err(BluetoothError::NoBluetoothAdapters);
387        }
388
389        for adapter in adapters {
390            self.stop_discovery_on_adapter(&adapter.id).await?;
391        }
392
393        Ok(())
394    }
395
396    /// Stop scanning for devices on the given Bluetooth adapters.
397    pub async fn stop_discovery_on_adapter(
398        &self,
399        adapter_id: &AdapterId,
400    ) -> Result<(), BluetoothError> {
401        let adapter = self.adapter(adapter_id);
402        adapter.stop_discovery().await?;
403        Ok(())
404    }
405
406    /// Get a list of all Bluetooth adapters on the system.
407    pub async fn get_adapters(&self) -> Result<Vec<AdapterInfo>, BluetoothError> {
408        let bluez_root = Proxy::new(
409            "org.bluez",
410            "/",
411            DBUS_METHOD_CALL_TIMEOUT,
412            self.connection.clone(),
413        );
414        // TODO: See whether there is a way to do this with introspection instead, rather than
415        // getting lots of objects we don't care about.
416        let tree = bluez_root.get_managed_objects().await?;
417        Ok(tree
418            .into_iter()
419            .filter_map(|(object_path, interfaces)| {
420                let adapter_properties = OrgBluezAdapter1Properties::from_interfaces(&interfaces)?;
421                AdapterInfo::from_properties(AdapterId { object_path }, adapter_properties).ok()
422            })
423            .collect())
424    }
425
426    /// Get a list of all Bluetooth devices which have been discovered so far.
427    pub async fn get_devices(&self) -> Result<Vec<DeviceInfo>, BluetoothError> {
428        let bluez_root = Proxy::new(
429            "org.bluez",
430            "/",
431            DBUS_METHOD_CALL_TIMEOUT,
432            self.connection.clone(),
433        );
434        let tree = bluez_root.get_managed_objects().await?;
435
436        let devices = tree
437            .into_iter()
438            .filter_map(|(object_path, interfaces)| {
439                let device_properties = OrgBluezDevice1Properties::from_interfaces(&interfaces)?;
440                DeviceInfo::from_properties(DeviceId { object_path }, device_properties).ok()
441            })
442            .collect();
443        Ok(devices)
444    }
445
446    /// Get a list of all Bluetooth devices which have been discovered so far on a given adapter.
447    pub async fn get_devices_on_adapter(
448        &self,
449        adapter: &AdapterId,
450    ) -> Result<Vec<DeviceInfo>, BluetoothError> {
451        let devices = self.get_devices().await?;
452        Ok(devices
453            .into_iter()
454            .filter(|device| device.id.adapter() == *adapter)
455            .collect())
456    }
457
458    /// Get a list of all GATT services which the given Bluetooth device offers.
459    ///
460    /// Note that this won't be filled in until the device is connected.
461    pub async fn get_services(
462        &self,
463        device: &DeviceId,
464    ) -> Result<Vec<ServiceInfo>, BluetoothError> {
465        let device_node = self
466            .device(device, DBUS_METHOD_CALL_TIMEOUT)
467            .introspect_parse()
468            .await?;
469        let mut services = vec![];
470        for subnode in device_node.nodes {
471            let subnode_name = subnode.name.as_ref().unwrap();
472            // Service paths are always of the form
473            // /org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXXXX
474            if subnode_name.starts_with("service") {
475                let service_id = ServiceId {
476                    object_path: format!("{}/{}", device.object_path, subnode_name).into(),
477                };
478                let service = self.service(&service_id);
479                let uuid = Uuid::parse_str(&service.uuid().await?)?;
480                let primary = service.primary().await?;
481                services.push(ServiceInfo {
482                    id: service_id,
483                    uuid,
484                    primary,
485                });
486            }
487        }
488        Ok(services)
489    }
490
491    /// Get a list of all characteristics on the given GATT service.
492    pub async fn get_characteristics(
493        &self,
494        service: &ServiceId,
495    ) -> Result<Vec<CharacteristicInfo>, BluetoothError> {
496        let service_node = self.service(service).introspect_parse().await?;
497        let mut characteristics = vec![];
498        for subnode in service_node.nodes {
499            let subnode_name = subnode.name.as_ref().unwrap();
500            // Characteristic paths are always of the form
501            // /org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXXXX/charYYYY
502            if subnode_name.starts_with("char") {
503                let characteristic_id = CharacteristicId {
504                    object_path: format!("{}/{}", service.object_path, subnode_name).into(),
505                };
506                characteristics.push(self.get_characteristic_info(&characteristic_id).await?);
507            }
508        }
509        Ok(characteristics)
510    }
511
512    /// Get a list of all descriptors on the given GATT characteristic.
513    pub async fn get_descriptors(
514        &self,
515        characteristic: &CharacteristicId,
516    ) -> Result<Vec<DescriptorInfo>, BluetoothError> {
517        let characteristic_node = self
518            .characteristic(characteristic)
519            .introspect_parse()
520            .await?;
521        let mut descriptors = vec![];
522        for subnode in characteristic_node.nodes {
523            let subnode_name = subnode.name.as_ref().unwrap();
524            // Service paths are always of the form
525            // /org/bluez/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXXXX/charYYYY/descZZZZ
526            if subnode_name.starts_with("desc") {
527                let descriptor_id = DescriptorId {
528                    object_path: format!("{}/{}", characteristic.object_path, subnode_name).into(),
529                };
530                let uuid = Uuid::parse_str(&self.descriptor(&descriptor_id).uuid().await?)?;
531                descriptors.push(DescriptorInfo {
532                    id: descriptor_id,
533                    uuid,
534                });
535            }
536        }
537        Ok(descriptors)
538    }
539
540    /// Find a GATT service with the given UUID advertised by the given device, if any.
541    ///
542    /// Note that this generally won't work until the device is connected.
543    pub async fn get_service_by_uuid(
544        &self,
545        device: &DeviceId,
546        uuid: Uuid,
547    ) -> Result<ServiceInfo, BluetoothError> {
548        let services = self.get_services(device).await?;
549        services
550            .into_iter()
551            .find(|service_info| service_info.uuid == uuid)
552            .ok_or(BluetoothError::UuidNotFound { uuid })
553    }
554
555    /// Find a characteristic with the given UUID as part of the given GATT service advertised by a
556    /// device, if there is any.
557    pub async fn get_characteristic_by_uuid(
558        &self,
559        service: &ServiceId,
560        uuid: Uuid,
561    ) -> Result<CharacteristicInfo, BluetoothError> {
562        let characteristics = self.get_characteristics(service).await?;
563        characteristics
564            .into_iter()
565            .find(|characteristic_info| characteristic_info.uuid == uuid)
566            .ok_or(BluetoothError::UuidNotFound { uuid })
567    }
568
569    /// Convenience method to get a GATT charactacteristic with the given UUID advertised by a
570    /// device as part of the given service.
571    ///
572    /// This is equivalent to calling `get_service_by_uuid` and then `get_characteristic_by_uuid`.
573    pub async fn get_service_characteristic_by_uuid(
574        &self,
575        device: &DeviceId,
576        service_uuid: Uuid,
577        characteristic_uuid: Uuid,
578    ) -> Result<CharacteristicInfo, BluetoothError> {
579        let service = self.get_service_by_uuid(device, service_uuid).await?;
580        self.get_characteristic_by_uuid(&service.id, characteristic_uuid)
581            .await
582    }
583
584    /// Get information about the given Bluetooth device.
585    pub async fn get_device_info(&self, id: &DeviceId) -> Result<DeviceInfo, BluetoothError> {
586        let device = self.device(id, DBUS_METHOD_CALL_TIMEOUT);
587        let properties = device.get_all(ORG_BLUEZ_DEVICE1_NAME).await?;
588        DeviceInfo::from_properties(id.to_owned(), OrgBluezDevice1Properties(&properties))
589    }
590
591    /// Get information about the given Bluetooth adapter.
592    pub async fn get_adapter_info(&self, id: &AdapterId) -> Result<AdapterInfo, BluetoothError> {
593        let adapter = self.adapter(id);
594        let properties = adapter.get_all(ORG_BLUEZ_ADAPTER1_NAME).await?;
595        AdapterInfo::from_properties(id.to_owned(), OrgBluezAdapter1Properties(&properties))
596    }
597
598    /// Get information about the given GATT service.
599    pub async fn get_service_info(&self, id: &ServiceId) -> Result<ServiceInfo, BluetoothError> {
600        let service = self.service(id);
601        let uuid = Uuid::parse_str(&service.uuid().await?)?;
602        let primary = service.primary().await?;
603        Ok(ServiceInfo {
604            id: id.to_owned(),
605            uuid,
606            primary,
607        })
608    }
609
610    /// Get information about the given GATT characteristic.
611    pub async fn get_characteristic_info(
612        &self,
613        id: &CharacteristicId,
614    ) -> Result<CharacteristicInfo, BluetoothError> {
615        let characteristic = self.characteristic(id);
616        let properties = characteristic
617            .get_all(ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME)
618            .await?;
619        CharacteristicInfo::from_properties(
620            id.to_owned(),
621            OrgBluezGattCharacteristic1Properties(&properties),
622        )
623    }
624
625    /// Get information about the given GATT descriptor.
626    pub async fn get_descriptor_info(
627        &self,
628        id: &DescriptorId,
629    ) -> Result<DescriptorInfo, BluetoothError> {
630        let uuid = Uuid::parse_str(&self.descriptor(id).uuid().await?)?;
631        Ok(DescriptorInfo {
632            id: id.to_owned(),
633            uuid,
634        })
635    }
636
637    fn adapter(
638        &self,
639        id: &AdapterId,
640    ) -> impl OrgBluezAdapter1 + Introspectable + Properties + use<> {
641        Proxy::new(
642            "org.bluez",
643            id.object_path.to_owned(),
644            DBUS_METHOD_CALL_TIMEOUT,
645            self.connection.clone(),
646        )
647    }
648
649    fn device(
650        &self,
651        id: &DeviceId,
652        timeout: Duration,
653    ) -> impl OrgBluezDevice1 + Introspectable + Properties + use<> {
654        let timeout = timeout.min(DBUS_METHOD_CALL_MAX_TIMEOUT);
655        Proxy::new(
656            "org.bluez",
657            id.object_path.to_owned(),
658            timeout,
659            self.connection.clone(),
660        )
661    }
662
663    fn service(
664        &self,
665        id: &ServiceId,
666    ) -> impl OrgBluezGattService1 + Introspectable + Properties + use<> {
667        Proxy::new(
668            "org.bluez",
669            id.object_path.to_owned(),
670            DBUS_METHOD_CALL_TIMEOUT,
671            self.connection.clone(),
672        )
673    }
674
675    fn characteristic(
676        &self,
677        id: &CharacteristicId,
678    ) -> impl OrgBluezGattCharacteristic1 + Introspectable + Properties + use<> {
679        Proxy::new(
680            "org.bluez",
681            id.object_path.to_owned(),
682            DBUS_METHOD_CALL_TIMEOUT,
683            self.connection.clone(),
684        )
685    }
686
687    fn descriptor(
688        &self,
689        id: &DescriptorId,
690    ) -> impl OrgBluezGattDescriptor1 + Introspectable + Properties + use<> {
691        Proxy::new(
692            "org.bluez",
693            id.object_path.to_owned(),
694            DBUS_METHOD_CALL_TIMEOUT,
695            self.connection.clone(),
696        )
697    }
698
699    /// Wait until the services of the given device have been resolved.
700    async fn await_service_discovery(&self, device_id: &DeviceId) -> Result<(), BluetoothError> {
701        // We need to subscribe to events before checking current value to avoid a race condition.
702        let mut events = self.device_event_stream(device_id).await?;
703        if self
704            .device(device_id, DBUS_METHOD_CALL_TIMEOUT)
705            .services_resolved()
706            .await?
707        {
708            log::info!("Services already resolved.");
709            return Ok(());
710        }
711        timeout(SERVICE_DISCOVERY_TIMEOUT, async {
712            while let Some(event) = events.next().await {
713                if matches!(event, BluetoothEvent::Device {
714                    id,
715                    event: DeviceEvent::ServicesResolved,
716                } if device_id == &id)
717                {
718                    return Ok(());
719                }
720            }
721
722            // Stream ended prematurely. This shouldn't happen, so something has gone wrong.
723            Err(BluetoothError::ServiceDiscoveryTimedOut)
724        })
725        .await
726        .unwrap_or(Err(BluetoothError::ServiceDiscoveryTimedOut))
727    }
728
729    /// Connect to the given Bluetooth device and initiate pairing, with the specified timeout.
730    pub async fn pair_with_timeout(
731        &self,
732        id: &DeviceId,
733        timeout: Duration,
734    ) -> Result<(), BluetoothError> {
735        self.device(id, timeout).pair().await?;
736        self.await_service_discovery(id).await
737    }
738
739    /// Cancel a pairing operation with the given Bluetooth device.
740    pub async fn cancel_pairing(&self, id: &DeviceId) -> Result<(), BluetoothError> {
741        Ok(self
742            .device(id, DBUS_METHOD_CALL_TIMEOUT)
743            .cancel_pairing()
744            .await?)
745    }
746
747    /// Connect to the given Bluetooth device.
748    pub async fn connect(&self, id: &DeviceId) -> Result<(), BluetoothError> {
749        self.connect_with_timeout(id, DBUS_METHOD_CALL_TIMEOUT)
750            .await
751    }
752
753    /// Connect to the given Bluetooth device with the specified timeout.
754    pub async fn connect_with_timeout(
755        &self,
756        id: &DeviceId,
757        timeout: Duration,
758    ) -> Result<(), BluetoothError> {
759        self.device(id, timeout).connect().await?;
760        self.await_service_discovery(id).await
761    }
762
763    /// Disconnect from the given Bluetooth device.
764    pub async fn disconnect(&self, id: &DeviceId) -> Result<(), BluetoothError> {
765        Ok(self
766            .device(id, DBUS_METHOD_CALL_TIMEOUT)
767            .disconnect()
768            .await?)
769    }
770
771    /// Read the value of the given GATT characteristic.
772    ///
773    /// This is equivalent to calling `read_characteristic_value_with_offset(0)`.
774    pub async fn read_characteristic_value(
775        &self,
776        id: &CharacteristicId,
777    ) -> Result<Vec<u8>, BluetoothError> {
778        self.read_characteristic_value_with_offset(id, 0).await
779    }
780
781    /// Read the value of the given GATT characteristic, starting from the given offset.
782    pub async fn read_characteristic_value_with_offset(
783        &self,
784        id: &CharacteristicId,
785        offset: usize,
786    ) -> Result<Vec<u8>, BluetoothError> {
787        let characteristic = self.characteristic(id);
788        Ok(characteristic.read_value(offset_to_propmap(offset)).await?)
789    }
790
791    /// Write the given value to the given GATT characteristic, with default options.
792    ///
793    /// This is equivalent to calling `write_characteristic_value_with_options(WriteOptions::default())`.
794    pub async fn write_characteristic_value(
795        &self,
796        id: &CharacteristicId,
797        value: impl Into<Vec<u8>>,
798    ) -> Result<(), BluetoothError> {
799        self.write_characteristic_value_with_options(id, value, WriteOptions::default())
800            .await
801    }
802
803    /// Write the given value to the given GATT characteristic, with the given options.
804    pub async fn write_characteristic_value_with_options(
805        &self,
806        id: &CharacteristicId,
807        value: impl Into<Vec<u8>>,
808        options: WriteOptions,
809    ) -> Result<(), BluetoothError> {
810        let characteristic = self.characteristic(id);
811        Ok(characteristic
812            .write_value(value.into(), options.into())
813            .await?)
814    }
815
816    /// Read the value of the given GATT descriptor.
817    ///
818    /// This is equivalent to calling `read_descriptor_value_with_offset(0)`.
819    pub async fn read_descriptor_value(
820        &self,
821        id: &DescriptorId,
822    ) -> Result<Vec<u8>, BluetoothError> {
823        self.read_descriptor_value_with_offset(id, 0).await
824    }
825
826    /// Read the value of the given GATT descriptor, starting from the given offset.
827    pub async fn read_descriptor_value_with_offset(
828        &self,
829        id: &DescriptorId,
830        offset: usize,
831    ) -> Result<Vec<u8>, BluetoothError> {
832        let descriptor = self.descriptor(id);
833        Ok(descriptor.read_value(offset_to_propmap(offset)).await?)
834    }
835
836    /// Write the given value to the given GATT descriptor.
837    ///
838    /// This is equivalent to calling `write_descriptor_value_with_offset(0)`.
839    pub async fn write_descriptor_value(
840        &self,
841        id: &DescriptorId,
842        value: impl Into<Vec<u8>>,
843    ) -> Result<(), BluetoothError> {
844        self.write_descriptor_value_with_offset(id, value, 0).await
845    }
846
847    /// Write the given value to the given GATT descriptor, starting from the given offset.
848    pub async fn write_descriptor_value_with_offset(
849        &self,
850        id: &DescriptorId,
851        value: impl Into<Vec<u8>>,
852        offset: usize,
853    ) -> Result<(), BluetoothError> {
854        let descriptor = self.descriptor(id);
855        Ok(descriptor
856            .write_value(value.into(), offset_to_propmap(offset))
857            .await?)
858    }
859
860    /// Start notifications on the given GATT characteristic.
861    pub async fn start_notify(&self, id: &CharacteristicId) -> Result<(), BluetoothError> {
862        let characteristic = self.characteristic(id);
863        characteristic.start_notify().await?;
864        Ok(())
865    }
866
867    /// Stop notifications on the given GATT characteristic.
868    pub async fn stop_notify(&self, id: &CharacteristicId) -> Result<(), BluetoothError> {
869        let characteristic = self.characteristic(id);
870        characteristic.stop_notify().await?;
871        Ok(())
872    }
873
874    /// Get a stream of events for all devices.
875    pub async fn event_stream(
876        &self,
877    ) -> Result<impl Stream<Item = BluetoothEvent> + use<>, BluetoothError> {
878        self.filtered_event_stream(None::<&DeviceId>, true).await
879    }
880
881    /// Get a stream of events for a particular adapter. This includes events for all devices it
882    /// discovers or connects to.
883    pub async fn adapter_event_stream(
884        &self,
885        adapter: &AdapterId,
886    ) -> Result<impl Stream<Item = BluetoothEvent> + use<>, BluetoothError> {
887        self.filtered_event_stream(Some(adapter), true).await
888    }
889
890    /// Get a stream of events for a particular device. This includes events for all its
891    /// characteristics.
892    ///
893    /// Note that this will not include the device discovered event for that device, as it is
894    /// considered an event for the adapter rather than the device itself.
895    pub async fn device_event_stream(
896        &self,
897        device: &DeviceId,
898    ) -> Result<impl Stream<Item = BluetoothEvent> + use<>, BluetoothError> {
899        self.filtered_event_stream(Some(device), false).await
900    }
901
902    /// Get a stream of events for a particular characteristic of a device.
903    pub async fn characteristic_event_stream(
904        &self,
905        characteristic: &CharacteristicId,
906    ) -> Result<impl Stream<Item = BluetoothEvent> + use<>, BluetoothError> {
907        self.filtered_event_stream(Some(characteristic), false)
908            .await
909    }
910
911    async fn filtered_event_stream<P: Into<Path<'static>> + Clone>(
912        &self,
913        object: Option<&P>,
914        device_discovery: bool,
915    ) -> Result<impl Stream<Item = BluetoothEvent> + use<P>, BluetoothError> {
916        let mut message_streams = vec![];
917        for match_rule in BluetoothEvent::match_rules(object.cloned(), device_discovery) {
918            let msg_match = self.connection.add_match(match_rule).await?;
919            message_streams.push(MessageStream::new(msg_match, self.connection.clone()));
920        }
921        Ok(select_all(message_streams)
922            .flat_map(|message| stream::iter(BluetoothEvent::message_to_events(message))))
923    }
924}
925
926fn offset_to_propmap(offset: usize) -> PropMap {
927    let mut map: PropMap = HashMap::new();
928    if offset != 0 {
929        map.insert("offset".to_string(), Variant(Box::new(offset as u64)));
930    }
931    map
932}