Buttplug sex toy control library
at dev 8.9 kB view raw
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}