bluer/adapter.rs
1//! Bluetooth adapter.
2
3use dbus::{
4 arg::{PropMap, RefArg, Variant},
5 nonblock::{Proxy, SyncConnection},
6 Path,
7};
8use futures::{
9 future,
10 stream::{self, SelectAll},
11 Stream, StreamExt,
12};
13use std::{
14 collections::{BTreeSet, HashMap, HashSet},
15 fmt::{Debug, Formatter},
16 sync::Arc,
17};
18use strum::{Display, EnumString};
19use tokio::sync::mpsc;
20use tokio_stream::wrappers::ReceiverStream;
21use uuid::Uuid;
22
23use crate::{
24 adv,
25 adv::{Advertisement, AdvertisementHandle, Capabilities, Feature, PlatformFeature, SecondaryChannel},
26 all_dbus_objects, device,
27 device::Device,
28 gatt,
29 monitor::MonitorManager,
30 Address, AddressType, Error, ErrorKind, Event, InternalErrorKind, Modalias, Result, SessionInner,
31 SingleSessionToken, SERVICE_NAME, TIMEOUT,
32};
33
34pub(crate) const INTERFACE: &str = "org.bluez.Adapter1";
35pub(crate) const PATH: &str = "/org/bluez";
36pub(crate) const PREFIX: &str = "/org/bluez/";
37
38/// Default adapter name.
39pub(crate) const DEFAULT_NAME: &str = "hci0";
40
41/// Interface to a Bluetooth adapter.
42#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
43#[derive(Clone)]
44pub struct Adapter {
45 inner: Arc<SessionInner>,
46 dbus_path: Path<'static>,
47 name: Arc<String>,
48}
49
50impl Debug for Adapter {
51 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
52 f.debug_struct("Adapter").field("name", &self.name()).finish()
53 }
54}
55
56impl Adapter {
57 /// Create Bluetooth adapter interface for adapter with specified name.
58 pub(crate) fn new(inner: Arc<SessionInner>, name: &str) -> Result<Self> {
59 Ok(Self {
60 inner,
61 dbus_path: Path::new(PREFIX.to_string() + name)
62 .map_err(|_| Error::new(ErrorKind::InvalidName(name.to_string())))?,
63 name: Arc::new(name.to_string()),
64 })
65 }
66
67 fn proxy(&self) -> Proxy<'_, &SyncConnection> {
68 Proxy::new(SERVICE_NAME, &self.dbus_path, TIMEOUT, &*self.inner.connection)
69 }
70
71 pub(crate) fn dbus_path(adapter_name: &str) -> Result<Path<'static>> {
72 Path::new(format!("{PREFIX}{adapter_name}",))
73 .map_err(|_| Error::new(ErrorKind::InvalidName((*adapter_name).to_string())))
74 }
75
76 pub(crate) fn parse_dbus_path_prefix<'a>(path: &'a Path) -> Option<(&'a str, &'a str)> {
77 match path.strip_prefix(PREFIX) {
78 Some(p) => {
79 let sep = p.find('/').unwrap_or(p.len());
80 Some((&p[0..sep], &p[sep..]))
81 }
82 None => None,
83 }
84 }
85
86 pub(crate) fn parse_dbus_path<'a>(path: &'a Path) -> Option<&'a str> {
87 match Self::parse_dbus_path_prefix(path) {
88 Some((v, "")) => Some(v),
89 _ => None,
90 }
91 }
92
93 /// The Bluetooth adapter name.
94 ///
95 /// For example `hci0`.
96 pub fn name(&self) -> &str {
97 &self.name
98 }
99
100 /// Bluetooth addresses of discovered Bluetooth devices.
101 pub async fn device_addresses(&self) -> Result<Vec<Address>> {
102 let mut addrs = Vec::new();
103 for (path, interfaces) in all_dbus_objects(&self.inner.connection).await? {
104 match Device::parse_dbus_path(&path) {
105 Some((adapter, addr)) if adapter == *self.name && interfaces.contains_key(device::INTERFACE) => {
106 addrs.push(addr)
107 }
108 _ => (),
109 }
110 }
111 Ok(addrs)
112 }
113
114 /// Starts monitoring of advertisements.
115 ///
116 /// Once a monitoring job is activated by BlueZ, the client can expect to get
117 /// notified on the targeted advertisements no matter if there is an ongoing
118 /// discovery session.
119 ///
120 /// Use the returned [`MonitorManager`] to target advertisements
121 /// and drop it to stop monitoring advertisements.
122 pub async fn monitor(&self) -> Result<MonitorManager> {
123 MonitorManager::new(self.inner.clone(), self.name()).await
124 }
125
126 /// Get interface to Bluetooth device of specified address.
127 pub fn device(&self, address: Address) -> Result<Device> {
128 Device::new(self.inner.clone(), self.name.clone(), address)
129 }
130
131 /// Gets the filter used for device discovery.
132 pub async fn discovery_filter(&self) -> DiscoveryFilter {
133 self.inner.adapter_discovery_filter.lock().await.get(self.name()).cloned().unwrap_or_default()
134 }
135
136 /// Sets the filter used for device discovery.
137 ///
138 /// Setting a discovery filter does not guarantee that all its filters will be applied.
139 /// A discovery session from another program might be active, leading to merging of
140 /// the discovery filters by the Bluetooth daemon.
141 ///
142 /// The discovery filter can only be changed when no device discovery is currently active.
143 /// Otherwise a [DiscoveryActive error](ErrorKind::DiscoveryActive) will be returned.
144 pub async fn set_discovery_filter(&self, discovery_filter: DiscoveryFilter) -> Result<()> {
145 if self.inner.is_single_session_active(&self.dbus_path).await {
146 return Err(Error::new(ErrorKind::DiscoveryActive));
147 }
148
149 self.inner.adapter_discovery_filter.lock().await.insert(self.name().to_string(), discovery_filter);
150 Ok(())
151 }
152
153 /// This method starts the device discovery session.
154 ///
155 /// This includes an inquiry procedure and remote device name resolving.
156 ///
157 /// This process will start streaming device addresses as new devices are discovered.
158 /// A device may be discovered multiple times.
159 ///
160 /// All already known devices are also included in the device stream.
161 /// This may include devices that are currently not in range.
162 /// Check the [Device::rssi] property to see if the device is currently present.
163 ///
164 /// Device properties are queried asynchronously and may not be available
165 /// yet when a [DeviceAdded event](AdapterEvent::DeviceAdded) occurs.
166 /// Use [discover_devices_with_changes](Self::discover_devices_with_changes)
167 /// when you want to be notified when the device properties change.
168 ///
169 /// The discovery filter can be configured using [set_discovery_filter](Self::set_discovery_filter).
170 pub async fn discover_devices(&self) -> Result<impl Stream<Item = AdapterEvent>> {
171 let token = self.discovery_session().await?;
172 let change_events = self
173 .events()
174 .await?
175 .map(move |evt| {
176 let _token = &token;
177 evt
178 })
179 .take_while(|evt| {
180 future::ready(!matches!(evt, AdapterEvent::PropertyChanged(AdapterProperty::Discovering(false))))
181 });
182
183 let known = self.device_addresses().await?;
184 let known_events = stream::iter(known).map(AdapterEvent::DeviceAdded);
185
186 let all_events = known_events.chain(change_events);
187
188 Ok(all_events)
189 }
190
191 /// This method starts the device discovery session and notifies of device property changes.
192 ///
193 /// This includes an inquiry procedure and remote device name resolving.
194 ///
195 /// This process will start streaming device addresses as new devices are discovered.
196 /// Each time device properties change you will receive an additional
197 /// [DeviceAdded event](AdapterEvent::DeviceAdded) for that device.
198 ///
199 /// All already known devices are also included in the device stream.
200 /// This may include devices that are currently not in range.
201 /// Check the [Device::rssi] property to see if the device is currently present.
202 ///
203 /// The discovery filter can be configured using [set_discovery_filter](Self::set_discovery_filter).
204 pub async fn discover_devices_with_changes(&self) -> Result<impl Stream<Item = AdapterEvent>> {
205 let (tx, rx) = mpsc::channel(1);
206 let mut discovery = self.discover_devices().await?;
207 let adapter = self.clone();
208
209 tokio::spawn(async move {
210 let mut changes = SelectAll::new();
211
212 loop {
213 tokio::select! {
214 evt = discovery.next() => {
215 match evt {
216 Some(AdapterEvent::DeviceAdded(addr)) => {
217 if let Ok(dev) = adapter.device(addr) {
218 if let Ok(dev_evts) = dev.events().await {
219 changes.push(dev_evts.map(move |_| addr));
220 }
221 }
222 let _ = tx.send(AdapterEvent::DeviceAdded(addr)).await;
223 },
224 Some(AdapterEvent::DeviceRemoved(addr)) => {
225 let _ = tx.send(AdapterEvent::DeviceRemoved(addr)).await;
226 },
227 Some(_) => (),
228 None => break,
229 }
230 },
231 Some(addr) = changes.next(), if !changes.is_empty() => {
232 let _ = tx.send(AdapterEvent::DeviceAdded(addr)).await;
233 },
234 () = tx.closed() => break,
235 }
236 }
237 });
238
239 Ok(ReceiverStream::new(rx))
240 }
241
242 async fn discovery_session(&self) -> Result<SingleSessionToken> {
243 let dbus_path = self.dbus_path.clone();
244 let connection = self.inner.connection.clone();
245 let token = self
246 .inner
247 .single_session(
248 &self.dbus_path,
249 async move {
250 let () = self
251 .call_method("SetDiscoveryFilter", (self.discovery_filter().await.into_dict(),))
252 .await?;
253 let () = self.call_method("StartDiscovery", ()).await?;
254 Ok(())
255 },
256 async move {
257 log::trace!("{}: {}.StopDiscovery ()", &dbus_path, SERVICE_NAME);
258 let proxy = Proxy::new(SERVICE_NAME, &dbus_path, TIMEOUT, &*connection);
259 let result: std::result::Result<(), dbus::Error> =
260 proxy.method_call(INTERFACE, "StopDiscovery", ()).await;
261 log::trace!("{}: {}.StopDiscovery () -> {:?}", &dbus_path, SERVICE_NAME, &result);
262 },
263 )
264 .await?;
265 Ok(token)
266 }
267
268 dbus_interface!();
269 dbus_default_interface!(INTERFACE);
270
271 /// Streams adapter property and device changes.
272 ///
273 /// The stream ends when the adapter is removed.
274 pub async fn events(&self) -> Result<impl Stream<Item = AdapterEvent>> {
275 let name = self.name.clone();
276 let events = self.inner.events(self.dbus_path.clone(), true).await?;
277 let stream = events.flat_map(move |event| match event {
278 Event::ObjectAdded { object, .. } => match Device::parse_dbus_path(&object) {
279 Some((adapter, address)) if adapter == *name => {
280 stream::once(async move { AdapterEvent::DeviceAdded(address) }).boxed()
281 }
282 _ => stream::empty().boxed(),
283 },
284 Event::ObjectRemoved { object, .. } => match Device::parse_dbus_path(&object) {
285 Some((adapter, address)) if adapter == *name => {
286 stream::once(async move { AdapterEvent::DeviceRemoved(address) }).boxed()
287 }
288 _ => stream::empty().boxed(),
289 },
290 Event::PropertiesChanged { changed, .. } => stream::iter(
291 AdapterProperty::from_prop_map(changed).into_iter().map(AdapterEvent::PropertyChanged),
292 )
293 .boxed(),
294 });
295 Ok(stream)
296 }
297
298 /// Registers an advertisement object to be sent over the LE
299 /// Advertising channel.
300 ///
301 /// InvalidArguments error indicates that the object has
302 /// invalid or conflicting properties.
303 ///
304 /// InvalidLength error indicates that the data
305 /// provided generates a data packet which is too long.
306 ///
307 /// The properties of this object are parsed when it is
308 /// registered, and any changes are ignored.
309 ///
310 /// If the same object is registered twice it will result in
311 /// an AlreadyExists error.
312 ///
313 /// If the maximum number of advertisement instances is
314 /// reached it will result in NotPermitted error.
315 ///
316 /// Drop the returned [AdvertisementHandle] to unregister the advertisement.
317 pub async fn advertise(&self, le_advertisement: Advertisement) -> Result<AdvertisementHandle> {
318 le_advertisement.register(self.inner.clone(), self.name.clone()).await
319 }
320
321 /// Registers a local GATT services hierarchy (GATT Server).
322 ///
323 /// Registering a service allows applications to publish a *local* GATT service,
324 /// which then becomes available to remote devices.
325 ///
326 /// Drop the returned [ApplicationHandle](gatt::local::ApplicationHandle) to unregister the application.
327 pub async fn serve_gatt_application(
328 &self, gatt_application: gatt::local::Application,
329 ) -> Result<gatt::local::ApplicationHandle> {
330 gatt_application.register(self.inner.clone(), self.name.clone()).await
331 }
332
333 /// Registers local GATT profiles (GATT Client).
334 ///
335 /// By registering this type of object
336 /// an application effectively indicates support for a specific GATT profile
337 /// and requests automatic connections to be established to devices
338 /// supporting it.
339 ///
340 /// Drop the returned [ProfileHandle](gatt::local::ProfileHandle) to unregister the application.
341 pub async fn register_gatt_profile(
342 &self, gatt_profile: gatt::local::Profile,
343 ) -> Result<gatt::local::ProfileHandle> {
344 gatt_profile.register(self.inner.clone(), self.name.clone()).await
345 }
346
347 // ===========================================================================================
348 // Methods
349 // ===========================================================================================
350
351 /// This removes the remote device object for the given
352 /// device address.
353 ///
354 /// It will remove also the pairing information.
355 pub async fn remove_device(&self, address: Address) -> Result<()> {
356 let path = Device::dbus_path(self.name(), address)?;
357 let () = self.call_method("RemoveDevice", ((path),)).await?;
358 Ok(())
359 }
360
361 /// This method connects to device without need of
362 /// performing General Discovery.
363 ///
364 /// Connection mechanism is
365 /// similar to Connect method from Device1 interface with
366 /// exception that this method returns success when physical
367 /// connection is established. After this method returns,
368 /// services discovery will continue and any supported
369 /// profile will be connected. There is no need for calling
370 /// Connect on Device1 after this call. If connection was
371 /// successful this method returns the created
372 /// device object.
373 ///
374 /// Parameters are the following:
375 ///
376 /// * `address` -
377 /// The Bluetooth device address of the remote
378 /// device.
379 /// * `address_type` -
380 /// The Bluetooth device Address Type. This is
381 /// address type that should be used for initial
382 /// connection.
383 ///
384 /// This method is experimental.
385 pub async fn connect_device(&self, address: Address, address_type: AddressType) -> Result<Device> {
386 let mut m = PropMap::new();
387 m.insert("Address".to_string(), Variant(address.to_string().box_clone()));
388 match address_type {
389 AddressType::LePublic | AddressType::LeRandom => {
390 m.insert("AddressType".to_string(), Variant(address_type.to_string().box_clone()));
391 }
392 AddressType::BrEdr => (),
393 }
394 let (_path,): (Path,) = self.call_method("ConnectDevice", (m,)).await?;
395
396 self.device(address)
397 }
398}
399
400define_properties!(
401 Adapter,
402 /// Bluetooth adapter property.
403 pub AdapterProperty => {
404
405 // ===========================================================================================
406 // Adapter properties
407 // ===========================================================================================
408
409 /// The Bluetooth adapter address.
410 property(
411 Address, Address,
412 dbus: (INTERFACE, "Address", String, MANDATORY),
413 get: (address, v => { v.parse()? }),
414 );
415
416 /// The Bluetooth adapter address type.
417 ///
418 /// For dual-mode and BR/EDR
419 /// only adapter this defaults to "public". Single mode LE
420 /// adapters may have either value. With privacy enabled
421 /// this contains type of Identity Address and not type of
422 /// address used for connection.
423 property(
424 AddressType, AddressType,
425 dbus: (INTERFACE, "AddressType", String, MANDATORY),
426 get: (address_type, v => {v.parse()?}),
427 );
428
429 /// The Bluetooth system name (pretty hostname).
430 ///
431 /// This property is either a static system default
432 /// or controlled by an external daemon providing
433 /// access to the pretty hostname configuration.
434 property(
435 SystemName, String,
436 dbus: (INTERFACE, "Name", String, MANDATORY),
437 get: (system_name, v => {v.to_owned()}),
438 );
439
440 /// The Bluetooth friendly name.
441 ///
442 /// This value can be changed.
443 ///
444 /// In case no alias is set, it will return the system
445 /// provided name. Setting an empty string as alias will
446 /// convert it back to the system provided name.
447 ///
448 /// When resetting the alias with an empty string, the
449 /// property will default back to system name.
450 ///
451 /// On a well configured system, this property never
452 /// needs to be changed since it defaults to the system
453 /// name and provides the pretty hostname. Only if the
454 /// local name needs to be different from the pretty
455 /// hostname, this property should be used as last
456 /// resort.
457 property(
458 Alias, String,
459 dbus: (INTERFACE, "Alias", String, MANDATORY),
460 get: (alias, v => {v.to_owned()}),
461 set: (set_alias, v => {v}),
462 );
463
464 /// The Bluetooth class of device.
465 ///
466 /// This property represents the value that is either
467 /// automatically configured by DMI/ACPI information
468 /// or provided as static configuration.
469 property(
470 Class, u32,
471 dbus: (INTERFACE, "Class", u32, MANDATORY),
472 get: (class, v => {v.to_owned()}),
473 );
474
475 /// Switch an adapter on or off. This will also set the
476 /// appropriate connectable state of the controller.
477 ///
478 /// The value of this property is not persistent. After
479 /// restart or unplugging of the adapter it will reset
480 /// back to false.
481 property(
482 Powered, bool,
483 dbus: (INTERFACE, "Powered", bool, MANDATORY),
484 get: (is_powered, v => {v.to_owned()}),
485 set: (set_powered, v => {v}),
486 );
487
488 /// Switch an adapter to discoverable or non-discoverable
489 /// to either make it visible or hide it.
490 ///
491 /// This is a global
492 /// setting and should only be used by the settings
493 /// application.
494 ///
495 /// If the DiscoverableTimeout is set to a non-zero
496 /// value then the system will set this value back to
497 /// false after the timer expired.
498 ///
499 /// In case the adapter is switched off, setting this
500 /// value will fail.
501 ///
502 /// When changing the Powered property the new state of
503 /// this property will be updated via a PropertiesChanged
504 /// signal.
505 ///
506 /// For any new adapter this settings defaults to false.
507 ///
508 /// To send Bluetooth LE advertisements use the
509 /// [advertise](Adapter::advertise) method instead.
510 property(
511 Discoverable, bool,
512 dbus: (INTERFACE, "Discoverable", bool, MANDATORY),
513 get: (is_discoverable, v => {v.to_owned()}),
514 set: (set_discoverable, v => {v}),
515 );
516
517 /// Switch an adapter to pairable or non-pairable.
518 ///
519 /// This is
520 /// a global setting and should only be used by the
521 /// settings application.
522 ///
523 /// Note that this property only affects incoming pairing
524 /// requests.
525 ///
526 /// For any new adapter this settings defaults to true.
527 property(
528 Pairable, bool,
529 dbus: (INTERFACE, "Pairable", bool, MANDATORY),
530 get: (is_pairable, v => {v.to_owned()}),
531 set: (set_pairable, v => {v}),
532 );
533
534 /// The pairable timeout in seconds.
535 ///
536 /// A value of zero
537 /// means that the timeout is disabled and it will stay in
538 /// pairable mode forever.
539 ///
540 /// The default value for pairable timeout should be
541 /// disabled (value 0).
542 property(
543 PairableTimeout, u32,
544 dbus: (INTERFACE, "PairableTimeout", u32, MANDATORY),
545 get: (pairable_timeout, v => {v.to_owned()}),
546 set: (set_pairable_timeout, v => {v}),
547 );
548
549 /// The discoverable timeout in seconds.
550 ///
551 /// A value of zero
552 /// means that the timeout is disabled and it will stay in
553 /// discoverable/limited mode forever.
554 ///
555 /// The default value for the discoverable timeout should
556 /// be 180 seconds (3 minutes).
557 property(
558 DiscoverableTimeout, u32,
559 dbus: (INTERFACE, "DiscoverableTimeout", u32, MANDATORY),
560 get: (discoverable_timeout, v => {v.to_owned()}),
561 set: (set_discoverable_timeout, v => {v}),
562 );
563
564 /// Indicates that a device discovery procedure is active.
565 property(
566 Discovering, bool,
567 dbus: (INTERFACE, "Discovering", bool, MANDATORY),
568 get: (is_discovering, v => {v.to_owned()}),
569 );
570
571 /// List of 128-bit UUIDs that represents the available
572 /// local services.
573 property(
574 Uuids, HashSet<Uuid>,
575 dbus: (INTERFACE, "UUIDs", Vec<String>, OPTIONAL),
576 get: (uuids, v => {
577 v
578 .iter()
579 .map(|uuid| {
580 uuid.parse()
581 .map_err(|_| Error::new(ErrorKind::Internal(InternalErrorKind::InvalidUuid(uuid.to_string()))))
582 })
583 .collect::<Result<HashSet<Uuid>>>()?
584 }),
585 );
586
587 /// Local Device ID information in modalias format
588 /// used by the kernel and udev.
589 property(
590 Modalias, Modalias,
591 dbus: (INTERFACE, "Modalias", String, OPTIONAL),
592 get: (modalias, v => { v.parse()? }),
593 );
594
595 // ===========================================================================================
596 // LE advertising manager properties
597 // ===========================================================================================
598
599 /// Number of active advertising instances.
600 property(
601 ActiveAdvertisingInstances, u8,
602 dbus: (adv::MANAGER_INTERFACE, "ActiveInstances", u8, MANDATORY),
603 get: (active_advertising_instances, v => {v.to_owned()}),
604 );
605
606 /// Number of available advertising instances.
607 property(
608 SupportedAdvertisingInstances, u8,
609 dbus: (adv::MANAGER_INTERFACE, "SupportedInstances", u8, MANDATORY),
610 get: (supported_advertising_instances, v => {v.to_owned()}),
611 );
612
613 /// List of supported system includes.
614 property(
615 SupportedAdvertisingSystemIncludes, BTreeSet<Feature>,
616 dbus: (adv::MANAGER_INTERFACE, "SupportedIncludes", Vec<String>, MANDATORY),
617 get: (supported_advertising_system_includes, v => {
618 v.iter().filter_map(|s| s.parse().ok()).collect()
619 }),
620 );
621
622 /// List of supported Secondary channels.
623 ///
624 /// Secondary
625 /// channels can be used to advertise with the
626 /// corresponding PHY.
627 property(
628 SupportedAdvertisingSecondaryChannels, BTreeSet<SecondaryChannel>,
629 dbus: (adv::MANAGER_INTERFACE, "SupportedSecondaryChannels", Vec<String>, OPTIONAL),
630 get: (supported_advertising_secondary_channels, v => {
631 v.iter().filter_map(|s| s.parse().ok()).collect()
632 }),
633 );
634
635 /// Enumerates Advertising-related controller capabilities
636 /// useful to the client.
637 property(
638 SupportedAdvertisingCapabilities, Capabilities,
639 dbus: (adv::MANAGER_INTERFACE, "SupportedCapabilities", HashMap<String, Variant<Box<dyn RefArg + 'static>>>, OPTIONAL),
640 get: (supported_advertising_capabilities, v => {
641 Capabilities::from_dict(v)?
642 }),
643 );
644
645 /// List of supported platform features.
646 ///
647 /// If no features
648 /// are available on the platform, the SupportedFeatures
649 /// array will be empty.
650 property(
651 SupportedAdvertisingFeatures, BTreeSet<PlatformFeature>,
652 dbus: (adv::MANAGER_INTERFACE, "SupportedFeatures", Vec<String>, OPTIONAL),
653 get: (supported_advertising_features, v => {
654 v.iter().filter_map(|s| s.parse().ok()).collect()
655 }),
656 );
657 }
658);
659
660/// Bluetooth adapter event.
661#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
662#[derive(Clone, Debug)]
663#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
664pub enum AdapterEvent {
665 /// Bluetooth device with specified address was added.
666 DeviceAdded(Address),
667 /// Bluetooth device with specified address was removed.
668 DeviceRemoved(Address),
669 /// Bluetooth adapter property changed.
670 PropertyChanged(AdapterProperty),
671}
672
673/// Transport parameter determines the type of scan.
674#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
675#[derive(Debug, Clone, Copy, Eq, PartialEq, Display, EnumString)]
676#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
677#[non_exhaustive]
678pub enum DiscoveryTransport {
679 /// interleaved scan
680 #[strum(serialize = "auto")]
681 Auto,
682 /// BR/EDR inquiry
683 #[strum(serialize = "bredr")]
684 BrEdr,
685 /// LE scan only
686 #[strum(serialize = "le")]
687 Le,
688}
689
690impl Default for DiscoveryTransport {
691 fn default() -> Self {
692 Self::Auto
693 }
694}
695
696/// Bluetooth device discovery filter.
697///
698/// The default discovery filter does not restrict any devices and provides
699/// [duplicate data](Self::duplicate_data).
700#[cfg_attr(docsrs, doc(cfg(feature = "bluetoothd")))]
701#[derive(Default, Clone, Debug, Eq, PartialEq)]
702#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
703pub struct DiscoveryFilter {
704 /// Filter by service UUIDs, empty means match
705 /// _any_ UUID.
706 ///
707 /// When a remote device is found that advertises
708 /// any UUID from UUIDs, it will be reported if:
709 /// - pathloss and RSSI are both empty.
710 /// - only pathloss param is set, device advertise
711 /// TX power, and computed pathloss is less than
712 /// pathloss param.
713 /// - only RSSI param is set, and received RSSI is
714 /// higher than RSSI param.
715 pub uuids: HashSet<Uuid>,
716 /// RSSI threshold value.
717 ///
718 /// PropertiesChanged signals will be emitted
719 /// for already existing Device objects, with
720 /// updated RSSI value. If one or more discovery
721 /// filters have been set, the RSSI delta-threshold,
722 /// that is imposed by StartDiscovery by default,
723 /// will not be applied.
724 pub rssi: Option<i16>,
725 /// Pathloss threshold value.
726 ///
727 /// PropertiesChanged signals will be emitted
728 /// for already existing Device objects, with
729 /// updated Pathloss value.
730 ///
731 /// Must not be set when [`rssi`](Self::rssi) is set.
732 pub pathloss: Option<u16>,
733 /// Transport parameter determines the type of
734 /// scan.
735 ///
736 /// Possible values:
737 /// "auto" - interleaved scan
738 /// "bredr" - BR/EDR inquiry
739 /// "le" - LE scan only
740 ///
741 /// If "le" or "bredr" Transport is requested,
742 /// and the controller doesn't support it,
743 /// org.bluez.Error.Failed error will be returned.
744 ///
745 /// If "auto" transport is requested, scan will use
746 /// LE, BREDR, or both, depending on what's
747 /// currently enabled on the controller.
748 pub transport: DiscoveryTransport,
749 /// Disables duplicate detection of advertisement data.
750 ///
751 /// When enabled PropertiesChanged signals will be
752 /// generated for either ManufacturerData and
753 /// ServiceData every time they are discovered.
754 pub duplicate_data: bool,
755 /// Make adapter discoverable while discovering.
756 ///
757 /// If the adapter is already discoverable setting
758 /// this filter won't do anything.
759 pub discoverable: bool,
760 /// Discover devices where the pattern matches
761 /// either the prefix of the address or
762 /// device name which is convenient way to limited
763 /// the number of device objects created during a
764 /// discovery.
765 ///
766 /// When set disregards device discoverable flags.
767 ///
768 /// Note: The pattern matching is ignored if there
769 /// are other client that don't set any pattern as
770 /// it work as a logical OR, also setting empty
771 /// string "" pattern will match any device found.
772 pub pattern: Option<String>,
773 #[doc(hidden)]
774 #[cfg_attr(feature = "serde", serde(skip))]
775 pub _non_exhaustive: (),
776}
777
778impl DiscoveryFilter {
779 fn into_dict(self) -> HashMap<&'static str, Variant<Box<dyn RefArg>>> {
780 let mut hm: HashMap<&'static str, Variant<Box<dyn RefArg>>> = HashMap::new();
781 let Self { uuids, rssi, pathloss, transport, duplicate_data, discoverable, pattern, _non_exhaustive } =
782 self;
783 hm.insert("UUIDs", Variant(Box::new(uuids.into_iter().map(|uuid| uuid.to_string()).collect::<Vec<_>>())));
784 if let Some(rssi) = rssi {
785 hm.insert("RSSI", Variant(Box::new(rssi)));
786 }
787 if let Some(pathloss) = pathloss {
788 hm.insert("Pathloss", Variant(Box::new(pathloss)));
789 }
790 hm.insert("Transport", Variant(Box::new(transport.to_string())));
791
792 // WORKAROUND: bluetoothd has a bug that causes it to crash when deserializing a bool
793 // from D-Bus on some architectures (at least 32-bit ARM). We can partly
794 // work around this by omitting the bools from the dictionary, if they match
795 // the default values.
796 if duplicate_data {
797 hm.insert("DuplicateData", Variant(Box::new(true)));
798 }
799 if discoverable {
800 hm.insert("Discoverable", Variant(Box::new(true)));
801 }
802
803 if let Some(pattern) = pattern {
804 hm.insert("Pattern", Variant(Box::new(pattern)));
805 }
806 hm
807 }
808}