1use 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 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 offset: Option<u16>,
71 #[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
84pub 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 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 pub async fn is_primary(&self) -> Result<bool> {
116 self.proxy.primary().await.map_err(Error::from)
117 }
118
119 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 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
175pub 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 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 pub async fn mtu(&self) -> Result<u16> {
207 self.proxy.mtu().await.map_err(Error::from)
208 }
209
210 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 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 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#[derive(Debug)]
240pub struct CharacteristicFlags {
241 flags: Vec<String>,
242}
243
244impl CharacteristicFlags {
245 pub fn can_notify(&self) -> bool {
251 self.flags.iter().any(|s| s == "notify")
252 }
253
254 pub fn can_indicate(&self) -> bool {
260 self.flags.iter().any(|s| s == "indicate")
261 }
262
263 pub fn can_read(&self) -> bool {
269 self.flags.iter().any(|s| s == "read")
270 }
271
272 pub fn can_write(&self) -> bool {
275 self.flags.iter().any(|s| s == "write")
276 }
277}
278
279pub struct ValueStream {
283 stream: PropertyStream<'static, Vec<u8>>,
284}
285
286impl ValueStream {
287 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}