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}