Buttplug sex toy control library
at master 13 kB view raw
1use futures::{FutureExt, future}; 2use getset::{CopyGetters, Getters}; 3 4use buttplug_core::{ 5 errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, 6 message::{ 7 ButtplugDeviceMessageNameV4, 8 ButtplugServerMessageV4, 9 DeviceFeature, 10 DeviceFeatureOutputLimits, 11 InputCmdV4, 12 InputCommandType, 13 InputType, 14 InputTypeData, 15 OutputCmdV4, 16 OutputCommand, 17 OutputPositionWithDuration, 18 OutputType, 19 OutputValue, 20 }, 21}; 22 23use super::ClientDeviceOutputCommand; 24 25use crate::{ 26 ButtplugClientError, 27 ButtplugClientMessageSender, 28 ButtplugClientResultFuture, 29 create_boxed_future_client_error, 30 device::ClientDeviceCommandValue, 31}; 32 33#[derive(Getters, CopyGetters, Clone)] 34pub struct ClientDeviceFeature { 35 #[getset(get_copy = "pub")] 36 device_index: u32, 37 #[getset(get_copy = "pub")] 38 feature_index: u32, 39 #[getset(get = "pub")] 40 feature: DeviceFeature, 41 /// Sends commands from the [ButtplugClientDevice] instance to the 42 /// [ButtplugClient][super::ButtplugClient]'s event loop, which will then send 43 /// the message on to the [ButtplugServer][crate::server::ButtplugServer] 44 /// through the connector. 45 event_loop_sender: ButtplugClientMessageSender, 46} 47 48impl ClientDeviceFeature { 49 pub(super) fn new( 50 device_index: u32, 51 feature_index: u32, 52 feature: &DeviceFeature, 53 event_loop_sender: &ButtplugClientMessageSender, 54 ) -> Self { 55 Self { 56 device_index, 57 feature_index, 58 feature: feature.clone(), 59 event_loop_sender: event_loop_sender.clone(), 60 } 61 } 62 63 fn check_step_value( 64 &self, 65 feature_output: &dyn DeviceFeatureOutputLimits, 66 steps: i32, 67 ) -> Result<i32, ButtplugClientError> { 68 if feature_output.step_limit().contains(&(steps)) { 69 Ok(steps) 70 } else { 71 Err(ButtplugClientError::ButtplugOutputCommandConversionError( 72 format!( 73 "{} is larger than the maximum number of steps ({}).", 74 steps, 75 feature_output.step_count() 76 ), 77 )) 78 } 79 } 80 81 fn convert_float_value( 82 &self, 83 feature_output: &dyn DeviceFeatureOutputLimits, 84 float_amt: f64, 85 ) -> Result<i32, ButtplugClientError> { 86 if !(-1.0f64..=1.0f64).contains(&float_amt) { 87 Err(ButtplugClientError::ButtplugOutputCommandConversionError( 88 "Float values must be between 0.0 and 1.0".to_owned(), 89 )) 90 } else { 91 let mut val = float_amt * feature_output.step_count() as f64; 92 val = if val > 0.000001f64 { 93 val.ceil() 94 } else { 95 val.floor() 96 }; 97 Ok(val as i32) 98 } 99 } 100 101 pub(super) fn convert_client_cmd_to_output_cmd( 102 &self, 103 client_cmd: &ClientDeviceOutputCommand, 104 ) -> Result<OutputCmdV4, ButtplugClientError> { 105 let output_type: OutputType = client_cmd.into(); 106 // First off, make sure we support this output. 107 let output = self 108 .feature 109 .output() 110 .as_ref() 111 .ok_or(ButtplugClientError::ButtplugOutputCommandConversionError( 112 format!( 113 "Device feature does not support output type {}", 114 output_type 115 ), 116 ))? 117 .get(output_type) 118 .ok_or(ButtplugClientError::ButtplugOutputCommandConversionError( 119 format!( 120 "Device feature does not support output type {}", 121 output_type 122 ), 123 ))?; 124 125 let output_cmd = match client_cmd { 126 ClientDeviceOutputCommand::VibrateFloat(v) => { 127 OutputCommand::Vibrate(OutputValue::new(self.convert_float_value(output, *v)?)) 128 } 129 ClientDeviceOutputCommand::OscillateFloat(v) => { 130 OutputCommand::Oscillate(OutputValue::new(self.convert_float_value(output, *v)?)) 131 } 132 ClientDeviceOutputCommand::RotateFloat(v) => { 133 OutputCommand::Rotate(OutputValue::new(self.convert_float_value(output, *v)?)) 134 } 135 ClientDeviceOutputCommand::ConstrictFloat(v) => { 136 OutputCommand::Constrict(OutputValue::new(self.convert_float_value(output, *v)?)) 137 } 138 ClientDeviceOutputCommand::TemperatureFloat(v) => { 139 OutputCommand::Temperature(OutputValue::new(self.convert_float_value(output, *v)?)) 140 } 141 ClientDeviceOutputCommand::LedFloat(v) => { 142 OutputCommand::Led(OutputValue::new(self.convert_float_value(output, *v)?)) 143 } 144 ClientDeviceOutputCommand::SprayFloat(v) => { 145 OutputCommand::Spray(OutputValue::new(self.convert_float_value(output, *v)?)) 146 } 147 ClientDeviceOutputCommand::PositionFloat(v) => { 148 OutputCommand::Position(OutputValue::new(self.convert_float_value(output, *v)?)) 149 } 150 ClientDeviceOutputCommand::PositionWithDurationFloat(v, d) => { 151 OutputCommand::PositionWithDuration(OutputPositionWithDuration::new( 152 self.convert_float_value(output, *v)? as u32, 153 *d, 154 )) 155 } 156 ClientDeviceOutputCommand::Vibrate(v) => { 157 OutputCommand::Vibrate(OutputValue::new(self.check_step_value(output, *v as i32)?)) 158 } 159 ClientDeviceOutputCommand::Oscillate(v) => { 160 OutputCommand::Oscillate(OutputValue::new(self.check_step_value(output, *v as i32)?)) 161 } 162 ClientDeviceOutputCommand::Rotate(v) => { 163 OutputCommand::Rotate(OutputValue::new(self.check_step_value(output, *v)?)) 164 } 165 ClientDeviceOutputCommand::Constrict(v) => { 166 OutputCommand::Constrict(OutputValue::new(self.check_step_value(output, *v as i32)?)) 167 } 168 ClientDeviceOutputCommand::Temperature(v) => { 169 OutputCommand::Temperature(OutputValue::new(self.check_step_value(output, *v as i32)?)) 170 } 171 ClientDeviceOutputCommand::Led(v) => { 172 OutputCommand::Led(OutputValue::new(self.check_step_value(output, *v as i32)?)) 173 } 174 ClientDeviceOutputCommand::Spray(v) => { 175 OutputCommand::Spray(OutputValue::new(self.check_step_value(output, *v as i32)?)) 176 } 177 ClientDeviceOutputCommand::Position(v) => { 178 OutputCommand::Position(OutputValue::new(self.check_step_value(output, *v as i32)?)) 179 } 180 ClientDeviceOutputCommand::PositionWithDuration(v, d) => OutputCommand::PositionWithDuration( 181 OutputPositionWithDuration::new(self.check_step_value(output, *v as i32)? as u32, *d), 182 ), 183 }; 184 Ok(OutputCmdV4::new( 185 self.device_index, 186 self.feature_index, 187 output_cmd, 188 )) 189 } 190 191 pub fn send_command( 192 &self, 193 client_device_command: &ClientDeviceOutputCommand, 194 ) -> ButtplugClientResultFuture { 195 match self.convert_client_cmd_to_output_cmd(client_device_command) { 196 Ok(cmd) => self.event_loop_sender.send_message_expect_ok(cmd.into()), 197 Err(e) => future::ready(Err(e)).boxed(), 198 } 199 } 200 201 pub fn vibrate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 202 let val = level.into(); 203 self.send_command(&match val { 204 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v as u32), 205 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::VibrateFloat(f), 206 }) 207 } 208 209 pub fn oscillate( 210 &self, 211 level: impl Into<ClientDeviceCommandValue>, 212 ) -> ButtplugClientResultFuture { 213 let val = level.into(); 214 self.send_command(&match val { 215 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Oscillate(v as u32), 216 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::OscillateFloat(f), 217 }) 218 } 219 220 pub fn rotate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 221 let val = level.into(); 222 self.send_command(&match val { 223 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Rotate(v), 224 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::RotateFloat(f), 225 }) 226 } 227 228 pub fn spray(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 229 let val = level.into(); 230 self.send_command(&match val { 231 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Spray(v as u32), 232 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::SprayFloat(f), 233 }) 234 } 235 236 pub fn constrict( 237 &self, 238 level: impl Into<ClientDeviceCommandValue>, 239 ) -> ButtplugClientResultFuture { 240 let val = level.into(); 241 self.send_command(&match val { 242 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Constrict(v as u32), 243 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::ConstrictFloat(f), 244 }) 245 } 246 247 pub fn position(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 248 let val = level.into(); 249 self.send_command(&match val { 250 ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Position(v as u32), 251 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::PositionFloat(f), 252 }) 253 } 254 255 pub fn position_with_duration( 256 &self, 257 position: impl Into<ClientDeviceCommandValue>, 258 duration_in_ms: u32, 259 ) -> ButtplugClientResultFuture { 260 let val = position.into(); 261 self.send_command(&match val { 262 ClientDeviceCommandValue::Int(v) => { 263 ClientDeviceOutputCommand::PositionWithDuration(v as u32, duration_in_ms) 264 } 265 ClientDeviceCommandValue::Float(f) => { 266 ClientDeviceOutputCommand::PositionWithDurationFloat(f, duration_in_ms) 267 } 268 }) 269 } 270 271 pub fn subscribe_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture { 272 if let Some(sensor_map) = self.feature.input() 273 && let Some(sensor) = sensor_map.get(sensor_type) 274 && sensor 275 .input_commands() 276 .contains(&InputCommandType::Subscribe) 277 { 278 let msg = InputCmdV4::new( 279 self.device_index, 280 self.feature_index, 281 sensor_type, 282 InputCommandType::Subscribe, 283 ) 284 .into(); 285 return self.event_loop_sender.send_message_expect_ok(msg); 286 } 287 create_boxed_future_client_error( 288 ButtplugDeviceError::MessageNotSupported(ButtplugDeviceMessageNameV4::InputCmd.to_string()) 289 .into(), 290 ) 291 } 292 293 pub fn unsubscribe_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture { 294 if let Some(sensor_map) = self.feature.input() 295 && let Some(sensor) = sensor_map.get(sensor_type) 296 && sensor 297 .input_commands() 298 .contains(&InputCommandType::Subscribe) 299 { 300 let msg = InputCmdV4::new( 301 self.device_index, 302 self.feature_index, 303 sensor_type, 304 InputCommandType::Unsubscribe, 305 ) 306 .into(); 307 return self.event_loop_sender.send_message_expect_ok(msg); 308 } 309 create_boxed_future_client_error( 310 ButtplugDeviceError::MessageNotSupported(ButtplugDeviceMessageNameV4::InputCmd.to_string()) 311 .into(), 312 ) 313 } 314 315 fn read_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture<InputTypeData> { 316 if let Some(sensor_map) = self.feature.input() 317 && let Some(sensor) = sensor_map.get(sensor_type) 318 && sensor.input_commands().contains(&InputCommandType::Read) 319 { 320 let msg = InputCmdV4::new( 321 self.device_index, 322 self.feature_index, 323 sensor_type, 324 InputCommandType::Read, 325 ) 326 .into(); 327 let reply = self.event_loop_sender.send_message(msg); 328 return async move { 329 if let ButtplugServerMessageV4::InputReading(data) = reply.await? { 330 if sensor_type == data.data().as_input_type() { 331 Ok(data.data()) 332 } else { 333 Err( 334 ButtplugError::ButtplugMessageError(ButtplugMessageError::UnexpectedMessageType( 335 "InputReading".to_owned(), 336 )) 337 .into(), 338 ) 339 } 340 } else { 341 Err( 342 ButtplugError::ButtplugMessageError(ButtplugMessageError::UnexpectedMessageType( 343 "InputReading".to_owned(), 344 )) 345 .into(), 346 ) 347 } 348 } 349 .boxed(); 350 } 351 create_boxed_future_client_error( 352 ButtplugDeviceError::MessageNotSupported(ButtplugDeviceMessageNameV4::InputCmd.to_string()) 353 .into(), 354 ) 355 } 356 357 pub fn battery_level(&self) -> ButtplugClientResultFuture<u32> { 358 if self 359 .feature() 360 .input() 361 .as_ref() 362 .ok_or(false) 363 .unwrap() 364 .contains(InputType::Battery) 365 { 366 let send_fut = self.read_sensor(InputType::Battery); 367 Box::pin(async move { 368 let data = send_fut.await?; 369 let battery_level = if let InputTypeData::Battery(level) = data { 370 level.data() 371 } else { 372 0 373 }; 374 Ok(battery_level as u32) 375 }) 376 } else { 377 create_boxed_future_client_error( 378 ButtplugDeviceError::DeviceFeatureMismatch("Device feature is not battery".to_owned()) 379 .into(), 380 ) 381 } 382 } 383 384 pub fn rssi_level(&self) -> ButtplugClientResultFuture<i8> { 385 if self 386 .feature() 387 .input() 388 .as_ref() 389 .ok_or(false) 390 .unwrap() 391 .contains(InputType::Rssi) 392 { 393 let send_fut = self.read_sensor(InputType::Rssi); 394 Box::pin(async move { 395 let data = send_fut.await?; 396 let rssi_level = if let InputTypeData::Rssi(level) = data { 397 level.data() 398 } else { 399 0 400 }; 401 Ok(rssi_level) 402 }) 403 } else { 404 create_boxed_future_client_error( 405 ButtplugDeviceError::DeviceFeatureMismatch("Device feature is not RSSI".to_owned()).into(), 406 ) 407 } 408 } 409}