vex_v5_serial/
bluetooth.rs

1use std::pin::Pin;
2use std::time::{Duration, Instant};
3
4use btleplug::api::{
5    Central, CentralEvent, Characteristic, Manager as _, Peripheral as _, ScanFilter,
6    ValueNotification, WriteType,
7};
8use btleplug::platform::{Manager, Peripheral};
9use futures::Stream;
10use log::{debug, error, trace, warn};
11use thiserror::Error;
12use tokio::select;
13use tokio::time::sleep;
14use tokio_stream::StreamExt;
15use uuid::Uuid;
16
17use crate::trim_packets;
18
19use vex_cdc::{Decode, DecodeError, Encode, FixedStringSizeError, cdc2::Cdc2Ack};
20
21use super::{CheckHeader, Connection, ConnectionType, RawPacket};
22
23/// The BLE GATT Service that V5 Brains provide
24pub const V5_SERVICE: Uuid = Uuid::from_u128(0x08590f7e_db05_467e_8757_72f6faeb13d5);
25
26/// System port GATT characteristic
27pub const CHARACTERISTIC_SYSTEM_TX: Uuid = Uuid::from_u128(0x08590f7e_db05_467e_8757_72f6faeb1306); // WRITE_WITHOUT_RESPONSE | NOTIFY | INDICATE
28pub const CHARACTERISTIC_SYSTEM_RX: Uuid = Uuid::from_u128(0x08590f7e_db05_467e_8757_72f6faeb13f5); // WRITE_WITHOUT_RESPONSE | WRITE | NOTIFY
29
30/// User port GATT characteristic
31pub const CHARACTERISTIC_USER_TX: Uuid = Uuid::from_u128(0x08590f7e_db05_467e_8757_72f6faeb1316); // WRITE_WITHOUT_RESPONSE | NOTIFY | INDICATE
32pub const CHARACTERISTIC_USER_RX: Uuid = Uuid::from_u128(0x08590f7e_db05_467e_8757_72f6faeb1326); // WRITE_WITHOUT_RESPONSE | WRITE | NOTIF
33
34/// PIN authentication characteristic
35pub const CHARACTERISTIC_PAIRING: Uuid = Uuid::from_u128(0x08590f7e_db05_467e_8757_72f6faeb13e5); // READ | WRITE_WITHOUT_RESPONSE | WRITE
36
37pub const UNPAIRED_MAGIC: u32 = 0xdeadface;
38
39#[derive(Debug, Clone)]
40pub struct BluetoothDevice(pub Peripheral);
41
42impl BluetoothDevice {
43    pub async fn connect(&self) -> Result<BluetoothConnection, BluetoothError> {
44        BluetoothConnection::open(self.clone()).await
45    }
46}
47
48/// Discover and locate bluetooth-compatible V5 peripherals.
49pub async fn find_devices(
50    scan_time: Duration,
51    max_device_count: Option<usize>,
52) -> Result<Vec<BluetoothDevice>, BluetoothError> {
53    // Create a new bluetooth device manager.
54    let manager = Manager::new().await?;
55
56    // Use the first adapter we can find.
57    let adapter = if let Some(adapter) = manager.adapters().await?.into_iter().next() {
58        adapter
59    } else {
60        // No bluetooth adapters were found.
61        return Err(BluetoothError::NoBluetoothAdapter);
62    };
63
64    // Our bluetooth adapter will give us an event stream that can tell us when
65    // a device is discovered. We can use this to get information on when a scan
66    // has found a device.
67    let mut events = adapter.events().await?;
68
69    // List of devices that we'll add to during discovery.
70    let mut devices = Vec::<BluetoothDevice>::new();
71
72    // Scan for peripherals using the V5 service UUID.
73    let scan_start_time = Instant::now();
74    adapter
75        .start_scan(ScanFilter {
76            services: vec![V5_SERVICE],
77        })
78        .await?;
79
80    // Listen for events. When the adapter indicates that a device has been discovered,
81    // we'll ensure that the peripheral is correct and add it to our device list.
82    while let Some(event) = events.next().await {
83        match event {
84            CentralEvent::DeviceDiscovered(id) | CentralEvent::DeviceUpdated(id) => {
85                let peripheral = adapter.peripheral(&id).await?;
86
87                if let Some(properties) = peripheral.properties().await? {
88                    if properties.services.contains(&V5_SERVICE) {
89                        // Assuming the peripheral contains the V5 service UUID, we have a brain.
90                        debug!("Found V5 brain at {}", peripheral.address());
91
92                        devices.push(BluetoothDevice(peripheral));
93
94                        // Break the discovery loop if we have found enough devices.
95                        if let Some(count) = max_device_count {
96                            if devices.len() == count {
97                                break;
98                            }
99                        }
100                    }
101                }
102            }
103            _ => {}
104        }
105
106        // Also break if we've exceeded the provided scan time.
107        if scan_start_time.elapsed() > scan_time {
108            break;
109        }
110    }
111
112    debug!(
113        "Found {} devices in {:?}",
114        devices.len(),
115        scan_start_time.elapsed()
116    );
117
118    Ok(devices)
119}
120
121pub struct BluetoothConnection {
122    pub peripheral: Peripheral,
123    pub system_tx: Characteristic,
124    pub system_rx: Characteristic,
125    pub user_tx: Characteristic,
126    pub user_rx: Characteristic,
127    pub pairing: Characteristic,
128
129    notification_stream: Pin<Box<dyn Stream<Item = ValueNotification> + Send>>,
130    incoming_packets: Vec<RawPacket>,
131}
132
133impl BluetoothConnection {
134    pub const MAX_PACKET_SIZE: usize = 244;
135
136    pub async fn open(device: BluetoothDevice) -> Result<Self, BluetoothError> {
137        let peripheral = device.0;
138
139        if !peripheral.is_connected().await? {
140            peripheral.connect().await?;
141        } else {
142            warn!("Peripheral already connected?");
143        }
144
145        peripheral.discover_services().await?;
146
147        let mut system_tx: Option<Characteristic> = None;
148        let mut system_rx: Option<Characteristic> = None;
149        let mut user_tx: Option<Characteristic> = None;
150        let mut user_rx: Option<Characteristic> = None;
151        let mut pairing: Option<Characteristic> = None;
152
153        for characteric in peripheral.characteristics() {
154            match characteric.uuid {
155                CHARACTERISTIC_SYSTEM_TX => {
156                    system_tx = Some(characteric);
157                }
158                CHARACTERISTIC_SYSTEM_RX => {
159                    system_rx = Some(characteric);
160                }
161                CHARACTERISTIC_USER_TX => {
162                    user_tx = Some(characteric);
163                }
164                CHARACTERISTIC_USER_RX => {
165                    user_rx = Some(characteric);
166                }
167                CHARACTERISTIC_PAIRING => {
168                    pairing = Some(characteric);
169                }
170                _ => {}
171            }
172        }
173
174        let connection = Self {
175            notification_stream: peripheral.notifications().await?,
176            peripheral,
177            system_tx: system_tx.ok_or(BluetoothError::MissingCharacteristic)?,
178            system_rx: system_rx.ok_or(BluetoothError::MissingCharacteristic)?,
179            user_tx: user_tx.ok_or(BluetoothError::MissingCharacteristic)?,
180            user_rx: user_rx.ok_or(BluetoothError::MissingCharacteristic)?,
181            pairing: pairing.ok_or(BluetoothError::MissingCharacteristic)?,
182            incoming_packets: Vec::new(),
183        };
184
185        connection
186            .peripheral
187            .subscribe(&connection.system_tx)
188            .await?;
189        connection.peripheral.subscribe(&connection.user_tx).await?;
190
191        Ok(connection)
192    }
193
194    pub async fn is_paired(&self) -> Result<bool, BluetoothError> {
195        let auth_bytes = self.peripheral.read(&self.pairing).await?;
196        Ok(u32::from_be_bytes(auth_bytes[0..4].try_into().unwrap()) != UNPAIRED_MAGIC)
197    }
198
199    pub async fn request_pairing(&mut self) -> Result<(), BluetoothError> {
200        self.peripheral
201            .write(
202                &self.pairing,
203                &[0xFF, 0xFF, 0xFF, 0xFF],
204                WriteType::WithoutResponse,
205            )
206            .await?;
207
208        Ok(())
209    }
210
211    pub async fn authenticate_pairing(&mut self, pin: [u8; 4]) -> Result<(), BluetoothError> {
212        self.peripheral
213            .write(&self.pairing, &pin, WriteType::WithoutResponse)
214            .await?;
215
216        let read = self.peripheral.read(&self.pairing).await?;
217
218        if read != pin {
219            return Err(BluetoothError::IncorrectPin);
220        }
221
222        Ok(())
223    }
224
225    async fn receive_one_packet(&mut self) -> Result<(), BluetoothError> {
226        loop {
227            let Some(notification) = self.notification_stream.next().await else {
228                return Err(BluetoothError::NoResponse);
229            };
230
231            if notification.uuid == CHARACTERISTIC_SYSTEM_TX {
232                let data = notification.value;
233                trace!("received packet: {:x?}", data);
234                let packet = RawPacket::new(data);
235                self.incoming_packets.push(packet);
236                break;
237            }
238        }
239
240        Ok(())
241    }
242}
243
244impl Connection for BluetoothConnection {
245    type Error = BluetoothError;
246
247    fn connection_type(&self) -> ConnectionType {
248        ConnectionType::Bluetooth
249    }
250
251    async fn send(&mut self, packet: impl Encode) -> Result<(), BluetoothError> {
252        if !self.is_paired().await? {
253            return Err(BluetoothError::PairingRequired);
254        }
255
256        // Encode the packet
257        let mut encoded = vec![0; packet.size()];
258        packet.encode(&mut encoded);
259
260        trace!("sent packet: {:x?}", encoded);
261
262        // Write the packet to the system rx characteristic.
263        self.peripheral
264            .write(&self.system_rx, &encoded, WriteType::WithoutResponse)
265            .await?;
266
267        Ok(())
268    }
269
270    async fn recv<P: Decode + CheckHeader>(
271        &mut self,
272        timeout: Duration,
273    ) -> Result<P, BluetoothError> {
274        // Return an error if the right packet is not received within the timeout
275        select! {
276            result = async {
277                loop {
278                    for packet in self.incoming_packets.iter_mut() {
279                        if packet.check_header::<P>() {
280                            match packet.decode_and_use::<P>() {
281                                Ok(decoded) => {
282                                    trim_packets(&mut self.incoming_packets);
283                                    return Ok(decoded);
284                                }
285                                Err(e) => {
286                                    error!("Failed to decode packet with valid header: {}", e);
287                                    packet.used = true;
288                                    return Err(BluetoothError::DecodeError(e));
289                                }
290                            }
291                        }
292                    }
293                    trim_packets(&mut self.incoming_packets);
294                    self.receive_one_packet().await?;
295                }
296            } => result,
297            _ = sleep(timeout) => Err(BluetoothError::Timeout)
298        }
299    }
300
301    async fn read_user(&mut self, buf: &mut [u8]) -> Result<usize, BluetoothError> {
302        let value = self.peripheral.read(&self.user_tx).await?;
303        let n = value.len().min(buf.len());
304        buf[..n].copy_from_slice(&value[..n]);
305
306        Ok(n)
307    }
308
309    async fn write_user(&mut self, buf: &[u8]) -> Result<usize, BluetoothError> {
310        self.peripheral
311            .write(&self.user_rx, buf, WriteType::WithoutResponse)
312            .await?;
313        Ok(buf.len())
314    }
315}
316
317#[derive(Error, Debug)]
318pub enum BluetoothError {
319    #[error("IO Error: {0}")]
320    IoError(#[from] std::io::Error),
321    #[error("Packet decoding error: {0}")]
322    DecodeError(#[from] DecodeError),
323    #[error("Packet timeout")]
324    Timeout,
325    #[error("NACK received: {0:?}")]
326    Nack(#[from] Cdc2Ack),
327    #[error("Bluetooth Error")]
328    Btleplug(#[from] btleplug::Error),
329    #[error("No response received over bluetooth")]
330    NoResponse,
331    #[error("No Bluetooth Adapter Found")]
332    NoBluetoothAdapter,
333    #[error("Expected a Bluetooth characteristic that didn't exist")]
334    MissingCharacteristic,
335    #[error("Authentication PIN code was incorrect")]
336    IncorrectPin,
337    #[error("Pairing is required")]
338    PairingRequired,
339    #[error(transparent)]
340    FixedStringSizeError(#[from] FixedStringSizeError),
341}