bluest/
device.rs

1#![allow(clippy::let_unit_value)]
2
3use futures_core::Stream;
4use futures_lite::StreamExt;
5
6use crate::error::ErrorKind;
7#[cfg(feature = "l2cap")]
8use crate::l2cap_channel::L2capChannel;
9use crate::pairing::PairingAgent;
10use crate::{sys, DeviceId, Error, Result, Service, Uuid};
11
12/// A Bluetooth LE device
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct Device(pub(crate) sys::device::DeviceImpl);
15
16impl std::fmt::Display for Device {
17    #[inline]
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        std::fmt::Display::fmt(&self.0, f)
20    }
21}
22
23impl Device {
24    /// This device's unique identifier
25    #[inline]
26    pub fn id(&self) -> DeviceId {
27        self.0.id()
28    }
29
30    /// The local name for this device, if available
31    ///
32    /// This can either be a name advertised or read from the device, or a name assigned to the device by the OS.
33    ///
34    /// # Panics
35    ///
36    /// On Linux, this method will panic if there is a current Tokio runtime and it is single-threaded or if there is
37    /// no current Tokio runtime and creating one fails.
38    #[inline]
39    pub fn name(&self) -> Result<String> {
40        self.0.name()
41    }
42
43    /// The local name for this device, if available
44    ///
45    /// This can either be a name advertised or read from the device, or a name assigned to the device by the OS.
46    #[inline]
47    pub async fn name_async(&self) -> Result<String> {
48        self.0.name_async().await
49    }
50
51    /// The connection status for this device
52    #[inline]
53    pub async fn is_connected(&self) -> bool {
54        self.0.is_connected().await
55    }
56
57    /// The pairing status for this device
58    #[inline]
59    pub async fn is_paired(&self) -> Result<bool> {
60        self.0.is_paired().await
61    }
62
63    /// Attempt to pair this device using the system default pairing UI
64    ///
65    /// # Platform specific
66    ///
67    /// ## MacOS/iOS
68    ///
69    /// Device pairing is performed automatically by the OS when a characteristic requiring security is accessed. This
70    /// method is a no-op.
71    ///
72    /// ## Windows
73    ///
74    /// This will fail unless it is called from a UWP application.
75    #[inline]
76    pub async fn pair(&self) -> Result<()> {
77        self.0.pair().await
78    }
79
80    /// Attempt to pair this device using the system default pairing UI
81    ///
82    /// # Platform specific
83    ///
84    /// On MacOS/iOS, device pairing is performed automatically by the OS when a characteristic requiring security is
85    /// accessed. This method is a no-op.
86    #[inline]
87    pub async fn pair_with_agent<T: PairingAgent + 'static>(&self, agent: &T) -> Result<()> {
88        self.0.pair_with_agent(agent).await
89    }
90
91    /// Disconnect and unpair this device from the system
92    ///
93    /// # Platform specific
94    ///
95    /// Not supported on MacOS/iOS.
96    #[inline]
97    pub async fn unpair(&self) -> Result<()> {
98        self.0.unpair().await
99    }
100
101    /// Discover the primary services of this device.
102    #[inline]
103    pub async fn discover_services(&self) -> Result<Vec<Service>> {
104        self.0.discover_services().await
105    }
106
107    /// Discover the primary service(s) of this device with the given [`Uuid`].
108    #[inline]
109    pub async fn discover_services_with_uuid(&self, uuid: Uuid) -> Result<Vec<Service>> {
110        self.0.discover_services_with_uuid(uuid).await
111    }
112
113    /// Get previously discovered services.
114    ///
115    /// If no services have been discovered yet, this method will perform service discovery.
116    #[inline]
117    pub async fn services(&self) -> Result<Vec<Service>> {
118        self.0.services().await
119    }
120
121    /// Asynchronously blocks until a GATT services changed packet is received
122    ///
123    /// # Platform specific
124    ///
125    /// See [`Device::service_changed_indications`].
126    pub async fn services_changed(&self) -> Result<()> {
127        self.service_changed_indications()
128            .await?
129            .next()
130            .await
131            .ok_or(Error::from(ErrorKind::AdapterUnavailable))
132            .map(|x| x.map(|_| ()))?
133    }
134
135    /// Monitors the device for service changed indications.
136    ///
137    /// # Platform specific
138    ///
139    /// On Windows an event is generated whenever the `services` value is updated. In addition to actual service change
140    /// indications this occurs when, for example, `discover_services` is called or when an unpaired device disconnects.
141    #[inline]
142    pub async fn service_changed_indications(
143        &self,
144    ) -> Result<impl Stream<Item = Result<ServicesChanged>> + Send + Unpin + '_> {
145        self.0.service_changed_indications().await
146    }
147
148    /// Get the current signal strength from the device in dBm.
149    ///
150    /// # Platform specific
151    ///
152    /// Returns [`NotSupported`][crate::error::ErrorKind::NotSupported] on Windows and Linux.
153    #[inline]
154    pub async fn rssi(&self) -> Result<i16> {
155        self.0.rssi().await
156    }
157
158    /// Open an L2CAP connection-oriented channel (CoC) to this device.
159    ///
160    /// # Platform specific
161    ///
162    /// Returns [`NotSupported`][crate::error::ErrorKind::NotSupported] on iOS/MacOS and Linux.
163    /// The `l2cap` feature is not available on Windows.
164    #[inline]
165    #[cfg(feature = "l2cap")]
166    pub async fn open_l2cap_channel(&self, psm: u16, secure: bool) -> Result<L2capChannel> {
167        let (reader, writer) = self.0.open_l2cap_channel(psm, secure).await?;
168        Ok(L2capChannel { reader, writer })
169    }
170}
171
172/// A services changed notification
173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub struct ServicesChanged(pub(crate) sys::device::ServicesChangedImpl);
175
176impl ServicesChanged {
177    /// Check if `service` was invalidated by this service changed indication.
178    ///
179    /// # Platform specific
180    ///
181    /// Windows does not indicate which services were affected by a services changed event, so this method will
182    /// pessimistically return true for all services.
183    pub fn was_invalidated(&self, service: &Service) -> bool {
184        self.0.was_invalidated(service)
185    }
186}