Buttplug sex toy control library
at dev 8.1 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::fleshlight_launch_helper::calculate_speed; 9use crate::{ 10 device::{ 11 hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd}, 12 protocol::{ProtocolHandler, generic_protocol_setup}, 13 }, 14 message::ButtplugServerDeviceMessage, 15}; 16use buttplug_core::{ 17 errors::ButtplugDeviceError, 18 message::{InputData, InputReadingV4, InputTypeData}, 19 util::stream::convert_broadcast_receiver_to_stream, 20}; 21use buttplug_server_device_config::Endpoint; 22// use dashmap::DashSet; 23use futures::{FutureExt, StreamExt, future::BoxFuture}; 24use std::{ 25 default::Default, 26 pin::Pin, 27 sync::{ 28 Arc, 29 atomic::{AtomicU8, Ordering::Relaxed}, 30 }, 31}; 32use tokio::sync::broadcast; 33use uuid::Uuid; 34 35generic_protocol_setup!(KiirooV21, "kiiroo-v21"); 36 37pub struct KiirooV21 { 38 previous_position: Arc<AtomicU8>, 39 // Set of sensors we've subscribed to for updates. 40 // subscribed_sensors: Arc<DashSet<u32>>, 41 event_stream: broadcast::Sender<ButtplugServerDeviceMessage>, 42} 43 44impl Default for KiirooV21 { 45 fn default() -> Self { 46 let (sender, _) = broadcast::channel(256); 47 Self { 48 previous_position: Default::default(), 49 // subscribed_sensors: Arc::new(DashSet::new()), 50 event_stream: sender, 51 } 52 } 53} 54 55impl ProtocolHandler for KiirooV21 { 56 fn handle_output_vibrate_cmd( 57 &self, 58 _feature_index: u32, 59 feature_id: Uuid, 60 speed: u32, 61 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 62 Ok(vec![ 63 HardwareWriteCmd::new(&[feature_id], Endpoint::Tx, vec![0x01, speed as u8], false).into(), 64 ]) 65 } 66 67 fn handle_position_with_duration_cmd( 68 &self, 69 _feature_index: u32, 70 feature_id: Uuid, 71 position: u32, 72 duration: u32, 73 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 74 // In the protocol, we know max speed is 99, so convert here. We have to 75 // use AtomicU8 because there's no AtomicF64 yet. 76 let previous_position = self.previous_position.load(Relaxed); 77 let distance = (previous_position as f64 - (position as f64)).abs() / 99f64; 78 let position = position as u8; 79 let speed = (calculate_speed(distance, duration) * 99f64) as u8; 80 self.previous_position.store(position, Relaxed); 81 Ok(vec![ 82 HardwareWriteCmd::new( 83 &[feature_id], 84 Endpoint::Tx, 85 [0x03, 0x00, speed, position].to_vec(), 86 false, 87 ) 88 .into(), 89 ]) 90 } 91 92 fn handle_battery_level_cmd( 93 &self, 94 device_index: u32, 95 device: Arc<Hardware>, 96 feature_index: u32, 97 feature_id: Uuid, 98 ) -> BoxFuture<'_, Result<InputReadingV4, ButtplugDeviceError>> { 99 debug!("Trying to get battery reading."); 100 // Reading the "whitelist" endpoint for this device retrieves the battery level, 101 // which is byte 5. All other bytes of the 20-byte result are unknown. 102 let msg = HardwareReadCmd::new(feature_id, Endpoint::Whitelist, 20, 0); 103 let fut = device.read_value(&msg); 104 async move { 105 let hw_msg = fut.await?; 106 let data = hw_msg.data(); 107 if data.len() != 20 { 108 // Maybe not the Kiiroo Pearl 2.1? 109 return Err(ButtplugDeviceError::DeviceCommunicationError( 110 "Kiiroo battery data not expected length!".to_owned(), 111 )); 112 } 113 let battery_level = data[5]; 114 let battery_reading = InputReadingV4::new( 115 device_index, 116 feature_index, 117 InputTypeData::Battery(InputData::new(battery_level)), 118 ); 119 debug!("Got battery reading: {}", battery_level); 120 Ok(battery_reading) 121 } 122 .boxed() 123 } 124 125 fn event_stream( 126 &self, 127 ) -> Pin<Box<dyn futures::Stream<Item = ButtplugServerDeviceMessage> + Send>> { 128 convert_broadcast_receiver_to_stream(self.event_stream.subscribe()).boxed() 129 } 130 131 /* 132 fn handle_input_subscribe_cmd( 133 &self, 134 device_index: u32, 135 device: Arc<Hardware>, 136 feature_index: u32, 137 feature_id: Uuid, 138 _sensor_type: InputType, 139 ) -> BoxFuture<Result<(), ButtplugDeviceError>> { 140 if self.subscribed_sensors.contains(&feature_index) { 141 return future::ready(Ok(())).boxed(); 142 } 143 let sensors = self.subscribed_sensors.clone(); 144 // Format for the Kiiroo Pearl 2.1: 145 // Byte 0-1: Raw u16be pressure sensor, smaller values indicate more pressure, channel 1. 146 // Zero values differ even between sensors on same device. 147 // Legal range is not known (might even be i16le), 148 // actual range on one device is around 850±50. 149 // Byte 2-3: Same, channel 2. 150 // Byte 4-5: Same, channel 3. 151 // Byte 6-7: Same, channel 4. 152 // Byte 8: Flags corresponding to pressure regions, thresholded on device: 153 // LSB is channel 1 pressed, next least significant bit is channel 2, etc. 154 async move { 155 // If we have no sensors we're currently subscribed to, we'll need to bring up our BLE 156 // characteristic subscription. 157 if sensors.is_empty() { 158 device 159 .subscribe(&HardwareSubscribeCmd::new(feature_id, Endpoint::Rx)) 160 .await?; 161 let sender = self.event_stream.clone(); 162 let mut hardware_stream = device.event_stream(); 163 let stream_sensors = sensors.clone(); 164 // If we subscribe successfully, we need to set up our event handler. 165 async_manager::spawn(async move { 166 while let Ok(info) = hardware_stream.recv().await { 167 // If we have no receivers, quit. 168 if sender.receiver_count() == 0 || stream_sensors.is_empty() { 169 return; 170 } 171 if let HardwareEvent::Notification(_, endpoint, data) = info { 172 if endpoint == Endpoint::Rx { 173 if data.len() != 9 { 174 // Maybe not the Kiiroo Pearl 2.1? 175 error!("Kiiroo sensor data not expected length!"); 176 continue; 177 } 178 // Extract our pressure values. 179 // Invert analog values so that the value increases with pressure. 180 let analog: Vec<i32> = (0..4) 181 .map(|i| { 182 (u16::MAX as i32) - ((data[2 * i] as i32) << 8 | (data[2 * i + 1] as i32)) 183 }) 184 .collect(); 185 let digital: Vec<i32> = (0..4).map(|i| ((data[8] as i32) >> i) & 1).collect(); 186 for ((sensor_index, sensor_type), sensor_data) in (0u32..) 187 .zip([InputType::Pressure, InputType::Button]) 188 .zip([analog, digital]) 189 { 190 if stream_sensors.contains(&sensor_index) 191 && sender 192 .send( 193 InputReadingV4::new(device_index, sensor_index, sensor_type, sensor_data) 194 .into(), 195 ) 196 .is_err() 197 { 198 debug!( 199 "Hardware device listener for Kiiroo 2.1 device shut down, returning from task." 200 ); 201 return; 202 } 203 } 204 } 205 } 206 } 207 }); 208 } 209 sensors.insert(feature_index); 210 Ok(()) 211 } 212 .boxed() 213 } 214 215 fn handle_input_unsubscribe_cmd( 216 &self, 217 device: Arc<Hardware>, 218 feature_index: u32, 219 feature_id: Uuid, 220 _sensor_type: InputType, 221 ) -> BoxFuture<Result<(), ButtplugDeviceError>> { 222 if !self.subscribed_sensors.contains(&feature_index) { 223 return future::ready(Ok(())).boxed(); 224 } 225 let sensors = self.subscribed_sensors.clone(); 226 async move { 227 // If we have no sensors we're currently subscribed to, we'll need to end our BLE 228 // characteristic subscription. 229 sensors.remove(&feature_index); 230 if sensors.is_empty() { 231 device 232 .unsubscribe(&HardwareUnsubscribeCmd::new(feature_id, Endpoint::Rx)) 233 .await?; 234 } 235 Ok(()) 236 } 237 .boxed() 238 } 239 */ 240}