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    /// Set the discoverable property
131    async fn set_discoverable(&self, d: bool) -> Result<(), ()>;
132}
133
134/// The pairing status of a bluetooth device
135pub enum PairingStatus {
136    /// The device is not paired
137    NotPaired,
138    /// The device is in the pairing process
139    Pairing,
140    /// The device is paired
141    Paired,
142    /// The status is unknown or invalid
143    Unknown,
144}
145
146/// The trait that all bluetooth devices must implement
147#[enum_dispatch::enum_dispatch]
148pub trait BluetoothDeviceTrait {
149    /// Get all known uuids for this device
150    fn get_uuids(&mut self) -> Result<Vec<BluetoothUuid>, std::io::Error>;
151
152    /// Retrieve the device name
153    fn get_name(&self) -> Result<String, std::io::Error>;
154
155    /// Retrieve the device address
156    fn get_address(&mut self) -> Result<String, std::io::Error>;
157
158    /// Retrieve the device pairing status
159    fn get_pair_state(&self) -> Result<PairingStatus, std::io::Error>;
160
161    /// Attempt to get an rfcomm socket for the given uuid and seciruty setting
162    fn get_rfcomm_socket(
163        &mut self,
164        uuid: BluetoothUuid,
165        is_secure: bool,
166    ) -> Result<BluetoothRfcommSocket, String>;
167}
168
169/// A bluetooth device
170#[enum_dispatch::enum_dispatch(BluetoothDeviceTrait)]
171pub enum BluetoothDevice {
172    /// Bluetooth device on android
173    #[cfg(target_os = "android")]
174    Android(android::BluetoothDevice),
175    /// Bluetooth device on linux using the bluez library
176    #[cfg(target_os = "linux")]
177    Bluez(bluer::Device),
178}
179
180/// Represents a bluetooth adapter that communicates to bluetooth devices
181#[enum_dispatch::enum_dispatch(BluetoothAdapterTrait)]
182pub enum BluetoothAdapter {
183    /// The bluetooth adapter for android systems
184    #[cfg(target_os = "android")]
185    Android(android::Bluetooth),
186    /// On linux, bluetooth adapter using the bluez library
187    #[cfg(target_os = "linux")]
188    Bluez(linux::BluetoothHandler),
189}
190
191/// A builder for `BluetoothAdapter`
192pub struct BluetoothAdapterBuilder {
193    /// The androidapp object
194    #[cfg(target_os = "android")]
195    app: Option<AndroidApp>,
196    /// The sender to send messages to the bluetooth host
197    s: Option<tokio::sync::mpsc::Sender<MessageToBluetoothHost>>,
198}
199
200impl Default for BluetoothAdapterBuilder {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206impl BluetoothAdapterBuilder {
207    /// Construct a new self
208    pub fn new() -> Self {
209        Self {
210            #[cfg(target_os = "android")]
211            app: None,
212            s: None,
213        }
214    }
215
216    /// Put the required `AndroidApp` object into the builder
217    #[cfg(target_os = "android")]
218    pub fn with_android_app(&mut self, app: AndroidApp) {
219        self.app = Some(app);
220    }
221
222    /// Add the sender to the builder
223    pub fn with_sender(&mut self, s: tokio::sync::mpsc::Sender<MessageToBluetoothHost>) {
224        self.s = Some(s);
225    }
226
227    /// Do the build
228    pub async fn build(self) -> Result<BluetoothAdapter, String> {
229        #[cfg(target_os = "android")]
230        {
231            let java = android::Java::make(self.app.unwrap());
232            return Ok(BluetoothAdapter::Android(android::Bluetooth::new(
233                Arc::new(Mutex::new(java)),
234            )));
235        }
236        #[cfg(target_os = "linux")]
237        {
238            return Ok(BluetoothAdapter::Bluez(
239                linux::BluetoothHandler::new(self.s.unwrap()).await?,
240            ));
241        }
242        Err("No builders available".to_string())
243    }
244}
245
246#[cfg(target_os = "android")]
247impl<'a> BluetoothSocket<'a> {
248    /// Attempts to connect to a remote device. When connected, it creates a
249    /// backgrond thread for reading data, which terminates itself on disconnection.
250    /// Do not reuse the socket after disconnection, because the underlying OS
251    /// implementation is probably incapable of reconnecting the device, just like
252    /// `java.net.Socket`.
253    pub fn connect(&mut self) -> Result<(), std::io::Error> {
254        self.0.connect()
255    }
256}
257
258/// An active stream for bluetooth communications
259pub enum BluetoothStream {
260    /// On linux, a stream using the bluez library
261    #[cfg(target_os = "linux")]
262    Bluez(std::pin::Pin<Box<bluer::rfcomm::Stream>>),
263    /// Android code for a bluetooth stream
264    #[cfg(target_os = "android")]
265    Android(std::pin::Pin<Box<android::RfcommStream>>),
266}
267
268impl tokio::io::AsyncRead for BluetoothStream {
269    fn poll_read(
270        self: std::pin::Pin<&mut Self>,
271        cx: &mut std::task::Context<'_>,
272        buf: &mut tokio::io::ReadBuf<'_>,
273    ) -> std::task::Poll<std::io::Result<()>> {
274        match self.get_mut() {
275            #[cfg(target_os = "linux")]
276            BluetoothStream::Bluez(s) => s.as_mut().poll_read(cx, buf),
277            #[cfg(target_os = "android")]
278            BluetoothStream::Android(s) => s.as_mut().poll_read(cx, buf),
279        }
280    }
281}
282
283impl tokio::io::AsyncWrite for BluetoothStream {
284    fn poll_write(
285        self: std::pin::Pin<&mut Self>,
286        cx: &mut std::task::Context<'_>,
287        buf: &[u8],
288    ) -> std::task::Poll<Result<usize, std::io::Error>> {
289        match self.get_mut() {
290            #[cfg(target_os = "linux")]
291            BluetoothStream::Bluez(s) => s.as_mut().poll_write(cx, buf),
292            #[cfg(target_os = "android")]
293            BluetoothStream::Android(s) => s.as_mut().poll_write(cx, buf),
294        }
295    }
296
297    fn poll_flush(
298        self: std::pin::Pin<&mut Self>,
299        cx: &mut std::task::Context<'_>,
300    ) -> std::task::Poll<Result<(), std::io::Error>> {
301        match self.get_mut() {
302            #[cfg(target_os = "linux")]
303            BluetoothStream::Bluez(s) => s.as_mut().poll_flush(cx),
304            #[cfg(target_os = "android")]
305            BluetoothStream::Android(s) => s.as_mut().poll_flush(cx),
306        }
307    }
308
309    fn poll_shutdown(
310        self: std::pin::Pin<&mut Self>,
311        cx: &mut std::task::Context<'_>,
312    ) -> std::task::Poll<Result<(), std::io::Error>> {
313        match self.get_mut() {
314            #[cfg(target_os = "linux")]
315            BluetoothStream::Bluez(s) => s.as_mut().poll_shutdown(cx),
316            #[cfg(target_os = "android")]
317            BluetoothStream::Android(s) => s.as_mut().poll_shutdown(cx),
318        }
319    }
320}
321
322/// The trait for bluetooth rfcomm objects that can be connected or accepted
323#[enum_dispatch::enum_dispatch]
324pub trait BluetoothRfcommConnectableTrait {
325    /// Accept a connection from a bluetooth peer
326    async fn accept(self) -> Result<BluetoothStream, String>;
327}
328
329/// A bluetooth profile for rfcomm channels
330#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableTrait)]
331pub enum BluetoothRfcommConnectable {
332    /// The bluez library in linux is responsible for the profile
333    #[cfg(target_os = "linux")]
334    Bluez(bluer::rfcomm::ConnectRequest),
335}
336
337/// Allows building an object to connect to bluetooth devices
338#[enum_dispatch::enum_dispatch]
339pub trait BluetoothRfcommProfileTrait {
340    /// Get an object in order to accept a connection from or connect to a bluetooth peer
341    async fn connectable(&mut self) -> Result<BluetoothRfcommConnectable, String>;
342}
343
344/// A bluetooth profile for rfcomm channels
345#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileTrait)]
346pub enum BluetoothRfcommProfile {
347    /// Android rfcomm profile
348    #[cfg(target_os = "android")]
349    Android(android::BluetoothRfcommProfile),
350    /// The bluez library in linux is responsible for the profile
351    #[cfg(target_os = "linux")]
352    Bluez(bluer::rfcomm::ProfileHandle),
353}
354
355/// The common functions for all bluetooth rfcomm sockets
356#[enum_dispatch::enum_dispatch]
357pub trait BluetoothRfcommSocketTrait {}
358
359/// A bluetooth rfcomm socket
360#[enum_dispatch::enum_dispatch(BluetoothRfcommSocketTrait)]
361pub enum BluetoothRfcommSocket<'a> {
362    /// The android based rfcomm socket
363    #[cfg(target_os = "android")]
364    Android(&'a mut android::BluetoothSocket),
365    /// Linux using bluez library
366    #[cfg(target_os = "linux")]
367    Bluez(&'a mut linux::BluetoothRfcommSocket),
368}