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
75pub struct BluetoothRfcommProfileSettings {
76    /// The uuid for the profile
77    pub uuid: String,
78    /// User readable name for the profile
79    pub name: Option<String>,
80    /// The service uuid for the profile (can be the same as service)
81    pub service_uuid: Option<String>,
82    /// The channel to use
83    pub channel: Option<u16>,
84    /// PSM number used for UUIDS and SDP (if applicable)
85    pub psm: Option<u16>,
86    /// Is authentication required for a connection
87    pub authenticate: Option<bool>,
88    /// Is authorization required for a connection
89    pub authorize: Option<bool>,
90    /// For client profiles, This will force connection of the channel when a remote device is connected
91    pub auto_connect: Option<bool>,
92    /// manual SDP record
93    pub sdp_record: Option<String>,
94    /// SDP version
95    pub sdp_version: Option<u16>,
96    /// SDP profile features
97    pub sdp_features: Option<u16>,
98}
99
100/// The trait that implements managing when bluetooth discovery is enabled
101#[enum_dispatch::enum_dispatch]
102pub trait BluetoothDiscoveryTrait {}
103
104/// The trait for the object that manages bluetooth discovery
105#[enum_dispatch::enum_dispatch(BluetoothDiscoveryTrait)]
106pub enum BluetoothDiscovery<'a> {
107    /// The android version
108    #[cfg(target_os = "android")]
109    Android(android::BluetoothDiscovery<'a>),
110    /// Linux bluez library implementation
111    #[cfg(target_os = "linux")]
112    Bluez(linux::BluetoothDiscovery<'a>),
113}
114
115/// Common functionality for the bluetooth adapter
116#[enum_dispatch::enum_dispatch]
117pub trait BluetoothAdapterTrait {
118    /// Attempt to register a new rfcomm profile
119    async fn register_rfcomm_profile(
120        &self,
121        settings: BluetoothRfcommProfileSettings,
122    ) -> Result<BluetoothRfcommProfile, String>;
123    ///Get a list of paired bluetooth devices
124    fn get_paired_devices(&mut self) -> Option<Vec<BluetoothDevice>>;
125    /// Start discovery of bluetooth devices. Run this and drop the result to cancel discovery
126    fn start_discovery(&mut self) -> BluetoothDiscovery;
127}
128
129/// The pairing status of a bluetooth device
130pub enum PairingStatus {
131    /// The device is not paired
132    NotPaired,
133    /// The device is in the pairing process
134    Pairing,
135    /// The device is paired
136    Paired,
137    /// The status is unknown or invalid
138    Unknown,
139}
140
141/// The trait that all bluetooth devices must implement
142#[enum_dispatch::enum_dispatch]
143pub trait BluetoothDeviceTrait {
144    /// Get all known uuids for this device
145    fn get_uuids(&mut self) -> Result<Vec<BluetoothUuid>, std::io::Error>;
146
147    /// Retrieve the device name
148    fn get_name(&self) -> Result<String, std::io::Error>;
149
150    /// Retrieve the device address
151    fn get_address(&mut self) -> Result<String, std::io::Error>;
152
153    /// Retrieve the device pairing status
154    fn get_pair_state(&self) -> Result<PairingStatus, std::io::Error>;
155
156    /// Attempt to get an rfcomm socket for the given uuid and seciruty setting
157    fn get_rfcomm_socket(
158        &mut self,
159        uuid: BluetoothUuid,
160        is_secure: bool,
161    ) -> Result<BluetoothRfcommSocket, String>;
162}
163
164/// A bluetooth device
165#[enum_dispatch::enum_dispatch(BluetoothDeviceTrait)]
166pub enum BluetoothDevice {
167    /// Bluetooth device on android
168    #[cfg(target_os = "android")]
169    Android(android::BluetoothDevice),
170    /// Bluetooth device on linux using the bluez library
171    #[cfg(target_os = "linux")]
172    Bluez(bluer::Device),
173}
174
175/// Represents a bluetooth adapter that communicates to bluetooth devices
176#[enum_dispatch::enum_dispatch(BluetoothAdapterTrait)]
177pub enum BluetoothAdapter {
178    /// The bluetooth adapter for android systems
179    #[cfg(target_os = "android")]
180    Android(android::Bluetooth),
181    /// On linux, bluetooth adapter using the bluez library
182    #[cfg(target_os = "linux")]
183    Bluez(linux::BluetoothHandler),
184}
185
186/// A builder for `BluetoothAdapter`
187pub struct BluetoothAdapterBuilder {
188    /// The androidapp object
189    #[cfg(target_os = "android")]
190    app: Option<AndroidApp>,
191    /// The sender to send messages to the bluetooth host
192    s: Option<tokio::sync::mpsc::Sender<MessageToBluetoothHost>>,
193}
194
195impl Default for BluetoothAdapterBuilder {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201impl BluetoothAdapterBuilder {
202    /// Construct a new self
203    pub fn new() -> Self {
204        Self {
205            #[cfg(target_os = "android")]
206            app: None,
207            s: None,
208        }
209    }
210
211    /// Put the required `AndroidApp` object into the builder
212    #[cfg(target_os = "android")]
213    pub fn with_android_app(&mut self, app: AndroidApp) {
214        self.app = Some(app);
215    }
216
217    /// Add the sender to the builder
218    pub fn with_sender(&mut self, s: tokio::sync::mpsc::Sender<MessageToBluetoothHost>) {
219        self.s = Some(s);
220    }
221
222    /// Do the build
223    pub async fn build(self) -> Result<BluetoothAdapter, String> {
224        #[cfg(target_os = "android")]
225        {
226            let java = android::Java::make(self.app.unwrap());
227            return Ok(BluetoothAdapter::Android(android::Bluetooth::new(
228                Arc::new(Mutex::new(java)),
229            )));
230        }
231        #[cfg(target_os = "linux")]
232        {
233            return Ok(BluetoothAdapter::Bluez(
234                linux::BluetoothHandler::new(self.s.unwrap()).await?,
235            ));
236        }
237        Err("No builders available".to_string())
238    }
239}
240
241#[cfg(target_os = "android")]
242impl<'a> BluetoothSocket<'a> {
243    /// Attempts to connect to a remote device. When connected, it creates a
244    /// backgrond thread for reading data, which terminates itself on disconnection.
245    /// Do not reuse the socket after disconnection, because the underlying OS
246    /// implementation is probably incapable of reconnecting the device, just like
247    /// `java.net.Socket`.
248    pub fn connect(&mut self) -> Result<(), std::io::Error> {
249        self.0.connect()
250    }
251}
252
253/// An active stream for bluetooth communications
254pub enum BluetoothStream {
255    /// On linux, a stream using the bluez library
256    #[cfg(target_os = "linux")]
257    Bluez(std::pin::Pin<Box<bluer::rfcomm::Stream>>),
258    /// Android code for a bluetooth stream
259    #[cfg(target_os = "android")]
260    Android(std::pin::Pin<Box<android::RfcommStream>>),
261}
262
263impl tokio::io::AsyncRead for BluetoothStream {
264    fn poll_read(
265        self: std::pin::Pin<&mut Self>,
266        cx: &mut std::task::Context<'_>,
267        buf: &mut tokio::io::ReadBuf<'_>,
268    ) -> std::task::Poll<std::io::Result<()>> {
269        match self.get_mut() {
270            #[cfg(target_os = "linux")]
271            BluetoothStream::Bluez(s) => s.as_mut().poll_read(cx, buf),
272            #[cfg(target_os = "android")]
273            BluetoothStream::Android(s) => s.as_mut().poll_read(cx, buf),
274        }
275    }
276}
277
278impl tokio::io::AsyncWrite for BluetoothStream {
279    fn poll_write(
280        self: std::pin::Pin<&mut Self>,
281        cx: &mut std::task::Context<'_>,
282        buf: &[u8],
283    ) -> std::task::Poll<Result<usize, std::io::Error>> {
284        match self.get_mut() {
285            #[cfg(target_os = "linux")]
286            BluetoothStream::Bluez(s) => s.as_mut().poll_write(cx, buf),
287            #[cfg(target_os = "android")]
288            BluetoothStream::Android(s) => s.as_mut().poll_write(cx, buf),
289        }
290    }
291
292    fn poll_flush(
293        self: std::pin::Pin<&mut Self>,
294        cx: &mut std::task::Context<'_>,
295    ) -> std::task::Poll<Result<(), std::io::Error>> {
296        match self.get_mut() {
297            #[cfg(target_os = "linux")]
298            BluetoothStream::Bluez(s) => s.as_mut().poll_flush(cx),
299            #[cfg(target_os = "android")]
300            BluetoothStream::Android(s) => s.as_mut().poll_flush(cx),
301        }
302    }
303
304    fn poll_shutdown(
305        self: std::pin::Pin<&mut Self>,
306        cx: &mut std::task::Context<'_>,
307    ) -> std::task::Poll<Result<(), std::io::Error>> {
308        match self.get_mut() {
309            #[cfg(target_os = "linux")]
310            BluetoothStream::Bluez(s) => s.as_mut().poll_shutdown(cx),
311            #[cfg(target_os = "android")]
312            BluetoothStream::Android(s) => s.as_mut().poll_shutdown(cx),
313        }
314    }
315}
316
317/// The trait for bluetooth rfcomm objects that can be connected or accepted
318#[enum_dispatch::enum_dispatch]
319pub trait BluetoothRfcommConnectableTrait {
320    /// Accept a connection from a bluetooth peer
321    async fn accept(self) -> Result<BluetoothStream, String>;
322}
323
324/// A bluetooth profile for rfcomm channels
325#[enum_dispatch::enum_dispatch(BluetoothRfcommConnectableTrait)]
326pub enum BluetoothRfcommConnectable {
327    /// The bluez library in linux is responsible for the profile
328    #[cfg(target_os = "linux")]
329    Bluez(bluer::rfcomm::ConnectRequest),
330}
331
332/// Allows building an object to connect to bluetooth devices
333#[enum_dispatch::enum_dispatch]
334pub trait BluetoothRfcommProfileTrait {
335    /// Get an object in order to accept a connection from or connect to a bluetooth peer
336    async fn connectable(&mut self) -> Result<BluetoothRfcommConnectable, String>;
337}
338
339/// A bluetooth profile for rfcomm channels
340#[enum_dispatch::enum_dispatch(BluetoothRfcommProfileTrait)]
341pub enum BluetoothRfcommProfile {
342    /// Android rfcomm profile
343    #[cfg(target_os = "android")]
344    Android(android::BluetoothRfcommProfile),
345    /// The bluez library in linux is responsible for the profile
346    #[cfg(target_os = "linux")]
347    Bluez(bluer::rfcomm::ProfileHandle),
348}
349
350/// The common functions for all bluetooth rfcomm sockets
351#[enum_dispatch::enum_dispatch]
352pub trait BluetoothRfcommSocketTrait {}
353
354/// A bluetooth rfcomm socket
355#[enum_dispatch::enum_dispatch(BluetoothRfcommSocketTrait)]
356pub enum BluetoothRfcommSocket<'a> {
357    /// The android based rfcomm socket
358    #[cfg(target_os = "android")]
359    Android(&'a mut android::BluetoothSocket),
360    /// Linux using bluez library
361    #[cfg(target_os = "linux")]
362    Bluez(&'a mut linux::BluetoothRfcommSocket),
363}