blues/
gatt.rs

1//! GATT [`Service`]s and [`Characteristic`]s exported by BLE devices.
2
3use futures_util::StreamExt;
4use zbus::{
5    zvariant::{ObjectPath, Value},
6    PropertyStream,
7};
8
9use crate::{uuid::Uuid, Error, Result, Session};
10
11mod private {
12    use zbus::{
13        dbus_proxy,
14        zvariant::{ObjectPath, SerializeDict, Type},
15    };
16
17    #[dbus_proxy(
18        interface = "org.bluez.GattService1",
19        default_service = "org.bluez",
20        assume_defaults = false
21    )]
22    trait GattService {
23        #[dbus_proxy(property, name = "UUID")]
24        fn uuid(&self) -> zbus::Result<String>;
25
26        #[dbus_proxy(property)]
27        fn primary(&self) -> zbus::Result<bool>;
28    }
29
30    #[dbus_proxy(
31        interface = "org.bluez.GattCharacteristic1",
32        default_service = "org.bluez",
33        assume_defaults = false
34    )]
35    trait GattCharacteristic {
36        fn read_value(&self, options: &ReadOptions) -> zbus::Result<Vec<u8>>;
37        fn write_value(&self, value: &[u8], options: &WriteOptions) -> zbus::Result<()>;
38
39        fn start_notify(&self) -> zbus::Result<()>;
40        fn stop_notify(&self) -> zbus::Result<()>;
41
42        #[dbus_proxy(property, name = "UUID")]
43        fn uuid(&self) -> zbus::Result<String>;
44
45        #[dbus_proxy(property)]
46        fn value(&self) -> zbus::Result<Vec<u8>>;
47
48        #[dbus_proxy(property)]
49        fn flags(&self) -> zbus::Result<Vec<String>>;
50
51        #[dbus_proxy(property, name = "MTU")]
52        fn mtu(&self) -> zbus::Result<u16>;
53    }
54
55    #[derive(SerializeDict, Type)]
56    #[zvariant(signature = "dict")]
57    pub struct ReadOptions {
58        // FIXME: `pub` because zbus' `dbus_proxy` macro *always* generates public proxy types and
59        // methods instead of copying the trait visibility
60        offset: Option<u16>,
61        mtu: Option<u16>,
62        device: Option<ObjectPath<'static>>,
63    }
64
65    #[derive(Default, SerializeDict, Type)]
66    #[zvariant(signature = "dict")]
67    pub struct WriteOptions {
68        // FIXME: `pub` because zbus' `dbus_proxy` macro *always* generates public proxy types and
69        // methods instead of copying the trait visibility
70        offset: Option<u16>,
71        /// `command`, `request`, `reliable`
72        #[zvariant(rename = "type")]
73        ty: Option<&'static str>,
74        mtu: Option<u16>,
75        device: Option<ObjectPath<'static>>,
76        link: Option<String>,
77        #[zvariant(rename = "prepare-authorize")]
78        prepare_authorize: Option<bool>,
79    }
80}
81
82use self::private::{GattCharacteristicProxy, GattServiceProxy, WriteOptions};
83
84/// A GATT service of a Bluetooth LE device.
85///
86/// To enumerate [`Service`]s, use [`Device::gatt_services`].
87///
88/// [`Device::gatt_services`]: crate::device::Device::gatt_services
89pub struct Service {
90    proxy: GattServiceProxy<'static>,
91    session: Session,
92}
93
94impl Service {
95    pub(crate) async fn new(session: Session, path: &ObjectPath<'static>) -> Result<Self> {
96        Ok(Self {
97            proxy: GattServiceProxy::new(&session.conn, path)
98                .await
99                .map_err(Error::from)?,
100            session,
101        })
102    }
103
104    /// Returns the [`Uuid`] identifying this [`Service`].
105    pub async fn uuid(&self) -> Result<Uuid> {
106        match self.proxy.uuid().await {
107            Ok(uuid) => uuid.parse().map_err(Error::from),
108            Err(e) => Err(Error::from(e)),
109        }
110    }
111
112    /// Returns a [`bool`] indicating whether this [`Service`] is a primary service.
113    ///
114    /// If `false`, the service is secondary.
115    pub async fn is_primary(&self) -> Result<bool> {
116        self.proxy.primary().await.map_err(Error::from)
117    }
118
119    /// Returns the [`Characteristic`] associated with this [`Service`] identified by the given
120    /// [`Uuid`].
121    ///
122    /// Returns an error if the [`Service`] does not expose any [`Characteristic`] with the given
123    /// [`Uuid`].
124    pub async fn characteristic(&self, uuid: Uuid) -> Result<Characteristic> {
125        let objects = self
126            .session
127            .object_manager()
128            .await?
129            .get_managed_objects()
130            .await
131            .map_err(Error::from)?;
132
133        let value = Value::from(uuid.to_string());
134        for (path, intfs) in objects {
135            if !path.starts_with(self.proxy.path().as_str()) {
136                continue;
137            }
138
139            let Some(props) = intfs.get("org.bluez.GattCharacteristic1") else { continue };
140            let Some(s) = props.get("UUID") else { continue };
141            if **s == value {
142                return Characteristic::new(&self.session, &path).await;
143            }
144        }
145
146        Err(Error::from(format!(
147            "no characteristic with UUID {} found in service",
148            uuid
149        )))
150    }
151
152    /// Returns a list of all [`Characteristic`]s associated with this [`Service`].
153    pub async fn characteristics(&self) -> Result<Vec<Characteristic>> {
154        let objects = self
155            .session
156            .object_manager()
157            .await?
158            .get_managed_objects()
159            .await
160            .map_err(Error::from)?;
161
162        let mut characteristics = Vec::new();
163        for (path, intfs) in objects {
164            if path.starts_with(self.proxy.path().as_str())
165                && intfs.contains_key("org.bluez.GattCharacteristic1")
166            {
167                characteristics.push(Characteristic::new(&self.session, &path).await?);
168            }
169        }
170
171        Ok(characteristics)
172    }
173}
174
175/// A Bluetooth characteristic that is part of some [`Service`].
176///
177/// A characteristic stores a value that can be (depending on the specific characteristic) read
178/// and/or written by the host.
179pub struct Characteristic {
180    proxy: GattCharacteristicProxy<'static>,
181}
182
183impl Characteristic {
184    async fn new(session: &Session, path: &ObjectPath<'static>) -> Result<Self> {
185        Ok(Self {
186            proxy: GattCharacteristicProxy::new(&session.conn, path)
187                .await
188                .map_err(Error::from)?,
189        })
190    }
191
192    /// Returns the [`Uuid`] identifying this [`Characteristic`].
193    ///
194    /// The returned [`Uuid`] determines the data format of the characteristic's value. For standard
195    /// services and characteristics, [`Uuid`]s are assigned by the Bluetooth SIG and documented in
196    /// their "Assigned Numbers" document. For vendor-specific characteristics, consult the vendor
197    /// for documentation.
198    pub async fn uuid(&self) -> Result<Uuid> {
199        match self.proxy.uuid().await {
200            Ok(s) => s.parse().map_err(Error::from),
201            Err(e) => Err(Error::from(e)),
202        }
203    }
204
205    /// Returns the Maximum Transmission Unit (MTU) of this characteristic in Bytes.
206    pub async fn mtu(&self) -> Result<u16> {
207        self.proxy.mtu().await.map_err(Error::from)
208    }
209
210    /// Returns the [`CharacteristicFlags`] associated with this [`Characteristic`].
211    ///
212    /// These flags indicate which operations the [`Characteristic`] supports.
213    pub async fn flags(&self) -> Result<CharacteristicFlags> {
214        self.proxy
215            .flags()
216            .await
217            .map_err(Error::from)
218            .map(|flags| CharacteristicFlags { flags })
219    }
220
221    /// Enables notifications/indications for this [`Characteristic`] and returns a [`ValueStream`]
222    /// that will report changes to the [`Characteristic`]'s value.
223    pub async fn subscribe(&self) -> Result<ValueStream> {
224        self.proxy.start_notify().await.map_err(Error::from)?;
225        let stream = self.proxy.receive_value_changed().await;
226        Ok(ValueStream { stream })
227    }
228
229    /// Writes a new value to this [`Characteristic`].
230    pub async fn write(&self, value: &[u8]) -> Result<()> {
231        self.proxy
232            .write_value(value, &WriteOptions::default())
233            .await
234            .map_err(Error::from)
235    }
236}
237
238/// A set of flags detailing the supported operations on a [`Characteristic`].
239#[derive(Debug)]
240pub struct CharacteristicFlags {
241    flags: Vec<String>,
242}
243
244impl CharacteristicFlags {
245    /// Returns a [`bool`] indicating whether the device can notify the host of changes made to the
246    /// [`Characteristic`]'s value.
247    ///
248    /// If this returns `true`, [`Characteristic::subscribe`] can be used to obtain a
249    /// [`ValueStream`] that reports every notification.
250    pub fn can_notify(&self) -> bool {
251        self.flags.iter().any(|s| s == "notify")
252    }
253
254    /// Returns a [`bool`] indicating whether the device supports sending *indications* of changes
255    /// made to the [`Characteristic`]'s value.
256    ///
257    /// Indications work almost exactly like notifications, but include an acknowledgement by the
258    /// GATT client (host).
259    pub fn can_indicate(&self) -> bool {
260        self.flags.iter().any(|s| s == "indicate")
261    }
262
263    /// Returns a [`bool`] indicating whether the device allows host-initiated reads of the
264    /// [`Characteristic`]'s value.
265    ///
266    /// Note that many [`Characteristic`]s do *not* allow host-initiated reads, but *do* support
267    /// device-initiated notifications (see [`CharacteristicFlags::can_notify`]).
268    pub fn can_read(&self) -> bool {
269        self.flags.iter().any(|s| s == "read")
270    }
271
272    /// Returns a [`bool`] indicating whether the device allows the host to set the
273    /// [`Characteristic`]'s value.
274    pub fn can_write(&self) -> bool {
275        self.flags.iter().any(|s| s == "write")
276    }
277}
278
279/// A stream of changes to the value of a [`Characteristic`].
280///
281/// Returned by [`Characteristic::subscribe`].
282pub struct ValueStream {
283    stream: PropertyStream<'static, Vec<u8>>,
284}
285
286impl ValueStream {
287    /// Waits for the next notification or indication to arrive, and returns the new value of the
288    /// [`Characteristic`].
289    ///
290    /// # Errors
291    ///
292    /// Once this method returns [`Err`], subsequent calls to it will generally not succeed. The
293    /// caller should assume that something higher up has gone wrong that will not recover on its
294    /// own. The [`ValueStream`] should be recreated.
295    ///
296    /// Note that this method is not guaranteed to fail if the [`Device`] is disconnected (it can
297    /// block forever). It is recommended to use a timeout with this method (or on some containing
298    /// future).
299    ///
300    /// [`Device`]: crate::device::Device
301    pub async fn next(&mut self) -> Result<Vec<u8>> {
302        match self.stream.next().await {
303            Some(changed) => changed.get().await.map_err(Error::from),
304            None => Err(Error::from("notification stream ended")),
305        }
306    }
307}