bluetooth_rust/
lib.rs

1#![deny(missing_docs)]
2#![deny(clippy::missing_docs_in_private_items)]
3#![warn(unused_extern_crates)]
4
5//! This library is intended to eventually be a cross-platform bluetooth handling platform
6//! Android portions adapted from <https://github.com/wuwbobo2021/android-bluetooth-serial-rs>
7
8#[cfg(target_os = "android")]
9use std::sync::Arc;
10#[cfg(target_os = "android")]
11use std::sync::Mutex;
12#[cfg(target_os = "android")]
13mod android;
14#[cfg(target_os = "android")]
15use android::Java;
16#[cfg(target_os = "android")]
17use winit::platform::android::activity::AndroidApp;
18
19#[cfg(target_os = "linux")]
20mod linux;
21
22mod bluetooth_uuid;
23pub use bluetooth_uuid::BluetoothUuid;
24
25/// Commands issued to the library
26#[derive(Debug, serde::Deserialize, serde::Serialize)]
27pub enum BluetoothCommand {
28    /// Detect all bluetooth adapters present on the system
29    DetectAdapters,
30    /// Find out how many bluetooth adapters are detected
31    QueryNumAdapters,
32}
33
34/// Messages that can be sent specifically to the app user hosting the bluetooth controls
35pub enum MessageToBluetoothHost {
36    /// The passkey used for pairing devices
37    DisplayPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
38    /// The passkey to confirm for pairing
39    ConfirmPasskey(u32, tokio::sync::mpsc::Sender<ResponseToPasskey>),
40    /// Cancal the passkey display
41    CancelDisplayPasskey,
42}
43
44#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
45/// Messages that are send directly from the bluetooth host
46pub enum MessageFromBluetoothHost {
47    /// A response about the active pairing passkey
48    PasskeyMessage(ResponseToPasskey),
49}
50
51#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
52/// The user response to a bluetooth passkey
53pub enum ResponseToPasskey {
54    /// The passkey is accepted
55    Yes,
56    /// The passkey is not accepted
57    No,
58    /// The process is canceled by the user
59    Cancel,
60    /// Waiting on the user to decide
61    Waiting,
62}
63
64/// Responses issued by the library
65pub enum BluetoothResponse {
66    /// The number of bluetooth adapters detected
67    Adapters(usize),
68}
69
70/// A bluetooth device
71#[cfg(target_os = "android")]
72pub struct BluetoothSocket<'a>(&'a mut android::BluetoothSocket);
73
74/// Settings for an rfcomm profile
75#[derive(Clone)]
76pub struct BluetoothRfcommProfileSettings {
77    /// The uuid for the profile
78    pub uuid: String,
79    /// User readable name for the profile
80    pub name: Option<String>,
81    /// The service uuid for the profile (can be the same as service)
82    pub service_uuid: Option<String>,
83    /// The channel to use
84    pub channel: Option<u16>,
85    /// PSM number used for UUIDS and SDP (if applicable)
86    pub psm: Option<u16>,
87    /// Is authentication required for a connection
88    pub authenticate: Option<bool>,
89    /// Is authorization required for a connection
90    pub authorize: Option<bool>,
91    /// For client profiles, This will force connection of the channel when a remote device is connected
92    pub auto_connect: Option<bool>,
93    /// manual SDP record
94    pub sdp_record: Option<String>,
95    /// SDP version
96    pub sdp_version: Option<u16>,
97    /// SDP profile features
98    pub sdp_features: Option<u16>,
99}
100
101/// The trait that implements managing when bluetooth discovery is enabled
102#[enum_dispatch::enum_dispatch]
103pub trait BluetoothDiscoveryTrait {}
104
105/// The trait for the object that manages bluetooth discovery
106#[enum_dispatch::enum_dispatch(BluetoothDiscoveryTrait)]
107pub enum BluetoothDiscovery<'a> {
108    /// The android version
109    #[cfg(target_os = "android")]
110    Android(android::BluetoothDiscovery<'a>),
111    /// Linux bluez library implementation
112    #[cfg(target_os = "linux")]
113    Bluez(linux::BluetoothDiscovery<'a>),
114}
115
116/// Common functionality for the bluetooth adapter
117#[enum_dispatch::enum_dispatch]
118pub trait BluetoothAdapterTrait {
119    /// Attempt to register a new rfcomm profile
120    async fn register_rfcomm_profile(
121        &self,
122        settings: BluetoothRfcommProfileSettings,
123    ) -> Result<BluetoothRfcommProfile, String>;
124    ///Get a list of paired bluetooth devices
125    fn get_paired_devices(&self) -> Option<Vec<BluetoothDevice>>;
126    /// Start discovery of bluetooth devices. Run this and drop the result to cancel discovery
127    fn start_discovery(&self) -> BluetoothDiscovery;
128    /// Get the mac addresses of all bluetooth adapters for the system
129    async fn addresses(&self) -> Vec<[u8;6]>;
130}
131
132/// The pairing status of a bluetooth device
133pub enum PairingStatus {
134    /// The device is not paired
135    NotPaired,
136    /// The device is in the pairing process
137    Pairing,
138    /// The device is paired
139    Paired,
140    /// The status is unknown or invalid
141    Unknown,
142}
143
144/// The trait that all bluetooth devices must implement
145#[enum_dispatch::enum_dispatch]
146pub trait BluetoothDeviceTrait {
147    /// Get all known uuids for this device
148    fn get_uuids(&mut self) -> Result<Vec<BluetoothUuid>, std::io::Error>;
149
150    /// Retrieve the device name
151    fn get_name(&self) -> Result<String, std::io::Error>;
152
153    /// Retrieve the device address
154    fn get_address(&mut self) -> Result<String, std::io::Error>;
155
156    /// Retrieve the device pairing status
157    fn get_pair_state(&self) -> Result<PairingStatus, std::io::Error>;
158
159    /// Attempt to get an rfcomm socket for the given uuid and seciruty setting
160    fn get_rfcomm_socket(
161        &mut self,
162        uuid: BluetoothUuid,
163        is_secure: bool,
164    ) -> Result<BluetoothRfcommSocket, String>;
165}
166
167/// A bluetooth device
168#[enum_dispatch::enum_dispatch(BluetoothDeviceTrait)]
169pub enum BluetoothDevice {
170    /// Bluetooth device on android
171    #[cfg(target_os = "android")]
172    Android(android::BluetoothDevice),
173    /// Bluetooth device on linux using the bluez library
174    #[cfg(target_os = "linux")]
175    Bluez(bluer::Device),
176}
177
178/// Represents a bluetooth adapter that communicates to bluetooth devices
179#[enum_dispatch::enum_dispatch(BluetoothAdapterTrait)]
180pub enum BluetoothAdapter {
181    /// The bluetooth adapter for android systems
182    #[cfg(target_os = "android")]
183    Android(android::Bluetooth),
184    /// On linux, bluetooth adapter using the bluez library
185    #[cfg(target_os = "linux")]
186    Bluez(linux::BluetoothHandler),
187}
188
189/// A builder for `BluetoothAdapter`
190pub struct BluetoothAdapterBuilder {
191    /// The androidapp object
192    #[cfg(target_os = "android")]
193    app: Option<AndroidApp>,
194    /// The sender to send messages to the bluetooth host
195    s: Option<tokio::sync::mpsc::Sender<MessageToBluetoothHost>>,
196}
197
198impl Default for BluetoothAdapterBuilder {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204impl BluetoothAdapterBuilder {
205    /// Construct a new self
206    pub fn new() -> Self {
207        Self {
208            #[cfg(target_os = "android")]
209            app: None,
210            s: None,
211        }
212    }
213
214    /// Put the required `AndroidApp` object into the builder
215    #[cfg(target_os = "android")]
216    pub fn with_android_app(&mut self, app: AndroidApp) {
217        self.app = Some(app);
218    }
219
220    /// Add the sender to the builder
221    pub fn with_sender(&mut self, s: tokio::sync::mpsc::Sender<MessageToBluetoothHost>) {
222        self.s = Some(s);
223    }
224
225    /// Do the build
226    pub async fn build(self) -> Result<BluetoothAdapter, String> {
227        #[cfg(target_os = "android")]
228        {
229            let java = android::Java::make(self.app.unwrap());
230            return Ok(BluetoothAdapter::Android(android::Bluetooth::new(
231                Arc::new(Mutex::new(java)),
232            )));
233        }
234        #[cfg(target_os = "linux")]
235        {
236            return Ok(BluetoothAdapter::Bluez(
237                linux::BluetoothHandler::new(self.s.unwrap()).await?,
238            ));
239        }
240        Err("No builders available".to_string())
241    }
242}
243
244#[cfg(target_os = "android")]
245impl<'a> BluetoothSocket<'a> {
246    /// Attempts to connect to a remote device. When connected, it creates a
247    /// backgrond thread for reading data, which terminates itself on disconnection.
248    /// Do not reuse the socket after disconnection, because the underlying OS
249    /// implementation is probably incapable of reconnecting the device, just like
250    /// `java.net.Socket`.
251    pub fn connect(&mut self) -> Result<(), std::io::Error> {
252        self.0.connect()
253    }
254}
255
256/// An active stream for bluetooth communications
257pub enum BluetoothStream {
258    /// On linux, a stream using the bluez library
259    #[cfg(target_os = "linux")]
260    Bluez(std::pin::Pin<Box<bluer::rfcomm::Stream>>),
261    /// Android code for a bluetooth stream
262    #[cfg(target_os = "android")]
263    Android(std::pin::Pin<Box<android::RfcommStream>>),
264}
265
266impl tokio::io::AsyncRead for BluetoothStream {
267    fn poll_read(
268        self: std::pin::Pin<&mut Self>,
269        cx: &mut std::task::Context<'_>,
270        buf: &mut tokio::io::ReadBuf<'_>,
271    ) -> std::task::Poll<std::io::Result<()>> {
272        match self.get_mut() {
273            #[cfg(target_os = "linux")]
274            BluetoothStream::Bluez(s) => s.as_mut().poll_read(cx, buf),
275            #[cfg(target_os = "android")]
276            BluetoothStream::Android(s) => s.as_mut().poll_read(cx, buf),
277        }
278    }
279}
280
281impl tokio::io::AsyncWrite for BluetoothStream {
282    fn poll_write(
283        self: std::pin::Pin<&mut Self>,
284        cx: &mut std::task::Context<'_>,
285        buf: &[u8],
286    ) -> std::task::Poll<Result<usize, std::io::Error>> {
287        match self.get_mut() {
288            #[cfg(target_os = "linux")]
289            BluetoothStream::Bluez(s) => s.as_mut().poll_write(cx, buf),
290            #[cfg(target_os = "android")]
291            BluetoothStream::Android(s) => s.as_mut().poll_write(cx, buf),
292        }
293    }
294
295    fn poll_flush(
296        self: std::pin::Pin<&mut Self>,
297        cx: &mut std::task::Context<'_>,
298    ) -> std::task::Poll<Result<(), std::io::Error>> {
299        match self.get_mut() {
300            #[cfg(target_os = "linux")]
301            BluetoothStream::Bluez(s) => s.as_mut().poll_flush(cx),
302            #[cfg(target_os = "android")]
303            BluetoothStream::Android(s) => s.as_mut().poll_flush(cx),
304        }
305    }
306
307    fn poll_shutdown(
308        self: std::pin::Pin<&mut Self>,
309        cx: &mut std::task::Context<'_>,
310    ) -> std::task::Poll<Result<(), std::io::Error>> {
311        match self.get_mut() {
312            #[cfg(target_os = "linux")]
313            BluetoothStream::Bluez(s) => s.as_mut().poll_shutdown(cx),
314            #[cfg(target_os = "android")]
315            BluetoothStream::Android(s) => s.as_mut().poll_shutdown(cx),
316        }
317    }
318}
319
320/// The trait for bluetooth rfcomm objects that can be connected or accepted
321#[enum_dispatch::enum_dispatch]
322pub trait BluetoothRfcommConnectableTrait {
323    /// Accept a connection from a bluetooth peer
324    async fn accept(self) -> Result<BluetoothStream, String>;
325}
326
327/// A bluetooth profile for rfcomm channels
328#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableTrait)]
329pub enum BluetoothRfcommConnectable {
330    /// The bluez library in linux is responsible for the profile
331    #[cfg(target_os = "linux")]
332    Bluez(bluer::rfcomm::ConnectRequest),
333}
334
335/// Allows building an object to connect to bluetooth devices
336#[enum_dispatch::enum_dispatch]
337pub trait BluetoothRfcommProfileTrait {
338    /// Get an object in order to accept a connection from or connect to a bluetooth peer
339    async fn connectable(&mut self) -> Result<BluetoothRfcommConnectable, String>;
340}
341
342/// A bluetooth profile for rfcomm channels
343#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileTrait)]
344pub enum BluetoothRfcommProfile {
345    /// Android rfcomm profile
346    #[cfg(target_os = "android")]
347    Android(android::BluetoothRfcommProfile),
348    /// The bluez library in linux is responsible for the profile
349    #[cfg(target_os = "linux")]
350    Bluez(bluer::rfcomm::ProfileHandle),
351}
352
353/// The common functions for all bluetooth rfcomm sockets
354#[enum_dispatch::enum_dispatch]
355pub trait BluetoothRfcommSocketTrait {}
356
357/// A bluetooth rfcomm socket
358#[enum_dispatch::enum_dispatch(BluetoothRfcommSocketTrait)]
359pub enum BluetoothRfcommSocket<'a> {
360    /// The android based rfcomm socket
361    #[cfg(target_os = "android")]
362    Android(&'a mut android::BluetoothSocket),
363    /// Linux using bluez library
364    #[cfg(target_os = "linux")]
365    Bluez(&'a mut linux::BluetoothRfcommSocket),
366}