1use std::{fmt::Display, pin::Pin, time::Duration};
4
5use btleplug::{
6 api::{
7 BDAddr, Central as _, Characteristic, Manager as _, Peripheral, ScanFilter,
8 ValueNotification, WriteType,
9 },
10 platform::Manager,
11};
12use futures::{stream::StreamExt, Stream};
13use tracing::{debug, error, trace, warn};
14use uuid::{uuid, Uuid};
15
16use super::{Exchange, Transport};
17use crate::{
18 info::{ConnInfo, LedgerInfo, Model},
19 Error,
20};
21
22pub struct BleTransport {
24 manager: Manager,
25 peripherals: Vec<(LedgerInfo, btleplug::platform::Peripheral)>,
26}
27
28#[derive(Clone, Debug, PartialEq)]
30pub struct BleInfo {
31 name: String,
32 addr: BDAddr,
33}
34
35impl Display for BleInfo {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}", self.name)
38 }
39}
40
41pub struct BleDevice {
43 pub info: BleInfo,
44 mtu: u8,
45 p: btleplug::platform::Peripheral,
46 c_write: Characteristic,
47 c_read: Characteristic,
48}
49
50#[derive(Clone, PartialEq, Debug)]
53struct BleSpec {
54 pub model: Model,
55 pub service_uuid: Uuid,
56 pub notify_uuid: Uuid,
57 pub write_uuid: Uuid,
58 pub write_cmd_uuid: Uuid,
59}
60
61const BLE_SPECS: &[BleSpec] = &[
63 BleSpec {
64 model: Model::NanoX,
65 service_uuid: uuid!("13d63400-2c97-0004-0000-4c6564676572"),
66 notify_uuid: uuid!("13d63400-2c97-0004-0001-4c6564676572"),
67 write_uuid: uuid!("13d63400-2c97-0004-0002-4c6564676572"),
68 write_cmd_uuid: uuid!("13d63400-2c97-0004-0003-4c6564676572"),
69 },
70 BleSpec {
71 model: Model::Stax,
72 service_uuid: uuid!("13d63400-2c97-6004-0000-4c6564676572"),
73 notify_uuid: uuid!("13d63400-2c97-6004-0001-4c6564676572"),
74 write_uuid: uuid!("13d63400-2c97-6004-0002-4c6564676572"),
75 write_cmd_uuid: uuid!("13d63400-2c97-6004-0003-4c6564676572"),
76 },
77];
78
79impl BleTransport {
80 pub async fn new() -> Result<Self, Error> {
81 let manager = Manager::new().await?;
83
84 Ok(Self {
85 manager,
86 peripherals: vec![],
87 })
88 }
89
90 async fn scan_internal(
92 &self,
93 duration: Duration,
94 ) -> Result<Vec<(LedgerInfo, btleplug::platform::Peripheral)>, Error> {
95 let mut matched = vec![];
96
97 let adapters = self.manager.adapters().await?;
99
100 let f = ScanFilter { services: vec![] };
102
103 for adapter in adapters.iter() {
105 let info = adapter.adapter_info().await?;
106 debug!("Scan with adapter {info}");
107
108 adapter.start_scan(f.clone()).await?;
110
111 tokio::time::sleep(duration).await;
112
113 let mut peripherals = adapter.peripherals().await?;
115 if peripherals.is_empty() {
116 debug!("No peripherals found on adaptor {info}");
117 continue;
118 }
119
120 for p in peripherals.drain(..) {
122 let (properties, _connected) = (p.properties().await?, p.is_connected().await?);
124
125 let properties = match properties {
127 Some(v) => v,
128 None => {
129 debug!("Failed to fetch properties for peripheral: {p:?}");
130 continue;
131 }
132 };
133
134 let name = match &properties.local_name {
136 Some(v) => v,
137 None => continue,
138 };
139
140 debug!("Peripheral: {p:?} props: {properties:?}");
141
142 let model = if name.contains("Nano X") {
144 Model::NanoX
145 } else if name.contains("Stax") {
146 Model::Stax
147 } else {
148 continue;
149 };
150
151 matched.push((
153 LedgerInfo {
154 model: model.clone(),
155 conn: BleInfo {
156 name: name.clone(),
157 addr: properties.address,
158 }
159 .into(),
160 },
161 p,
162 ));
163 }
164 }
165
166 Ok(matched)
167 }
168}
169
170#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
172impl Transport for BleTransport {
173 type Filters = ();
174 type Info = BleInfo;
175 type Device = BleDevice;
176
177 async fn list(&mut self, _filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error> {
179 let devices = self.scan_internal(Duration::from_millis(1000)).await?;
181
182 let info: Vec<_> = devices.iter().map(|d| d.0.clone()).collect();
184
185 self.peripherals = devices;
187
188 Ok(info)
189 }
190
191 async fn connect(&mut self, info: Self::Info) -> Result<Self::Device, Error> {
195 let (d, p) = match self
197 .peripherals
198 .iter()
199 .find(|(d, _p)| d.conn == info.clone().into())
200 {
201 Some(v) => v,
202 None => {
203 warn!("No device found matching: {info:?}");
204 return Err(Error::NoDevices);
205 }
206 };
207 let i = match &d.conn {
208 ConnInfo::Ble(i) => i,
209 _ => unreachable!(),
210 };
211
212 let name = &i.name;
213
214 let properties = p.properties().await?;
216
217 let specs = match BLE_SPECS.iter().find(|s| s.model == d.model) {
220 Some(v) => v,
221 None => {
222 warn!("No specs for model: {:?}", d.model);
223 return Err(Error::Unknown);
224 }
225 };
226
227 if !p.is_connected().await? {
229 if let Err(e) = p.connect().await {
230 warn!("Failed to connect to {name}: {e:?}");
231 return Err(Error::Unknown);
232 }
233
234 if !p.is_connected().await? {
235 warn!("Not connected to {name}");
236 return Err(Error::Unknown);
237 }
238 }
239
240 debug!("peripheral {name}: {p:?} properties: {properties:?}");
241
242 p.discover_services().await?;
244
245 let characteristics = p.characteristics();
246
247 trace!("Characteristics: {characteristics:?}");
248
249 let c_write = characteristics.iter().find(|c| c.uuid == specs.write_uuid);
250 let c_read = characteristics.iter().find(|c| c.uuid == specs.notify_uuid);
251
252 let (c_write, c_read) = match (c_write, c_read) {
253 (Some(w), Some(r)) => (w, r),
254 _ => {
255 error!("Failed to match read and write characteristics for {name}");
256 return Err(Error::Unknown);
257 }
258 };
259
260 let mut d = BleDevice {
262 info: info.clone(),
263 mtu: 23,
264 p: p.clone(),
265 c_write: c_write.clone(),
266 c_read: c_read.clone(),
267 };
268
269 match d.fetch_mtu().await {
271 Ok(mtu) => d.mtu = mtu,
272 Err(e) => {
273 warn!("Failed to fetch MTU: {:?}", e);
274 }
275 }
276
277 debug!("using MTU: {}", d.mtu);
278
279 Ok(d)
280 }
281}
282
283const BLE_HEADER_LEN: usize = 3;
284
285impl BleDevice {
286 async fn write_command(&mut self, cmd: u8, payload: &[u8]) -> Result<(), Error> {
288 let mut data = Vec::with_capacity(payload.len() + 2);
290 data.extend_from_slice(&(payload.len() as u16).to_be_bytes()); data.extend_from_slice(payload); debug!("TX cmd: 0x{cmd:02x} payload: {data:02x?}");
294
295 for (i, c) in data.chunks(self.mtu as usize - BLE_HEADER_LEN).enumerate() {
297 let mut buff = Vec::with_capacity(self.mtu as usize);
299 let cmd = match i == 0 {
300 true => cmd,
301 false => 0x03,
302 };
303
304 buff.push(cmd); buff.extend_from_slice(&(i as u16).to_be_bytes()); buff.extend_from_slice(c);
307
308 debug!("Write chunk {i}: {:02x?}", buff);
309
310 self.p
311 .write(&self.c_write, &buff, WriteType::WithResponse)
312 .await?;
313 }
314
315 Ok(())
316 }
317
318 async fn read_data(
320 &mut self,
321 mut notifications: Pin<Box<dyn Stream<Item = ValueNotification> + Send>>,
322 ) -> Result<Vec<u8>, Error> {
323 let v = match notifications.next().await {
325 Some(v) => v.value,
326 None => {
327 return Err(Error::Closed);
328 }
329 };
330
331 debug!("RX: {:02x?}", v);
332
333 if v.len() < 5 {
335 error!("response too short");
336 return Err(Error::UnexpectedResponse);
337 } else if v[0] != 0x05 {
338 error!("unexpected response type: {:?}", v[0]);
339 return Err(Error::UnexpectedResponse);
340 }
341
342 let len = v[4] as usize;
344 if len == 0 {
345 return Err(Error::EmptyResponse);
346 }
347
348 trace!("Expecting response length: {}", len);
349
350 let mut buff = Vec::with_capacity(len);
352 buff.extend_from_slice(&v[5..]);
353
354 while buff.len() < len {
357 let v = match notifications.next().await {
359 Some(v) => v.value,
360 None => {
361 error!("Failed to fetch next chunk from peripheral");
362 self.p.unsubscribe(&self.c_read).await?;
363 return Err(Error::Closed);
364 }
365 };
366
367 debug!("RX: {v:02x?}");
368
369 buff.extend_from_slice(&v[5..]);
373 }
374
375 Ok(buff)
376 }
377
378 async fn fetch_mtu(&mut self) -> Result<u8, Error> {
380 self.p.subscribe(&self.c_read).await?;
382 let mut n = self.p.notifications().await?;
383
384 self.write_command(0x08, &[]).await?;
386
387 let mtu = match n.next().await {
389 Some(r) if r.value[0] == 0x08 && r.value.len() == 6 => {
390 debug!("RX: {:02x?}", r);
391 r.value[5]
392 }
393 Some(r) => {
394 warn!("Unexpected MTU response: {r:02x?}");
395 return Err(Error::Unknown);
396 }
397 None => {
398 warn!("Failed to request MTU");
399 return Err(Error::Unknown);
400 }
401 };
402
403 self.p.unsubscribe(&self.c_read).await?;
405
406 Ok(mtu)
407 }
408
409 pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
410 let c = self.p.is_connected().await?;
411 Ok(c)
412 }
413}
414
415#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
417impl Exchange for BleDevice {
418 async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
419 self.p.subscribe(&self.c_read).await?;
421 let notifications = self.p.notifications().await?;
422
423 if let Err(e) = self.write_command(0x05, command).await {
425 self.p.unsubscribe(&self.c_read).await?;
426 return Err(e);
427 }
428
429 debug!("Await response");
430
431 let buff = match tokio::time::timeout(timeout, self.read_data(notifications)).await {
433 Ok(Ok(v)) => v,
434 Ok(Err(e)) => {
435 self.p.unsubscribe(&self.c_read).await?;
436 return Err(e);
437 }
438 Err(e) => {
439 self.p.unsubscribe(&self.c_read).await?;
440 return Err(e.into());
441 }
442 };
443
444 Ok(buff)
445 }
446}