1use crate::capability::{CapabilityType, OperationCapability};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
7pub enum BluetoothPermission {
8 #[default]
9 Unknown,
10 Granted,
11 Denied,
12 Restricted,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub enum BluetoothMode {
17 Classic,
18 LowEnergy,
19}
20
21#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BluetoothDevice {
23 pub id: String,
24 pub name: Option<String>,
25 pub address: Option<String>,
26 pub rssi: Option<i16>,
27 pub paired: bool,
28 pub modes: Vec<BluetoothMode>,
29}
30
31#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
32pub struct BluetoothAvailability {
33 pub permission: BluetoothPermission,
34 pub enabled: bool,
35 pub supports_classic: bool,
36 pub supports_low_energy: bool,
37}
38
39#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
40pub struct BluetoothPermissionRequest {
41 pub reason: Option<String>,
42}
43
44#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
45pub struct BluetoothScanRequest {
46 pub service_uuids: Vec<String>,
47 pub timeout_ms: Option<u64>,
48 pub include_paired: bool,
49 pub allow_duplicates: bool,
50}
51
52#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
53pub struct BluetoothScanResult {
54 pub devices: Vec<BluetoothDevice>,
55}
56
57#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
58pub struct BluetoothConnectRequest {
59 pub device_id: String,
60 pub service_uuids: Vec<String>,
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
64pub struct BluetoothConnection {
65 pub connection_id: String,
66 pub device: BluetoothDevice,
67}
68
69#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
70pub struct BluetoothDisconnectRequest {
71 pub connection_id: String,
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
75pub struct BluetoothReadRequest {
76 pub connection_id: String,
77 pub service_uuid: String,
78 pub characteristic_uuid: String,
79}
80
81#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
82pub struct BluetoothReadResult {
83 pub value: Vec<u8>,
84}
85
86#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
87pub struct BluetoothWriteRequest {
88 pub connection_id: String,
89 pub service_uuid: String,
90 pub characteristic_uuid: String,
91 pub value: Vec<u8>,
92 pub with_response: bool,
93}
94
95#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
96pub struct BluetoothAdvertiseRequest {
97 pub service_uuid: String,
98 pub service_data: Vec<u8>,
99 pub local_name: Option<String>,
100 pub timeout_ms: Option<u64>,
101}
102
103#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
104pub struct BluetoothAdvertiseReceipt {
105 pub advertisement_id: String,
106}
107
108#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
109pub struct BluetoothStopAdvertiseRequest {
110 pub advertisement_id: String,
111}
112
113#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
114pub struct BluetoothError {
115 pub code: String,
116 pub message: String,
117}
118
119impl BluetoothError {
120 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
127 Self {
128 code: code.into(),
129 message: message.into(),
130 }
131 }
132
133 pub fn unsupported(operation: impl Into<String>) -> Self {
139 Self::new(
140 "unsupported",
141 format!(
142 "Bluetooth operation `{}` is not supported by this host",
143 operation.into()
144 ),
145 )
146 }
147}
148
149pub struct GetBluetoothAvailabilityCapability;
150impl OperationCapability for GetBluetoothAvailabilityCapability {
151 type Request = ();
152 type Ok = BluetoothAvailability;
153 type Err = BluetoothError;
154}
155
156pub struct RequestBluetoothPermissionCapability;
157impl OperationCapability for RequestBluetoothPermissionCapability {
158 type Request = BluetoothPermissionRequest;
159 type Ok = BluetoothPermission;
160 type Err = BluetoothError;
161}
162
163pub struct ScanBluetoothDevicesCapability;
164impl OperationCapability for ScanBluetoothDevicesCapability {
165 type Request = BluetoothScanRequest;
166 type Ok = BluetoothScanResult;
167 type Err = BluetoothError;
168}
169
170pub struct ConnectBluetoothDeviceCapability;
171impl OperationCapability for ConnectBluetoothDeviceCapability {
172 type Request = BluetoothConnectRequest;
173 type Ok = BluetoothConnection;
174 type Err = BluetoothError;
175}
176
177pub struct DisconnectBluetoothDeviceCapability;
178impl OperationCapability for DisconnectBluetoothDeviceCapability {
179 type Request = BluetoothDisconnectRequest;
180 type Ok = ();
181 type Err = BluetoothError;
182}
183
184pub struct ReadBluetoothCharacteristicCapability;
185impl OperationCapability for ReadBluetoothCharacteristicCapability {
186 type Request = BluetoothReadRequest;
187 type Ok = BluetoothReadResult;
188 type Err = BluetoothError;
189}
190
191pub struct WriteBluetoothCharacteristicCapability;
192impl OperationCapability for WriteBluetoothCharacteristicCapability {
193 type Request = BluetoothWriteRequest;
194 type Ok = ();
195 type Err = BluetoothError;
196}
197
198pub struct StartBluetoothAdvertisingCapability;
199impl OperationCapability for StartBluetoothAdvertisingCapability {
200 type Request = BluetoothAdvertiseRequest;
201 type Ok = BluetoothAdvertiseReceipt;
202 type Err = BluetoothError;
203}
204
205pub struct StopBluetoothAdvertisingCapability;
206impl OperationCapability for StopBluetoothAdvertisingCapability {
207 type Request = BluetoothStopAdvertiseRequest;
208 type Ok = ();
209 type Err = BluetoothError;
210}
211
212pub const GET_BLUETOOTH_AVAILABILITY: CapabilityType<GetBluetoothAvailabilityCapability> =
213 CapabilityType::new("fission.bluetooth.get_availability");
214pub const REQUEST_BLUETOOTH_PERMISSION: CapabilityType<RequestBluetoothPermissionCapability> =
215 CapabilityType::new("fission.bluetooth.request_permission");
216pub const SCAN_BLUETOOTH_DEVICES: CapabilityType<ScanBluetoothDevicesCapability> =
217 CapabilityType::new("fission.bluetooth.scan_devices");
218pub const CONNECT_BLUETOOTH_DEVICE: CapabilityType<ConnectBluetoothDeviceCapability> =
219 CapabilityType::new("fission.bluetooth.connect_device");
220pub const DISCONNECT_BLUETOOTH_DEVICE: CapabilityType<DisconnectBluetoothDeviceCapability> =
221 CapabilityType::new("fission.bluetooth.disconnect_device");
222pub const READ_BLUETOOTH_CHARACTERISTIC: CapabilityType<ReadBluetoothCharacteristicCapability> =
223 CapabilityType::new("fission.bluetooth.read_characteristic");
224pub const WRITE_BLUETOOTH_CHARACTERISTIC: CapabilityType<WriteBluetoothCharacteristicCapability> =
225 CapabilityType::new("fission.bluetooth.write_characteristic");
226pub const START_BLUETOOTH_ADVERTISING: CapabilityType<StartBluetoothAdvertisingCapability> =
227 CapabilityType::new("fission.bluetooth.start_advertising");
228pub const STOP_BLUETOOTH_ADVERTISING: CapabilityType<StopBluetoothAdvertisingCapability> =
229 CapabilityType::new("fission.bluetooth.stop_advertising");
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn bluetooth_scan_request_round_trips() {
237 let request = BluetoothScanRequest {
238 service_uuids: vec!["180D".into()],
239 timeout_ms: Some(10_000),
240 include_paired: true,
241 allow_duplicates: false,
242 };
243
244 let bytes = serde_json::to_vec(&request).unwrap();
245 let decoded: BluetoothScanRequest = serde_json::from_slice(&bytes).unwrap();
246
247 assert_eq!(decoded, request);
248 }
249
250 #[test]
251 fn bluetooth_connection_round_trips() {
252 let connection = BluetoothConnection {
253 connection_id: "connection-1".into(),
254 device: BluetoothDevice {
255 id: "device-1".into(),
256 name: Some("Heart monitor".into()),
257 address: Some("00:11:22:33:44:55".into()),
258 rssi: Some(-42),
259 paired: true,
260 modes: vec![BluetoothMode::LowEnergy],
261 },
262 };
263
264 let bytes = serde_json::to_vec(&connection).unwrap();
265 let decoded: BluetoothConnection = serde_json::from_slice(&bytes).unwrap();
266
267 assert_eq!(decoded, connection);
268 }
269}