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}