wayle-bluetooth 0.1.2

Bluetooth device management and discovery
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
pub(crate) mod controls;
pub(crate) mod monitoring;
pub(crate) mod types;

use std::{collections::HashMap, sync::Arc};

use controls::AdapterControls;
use derive_more::Debug;
use tokio_util::sync::CancellationToken;
use types::AdapterProperties;
pub use types::{AdapterParams, LiveAdapterParams};
use wayle_core::{Property, unwrap_dbus};
use wayle_traits::{ModelMonitoring, Reactive};
use zbus::{
    Connection,
    zvariant::{OwnedObjectPath, Value},
};

use crate::{
    error::Error,
    proxy::adapter::Adapter1Proxy,
    types::{
        UUID,
        adapter::{AdapterRole, AddressType, DiscoveryFilterOptions, PowerState},
    },
};

/// Bluetooth adapter from BlueZ.
///
/// Instances from service fields are **live** and auto-update.
/// Instances from [`BluetoothService::adapter()`](crate::BluetoothService::adapter) are **snapshots**.
/// Use [`BluetoothService::adapter_monitored()`](crate::BluetoothService::adapter_monitored) for a live instance by path.
///
/// # Control Methods
///
/// - [`set_powered()`](Self::set_powered) - Power on/off
/// - [`set_discoverable()`](Self::set_discoverable) /
///   [`set_discoverable_timeout()`](Self::set_discoverable_timeout) - Visibility
/// - [`set_pairable()`](Self::set_pairable) /
///   [`set_pairable_timeout()`](Self::set_pairable_timeout) - Pairing acceptance
/// - [`start_discovery()`](Self::start_discovery) /
///   [`stop_discovery()`](Self::stop_discovery) - Device scanning
/// - [`set_discovery_filter()`](Self::set_discovery_filter) - Filter discovered devices
/// - [`remove_device()`](Self::remove_device) - Remove a device from the adapter
/// - [`connect_device()`](Self::connect_device) - Direct connection without discovery
#[derive(Debug, Clone)]
pub struct Adapter {
    #[debug(skip)]
    pub(crate) zbus_connection: Connection,
    #[debug(skip)]
    pub(crate) cancellation_token: Option<CancellationToken>,

    /// D-Bus object path for this device.
    pub object_path: OwnedObjectPath,

    /// The Bluetooth device address.
    pub address: Property<String>,

    /// The Bluetooth Address Type. For dual-mode and BR/EDR only adapter this defaults
    /// to "public". Single mode LE adapters may have either value. With privacy enabled
    /// this contains type of Identity Address and not type of address used for
    /// connection.
    pub address_type: Property<AddressType>,

    /// The Bluetooth system name (pretty hostname).
    ///
    /// This property is either a static system default or controlled by an external
    /// daemon providing access to the pretty hostname configuration.
    pub name: Property<String>,

    /// The Bluetooth friendly name. This value can be changed.
    ///
    /// In case no alias is set, it will return the system provided name. Setting an
    /// empty string as alias will convert it back to the system provided name.
    ///
    /// When resetting the alias with an empty string, the property will default back to
    /// system name.
    ///
    /// On a well configured system, this property never needs to be changed since it
    /// defaults to the system name and provides the pretty hostname.
    ///
    /// Only if the local name needs to be different from the pretty hostname, this
    /// property should be used as last resort.
    pub alias: Property<String>,

    /// The Bluetooth class of device.
    ///
    /// This property represents the value that is either automatically configured by
    /// DMI/ACPI information or provided as static configuration.
    pub class: Property<u32>,

    /// Set an adapter to connectable or non-connectable. This is a global setting and
    /// should only be used by the settings application.
    ///
    /// Setting this property to false will set the Discoverable property of the adapter
    /// to false as well, which will not be reverted if Connectable is set back to true.
    ///
    /// If required, the application will need to manually set Discoverable to true.
    ///
    /// Note that this property only affects incoming connections.
    pub connectable: Property<bool>,

    /// Switch an adapter on or off. This will also set the appropriate connectable
    /// state of the controller.
    ///
    /// The value of this property is not persistent. After restart or unplugging of the
    /// adapter it will reset back to false.
    pub powered: Property<bool>,

    /// The power state of an adapter.
    ///
    /// The power state will show whether the adapter is turning off, or turning on, as
    /// well as being on or off.
    ///
    /// (BlueZ experimental)
    pub power_state: Property<PowerState>,

    /// Switch an adapter to discoverable or non-discoverable to either make it visible
    /// or hide it. This is a global setting and should only be used by the settings
    /// application.
    ///
    /// If the DiscoverableTimeout is set to a non-zero value then the system will set
    /// this value back to false after the timer expired.
    ///
    /// In case the adapter is switched off, setting this value will fail.
    ///
    /// When changing the Powered property the new state of this property will be
    /// updated via a PropertiesChanged signal.
    ///
    /// Default: false
    pub discoverable: Property<bool>,

    /// The discoverable timeout in seconds. A value of zero means that the timeout is
    /// disabled and it will stay in discoverable/limited mode forever.
    ///
    /// Default: 180
    pub discoverable_timeout: Property<u32>,

    /// Indicates that a device discovery procedure is active.
    pub discovering: Property<bool>,

    /// Switch an adapter to pairable or non-pairable. This is a global setting and
    /// should only be used by the settings application.
    ///
    /// Note that this property only affects incoming pairing requests.
    ///
    /// Default: true
    pub pairable: Property<bool>,

    /// The pairable timeout in seconds. A value of zero means that the timeout is
    /// disabled and it will stay in pairable mode forever.
    ///
    /// Default: 0
    pub pairable_timeout: Property<u32>,

    /// List of 128-bit UUIDs that represents the available local services.
    pub uuids: Property<Vec<UUID>>,

    /// Local Device ID information in modalias format used by the kernel and udev.
    pub modalias: Property<Option<String>>,

    /// List of supported roles.
    pub roles: Property<Vec<AdapterRole>>,

    /// List of 128-bit UUIDs that represents the experimental features currently
    /// enabled.
    pub experimental_features: Property<Vec<UUID>>,

    /// The manufacturer of the device, as a uint16 company identifier defined by the
    /// Core Bluetooth Specification.
    pub manufacturer: Property<u16>,

    /// The Bluetooth version supported by the device, as a core version code defined by
    /// the Core Bluetooth Specification.
    pub version: Property<u8>,
}

impl PartialEq for Adapter {
    fn eq(&self, other: &Self) -> bool {
        self.object_path == other.object_path
    }
}

impl Reactive for Adapter {
    type Error = Error;
    type Context<'a> = AdapterParams<'a>;
    type LiveContext<'a> = LiveAdapterParams<'a>;

    async fn get(context: Self::Context<'_>) -> Result<Self, Self::Error> {
        let adapter_proxy = Adapter1Proxy::new(context.connection, &context.path).await?;
        let props = Self::fetch_properties(&adapter_proxy).await?;
        Ok(Self::from_properties(
            props,
            context.connection,
            context.path,
            None,
        ))
    }

    async fn get_live(context: Self::LiveContext<'_>) -> Result<Arc<Self>, Self::Error> {
        let adapter_proxy = Adapter1Proxy::new(context.connection, &context.path).await?;
        let props = Self::fetch_properties(&adapter_proxy).await?;
        let adapter = Self::from_properties(
            props,
            context.connection,
            context.path.clone(),
            Some(context.cancellation_token.child_token()),
        );
        let adapter_arc = Arc::new(adapter);

        adapter_arc.clone().start_monitoring().await?;

        Ok(adapter_arc)
    }
}

impl Adapter {
    /// Sets the Bluetooth friendly name (alias) of the adapter.
    ///
    /// Setting an empty string will revert to the system-provided name.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_alias(&self, alias: &str) -> Result<(), Error> {
        AdapterControls::set_alias(&self.zbus_connection, &self.object_path, alias).await
    }

    /// Sets whether the adapter is connectable.
    ///
    /// Note: Setting this to false will also set Discoverable to false.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_connectable(&self, connectable: bool) -> Result<(), Error> {
        AdapterControls::set_connectable(&self.zbus_connection, &self.object_path, connectable)
            .await
    }

    /// Powers the adapter on or off.
    ///
    /// This will also set the appropriate connectable state of the controller.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_powered(&self, powered: bool) -> Result<(), Error> {
        AdapterControls::set_powered(&self.zbus_connection, &self.object_path, powered).await
    }

    /// Sets whether the adapter is discoverable.
    ///
    /// This is a global setting and should only be used by a settings application.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_discoverable(&self, discoverable: bool) -> Result<(), Error> {
        AdapterControls::set_discoverable(&self.zbus_connection, &self.object_path, discoverable)
            .await
    }

    /// Sets the discoverable timeout in seconds.
    ///
    /// A value of 0 means that the timeout is disabled and the adapter will stay in discoverable mode indefinitely.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_discoverable_timeout(&self, timeout: u32) -> Result<(), Error> {
        AdapterControls::set_discoverable_timeout(&self.zbus_connection, &self.object_path, timeout)
            .await
    }

    /// Sets whether the adapter is pairable.
    ///
    /// This is a global setting and should only be used by a settings application.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_pairable(&self, pairable: bool) -> Result<(), Error> {
        AdapterControls::set_pairable(&self.zbus_connection, &self.object_path, pairable).await
    }

    /// Sets the pairable timeout in seconds.
    ///
    /// A value of 0 means that the timeout is disabled and the adapter will stay in pairable mode indefinitely.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_pairable_timeout(&self, timeout: u32) -> Result<(), Error> {
        AdapterControls::set_pairable_timeout(&self.zbus_connection, &self.object_path, timeout)
            .await
    }

    /// Sets the device discovery filter for the caller. When this method is called with
    /// no filter parameter, filter is removed.
    ///
    /// When discovery filter is set, Device objects will be created as new devices with
    /// matching criteria are discovered regardless of they are connectable or
    /// discoverable which enables listening to non-connectable and non-discoverable
    /// devices.
    ///
    /// When multiple clients call SetDiscoveryFilter, their filters are internally
    /// merged, and notifications about new devices are sent to all clients. Therefore,
    /// each client must check that device updates actually match its filter.
    ///
    /// When SetDiscoveryFilter is called multiple times by the same client, last filter
    /// passed will be active for given client.
    ///
    /// SetDiscoveryFilter can be called before StartDiscovery.
    /// It is useful when client will create first discovery session, to ensure that
    /// proper scan will be started right after call to StartDiscovery.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn set_discovery_filter(
        &self,
        options: DiscoveryFilterOptions<'_>,
    ) -> Result<(), Error> {
        let filter = options.to_filter();
        AdapterControls::set_discovery_filter(&self.zbus_connection, &self.object_path, filter)
            .await
    }

    /// Starts device discovery session which may include starting an inquiry and/or
    /// scanning procedures and remote device name resolving.
    ///
    /// This process will start creating Device objects as new devices are discovered.
    /// Each client can request a single device discovery session per adapter.
    ///
    /// # Errors
    ///
    /// - `NotReady` - Adapter not ready
    /// - `Failed` - Operation failed
    /// - `InProgress` - Discovery already in progress
    pub async fn start_discovery(&self) -> Result<(), Error> {
        AdapterControls::start_discovery(&self.zbus_connection, &self.object_path).await
    }

    /// Stops device discovery session started by start_discovery.
    ///
    /// Note that a discovery procedure is shared between all discovery sessions thus
    /// calling stop_discovery will only release a single session and discovery will stop
    /// when all sessions from all clients have finished.
    ///
    /// # Errors
    ///
    /// - `NotReady` - Adapter not ready
    /// - `Failed` - Operation failed
    /// - `NotAuthorized` - Not authorized to stop discovery
    pub async fn stop_discovery(&self) -> Result<(), Error> {
        AdapterControls::stop_discovery(&self.zbus_connection, &self.object_path).await
    }

    /// Removes the remote device object at the given path including cached information
    /// such as bonding information.
    ///
    /// # Errors
    ///
    /// - `InvalidArguments` - Invalid device path
    /// - `Failed` - Operation failed
    pub async fn remove_device(&self, device_path: &OwnedObjectPath) -> Result<(), Error> {
        AdapterControls::remove_device(&self.zbus_connection, &self.object_path, device_path).await
    }

    /// Returns available filters that can be given to set_discovery_filter.
    ///
    /// # Errors
    ///
    /// Returns error if the D-Bus operation fails or the adapter is not available.
    pub async fn get_discovery_filters(&self) -> Result<Vec<String>, Error> {
        AdapterControls::get_discovery_filters(&self.zbus_connection, &self.object_path).await
    }

    /// Connects to device without need of performing General Discovery.
    ///
    /// Connection mechanism is similar to Device connect method with exception that this
    /// method returns success when physical connection is established and you can specify
    /// bearer to connect with parameter.
    ///
    /// After this method returns, services discovery will continue and any supported
    /// profile will be connected. Returns object path to created device object or device that already exists.
    ///
    /// (BlueZ experimental)
    ///
    /// # Errors
    ///
    /// - `InvalidArguments` - Invalid properties
    /// - `AlreadyExists` - Device already exists
    /// - `NotSupported` - Not supported
    /// - `NotReady` - Adapter not ready
    /// - `Failed` - Operation failed
    pub async fn connect_device(
        &self,
        properties: HashMap<String, Value<'_>>,
    ) -> Result<OwnedObjectPath, Error> {
        AdapterControls::connect_device(&self.zbus_connection, &self.object_path, properties).await
    }

    #[allow(clippy::too_many_lines)]
    async fn fetch_properties(proxy: &Adapter1Proxy<'_>) -> Result<AdapterProperties, Error> {
        let (
            address,
            address_type,
            name,
            alias,
            class,
            connectable,
            powered,
            power_state,
            discoverable,
            discoverable_timeout,
            discovering,
            pairable,
            pairable_timeout,
            uuids,
            modalias,
            roles,
            experimental_features,
            manufacturer,
            version,
        ) = tokio::join!(
            proxy.address(),
            proxy.address_type(),
            proxy.name(),
            proxy.alias(),
            proxy.class(),
            proxy.connectable(),
            proxy.powered(),
            proxy.power_state(),
            proxy.discoverable(),
            proxy.discoverable_timeout(),
            proxy.discovering(),
            proxy.pairable(),
            proxy.pairable_timeout(),
            proxy.uuids(),
            proxy.modalias(),
            proxy.roles(),
            proxy.experimental_features(),
            proxy.manufacturer(),
            proxy.version()
        );

        Ok(AdapterProperties {
            address: unwrap_dbus!(address),
            address_type: unwrap_dbus!(address_type),
            name: unwrap_dbus!(name),
            alias: unwrap_dbus!(alias),
            class: unwrap_dbus!(class),
            connectable: unwrap_dbus!(connectable),
            powered: unwrap_dbus!(powered),
            power_state: unwrap_dbus!(power_state),
            discoverable: unwrap_dbus!(discoverable),
            discoverable_timeout: unwrap_dbus!(discoverable_timeout),
            discovering: unwrap_dbus!(discovering),
            pairable: unwrap_dbus!(pairable),
            pairable_timeout: unwrap_dbus!(pairable_timeout),
            uuids: unwrap_dbus!(uuids),
            modalias: modalias.ok(),
            roles: unwrap_dbus!(roles),
            experimental_features: unwrap_dbus!(experimental_features),
            manufacturer: unwrap_dbus!(manufacturer),
            version: unwrap_dbus!(version),
        })
    }

    fn from_properties(
        props: AdapterProperties,
        connection: &Connection,
        object_path: OwnedObjectPath,
        cancellation_token: Option<CancellationToken>,
    ) -> Self {
        Self {
            object_path,
            zbus_connection: connection.clone(),
            cancellation_token,
            address: Property::new(props.address),
            address_type: Property::new(AddressType::from(props.address_type.as_str())),
            name: Property::new(props.name),
            alias: Property::new(props.alias),
            class: Property::new(props.class),
            connectable: Property::new(props.connectable),
            powered: Property::new(props.powered),
            power_state: Property::new(PowerState::from(props.power_state.as_str())),
            discoverable: Property::new(props.discoverable),
            discoverable_timeout: Property::new(props.discoverable_timeout),
            discovering: Property::new(props.discovering),
            pairable: Property::new(props.pairable),
            pairable_timeout: Property::new(props.pairable_timeout),
            uuids: Property::new(
                props
                    .uuids
                    .into_iter()
                    .map(|s| UUID::from(s.as_str()))
                    .collect(),
            ),
            modalias: Property::new(props.modalias),
            roles: Property::new(
                props
                    .roles
                    .into_iter()
                    .map(|s| AdapterRole::from(s.as_str()))
                    .collect(),
            ),
            experimental_features: Property::new(
                props
                    .experimental_features
                    .into_iter()
                    .map(|s| UUID::from(s.as_str()))
                    .collect(),
            ),
            manufacturer: Property::new(props.manufacturer),
            version: Property::new(props.version),
        }
    }
}