Buttplug sex toy control library
at master 118 lines 3.7 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 crate::device::{ 9 hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 10 protocol::{ProtocolHandler, ProtocolKeepaliveStrategy}, 11}; 12use buttplug_core::{ 13 errors::ButtplugDeviceError, 14 message::InputReadingV4, 15 util::{async_manager, sleep}, 16}; 17use buttplug_server_device_config::Endpoint; 18use futures::future::BoxFuture; 19use std::{ 20 sync::{ 21 Arc, 22 atomic::{AtomicU32, Ordering}, 23 }, 24 time::Duration, 25}; 26use uuid::{Uuid, uuid}; 27 28const LOVENSE_STROKER_PROTOCOL_UUID: Uuid = uuid!("a97fc354-5561-459a-bc62-110d7c2868ac"); 29 30pub struct LovenseStroker { 31 linear_info: Arc<(AtomicU32, AtomicU32)>, 32} 33 34impl LovenseStroker { 35 pub fn new(hardware: Arc<Hardware>) -> Self { 36 let linear_info = Arc::new((AtomicU32::new(0), AtomicU32::new(0))); 37 async_manager::spawn(update_linear_movement( 38 hardware.clone(), 39 linear_info.clone(), 40 )); 41 Self { linear_info } 42 } 43} 44 45impl ProtocolHandler for LovenseStroker { 46 fn keepalive_strategy(&self) -> ProtocolKeepaliveStrategy { 47 super::keepalive_strategy() 48 } 49 50 fn handle_position_with_duration_cmd( 51 &self, 52 _feature_index: u32, 53 _feature_id: Uuid, 54 position: u32, 55 duration: u32, 56 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 57 self.linear_info.0.store(position, Ordering::Relaxed); 58 self.linear_info.1.store(duration, Ordering::Relaxed); 59 Ok(vec![]) 60 } 61 62 fn handle_battery_level_cmd( 63 &self, 64 device_index: u32, 65 device: Arc<Hardware>, 66 feature_index: u32, 67 feature_id: Uuid, 68 ) -> BoxFuture<'static, Result<InputReadingV4, ButtplugDeviceError>> { 69 super::handle_battery_level_cmd(device_index, device, feature_index, feature_id) 70 } 71} 72 73async fn update_linear_movement(device: Arc<Hardware>, linear_info: Arc<(AtomicU32, AtomicU32)>) { 74 let mut last_goal_position = 0i32; 75 let mut current_move_amount = 0i32; 76 let mut current_position = 0i32; 77 loop { 78 // See if we've updated our goal position 79 let goal_position = linear_info.0.load(Ordering::Relaxed) as i32; 80 // If we have and it's not the same, recalculate based on current status. 81 if last_goal_position != goal_position { 82 last_goal_position = goal_position; 83 // We move every 100ms, so divide the movement into that many chunks. 84 // If we're moving so fast it'd be under our 100ms boundary, just move in 1 step. 85 let move_steps = (linear_info.1.load(Ordering::Relaxed) / 100).max(1); 86 current_move_amount = (goal_position - current_position) / move_steps as i32; 87 } 88 89 // If we aren't going anywhere, just pause then restart 90 if current_position == last_goal_position { 91 sleep(Duration::from_millis(100)).await; 92 continue; 93 } 94 95 // Update our position, make sure we don't overshoot 96 current_position += current_move_amount; 97 if current_move_amount < 0 { 98 if current_position < last_goal_position { 99 current_position = last_goal_position; 100 } 101 } else if current_position > last_goal_position { 102 current_position = last_goal_position; 103 } 104 105 let lovense_cmd = format!("FSetSite:{current_position};"); 106 107 let hardware_cmd: HardwareWriteCmd = HardwareWriteCmd::new( 108 &[LOVENSE_STROKER_PROTOCOL_UUID], 109 Endpoint::Tx, 110 lovense_cmd.into_bytes(), 111 false, 112 ); 113 if device.write_value(&hardware_cmd).await.is_err() { 114 return; 115 } 116 sleep(Duration::from_millis(100)).await; 117 } 118}