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 8mod lovense_max; 9mod lovense_multi_actuator; 10mod lovense_rotate_vibrator; 11mod lovense_single_actuator; 12mod lovense_stroker; 13 14use lovense_max::LovenseMax; 15use lovense_multi_actuator::LovenseMultiActuator; 16use lovense_rotate_vibrator::LovenseRotateVibrator; 17use lovense_single_actuator::LovenseSingleActuator; 18use lovense_stroker::LovenseStroker; 19 20use crate::device::{ 21 hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareSubscribeCmd, HardwareWriteCmd}, 22 protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 23}; 24use async_trait::async_trait; 25use buttplug_core::{ 26 errors::ButtplugDeviceError, 27 message::{self, InputData, InputReadingV4, InputTypeData, OutputType}, 28 util::sleep, 29}; 30use buttplug_server_device_config::{ 31 Endpoint, 32 ProtocolCommunicationSpecifier, 33 ServerDeviceDefinition, 34 UserDeviceIdentifier, 35}; 36use futures::{FutureExt, future::BoxFuture}; 37use regex::Regex; 38use std::{sync::Arc, time::Duration}; 39use tokio::select; 40use uuid::{Uuid, uuid}; 41 42// Constants for dealing with the Lovense subscript/write race condition. The 43// timeout needs to be VERY long, otherwise this trips up old lovense serial 44// adapters. 45// 46// Just buy new adapters, people. 47const LOVENSE_COMMAND_TIMEOUT_MS: u64 = 500; 48const LOVENSE_COMMAND_RETRY: u64 = 5; 49 50const LOVENSE_PROTOCOL_UUID: Uuid = uuid!("cfa3fac5-48bb-4d87-817e-a439965956e1"); 51 52pub mod setup { 53 use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory}; 54 #[derive(Default)] 55 pub struct LovenseIdentifierFactory {} 56 57 impl ProtocolIdentifierFactory for LovenseIdentifierFactory { 58 fn identifier(&self) -> &str { 59 "lovense" 60 } 61 62 fn create(&self) -> Box<dyn ProtocolIdentifier> { 63 Box::new(super::LovenseIdentifier::default()) 64 } 65 } 66} 67 68#[derive(Default)] 69pub struct LovenseIdentifier {} 70 71fn lovense_model_resolver(type_response: String) -> String { 72 let parts = type_response.split(':').collect::<Vec<&str>>(); 73 if parts.len() < 2 { 74 warn!( 75 "Lovense Device returned invalid DeviceType info: {}", 76 type_response 77 ); 78 return "lovense".to_string(); 79 } 80 81 let identifier = parts[0].to_owned(); 82 let version = parts[1].to_owned().parse::<i32>().unwrap_or(0); 83 84 info!("Identified device type {} version {}", identifier, version); 85 86 // Flexer: version must be 3+ to control actuators separately 87 if identifier == "EI" && version >= 3 { 88 return "EI-FW3".to_string(); 89 } 90 91 identifier 92} 93 94#[async_trait] 95impl ProtocolIdentifier for LovenseIdentifier { 96 async fn identify( 97 &mut self, 98 hardware: Arc<Hardware>, 99 _: ProtocolCommunicationSpecifier, 100 ) -> Result<(UserDeviceIdentifier, Box<dyn ProtocolInitializer>), ButtplugDeviceError> { 101 let mut event_receiver = hardware.event_stream(); 102 let mut count = 0; 103 hardware 104 .subscribe(&HardwareSubscribeCmd::new( 105 LOVENSE_PROTOCOL_UUID, 106 Endpoint::Rx, 107 )) 108 .await?; 109 110 loop { 111 let msg = HardwareWriteCmd::new( 112 &[LOVENSE_PROTOCOL_UUID], 113 Endpoint::Tx, 114 b"DeviceType;".to_vec(), 115 false, 116 ); 117 hardware.write_value(&msg).await?; 118 119 select! { 120 event = event_receiver.recv().fuse() => { 121 if let Ok(HardwareEvent::Notification(_, _, n)) = event { 122 let type_response = std::str::from_utf8(&n).map_err(|_| ButtplugDeviceError::ProtocolSpecificError("lovense".to_owned(), "Lovense device init got back non-UTF8 string.".to_owned()))?.to_owned(); 123 debug!("Lovense Device Type Response: {}", type_response); 124 let ident = lovense_model_resolver(type_response); 125 return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", &Some(ident.clone())), Box::new(LovenseInitializer::new(ident)))); 126 } else { 127 return Err( 128 ButtplugDeviceError::ProtocolSpecificError( 129 "Lovense".to_owned(), 130 "Lovense Device disconnected while getting DeviceType info.".to_owned(), 131 ), 132 ); 133 } 134 } 135 _ = sleep(Duration::from_millis(LOVENSE_COMMAND_TIMEOUT_MS)).fuse() => { 136 count += 1; 137 if count > LOVENSE_COMMAND_RETRY { 138 warn!("Lovense Device timed out while getting DeviceType info. ({} retries)", LOVENSE_COMMAND_RETRY); 139 let re = Regex::new(r"LVS-([A-Z]+)\d+").expect("Static regex shouldn't fail"); 140 if let Some(caps) = re.captures(hardware.name()) { 141 info!("Lovense Device identified by BLE name"); 142 return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", &Some(caps[1].to_string())), Box::new(LovenseInitializer::new(caps[1].to_string())))); 143 }; 144 return Ok((UserDeviceIdentifier::new(hardware.address(), "lovense", &None), Box::new(LovenseInitializer::new("".to_string())))); 145 } 146 } 147 } 148 } 149 } 150} 151pub struct LovenseInitializer { 152 device_type: String, 153} 154 155impl LovenseInitializer { 156 pub fn new(device_type: String) -> Self { 157 Self { device_type } 158 } 159} 160 161#[async_trait] 162impl ProtocolInitializer for LovenseInitializer { 163 async fn initialize( 164 &mut self, 165 hardware: Arc<Hardware>, 166 device_definition: &ServerDeviceDefinition, 167 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 168 let device_type = self.device_type.clone(); 169 170 let vibrator_count = device_definition 171 .features() 172 .iter() 173 .filter(|x| { 174 x.output() 175 .as_ref() 176 .is_some_and(|x| x.contains(OutputType::Vibrate) || x.contains(OutputType::Oscillate)) 177 }) 178 .count(); 179 180 let output_count = device_definition 181 .features() 182 .iter() 183 .filter(|x| x.output().is_some()) 184 .count(); 185 186 let vibrator_rotator = output_count == 2 187 && device_definition 188 .features() 189 .iter() 190 .filter(|x| { 191 x.output() 192 .as_ref() 193 .is_some_and(|x| x.contains(OutputType::Vibrate)) 194 }) 195 .count() 196 == 1 197 && device_definition 198 .features() 199 .iter() 200 .filter(|x| { 201 x.output() 202 .as_ref() 203 .is_some_and(|x| x.contains(OutputType::Rotate)) 204 }) 205 .count() 206 == 1; 207 208 let lovense_max = output_count == 2 209 && device_definition 210 .features() 211 .iter() 212 .filter(|x| { 213 x.output() 214 .as_ref() 215 .is_some_and(|x| x.contains(OutputType::Vibrate)) 216 }) 217 .count() 218 == 1 219 && device_definition 220 .features() 221 .iter() 222 .filter(|x| { 223 x.output() 224 .as_ref() 225 .is_some_and(|x| x.contains(OutputType::Constrict)) 226 }) 227 .count() 228 == 1; 229 230 // This might need better tuning if other complex Lovenses are released 231 // Currently this only applies to the Flexer/Lapis/Solace 232 let use_mply = 233 (vibrator_count == 2 && output_count > 2) || vibrator_count > 2 || device_type == "H"; 234 235 // New Lovense devices seem to be moving to the simplified LVS:<bytearray>; command format. 236 // I'm not sure if there's a good way to detect this. 237 //let use_lvs = device_type == "OC"; 238 239 debug!( 240 "Device type {} initialized with {} vibrators {} using Mply", 241 device_type, 242 vibrator_count, 243 if use_mply { "" } else { "not " } 244 ); 245 246 if device_type == "BA" { 247 Ok(Arc::new(LovenseStroker::new(hardware))) 248 } else if output_count == 1 { 249 Ok(Arc::new(LovenseSingleActuator::default())) 250 } else if lovense_max { 251 Ok(Arc::new(LovenseMax::default())) 252 } else if vibrator_rotator { 253 Ok(Arc::new(LovenseRotateVibrator::default())) 254 } else { 255 Ok(Arc::new(LovenseMultiActuator::new(vibrator_count as u32))) 256 } 257 } 258} 259/* 260pub struct Lovense { 261 rotation_direction: AtomicBool, 262 vibrator_values: Vec<AtomicU32>, 263 use_mply: bool, 264 use_lvs: bool, 265 device_type: String, 266 value_cache: DashMap<Uuid, u32>, 267 linear_info: Arc<(AtomicU32, AtomicU32)>, 268} 269 270impl Lovense { 271 pub fn new( 272 device_type: &str, 273 vibrator_count: usize, 274 use_mply: bool, 275 use_lvs: bool, 276 ) -> Self { 277 let linear_info = Arc::new((AtomicU32::new(0), AtomicU32::new(0))); 278 279 let mut vibrator_values = vec![]; 280 for _ in 0..vibrator_count { 281 vibrator_values.push(AtomicU32::new(0)); 282 } 283 284 Self { 285 rotation_direction: AtomicBool::new(false), 286 vibrator_values, 287 use_mply, 288 use_lvs, 289 device_type: device_type.to_owned(), 290 value_cache: DashMap::new(), 291 linear_info, 292 } 293 } 294 295 fn handle_lvs_cmd(&self) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 296 let mut speeds = "LVS:{}".as_bytes().to_vec(); 297 for i in self.vibrator_values.iter() { 298 speeds.push(i.load(Ordering::Relaxed) as u8); 299 } 300 speeds.push(0x3b); 301 302 Ok(vec![HardwareWriteCmd::new( 303 LOVENSE_PROTOCOL_UUID, 304 Endpoint::Tx, 305 speeds, 306 false, 307 ) 308 .into()]) 309 } 310 311 fn handle_mply_cmd(&self) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 312 /* 313 let mut speeds = cmds 314 .iter() 315 .map(|x| { 316 if let Some(val) = x { 317 val.1.to_string() 318 } else { 319 "-1".to_string() 320 } 321 }) 322 .collect::<Vec<_>>(); 323 324 if speeds.len() == 1 && self.device_type == "H" { 325 // Max range unless stopped 326 speeds.push(if speeds[0] == "0" { 327 "0".to_string() 328 } else { 329 "20".to_string() 330 }); 331 } 332 333 let lovense_cmd = format!("Mply:{};", speeds.join(":")).as_bytes().to_vec(); 334 335 Ok(vec![HardwareWriteCmd::new( 336 Endpoint::Tx, 337 lovense_cmd, 338 false, 339 ) 340 .into()]) 341 */ 342 Ok(vec![]) 343 } 344} 345 346impl ProtocolHandler for Lovense { 347 fn keepalive_strategy(&self) -> super::ProtocolKeepaliveStrategy { 348 // For Lovense, we'll just repeat the device type packet and drop the result. 349 super::ProtocolKeepaliveStrategy::HardwareRequiredRepeatPacketStrategy(HardwareWriteCmd::new( 350 LOVENSE_PROTOCOL_UUID, 351 Endpoint::Tx, 352 b"DeviceType;".to_vec(), 353 false, 354 )) 355 } 356 357 fn handle_output_vibrate_cmd( 358 &self, 359 feature_index: u32, 360 feature_id: Uuid, 361 speed: u32, 362 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 363 let current_vibrator_value = 364 self.vibrator_values[feature_index as usize].load(Ordering::Relaxed); 365 if current_vibrator_value == speed { 366 Ok(vec![]) 367 } else { 368 self.vibrator_values[feature_index as usize].store(speed, Ordering::Relaxed); 369 let speeds: Vec<u32> = self 370 .vibrator_values 371 .iter() 372 .map(|v| v.load(Ordering::Relaxed)) 373 .collect(); 374 if self.use_lvs { 375 self.handle_lvs_cmd() 376 } else if self.use_mply { 377 self.handle_mply_cmd() 378 } else { 379 let lovense_cmd = if self.vibrator_values.len() == 1 { 380 format!("Vibrate:{speed};").as_bytes().to_vec() 381 } else { 382 format!("Vibrate{}:{};", feature_index + 1, speed) 383 .as_bytes() 384 .to_vec() 385 }; 386 Ok(vec![HardwareWriteCmd::new( 387 &[feature_id], 388 Endpoint::Tx, 389 lovense_cmd, 390 false, 391 ) 392 .into()]) 393 } 394 } 395 /* 396 if self.use_lvs { 397 self.handle_lvs_cmd(cmd) 398 } else if self.use_mply { 399 self.handle_mply_cmd(cmd) 400 } else { 401 // Handle vibration commands, these will be by far the most common. Fucking machine oscillation 402 // uses lovense vibrate commands internally too, so we can include them here. 403 let vibrate_cmds: Vec<> = cmds 404 .iter() 405 .filter(|x| { 406 if let Some(val) = x { 407 [ActuatorType::Vibrate, ActuatorType::Oscillate].contains(&val.0) 408 } else { 409 false 410 } 411 }) 412 .map(|x| x.as_ref().expect("Already verified is some")) 413 .collect(); 414 if !vibrate_cmds.is_empty() { 415 // Lovense is the same situation as the Lovehoney Desire, where commands 416 // are different if we're addressing all motors or seperate motors. 417 // Difference here being that there's Lovense variants with different 418 // numbers of motors. 419 // 420 // Neat way of checking if everything is the same via 421 // https://sts10.github.io/2019/06/06/is-all-equal-function.html. 422 // 423 // Just make sure we're not matching on None, 'cause if that's the case 424 // we ain't got shit to do. 425 // 426 // Note that the windowed comparison causes mixed types as well as mixed 427 // speeds to fall back to separate commands. This is because the Gravity's 428 // thruster on Vibrate2 is independent of Vibrate 429 if self.vibrator_count == vibrate_cmds.len() 430 && (self.vibrator_count == 1 431 || vibrate_cmds 432 .windows(2) 433 .all(|w| w[0].0 == w[1].0 && w[0].1 == w[1].1)) 434 { 435 let lovense_cmd = format!("Vibrate:{};", vibrate_cmds[0].1) 436 .as_bytes() 437 .to_vec(); 438 hardware_cmds.push(HardwareWriteCmd::new(Endpoint::Tx, lovense_cmd, false).into()); 439 } else { 440 for (i, cmd) in cmds.iter().enumerate() { 441 if let Some((actuator, speed)) = cmd { 442 if ![ActuatorType::Vibrate, ActuatorType::Oscillate].contains(actuator) { 443 continue; 444 } 445 let lovense_cmd = format!("Vibrate{}:{};", i + 1, speed).as_bytes().to_vec(); 446 hardware_cmds.push(HardwareWriteCmd::new(Endpoint::Tx, lovense_cmd, false).into()); 447 } 448 } 449 } 450 } 451 */ 452 } 453} 454 */ 455 456fn handle_battery_level_cmd( 457 device_index: u32, 458 device: Arc<Hardware>, 459 feature_index: u32, 460 feature_id: Uuid, 461) -> BoxFuture<'static, Result<InputReadingV4, ButtplugDeviceError>> { 462 let mut device_notification_receiver = device.event_stream(); 463 async move { 464 let write_fut = device.write_value(&HardwareWriteCmd::new( 465 &[feature_id], 466 Endpoint::Tx, 467 b"Battery;".to_vec(), 468 false, 469 )); 470 write_fut.await?; 471 while let Ok(event) = device_notification_receiver.recv().await { 472 match event { 473 HardwareEvent::Notification(_, _, data) => { 474 if let Ok(data_str) = std::str::from_utf8(&data) { 475 debug!("Lovense event received: {}", data_str); 476 let len = data_str.len(); 477 // Depending on the state of the toy, we may get an initial 478 // character of some kind, i.e. if the toy is currently vibrating 479 // then battery level comes up as "s89;" versus just "89;". We'll 480 // need to chop the semicolon and make sure we only read the 481 // numbers in the string. 482 // 483 // Contains() is casting a wider net than we need here, but it'll 484 // do for now. 485 let start_pos = usize::from(data_str.contains('s')); 486 if let Ok(level) = data_str[start_pos..(len - 1)].parse::<u8>() { 487 return Ok(message::InputReadingV4::new( 488 device_index, 489 feature_index, 490 InputTypeData::Battery(InputData::new(level)), 491 )); 492 } 493 } 494 } 495 HardwareEvent::Disconnected(_) => { 496 return Err(ButtplugDeviceError::ProtocolSpecificError( 497 "Lovense".to_owned(), 498 "Lovense Device disconnected while getting Battery info.".to_owned(), 499 )); 500 } 501 } 502 } 503 Err(ButtplugDeviceError::ProtocolSpecificError( 504 "Lovense".to_owned(), 505 "Lovense Device disconnected while getting Battery info.".to_owned(), 506 )) 507 } 508 .boxed() 509} 510 511pub(super) fn keepalive_strategy() -> ProtocolKeepaliveStrategy { 512 // For Lovense, we'll just repeat the device type packet and drop the result. 513 ProtocolKeepaliveStrategy::HardwareRequiredRepeatPacketStrategy(HardwareWriteCmd::new( 514 &[LOVENSE_PROTOCOL_UUID], 515 Endpoint::Tx, 516 b"DeviceType;".to_vec(), 517 false, 518 )) 519} 520 521pub(super) fn form_lovense_command( 522 feature_id: Uuid, 523 command: &str, 524) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 525 Ok(vec![ 526 HardwareWriteCmd::new( 527 &[feature_id], 528 Endpoint::Tx, 529 command.as_bytes().to_vec(), 530 false, 531 ) 532 .into(), 533 ]) 534} 535 536pub(super) fn form_vibrate_command( 537 feature_id: Uuid, 538 speed: u32, 539) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 540 form_lovense_command(feature_id, &format!("Vibrate:{speed};")) 541} 542 543// Due to swapping direction with lovense requiring a seperate command, we have to treat these like 544// two seperate outputs, otherwise we'll stomp on ourselves. Luckily Lovense devices currently only 545// have one rotation mechanism. 546const LOVENSE_ROTATE_UUID: Uuid = uuid!("4a741489-922f-4f0b-a594-175b75482849"); 547const LOVENSE_ROTATE_DIRECTION_UUID: Uuid = uuid!("4ad23456-2ba8-4916-bd91-9b603811f253"); 548 549pub(super) fn form_rotate_with_direction_command( 550 speed: u32, 551 change_direction: bool, 552) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 553 let mut hardware_cmds = vec![]; 554 if change_direction { 555 hardware_cmds.push( 556 HardwareWriteCmd::new( 557 &[LOVENSE_ROTATE_DIRECTION_UUID], 558 Endpoint::Tx, 559 b"RotateChange;".to_vec(), 560 false, 561 ) 562 .into(), 563 ); 564 } 565 let lovense_cmd = format!("Rotate:{speed};").as_bytes().to_vec(); 566 hardware_cmds 567 .push(HardwareWriteCmd::new(&[LOVENSE_ROTATE_UUID], Endpoint::Tx, lovense_cmd, false).into()); 568 trace!("{:?}", hardware_cmds); 569 Ok(hardware_cmds) 570}