Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
4//
5// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6// for full license information.
7
8use super::btleplug_hardware::BtleplugHardwareConnector;
9use btleplug::{
10 api::{Central, CentralEvent, Manager as _, Peripheral, ScanFilter},
11 platform::{Adapter, Manager, PeripheralId},
12};
13use buttplug_server::device::hardware::communication::HardwareCommunicationManagerEvent;
14use futures::StreamExt;
15use std::{
16 collections::HashMap,
17 sync::{
18 Arc,
19 atomic::{AtomicBool, Ordering},
20 },
21 time::Duration,
22};
23use tokio::{
24 select,
25 sync::mpsc::{Receiver, Sender},
26 time::sleep,
27};
28use tracing::info_span;
29use uuid::Uuid;
30
31#[derive(Debug, Clone, Copy)]
32pub enum BtleplugAdapterCommand {
33 StartScanning,
34 StopScanning,
35}
36
37#[derive(Clone, PartialEq, Eq, Debug)]
38struct PeripheralInfo {
39 name: Option<String>,
40 peripheral_id: PeripheralId,
41 manufacturer_data: HashMap<u16, Vec<u8>>,
42 services: Vec<Uuid>,
43}
44
45pub struct BtleplugAdapterTask {
46 event_sender: Sender<HardwareCommunicationManagerEvent>,
47 command_receiver: Receiver<BtleplugAdapterCommand>,
48 adapter_connected: Arc<AtomicBool>,
49 requires_keepalive: bool,
50}
51
52impl BtleplugAdapterTask {
53 pub fn new(
54 event_sender: Sender<HardwareCommunicationManagerEvent>,
55 command_receiver: Receiver<BtleplugAdapterCommand>,
56 adapter_connected: Arc<AtomicBool>,
57 requires_keepalive: bool,
58 ) -> Self {
59 Self {
60 event_sender,
61 command_receiver,
62 adapter_connected,
63 requires_keepalive,
64 }
65 }
66
67 async fn maybe_add_peripheral(
68 &self,
69 peripheral_id: &PeripheralId,
70 adapter: &Adapter,
71 tried_addresses: &mut Vec<PeripheralInfo>,
72 ) {
73 let peripheral = if let Ok(peripheral) = adapter.peripheral(peripheral_id).await {
74 peripheral
75 } else {
76 error!("Peripheral with address {:?} not found.", peripheral_id);
77 return;
78 };
79 // If a device has no discernable name, we can't do anything with it, just ignore it.
80 let properties = if let Ok(Some(properties)) = peripheral.properties().await {
81 properties
82 } else {
83 error!(
84 "Cannot retreive peripheral properties for {:?}.",
85 peripheral_id
86 );
87 return;
88 };
89
90 let device_name = if let Some(name) = &properties.local_name {
91 name.clone()
92 } else {
93 String::new()
94 };
95
96 let peripheral_info = PeripheralInfo {
97 name: properties.local_name.clone(),
98 peripheral_id: peripheral_id.clone(),
99 manufacturer_data: properties.manufacturer_data.clone(),
100 services: properties.services.clone(),
101 };
102
103 if (!device_name.is_empty() || !properties.services.is_empty())
104 && !tried_addresses.contains(&peripheral_info)
105 {
106 let span = info_span!(
107 "btleplug enumeration",
108 address = tracing::field::display(format!("{peripheral_id:?}")),
109 name = tracing::field::display(&device_name)
110 );
111 let _enter = span.enter();
112
113 debug!(
114 "Found new bluetooth device advertisement: {:?}",
115 peripheral_info
116 );
117 tried_addresses.push(peripheral_info.clone());
118 let device_creator = Box::new(BtleplugHardwareConnector::new(
119 &device_name,
120 &properties.manufacturer_data,
121 &properties.services,
122 peripheral.clone(),
123 adapter.clone(),
124 self.requires_keepalive,
125 ));
126 if self
127 .event_sender
128 .send(HardwareCommunicationManagerEvent::DeviceFound {
129 name: device_name,
130 address: format!("{peripheral_id:?}"),
131 creator: device_creator,
132 })
133 .await
134 .is_err()
135 {
136 error!("Device manager receiver dropped, cannot send device found message.");
137 }
138 } else {
139 trace!(
140 "Device {} found, no advertised name, ignoring.",
141 properties.address
142 );
143 }
144 }
145
146 pub async fn run(&mut self) {
147 let manager = match Manager::new().await {
148 Ok(mgr) => mgr,
149 Err(e) => {
150 error!("Error creating btleplug manager: {:?}", e);
151 return;
152 }
153 };
154
155 // Start by assuming we'll find the adapter on the first try. If not, we'll print an error
156 // message then loop while trying to find it.
157 self.adapter_connected.store(true, Ordering::Relaxed);
158
159 let adapter;
160
161 loop {
162 let adapter_found = self.adapter_connected.load(Ordering::Relaxed);
163 if !adapter_found {
164 sleep(Duration::from_secs(1)).await;
165 }
166 adapter = match manager.adapters().await {
167 Ok(adapters) => {
168 if let Some(adapter) = adapters.into_iter().next() {
169 info!("Bluetooth LE adapter found.");
170 // Bluetooth dongle identification for Windows
171 #[cfg(target_os = "windows")]
172 {
173 use windows::Devices::Bluetooth::BluetoothAdapter;
174 let adapter_result = BluetoothAdapter::GetDefaultAsync()
175 .expect("If we're here, we got an adapter")
176 .await;
177 let adapter = adapter_result.expect("Considering infallible at this point");
178 let device_id = adapter
179 .DeviceId()
180 .expect("Considering infallible at this point")
181 .to_string();
182 info!("Windows Bluetooth Adapter ID: {:?}", device_id);
183 let device_manufacturer = if device_id.contains("VID_0A12") {
184 "Cambridge Silicon Radio (CSR)"
185 } else if device_id.contains("VID_0A5C") {
186 "Broadcom"
187 } else if device_id.contains("VID_8087") {
188 "Intel"
189 } else if device_id.contains("VID_0BDA") {
190 "RealTek"
191 } else if device_id.contains("VID_0B05") {
192 "Asus"
193 } else if device_id.contains("VID_13D3") {
194 "IMC"
195 } else if device_id.contains("VID_10D7") {
196 "Actions Semi"
197 } else {
198 "Unknown Manufacturer"
199 };
200 info!(
201 "Windows Bluetooth Adapter Manufacturer: {}",
202 device_manufacturer
203 );
204 }
205 adapter
206 } else {
207 if adapter_found {
208 self.adapter_connected.store(false, Ordering::Relaxed);
209 warn!(
210 "Bluetooth LE adapter not found, will not be using bluetooth scanning until found. Buttplug will continue polling for the adapter, but no more warning messages will be posted."
211 );
212 }
213 continue;
214 }
215 }
216 Err(e) => {
217 if adapter_found {
218 self.adapter_connected.store(false, Ordering::Relaxed);
219 error!("Error retreiving BTLE adapters: {:?}", e);
220 }
221 continue;
222 }
223 };
224 break;
225 }
226
227 let mut events = adapter
228 .events()
229 .await
230 .expect("Should always be able to retreive stream.");
231
232 let mut tried_addresses = vec![];
233
234 loop {
235 let event_fut = events.next();
236
237 select! {
238 event = event_fut => {
239 if let Some(event) = event {
240 match event {
241 CentralEvent::DeviceDiscovered(peripheral_id) | CentralEvent::DeviceUpdated(peripheral_id) => {
242 self.maybe_add_peripheral(&peripheral_id, &adapter, &mut tried_addresses).await;
243 }
244 CentralEvent::DeviceDisconnected(peripheral_id) => {
245 debug!("BTLEPlug Device disconnected: {:?}", peripheral_id);
246 tried_addresses.retain(|info| info.peripheral_id != peripheral_id);
247 }
248 event => {
249 trace!("Unhandled btleplug central event: {:?}", event)
250 }
251 }
252 } else {
253 error!("Event stream closed. Exiting loop.");
254 return;
255 }
256 },
257 command = self.command_receiver.recv() => {
258 if let Some(cmd) = command {
259 match cmd {
260 BtleplugAdapterCommand::StartScanning => {
261 tried_addresses.clear();
262 if let Err(err) = adapter.start_scan(ScanFilter::default()).await {
263 error!("Start scanning request failed. Ensure Bluetooth is enabled and permissions are granted: {}", err);
264 }
265 }
266 BtleplugAdapterCommand::StopScanning => {
267 if let Err(err) = adapter.stop_scan().await {
268 error!("Stop scanning request failed: {}", err);
269 }
270 }
271 }
272 } else {
273 debug!("Command stream closed. Exiting btleplug adapter loop.");
274 return;
275 }
276 }
277 }
278 }
279 }
280}