Buttplug sex toy control library

fix: Fix issues with rotation now being i32

Needed to update tests, fix schema, client calculation ranges

+71 -128
+9 -3
crates/buttplug_client/src/device/command.rs
··· 3 3 use crate::ButtplugClientError; 4 4 5 5 pub enum ClientDeviceCommandValue { 6 - Int(u32), 6 + Int(i32), 7 7 Float(f64), 8 8 } 9 9 10 + impl From<i32> for ClientDeviceCommandValue { 11 + fn from(val: i32) -> Self { 12 + ClientDeviceCommandValue::Int(val) 13 + } 14 + } 15 + 10 16 impl From<u32> for ClientDeviceCommandValue { 11 17 fn from(val: u32) -> Self { 12 - ClientDeviceCommandValue::Int(val) 18 + ClientDeviceCommandValue::Int(val as i32) 13 19 } 14 20 } 15 21 ··· 22 28 pub enum ClientDeviceOutputCommand { 23 29 // u32 types use steps, need to compare before sending 24 30 Vibrate(u32), 25 - Rotate(u32), 31 + Rotate(i32), 26 32 Oscillate(u32), 27 33 Constrict(u32), 28 34 Heater(u32),
+1 -1
crates/buttplug_client/src/device/device.rs
··· 213 213 pub fn vibrate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 214 214 let val = level.into(); 215 215 self.set_client_value(&match val { 216 - ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v), 216 + ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v as u32), 217 217 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::VibrateFloat(f), 218 218 }) 219 219 }
+10 -8
crates/buttplug_client/src/device/feature.rs
··· 83 83 feature_output: &dyn DeviceFeatureOutputLimits, 84 84 float_amt: f64, 85 85 ) -> Result<i32, ButtplugClientError> { 86 - if !(0.0f64..=1.0f64).contains(&float_amt) { 86 + if !(-1.0f64..=1.0f64).contains(&float_amt) { 87 87 Err(ButtplugClientError::ButtplugOutputCommandConversionError( 88 88 "Float values must be between 0.0 and 1.0".to_owned(), 89 89 )) 90 90 } else { 91 - Ok((float_amt * feature_output.step_count() as f64).ceil() as i32) 91 + let mut val = float_amt * feature_output.step_count() as f64; 92 + val = if val > 0.000001f64 { val.ceil() } else { val.floor() }; 93 + Ok(val as i32) 92 94 } 93 95 } 94 96 ··· 195 197 pub fn vibrate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 196 198 let val = level.into(); 197 199 self.send_command(&match val { 198 - ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v), 200 + ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v as u32), 199 201 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::VibrateFloat(f), 200 202 }) 201 203 } ··· 206 208 ) -> ButtplugClientResultFuture { 207 209 let val = level.into(); 208 210 self.send_command(&match val { 209 - ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Oscillate(v), 211 + ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Oscillate(v as u32), 210 212 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::OscillateFloat(f), 211 213 }) 212 214 } ··· 222 224 pub fn spray(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 223 225 let val = level.into(); 224 226 self.send_command(&match val { 225 - ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Spray(v), 227 + ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Spray(v as u32), 226 228 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::SprayFloat(f), 227 229 }) 228 230 } ··· 233 235 ) -> ButtplugClientResultFuture { 234 236 let val = level.into(); 235 237 self.send_command(&match val { 236 - ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Constrict(v), 238 + ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Constrict(v as u32), 237 239 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::ConstrictFloat(f), 238 240 }) 239 241 } ··· 241 243 pub fn position(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { 242 244 let val = level.into(); 243 245 self.send_command(&match val { 244 - ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Position(v), 246 + ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Position(v as u32), 245 247 ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::PositionFloat(f), 246 248 }) 247 249 } ··· 254 256 let val = position.into(); 255 257 self.send_command(&match val { 256 258 ClientDeviceCommandValue::Int(v) => { 257 - ClientDeviceOutputCommand::PositionWithDuration(v, duration_in_ms) 259 + ClientDeviceOutputCommand::PositionWithDuration(v as u32, duration_in_ms) 258 260 } 259 261 ClientDeviceCommandValue::Float(f) => { 260 262 ClientDeviceOutputCommand::PositionWithDurationFloat(f, duration_in_ms)
+1 -18
crates/buttplug_core/schema/buttplug-schema.json
··· 459 459 "type": "object", 460 460 "properties": { 461 461 "Value": { 462 - "type": "number", 463 - "minimum": 0 462 + "type": "number" 464 463 } 465 464 }, 466 465 "required": [ 467 466 "Value" 468 - ] 469 - }, 470 - "^RotateWithDirection$": { 471 - "type": "object", 472 - "properties": { 473 - "Speed": { 474 - "type": "number", 475 - "minimum": 0 476 - }, 477 - "Clockwise": { 478 - "type": "boolean" 479 - } 480 - }, 481 - "required": [ 482 - "Speed", 483 - "Clockwise" 484 467 ] 485 468 }, 486 469 "^PositionWithDuration$": {
+1 -1
crates/buttplug_core/src/errors.rs
··· 138 138 /// Device communication error: {0} 139 139 DeviceCommunicationError(String), 140 140 /// Device feature only has {0} steps for control, but {1} steps specified. 141 - DeviceStepRangeError(u32, u32), 141 + DeviceStepRangeError(i32, i32), 142 142 /// Device got {0} message but has no actuators 143 143 DeviceNoActuatorError(String), 144 144 /// Device got {0} message but has no sensors
-10
crates/buttplug_server/src/device/protocol.rs
··· 341 341 self.command_unimplemented("OutputCmd (Position w/ Duration Actuator)") 342 342 } 343 343 344 - fn handle_rotation_with_direction_cmd( 345 - &self, 346 - _feature_index: u32, 347 - _feature_id: Uuid, 348 - _speed: u32, 349 - _clockwise: bool, 350 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 351 - self.command_unimplemented("OutputCmd (Rotation w/ Direction Actuator)") 352 - } 353 - 354 344 fn handle_input_subscribe_cmd( 355 345 &self, 356 346 _device_index: u32,
-16
crates/buttplug_server/src/device/protocol_impl/lovense/lovense_rotate_vibrator.rs
··· 47 47 form_rotate_with_direction_command(speed.abs() as u32, speed < 0) 48 48 } 49 49 50 - fn handle_rotation_with_direction_cmd( 51 - &self, 52 - _feature_index: u32, 53 - _feature_id: Uuid, 54 - speed: u32, 55 - clockwise: bool, 56 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 57 - let change = if clockwise != self.clockwise.load(Ordering::Relaxed) { 58 - self.clockwise.store(clockwise, Ordering::Relaxed); 59 - true 60 - } else { 61 - false 62 - }; 63 - form_rotate_with_direction_command(speed, change) 64 - } 65 - 66 50 fn handle_battery_level_cmd( 67 51 &self, 68 52 device_index: u32,
+3 -4
crates/buttplug_server/src/device/protocol_impl/motorbunny.rs
··· 50 50 ]) 51 51 } 52 52 53 - fn handle_rotation_with_direction_cmd( 53 + fn handle_output_rotate_cmd( 54 54 &self, 55 55 _feature_index: u32, 56 56 feature_id: Uuid, 57 - speed: u32, 58 - clockwise: bool, 57 + speed: i32, 59 58 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 60 59 let mut command_vec: Vec<u8>; 61 60 if speed == 0 { 62 61 command_vec = vec![0xa0, 0x00, 0x00, 0x00, 0x00, 0xec]; 63 62 } else { 64 63 command_vec = vec![0xaf]; 65 - let mut rotate_command = [if clockwise { 0x2a } else { 0x29 }, speed as u8].repeat(7); 64 + let mut rotate_command = [if speed >= 0 { 0x2a } else { 0x29 }, speed.unsigned_abs() as u8].repeat(7); 66 65 let crc = rotate_command 67 66 .iter() 68 67 .fold(0u8, |a, b| a.overflowing_add(*b).0);
+7 -8
crates/buttplug_server/src/device/protocol_impl/nexus_revo.rs
··· 37 37 ]) 38 38 } 39 39 40 - fn handle_rotation_with_direction_cmd( 41 - &self, 42 - _feature_index: u32, 43 - feature_id: Uuid, 44 - speed: u32, 45 - clockwise: bool, 46 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 40 + fn handle_output_rotate_cmd( 41 + &self, 42 + _feature_index: u32, 43 + feature_id: Uuid, 44 + speed: i32, 45 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 47 46 Ok(vec![ 48 47 HardwareWriteCmd::new( 49 48 &[feature_id], ··· 53 52 0x01, 54 53 0x02, 55 54 0x00, 56 - speed as u8 + if speed != 0 && clockwise { 2 } else { 0 }, 55 + speed.unsigned_abs() as u8 + if speed > 0 { 2 } else { 0 }, 57 56 0x00, 58 57 ], 59 58 true,
+7 -8
crates/buttplug_server/src/device/protocol_impl/synchro.rs
··· 20 20 pub struct Synchro {} 21 21 22 22 impl ProtocolHandler for Synchro { 23 - fn handle_rotation_with_direction_cmd( 24 - &self, 25 - _feature_index: u32, 26 - feature_id: Uuid, 27 - speed: u32, 28 - clockwise: bool, 29 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 23 + fn handle_output_rotate_cmd( 24 + &self, 25 + _feature_index: u32, 26 + feature_id: Uuid, 27 + speed: i32, 28 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 30 29 Ok(vec![ 31 30 HardwareWriteCmd::new( 32 31 &[feature_id], ··· 34 33 vec![ 35 34 0xa1, 36 35 0x01, 37 - speed as u8 | if clockwise || speed == 0 { 0x00 } else { 0x80 }, 36 + speed.unsigned_abs() as u8 | if speed >= 0 { 0x00 } else { 0x80 }, 38 37 0x77, 39 38 0x55, 40 39 ],
+8 -9
crates/buttplug_server/src/device/protocol_impl/tryfun_meta2.rs
··· 54 54 ]) 55 55 } 56 56 57 - fn handle_rotation_with_direction_cmd( 58 - &self, 59 - _feature_index: u32, 60 - feature_id: Uuid, 61 - speed: u32, 62 - clockwise: bool, 63 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 57 + fn handle_output_rotate_cmd( 58 + &self, 59 + _feature_index: u32, 60 + feature_id: Uuid, 61 + speed: i32, 62 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 64 63 let mut speed = speed as i8; 65 - if clockwise { 64 + if speed >= 0 { 66 65 speed += 1; 67 - speed *= -1; 68 66 } 67 + speed *= -1; 69 68 let mut sum: u8 = 0xff; 70 69 let mut data = vec![ 71 70 self.packet_id.fetch_add(1, Ordering::Relaxed),
+7 -15
crates/buttplug_server/src/device/protocol_impl/vorze_sa/dual_rotator.rs
··· 26 26 } 27 27 28 28 impl ProtocolHandler for VorzeSADualRotator { 29 - fn handle_rotation_with_direction_cmd( 30 - &self, 31 - feature_index: u32, 32 - _feature_id: uuid::Uuid, 33 - speed: u32, 34 - clockwise: bool, 35 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 36 - self.speeds[feature_index as usize].store( 37 - if clockwise { 38 - speed as i8 39 - } else { 40 - -(speed as i8) 41 - }, 42 - Ordering::Relaxed, 43 - ); 29 + fn handle_output_rotate_cmd( 30 + &self, 31 + feature_index: u32, 32 + _feature_id: Uuid, 33 + speed: i32, 34 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 35 + self.speeds[feature_index as usize].store(speed as i8, Ordering::Relaxed); 44 36 let speed_left = self.speeds[0].load(Ordering::Relaxed); 45 37 let data_left = ((speed_left >= 0) as u8) << 7 | (speed_left.unsigned_abs()); 46 38 let speed_right = self.speeds[1].load(Ordering::Relaxed);
+8 -9
crates/buttplug_server/src/device/protocol_impl/vorze_sa/single_rotator.rs
··· 25 25 } 26 26 27 27 impl ProtocolHandler for VorzeSASingleRotator { 28 - fn handle_rotation_with_direction_cmd( 29 - &self, 30 - _feature_index: u32, 31 - feature_id: uuid::Uuid, 32 - speed: u32, 33 - clockwise: bool, 34 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 35 - let clockwise = if clockwise { 1u8 } else { 0 }; 36 - let data: u8 = (clockwise) << 7 | (speed as u8); 28 + fn handle_output_rotate_cmd( 29 + &self, 30 + _feature_index: u32, 31 + feature_id: uuid::Uuid, 32 + speed: i32, 33 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 34 + let clockwise = if speed >=0 { 1u8 } else { 0 }; 35 + let data: u8 = (clockwise) << 7 | (speed.unsigned_abs() as u8); 37 36 Ok(vec![ 38 37 HardwareWriteCmd::new( 39 38 &[feature_id],
+4 -1
crates/buttplug_server/src/message/v4/checked_output_cmd.rs
··· 109 109 let value = cmd.command().value(); 110 110 let new_value = output_map 111 111 .calculate_from_value(output_type, value as i32) 112 - .map_err(|_| ButtplugDeviceError::DeviceStepRangeError(0, value.abs() as u32))?; 112 + .map_err(|e| { 113 + error!("{:?}", e); 114 + ButtplugDeviceError::DeviceStepRangeError(0, value) 115 + })?; 113 116 let mut new_command = cmd.command(); 114 117 new_command.set_value(new_value); 115 118 // We can't make a private trait impl to turn a ValueCmd into a CheckedValueCmd, and this
+1 -1
crates/buttplug_server_device_config/src/server_device_feature.rs
··· 143 143 }; 144 144 let current_value = value.unsigned_abs(); 145 145 let mult = if value < 0 { -1 } else { 1 }; 146 - if value > 0 && range.contains(&(range.start() + current_value)) { 146 + if value != 0 && range.contains(&(range.start() + current_value)) { 147 147 Ok((range.start() + current_value) as i32 * mult) 148 148 } else if value == 0 { 149 149 Ok(0)
+3 -15
crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs
··· 7 7 ButtplugClient, 8 8 ButtplugClientDevice, 9 9 ButtplugClientEvent, 10 - device::{ClientDeviceFeature, ClientDeviceOutputCommand}, 10 + device::{ClientDeviceCommandValue, ClientDeviceFeature, ClientDeviceOutputCommand}, 11 11 }; 12 12 use buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder; 13 13 use buttplug_core::{message::OutputType, util::async_manager}; ··· 89 89 .feature() 90 90 .output() 91 91 .as_ref() 92 - .is_some_and(|x| x.contains(OutputType::RotateWithDirection)) 92 + .is_some_and(|x| x.contains(OutputType::Rotate)) 93 93 }) 94 94 .map(|(_, x)| x) 95 95 .collect(); 96 96 let f = rotate_features[cmd.index() as usize].clone(); 97 - f.rotate_with_direction( 98 - (cmd.speed() 99 - * f 100 - .feature() 101 - .output() 102 - .as_ref() 103 - .unwrap() 104 - .get(OutputType::RotateWithDirection) 105 - .unwrap() 106 - .step_count() as f64) 107 - .ceil() as u32, 108 - cmd.clockwise(), 109 - ) 97 + f.rotate(ClientDeviceCommandValue::Float(cmd.speed() * if cmd.clockwise() { 1f64 } else { -1f64 })) 110 98 }) 111 99 .collect(); 112 100 futures::future::try_join_all(fut_vec).await.unwrap();
+1 -1
crates/examples/src/bin/device_control.rs
··· 72 72 // We can use the convenience functions on ButtplugClientDevice to 73 73 // send the message. This version sets all of the motors on a 74 74 // vibrating device to the same speed. 75 - test_client_device.vibrate(10).await?; 75 + test_client_device.vibrate(0.5f64).await?; 76 76 77 77 // If we wanted to just set one motor on and the other off, we could 78 78 // try this version that uses an array. It'll throw an exception if