bluez_async/
events.rs

1use bluez_generated::{
2    OrgBluezAdapter1Properties, OrgBluezDevice1Properties, OrgBluezGattCharacteristic1Properties,
3    ORG_BLUEZ_ADAPTER1_NAME, ORG_BLUEZ_DEVICE1_NAME, ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME,
4};
5use dbus::message::{MatchRule, SignalArgs};
6use dbus::nonblock::stdintf::org_freedesktop_dbus::{
7    ObjectManagerInterfacesAdded, PropertiesPropertiesChanged,
8};
9use dbus::{Message, Path};
10use std::collections::HashMap;
11use uuid::Uuid;
12
13use super::device::{convert_manufacturer_data, convert_service_data, convert_services};
14use super::{AdapterId, CharacteristicId, DeviceId};
15
16/// An event relating to a Bluetooth device or adapter.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub enum BluetoothEvent {
19    /// An event related to a Bluetooth adapter.
20    Adapter {
21        /// The ID of the Bluetooth adapter in question.
22        id: AdapterId,
23        /// Details of the specific event.
24        event: AdapterEvent,
25    },
26    /// An event related to a Bluetooth device.
27    Device {
28        /// The ID of the Bluetooth device in question.
29        id: DeviceId,
30        /// Details of the specific event.
31        event: DeviceEvent,
32    },
33    /// An event related to a GATT characteristic of a Bluetooth device.
34    Characteristic {
35        /// The ID of the GATT characteristic in question.
36        id: CharacteristicId,
37        /// Details of the specific event.
38        event: CharacteristicEvent,
39    },
40}
41
42/// Details of an event related to a Bluetooth adapter.
43#[derive(Clone, Debug, Eq, PartialEq)]
44#[non_exhaustive]
45pub enum AdapterEvent {
46    /// The adapter has been powered on or off.
47    Powered { powered: bool },
48    /// The adapter has started or stopped scanning for devices.
49    Discovering { discovering: bool },
50}
51
52/// Details of an event related to a Bluetooth device.
53#[derive(Clone, Debug, Eq, PartialEq)]
54#[non_exhaustive]
55pub enum DeviceEvent {
56    /// A new device has been discovered.
57    Discovered,
58    /// The device has connected or disconnected.
59    Connected { connected: bool },
60    /// A new value is available for the RSSI of the device.
61    Rssi { rssi: i16 },
62    /// A new value is available for the manufacturer-specific advertisement data of the device.
63    ManufacturerData {
64        /// The manufacturer-specific advertisement data. The keys are 'manufacturer IDs'.
65        manufacturer_data: HashMap<u16, Vec<u8>>,
66    },
67    /// New GATT service advertisement data is available for the device.
68    ServiceData {
69        /// The new GATT service data. This is a map from the service UUID to its data.
70        service_data: HashMap<Uuid, Vec<u8>>,
71    },
72    /// The set of GATT services known for the device has changed.
73    Services {
74        /// The new set of GATT service UUIDs from the device's advertisement or service discovery.
75        services: Vec<Uuid>,
76    },
77    /// Service discovery has completed.
78    ServicesResolved,
79}
80
81/// Details of an event related to a GATT characteristic.
82#[derive(Clone, Debug, Eq, PartialEq)]
83#[non_exhaustive]
84pub enum CharacteristicEvent {
85    /// A new value of the characteristic has been received. This may be from a notification.
86    Value { value: Vec<u8> },
87}
88
89impl BluetoothEvent {
90    /// Return a set of `MatchRule`s which will match all D-Bus messages which represent Bluetooth
91    /// events, possibly limited to those for a particular object (such as a device, service or
92    /// characteristic).
93    ///
94    /// Set `interfaces_added` to true to include ObjectManager InterfacesAdded signals, which map
95    /// to `DeviceEvent::Discovered` events.
96    pub(crate) fn match_rules(
97        object: Option<impl Into<Path<'static>>>,
98        interfaces_added: bool,
99    ) -> Vec<MatchRule<'static>> {
100        // BusName validation just checks that the length and format is valid, so it should never
101        // fail for a constant that we know is valid.
102        let bus_name = "org.bluez".into();
103
104        let mut match_rules = vec![];
105
106        // If we aren't filtering to a single device or characteristic, then match ObjectManager
107        // signals so we can get events for new devices being discovered.
108        if interfaces_added {
109            let match_rule =
110                ObjectManagerInterfacesAdded::match_rule(Some(&bus_name), None).static_clone();
111            match_rules.push(match_rule);
112        }
113
114        // Match PropertiesChanged signals for the given device or characteristic and all objects
115        // under it. If no object is specified then this will match PropertiesChanged signals for
116        // all BlueZ objects.
117        let object_path = object.map(|o| o.into());
118        let mut match_rule =
119            PropertiesPropertiesChanged::match_rule(Some(&bus_name), object_path.as_ref())
120                .static_clone();
121        match_rule.path_is_namespace = true;
122        match_rules.push(match_rule);
123
124        match_rules
125    }
126
127    /// Return a list of Bluetooth events parsed from the given D-Bus message.
128    pub(crate) fn message_to_events(message: Message) -> Vec<BluetoothEvent> {
129        if let Some(properties_changed) = PropertiesPropertiesChanged::from_message(&message) {
130            let object_path = message.path().unwrap().into_static();
131            Self::properties_changed_to_events(object_path, properties_changed)
132        } else if let Some(interfaces_added) = ObjectManagerInterfacesAdded::from_message(&message)
133        {
134            Self::interfaces_added_to_events(interfaces_added)
135        } else {
136            log::info!("Unexpected message: {:?}", message);
137            vec![]
138        }
139    }
140
141    /// Return a list of Bluetooth events parsed from an InterfacesAdded signal.
142    fn interfaces_added_to_events(
143        interfaces_added: ObjectManagerInterfacesAdded,
144    ) -> Vec<BluetoothEvent> {
145        log::trace!("InterfacesAdded: {:?}", interfaces_added);
146        let mut events = vec![];
147        let object_path = interfaces_added.object;
148        if let Some(_device) =
149            OrgBluezDevice1Properties::from_interfaces(&interfaces_added.interfaces)
150        {
151            let id = DeviceId { object_path };
152            events.push(BluetoothEvent::Device {
153                id,
154                event: DeviceEvent::Discovered,
155            })
156        }
157        events
158    }
159
160    /// Return a list of Bluetooth events parsed from a PropertiesChanged signal.
161    fn properties_changed_to_events(
162        object_path: Path<'static>,
163        properties_changed: PropertiesPropertiesChanged,
164    ) -> Vec<BluetoothEvent> {
165        log::trace!(
166            "PropertiesChanged for {}: {:?}",
167            object_path,
168            properties_changed
169        );
170        let mut events = vec![];
171        let changed_properties = &properties_changed.changed_properties;
172        match properties_changed.interface_name.as_ref() {
173            ORG_BLUEZ_ADAPTER1_NAME => {
174                let id = AdapterId { object_path };
175                let adapter = OrgBluezAdapter1Properties(changed_properties);
176                if let Some(powered) = adapter.powered() {
177                    events.push(BluetoothEvent::Adapter {
178                        id: id.clone(),
179                        event: AdapterEvent::Powered { powered },
180                    })
181                }
182                if let Some(discovering) = adapter.discovering() {
183                    events.push(BluetoothEvent::Adapter {
184                        id,
185                        event: AdapterEvent::Discovering { discovering },
186                    });
187                }
188            }
189            ORG_BLUEZ_DEVICE1_NAME => {
190                let id = DeviceId { object_path };
191                let device = OrgBluezDevice1Properties(changed_properties);
192                if let Some(connected) = device.connected() {
193                    events.push(BluetoothEvent::Device {
194                        id: id.clone(),
195                        event: DeviceEvent::Connected { connected },
196                    });
197                }
198                if let Some(rssi) = device.rssi() {
199                    events.push(BluetoothEvent::Device {
200                        id: id.clone(),
201                        event: DeviceEvent::Rssi { rssi },
202                    });
203                }
204                if let Some(manufacturer_data) = device.manufacturer_data() {
205                    events.push(BluetoothEvent::Device {
206                        id: id.clone(),
207                        event: DeviceEvent::ManufacturerData {
208                            manufacturer_data: convert_manufacturer_data(manufacturer_data),
209                        },
210                    })
211                }
212                if let Some(service_data) = device.service_data() {
213                    events.push(BluetoothEvent::Device {
214                        id: id.clone(),
215                        event: DeviceEvent::ServiceData {
216                            service_data: convert_service_data(service_data),
217                        },
218                    })
219                }
220                if let Some(services) = device.uuids() {
221                    events.push(BluetoothEvent::Device {
222                        id: id.clone(),
223                        event: DeviceEvent::Services {
224                            services: convert_services(services),
225                        },
226                    })
227                }
228                if device.services_resolved() == Some(true) {
229                    events.push(BluetoothEvent::Device {
230                        id,
231                        event: DeviceEvent::ServicesResolved,
232                    });
233                }
234            }
235            ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME => {
236                let id = CharacteristicId { object_path };
237                let characteristic = OrgBluezGattCharacteristic1Properties(changed_properties);
238                if let Some(value) = characteristic.value() {
239                    events.push(BluetoothEvent::Characteristic {
240                        id,
241                        event: CharacteristicEvent::Value {
242                            value: value.to_owned(),
243                        },
244                    })
245                }
246            }
247            _ => {}
248        }
249        events
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::super::ServiceId;
256    use crate::uuid_from_u32;
257    use dbus::arg::{PropMap, RefArg, Variant};
258
259    use super::*;
260
261    #[test]
262    fn adapter_powered() {
263        let message = adapter_powered_message("/org/bluez/hci0", true);
264        let id = AdapterId::new("/org/bluez/hci0");
265        assert_eq!(
266            BluetoothEvent::message_to_events(message),
267            vec![BluetoothEvent::Adapter {
268                id,
269                event: AdapterEvent::Powered { powered: true }
270            }]
271        )
272    }
273
274    #[test]
275    fn device_rssi() {
276        let rssi = 42;
277        let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", rssi);
278        let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
279        assert_eq!(
280            BluetoothEvent::message_to_events(message),
281            vec![BluetoothEvent::Device {
282                id,
283                event: DeviceEvent::Rssi { rssi }
284            }]
285        )
286    }
287
288    #[test]
289    fn device_manufacturer_data() {
290        let mut manufacturer_data = HashMap::new();
291        manufacturer_data.insert(42, vec![1u8, 2, 3]);
292        let message = device_manufacturer_data_message(
293            "/org/bluez/hci0/dev_11_22_33_44_55_66",
294            manufacturer_data.clone(),
295        );
296        let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
297        assert_eq!(
298            BluetoothEvent::message_to_events(message),
299            vec![BluetoothEvent::Device {
300                id,
301                event: DeviceEvent::ManufacturerData { manufacturer_data }
302            }]
303        )
304    }
305
306    #[test]
307    fn device_service_data() {
308        let mut service_data = HashMap::new();
309        service_data.insert(uuid_from_u32(0x11223344), vec![1u8, 2, 3]);
310        let message = device_service_data_message(
311            "/org/bluez/hci0/dev_11_22_33_44_55_66",
312            service_data.clone(),
313        );
314        let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
315        assert_eq!(
316            BluetoothEvent::message_to_events(message),
317            vec![BluetoothEvent::Device {
318                id,
319                event: DeviceEvent::ServiceData { service_data }
320            }]
321        )
322    }
323
324    #[test]
325    fn device_services() {
326        let mut services = Vec::new();
327        services.push(uuid_from_u32(0x11223344));
328        let message =
329            device_services_message("/org/bluez/hci0/dev_11_22_33_44_55_66", services.clone());
330        let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
331        assert_eq!(
332            BluetoothEvent::message_to_events(message),
333            vec![BluetoothEvent::Device {
334                id,
335                event: DeviceEvent::Services { services }
336            }]
337        )
338    }
339
340    #[test]
341    fn characteristic_value() {
342        let value: Vec<u8> = vec![1, 2, 3];
343        let message = characteristic_value_message(
344            "/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
345            &value,
346        );
347        let id =
348            CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034");
349        assert_eq!(
350            BluetoothEvent::message_to_events(message),
351            vec![BluetoothEvent::Characteristic {
352                id,
353                event: CharacteristicEvent::Value { value }
354            }]
355        )
356    }
357
358    #[test]
359    fn device_discovered() {
360        let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
361        let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
362        assert_eq!(
363            BluetoothEvent::message_to_events(message),
364            vec![BluetoothEvent::Device {
365                id,
366                event: DeviceEvent::Discovered
367            }]
368        )
369    }
370
371    #[test]
372    fn match_rules_all() {
373        let match_rules = BluetoothEvent::match_rules(None::<DeviceId>, true);
374
375        let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
376        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
377
378        let message = adapter_powered_message("/org/bluez/hci0", true);
379        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
380
381        let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
382        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
383
384        let message = characteristic_value_message(
385            "/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
386            &vec![1, 2, 3],
387        );
388        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
389    }
390
391    #[test]
392    fn match_rules_device() {
393        let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
394        let match_rules = BluetoothEvent::match_rules(Some(id), false);
395
396        let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
397        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
398
399        let message = adapter_powered_message("/org/bluez/hci0", true);
400        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
401
402        let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
403        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
404
405        let message = characteristic_value_message(
406            "/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
407            &vec![1, 2, 3],
408        );
409        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
410    }
411
412    #[test]
413    fn match_rules_service() {
414        let id = ServiceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0012");
415        let match_rules = BluetoothEvent::match_rules(Some(id), false);
416
417        let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
418        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
419
420        let message = adapter_powered_message("/org/bluez/hci0", true);
421        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
422
423        let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
424        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
425
426        let message = characteristic_value_message(
427            "/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
428            &vec![1, 2, 3],
429        );
430        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
431    }
432
433    #[test]
434    fn match_rules_characteristic() {
435        let id =
436            CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034");
437        let match_rules = BluetoothEvent::match_rules(Some(id), false);
438
439        let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
440        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
441
442        let message = adapter_powered_message("/org/bluez/hci0", true);
443        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
444
445        let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
446        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
447
448        let message = characteristic_value_message(
449            "/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
450            &vec![1, 2, 3],
451        );
452        assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
453    }
454
455    fn new_device_message(device_path: &'static str) -> Message {
456        let properties = HashMap::new();
457        let mut interfaces = HashMap::new();
458        interfaces.insert("org.bluez.Device1".to_string(), properties);
459        let interfaces_added = ObjectManagerInterfacesAdded {
460            object: device_path.into(),
461            interfaces,
462        };
463        interfaces_added.to_emit_message(&"/".into())
464    }
465
466    fn adapter_powered_message(adapter_path: &'static str, powered: bool) -> Message {
467        let mut changed_properties: PropMap = HashMap::new();
468        changed_properties.insert("Powered".to_string(), Variant(Box::new(powered)));
469        let properties_changed = PropertiesPropertiesChanged {
470            interface_name: "org.bluez.Adapter1".to_string(),
471            changed_properties,
472            invalidated_properties: vec![],
473        };
474        properties_changed.to_emit_message(&adapter_path.into())
475    }
476
477    fn device_rssi_message(device_path: &'static str, rssi: i16) -> Message {
478        let mut changed_properties: PropMap = HashMap::new();
479        changed_properties.insert("RSSI".to_string(), Variant(Box::new(rssi)));
480        let properties_changed = PropertiesPropertiesChanged {
481            interface_name: "org.bluez.Device1".to_string(),
482            changed_properties,
483            invalidated_properties: vec![],
484        };
485        properties_changed.to_emit_message(&device_path.into())
486    }
487
488    fn device_manufacturer_data_message(
489        device_path: &'static str,
490        manufacturer_data: HashMap<u16, Vec<u8>>,
491    ) -> Message {
492        let manufacturer_data: HashMap<_, _> = manufacturer_data
493            .into_iter()
494            .map::<(u16, Variant<Box<dyn RefArg>>), _>(|(k, v)| (k, Variant(Box::new(v))))
495            .collect();
496        let mut changed_properties: PropMap = HashMap::new();
497        changed_properties.insert(
498            "ManufacturerData".to_string(),
499            Variant(Box::new(manufacturer_data)),
500        );
501        let properties_changed = PropertiesPropertiesChanged {
502            interface_name: "org.bluez.Device1".to_string(),
503            changed_properties,
504            invalidated_properties: vec![],
505        };
506        properties_changed.to_emit_message(&device_path.into())
507    }
508
509    fn device_service_data_message(
510        device_path: &'static str,
511        service_data: HashMap<Uuid, Vec<u8>>,
512    ) -> Message {
513        let service_data: HashMap<_, _> = service_data
514            .into_iter()
515            .map::<(String, Variant<Box<dyn RefArg>>), _>(|(k, v)| {
516                (k.to_string(), Variant(Box::new(v)))
517            })
518            .collect();
519        let mut changed_properties: HashMap<String, Variant<Box<dyn RefArg>>> = HashMap::new();
520        changed_properties.insert("ServiceData".to_string(), Variant(Box::new(service_data)));
521        let properties_changed = PropertiesPropertiesChanged {
522            interface_name: "org.bluez.Device1".to_string(),
523            changed_properties,
524            invalidated_properties: vec![],
525        };
526        properties_changed.to_emit_message(&device_path.into())
527    }
528
529    fn device_services_message(device_path: &'static str, services: Vec<Uuid>) -> Message {
530        let services: Vec<_> = services
531            .into_iter()
532            .map::<String, _>(|k| k.to_string())
533            .collect();
534        let mut changed_properties: HashMap<String, Variant<Box<dyn RefArg>>> = HashMap::new();
535        changed_properties.insert("UUIDs".to_string(), Variant(Box::new(services)));
536        let properties_changed = PropertiesPropertiesChanged {
537            interface_name: "org.bluez.Device1".to_string(),
538            changed_properties,
539            invalidated_properties: vec![],
540        };
541        properties_changed.to_emit_message(&device_path.into())
542    }
543
544    fn characteristic_value_message(characteristic_path: &'static str, value: &[u8]) -> Message {
545        let mut changed_properties: PropMap = HashMap::new();
546        changed_properties.insert("Value".to_string(), Variant(Box::new(value.to_owned())));
547        let properties_changed = PropertiesPropertiesChanged {
548            interface_name: "org.bluez.GattCharacteristic1".to_string(),
549            changed_properties,
550            invalidated_properties: vec![],
551        };
552        properties_changed.to_emit_message(&characteristic_path.into())
553    }
554}