Skip to main content

fission_shell_winit/
bluetooth.rs

1use fission_core::{
2    BluetoothAdvertiseReceipt, BluetoothAdvertiseRequest, BluetoothAvailability,
3    BluetoothConnectRequest, BluetoothConnection, BluetoothDevice, BluetoothDisconnectRequest,
4    BluetoothError, BluetoothMode, BluetoothPermission, BluetoothPermissionRequest,
5    BluetoothReadRequest, BluetoothReadResult, BluetoothScanRequest, BluetoothScanResult,
6    BluetoothStopAdvertiseRequest, BluetoothWriteRequest, CONNECT_BLUETOOTH_DEVICE,
7    DISCONNECT_BLUETOOTH_DEVICE, GET_BLUETOOTH_AVAILABILITY, READ_BLUETOOTH_CHARACTERISTIC,
8    REQUEST_BLUETOOTH_PERMISSION, SCAN_BLUETOOTH_DEVICES, START_BLUETOOTH_ADVERTISING,
9    STOP_BLUETOOTH_ADVERTISING, WRITE_BLUETOOTH_CHARACTERISTIC,
10};
11use fission_shell::async_host::AsyncRegistry;
12use std::sync::Arc;
13
14/// Host-side Bluetooth provider.
15pub trait BluetoothHost: Send + Sync + 'static {
16    /// Returns adapter, permission, and Bluetooth mode availability.
17    fn availability(&self) -> Result<BluetoothAvailability, BluetoothError>;
18    /// Requests Bluetooth or nearby-device permission from the host.
19    fn request_permission(
20        &self,
21        request: BluetoothPermissionRequest,
22    ) -> Result<BluetoothPermission, BluetoothError>;
23    /// Scans for Bluetooth devices matching the supplied filters.
24    fn scan_devices(
25        &self,
26        request: BluetoothScanRequest,
27    ) -> Result<BluetoothScanResult, BluetoothError>;
28    /// Connects to a Bluetooth device and returns a connection handle.
29    fn connect_device(
30        &self,
31        request: BluetoothConnectRequest,
32    ) -> Result<BluetoothConnection, BluetoothError>;
33    /// Disconnects a previously opened Bluetooth connection.
34    fn disconnect_device(&self, request: BluetoothDisconnectRequest) -> Result<(), BluetoothError>;
35    /// Reads bytes from a characteristic on an active connection.
36    fn read_characteristic(
37        &self,
38        request: BluetoothReadRequest,
39    ) -> Result<BluetoothReadResult, BluetoothError>;
40    /// Writes bytes to a characteristic on an active connection.
41    fn write_characteristic(&self, request: BluetoothWriteRequest) -> Result<(), BluetoothError>;
42    /// Starts Bluetooth advertising where the platform permits it.
43    fn start_advertising(
44        &self,
45        request: BluetoothAdvertiseRequest,
46    ) -> Result<BluetoothAdvertiseReceipt, BluetoothError>;
47    /// Stops a previously started Bluetooth advertisement.
48    fn stop_advertising(
49        &self,
50        request: BluetoothStopAdvertiseRequest,
51    ) -> Result<(), BluetoothError>;
52}
53
54#[derive(Debug, Default)]
55pub struct UnsupportedBluetoothHost;
56
57impl BluetoothHost for UnsupportedBluetoothHost {
58    fn availability(&self) -> Result<BluetoothAvailability, BluetoothError> {
59        Ok(BluetoothAvailability {
60            permission: BluetoothPermission::Denied,
61            enabled: false,
62            supports_classic: false,
63            supports_low_energy: false,
64        })
65    }
66
67    fn request_permission(
68        &self,
69        _request: BluetoothPermissionRequest,
70    ) -> Result<BluetoothPermission, BluetoothError> {
71        Err(BluetoothError::unsupported("request_permission"))
72    }
73
74    fn scan_devices(
75        &self,
76        _request: BluetoothScanRequest,
77    ) -> Result<BluetoothScanResult, BluetoothError> {
78        Err(BluetoothError::unsupported("scan_devices"))
79    }
80
81    fn connect_device(
82        &self,
83        _request: BluetoothConnectRequest,
84    ) -> Result<BluetoothConnection, BluetoothError> {
85        Err(BluetoothError::unsupported("connect_device"))
86    }
87
88    fn disconnect_device(
89        &self,
90        _request: BluetoothDisconnectRequest,
91    ) -> Result<(), BluetoothError> {
92        Err(BluetoothError::unsupported("disconnect_device"))
93    }
94
95    fn read_characteristic(
96        &self,
97        _request: BluetoothReadRequest,
98    ) -> Result<BluetoothReadResult, BluetoothError> {
99        Err(BluetoothError::unsupported("read_characteristic"))
100    }
101
102    fn write_characteristic(&self, _request: BluetoothWriteRequest) -> Result<(), BluetoothError> {
103        Err(BluetoothError::unsupported("write_characteristic"))
104    }
105
106    fn start_advertising(
107        &self,
108        _request: BluetoothAdvertiseRequest,
109    ) -> Result<BluetoothAdvertiseReceipt, BluetoothError> {
110        Err(BluetoothError::unsupported("start_advertising"))
111    }
112
113    fn stop_advertising(
114        &self,
115        _request: BluetoothStopAdvertiseRequest,
116    ) -> Result<(), BluetoothError> {
117        Err(BluetoothError::unsupported("stop_advertising"))
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct MemoryBluetoothHost {
123    availability: BluetoothAvailability,
124    devices: Vec<BluetoothDevice>,
125    read_result: BluetoothReadResult,
126}
127
128impl MemoryBluetoothHost {
129    pub fn new(
130        availability: BluetoothAvailability,
131        devices: Vec<BluetoothDevice>,
132        read_result: BluetoothReadResult,
133    ) -> Self {
134        Self {
135            availability,
136            devices,
137            read_result,
138        }
139    }
140}
141
142impl Default for MemoryBluetoothHost {
143    fn default() -> Self {
144        let device = BluetoothDevice {
145            id: "memory-bluetooth".into(),
146            name: Some("Memory Bluetooth".into()),
147            address: Some("00:11:22:33:44:55".into()),
148            rssi: Some(-42),
149            paired: true,
150            modes: vec![BluetoothMode::Classic, BluetoothMode::LowEnergy],
151        };
152        Self::new(
153            BluetoothAvailability {
154                permission: BluetoothPermission::Granted,
155                enabled: true,
156                supports_classic: true,
157                supports_low_energy: true,
158            },
159            vec![device],
160            BluetoothReadResult {
161                value: b"fission".to_vec(),
162            },
163        )
164    }
165}
166
167impl BluetoothHost for MemoryBluetoothHost {
168    fn availability(&self) -> Result<BluetoothAvailability, BluetoothError> {
169        Ok(self.availability.clone())
170    }
171
172    fn request_permission(
173        &self,
174        _request: BluetoothPermissionRequest,
175    ) -> Result<BluetoothPermission, BluetoothError> {
176        Ok(self.availability.permission)
177    }
178
179    fn scan_devices(
180        &self,
181        _request: BluetoothScanRequest,
182    ) -> Result<BluetoothScanResult, BluetoothError> {
183        Ok(BluetoothScanResult {
184            devices: self.devices.clone(),
185        })
186    }
187
188    fn connect_device(
189        &self,
190        request: BluetoothConnectRequest,
191    ) -> Result<BluetoothConnection, BluetoothError> {
192        let device = self
193            .devices
194            .iter()
195            .find(|device| device.id == request.device_id)
196            .cloned()
197            .ok_or_else(|| BluetoothError::new("not_found", "Bluetooth device not found"))?;
198        Ok(BluetoothConnection {
199            connection_id: format!("memory:{}", device.id),
200            device,
201        })
202    }
203
204    fn disconnect_device(
205        &self,
206        _request: BluetoothDisconnectRequest,
207    ) -> Result<(), BluetoothError> {
208        Ok(())
209    }
210
211    fn read_characteristic(
212        &self,
213        _request: BluetoothReadRequest,
214    ) -> Result<BluetoothReadResult, BluetoothError> {
215        Ok(self.read_result.clone())
216    }
217
218    fn write_characteristic(&self, _request: BluetoothWriteRequest) -> Result<(), BluetoothError> {
219        Ok(())
220    }
221
222    fn start_advertising(
223        &self,
224        _request: BluetoothAdvertiseRequest,
225    ) -> Result<BluetoothAdvertiseReceipt, BluetoothError> {
226        Ok(BluetoothAdvertiseReceipt {
227            advertisement_id: "memory-advertisement".into(),
228        })
229    }
230
231    fn stop_advertising(
232        &self,
233        _request: BluetoothStopAdvertiseRequest,
234    ) -> Result<(), BluetoothError> {
235        Ok(())
236    }
237}
238
239pub(crate) fn register_bluetooth_capabilities(
240    async_registry: &mut AsyncRegistry,
241    host: Arc<dyn BluetoothHost>,
242) {
243    let availability_host = host.clone();
244    async_registry.register_operation_capability(GET_BLUETOOTH_AVAILABILITY, move |(), _| {
245        let host = availability_host.clone();
246        async move { host.availability() }
247    });
248
249    let permission_host = host.clone();
250    async_registry.register_operation_capability(
251        REQUEST_BLUETOOTH_PERMISSION,
252        move |request, _| {
253            let host = permission_host.clone();
254            async move { host.request_permission(request) }
255        },
256    );
257
258    let scan_host = host.clone();
259    async_registry.register_operation_capability(SCAN_BLUETOOTH_DEVICES, move |request, _| {
260        let host = scan_host.clone();
261        async move { host.scan_devices(request) }
262    });
263
264    let connect_host = host.clone();
265    async_registry.register_operation_capability(CONNECT_BLUETOOTH_DEVICE, move |request, _| {
266        let host = connect_host.clone();
267        async move { host.connect_device(request) }
268    });
269
270    let disconnect_host = host.clone();
271    async_registry.register_operation_capability(DISCONNECT_BLUETOOTH_DEVICE, move |request, _| {
272        let host = disconnect_host.clone();
273        async move { host.disconnect_device(request) }
274    });
275
276    let read_host = host.clone();
277    async_registry.register_operation_capability(
278        READ_BLUETOOTH_CHARACTERISTIC,
279        move |request, _| {
280            let host = read_host.clone();
281            async move { host.read_characteristic(request) }
282        },
283    );
284
285    let write_host = host.clone();
286    async_registry.register_operation_capability(
287        WRITE_BLUETOOTH_CHARACTERISTIC,
288        move |request, _| {
289            let host = write_host.clone();
290            async move { host.write_characteristic(request) }
291        },
292    );
293
294    let advertise_host = host.clone();
295    async_registry.register_operation_capability(START_BLUETOOTH_ADVERTISING, move |request, _| {
296        let host = advertise_host.clone();
297        async move { host.start_advertising(request) }
298    });
299
300    async_registry.register_operation_capability(STOP_BLUETOOTH_ADVERTISING, move |request, _| {
301        let host = host.clone();
302        async move { host.stop_advertising(request) }
303    });
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn unsupported_host_reports_errors() {
312        let host = UnsupportedBluetoothHost;
313        assert!(host.scan_devices(BluetoothScanRequest::default()).is_err());
314    }
315
316    #[test]
317    fn memory_host_scans_connects_and_reads() {
318        let host = MemoryBluetoothHost::default();
319        let scan = host.scan_devices(BluetoothScanRequest::default()).unwrap();
320        assert_eq!(scan.devices.len(), 1);
321
322        let connection = host
323            .connect_device(BluetoothConnectRequest {
324                device_id: scan.devices[0].id.clone(),
325                service_uuids: Vec::new(),
326            })
327            .unwrap();
328        assert_eq!(connection.device.id, "memory-bluetooth");
329
330        let read = host
331            .read_characteristic(BluetoothReadRequest::default())
332            .unwrap();
333        assert_eq!(read.value, b"fission".to_vec());
334    }
335}