Buttplug sex toy control library

chore: Finish implementation of struct based input/output storage

+941 -627
+5 -5
crates/buttplug_client/src/device/device.rs
··· 159 159 if let Some(output) = x.1 160 160 .feature() 161 161 .output() { 162 - output.contains_key(&actuator_type) 162 + output.contains(actuator_type) 163 163 } else { 164 164 false 165 165 } ··· 212 212 self 213 213 .device_features 214 214 .iter() 215 - .any(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains_key(&InputType::Battery))) 215 + .any(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains(InputType::Battery))) 216 216 } 217 217 218 218 pub fn battery_level(&self) -> ButtplugClientResultFuture<u32> { 219 219 if let Some(battery) = self 220 220 .device_features 221 221 .iter() 222 - .find(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains_key(&InputType::Battery))) 222 + .find(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains(InputType::Battery))) 223 223 { 224 224 battery.1.battery_level() 225 225 } else { ··· 236 236 self 237 237 .device_features 238 238 .iter() 239 - .any(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains_key(&InputType::Rssi))) 239 + .any(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains(InputType::Rssi))) 240 240 } 241 241 242 242 pub fn rssi_level(&self) -> ButtplugClientResultFuture<i8> { 243 243 if let Some(rssi) = self 244 244 .device_features 245 245 .iter() 246 - .find(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains_key(&InputType::Rssi))) 246 + .find(|x| x.1.feature().input().as_ref().is_some_and(|x| x.contains(InputType::Rssi))) 247 247 { 248 248 rssi.1.rssi_level() 249 249 } else {
+10 -12
crates/buttplug_client/src/device/feature.rs
··· 1 - use std::sync::Arc; 2 - 3 1 use futures::{future, FutureExt}; 4 2 use getset::{CopyGetters, Getters}; 5 3 6 4 use buttplug_core::{ 7 5 errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, 8 6 message::{ 9 - ButtplugDeviceMessageNameV4, ButtplugServerMessageV4, DeviceFeature, DeviceFeatureOutput, InputCmdV4, InputCommandType, InputType, InputTypeData, OutputCmdV4, OutputCommand, OutputPositionWithDuration, OutputRotateWithDirection, OutputType, OutputValue 7 + ButtplugDeviceMessageNameV4, ButtplugServerMessageV4, DeviceFeature, DeviceFeatureOutput, DeviceFeatureOutputLimits, InputCmdV4, InputCommandType, InputType, InputTypeData, OutputCmdV4, OutputCommand, OutputPositionWithDuration, OutputRotateWithDirection, OutputType, OutputValue 10 8 }, 11 9 }; 12 10 ··· 46 44 } 47 45 } 48 46 49 - fn check_step_value(&self, feature_output: &DeviceFeatureOutput, steps: u32) -> Result<u32, ButtplugClientError> { 50 - if steps <= feature_output.step_count() { 47 + fn check_step_value(&self, feature_output: &dyn DeviceFeatureOutputLimits, steps: u32) -> Result<u32, ButtplugClientError> { 48 + if feature_output.step_limit().contains(&(steps as i32)) { 51 49 Ok(steps) 52 50 } else { 53 51 Err(ButtplugClientError::ButtplugOutputCommandConversionError(format!("{} is larger than the maximum number of steps ({}).", steps, feature_output.step_count()))) 54 52 } 55 53 } 56 54 57 - fn convert_float_value(&self, feature_output: &DeviceFeatureOutput, float_amt: f64) -> Result<u32, ButtplugClientError> { 55 + fn convert_float_value(&self, feature_output: &dyn DeviceFeatureOutputLimits, float_amt: f64) -> Result<u32, ButtplugClientError> { 58 56 if float_amt < 0.0f64 || float_amt > 1.0f64 { 59 57 Err(ButtplugClientError::ButtplugOutputCommandConversionError("Float values must be between 0.0 and 1.0".to_owned())) 60 58 } else { ··· 70 68 .output() 71 69 .as_ref() 72 70 .ok_or(ButtplugClientError::ButtplugOutputCommandConversionError(format!("Device feature does not support output type {}", output_type)))? 73 - .get(&output_type) 71 + .get(output_type) 74 72 .ok_or(ButtplugClientError::ButtplugOutputCommandConversionError(format!("Device feature does not support output type {}", output_type)))?; 75 73 76 74 let output_cmd = match client_cmd { ··· 175 173 176 174 pub fn subscribe_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture { 177 175 if let Some(sensor_map) = self.feature.input() { 178 - if let Some(sensor) = sensor_map.get(&sensor_type) { 176 + if let Some(sensor) = sensor_map.get(sensor_type) { 179 177 if sensor 180 178 .input_commands() 181 179 .contains(&InputCommandType::Subscribe) ··· 199 197 200 198 pub fn unsubscribe_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture { 201 199 if let Some(sensor_map) = self.feature.input() { 202 - if let Some(sensor) = sensor_map.get(&sensor_type) { 200 + if let Some(sensor) = sensor_map.get(sensor_type) { 203 201 if sensor 204 202 .input_commands() 205 203 .contains(&InputCommandType::Subscribe) ··· 223 221 224 222 fn read_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture<InputTypeData> { 225 223 if let Some(sensor_map) = self.feature.input() { 226 - if let Some(sensor) = sensor_map.get(&sensor_type) { 224 + if let Some(sensor) = sensor_map.get(sensor_type) { 227 225 if sensor.input_commands().contains(&InputCommandType::Read) { 228 226 let msg = InputCmdV4::new( 229 227 self.device_index, ··· 269 267 .as_ref() 270 268 .ok_or(false) 271 269 .unwrap() 272 - .contains_key(&InputType::Battery) 270 + .contains(InputType::Battery) 273 271 { 274 272 let send_fut = self.read_sensor(InputType::Battery); 275 273 Box::pin(async move { ··· 296 294 .as_ref() 297 295 .ok_or(false) 298 296 .unwrap() 299 - .contains_key(&InputType::Rssi) 297 + .contains(InputType::Rssi) 300 298 { 301 299 let send_fut = self.read_sensor(InputType::Rssi); 302 300 Box::pin(async move {
+1
crates/buttplug_core/Cargo.toml
··· 52 52 async-stream = "0.3.6" 53 53 strum_macros = "0.27.1" 54 54 strum = "0.27.1" 55 + derive_builder = "0.20.2"
+115 -49
crates/buttplug_core/src/message/device_feature.rs
··· 6 6 // for full license information. 7 7 8 8 use crate::message::InputCommandType; 9 + use derive_builder::Builder; 9 10 use getset::{CopyGetters, Getters, MutGetters, Setters}; 10 11 use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; 11 12 use std::{ 12 - collections::{HashMap, HashSet}, hash::Hash, ops::RangeInclusive 13 + collections::HashSet, hash::Hash, ops::RangeInclusive 13 14 }; 14 15 15 16 #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, EnumIter)] ··· 86 87 #[getset(get = "pub")] 87 88 #[serde(skip_serializing_if = "Option::is_none")] 88 89 #[serde(rename = "Output")] 89 - output: Option<HashSet<DeviceFeatureOutput>>, 90 + output: Option<DeviceFeatureOutput>, 90 91 #[getset(get = "pub")] 91 92 #[serde(skip_serializing_if = "Option::is_none")] 92 93 #[serde(rename = "Input")] 93 - input: Option<HashMap<InputType, DeviceFeatureInput>>, 94 + input: Option<DeviceFeatureInput>, 94 95 } 95 96 96 97 impl DeviceFeature { 97 98 pub fn new( 98 99 index: u32, 99 100 description: &str, 100 - output: &Option<HashSet<DeviceFeatureOutput>>, 101 - input: &Option<HashMap<InputType, DeviceFeatureInput>>, 101 + output: &Option<DeviceFeatureOutput>, 102 + input: &Option<DeviceFeatureInput>, 102 103 ) -> Self { 103 104 Self { 104 105 feature_index: index, ··· 123 124 seq.end() 124 125 } 125 126 127 + pub trait DeviceFeatureOutputLimits { 128 + fn step_count(&self) -> u32; 129 + fn step_limit(&self) -> RangeInclusive<i32>; 130 + } 131 + 126 132 #[derive(Serialize, Deserialize, Clone, Debug, Getters)] 127 133 pub struct DeviceFeatureOutputValueProperties { 128 134 #[getset(get = "pub")] ··· 134 140 pub fn new(value: &RangeInclusive<i32>) -> Self { 135 141 DeviceFeatureOutputValueProperties { value: value.clone() } 136 142 } 143 + 144 + pub fn step_count(&self) -> u32 { 145 + *self.value.end() as u32 146 + } 147 + } 148 + 149 + impl DeviceFeatureOutputLimits for DeviceFeatureOutputValueProperties { 150 + fn step_count(&self) -> u32 { 151 + self.step_count() 152 + } 153 + fn step_limit(&self) -> RangeInclusive<i32> { 154 + self.value.clone() 155 + } 137 156 } 138 157 139 158 #[derive(Serialize, Deserialize, Clone, Debug, Getters)] 140 159 pub struct DeviceFeatureOutputPositionWithDurationProperties { 141 160 #[getset(get = "pub")] 142 161 #[serde(rename = "Position")] 143 - position: RangeInclusive<u32>, 162 + position: RangeInclusive<i32>, 144 163 #[getset(get = "pub")] 145 164 #[serde(rename = "Duration")] 146 - duration: RangeInclusive<u32>, 165 + duration: RangeInclusive<i32>, 147 166 } 148 167 149 168 impl DeviceFeatureOutputPositionWithDurationProperties { 150 - pub fn new(position: &RangeInclusive<u32>, duration: &RangeInclusive<u32>) -> Self { 169 + pub fn new(position: &RangeInclusive<i32>, duration: &RangeInclusive<i32>) -> Self { 151 170 DeviceFeatureOutputPositionWithDurationProperties { position: position.clone(), duration: duration.clone() } 152 171 } 153 - } 154 172 155 - #[derive(Serialize, Deserialize, Clone, Debug)] 156 - pub enum DeviceFeatureOutput { 157 - Unknown, 158 - Vibrate(DeviceFeatureOutputValueProperties), 159 - Rotate(DeviceFeatureOutputValueProperties), 160 - RotateWithDirection(DeviceFeatureOutputValueProperties), 161 - Oscillate(DeviceFeatureOutputValueProperties), 162 - Constrict(DeviceFeatureOutputValueProperties), 163 - Heater(DeviceFeatureOutputValueProperties), 164 - Led(DeviceFeatureOutputValueProperties), 165 - Position(DeviceFeatureOutputValueProperties), 166 - PositionWithDuration(DeviceFeatureOutputPositionWithDurationProperties), 167 - Spray(DeviceFeatureOutputValueProperties), 173 + pub fn step_count(&self) -> u32 { 174 + *self.position.end() as u32 175 + } 168 176 } 169 177 170 - impl From<&DeviceFeatureOutput> for OutputType { 171 - fn from(value: &DeviceFeatureOutput) -> Self { 172 - match value { 173 - DeviceFeatureOutput::Constrict(_) => OutputType::Constrict, 174 - DeviceFeatureOutput::Heater(_) => OutputType::Heater, 175 - DeviceFeatureOutput::Led(_) => OutputType::Led, 176 - DeviceFeatureOutput::Oscillate(_) => OutputType::Oscillate, 177 - DeviceFeatureOutput::Position(_) => OutputType::Position, 178 - DeviceFeatureOutput::PositionWithDuration(_) => OutputType::PositionWithDuration, 179 - DeviceFeatureOutput::Rotate(_) => OutputType::Rotate, 180 - DeviceFeatureOutput::RotateWithDirection(_) => OutputType::RotateWithDirection, 181 - DeviceFeatureOutput::Spray(_) => OutputType::Spray, 182 - DeviceFeatureOutput::Unknown => OutputType::Unknown, 183 - DeviceFeatureOutput::Vibrate(_) => OutputType::Vibrate 184 - } 178 + impl DeviceFeatureOutputLimits for DeviceFeatureOutputPositionWithDurationProperties { 179 + fn step_count(&self) -> u32 { 180 + self.step_count() 181 + } 182 + fn step_limit(&self) -> RangeInclusive<i32> { 183 + self.position.clone() 185 184 } 186 185 } 187 186 188 - impl PartialEq for DeviceFeatureOutput { 189 - fn eq(&self, other: &Self) -> bool { 190 - // Just make sure our two DeviceFeatureOutput's are the same variant, their values may not match 191 - // but we should never store two of the same variant in the same structure. 192 - std::mem::discriminant(self) == std::mem::discriminant(other) 193 - } 187 + #[derive(Clone, Debug, Getters, Setters, Default, Serialize, Deserialize, Builder)] 188 + #[builder(setter(strip_option), default)] 189 + #[getset(get = "pub")] 190 + pub struct DeviceFeatureOutput { 191 + vibrate: Option<DeviceFeatureOutputValueProperties>, 192 + rotate: Option<DeviceFeatureOutputValueProperties>, 193 + rotate_with_direction: Option<DeviceFeatureOutputValueProperties>, 194 + oscillate: Option<DeviceFeatureOutputValueProperties>, 195 + constrict: Option<DeviceFeatureOutputValueProperties>, 196 + heater: Option<DeviceFeatureOutputValueProperties>, 197 + led: Option<DeviceFeatureOutputValueProperties>, 198 + position: Option<DeviceFeatureOutputValueProperties>, 199 + position_with_duration: Option<DeviceFeatureOutputPositionWithDurationProperties>, 200 + spray: Option<DeviceFeatureOutputValueProperties>, 194 201 } 195 202 196 - impl Eq for DeviceFeatureOutput {} 203 + impl DeviceFeatureOutput { 204 + pub fn contains(&self, output_type: OutputType) -> bool { 205 + match output_type { 206 + OutputType::Constrict => self.constrict.is_some(), 207 + OutputType::Heater => self.heater.is_some(), 208 + OutputType::Led => self.led.is_some(), 209 + OutputType::Oscillate => self.oscillate.is_some(), 210 + OutputType::Position => self.position.is_some(), 211 + OutputType::PositionWithDuration => self.position_with_duration.is_some(), 212 + OutputType::Rotate => self.rotate.is_some(), 213 + OutputType::RotateWithDirection => self.rotate_with_direction.is_some(), 214 + OutputType::Spray => self.spray.is_some(), 215 + OutputType::Unknown => false, 216 + OutputType::Vibrate => self.vibrate.is_some(), 217 + } 218 + } 197 219 198 - impl Hash for DeviceFeatureOutput { 199 - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { 200 - OutputType::from(self).hash(state) 220 + pub fn get(&self, output_type: OutputType) -> Option<&dyn DeviceFeatureOutputLimits> { 221 + match output_type { 222 + OutputType::Constrict => self.constrict().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 223 + OutputType::Heater => self.heater().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 224 + OutputType::Led => self.led().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 225 + OutputType::Oscillate => self.oscillate().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 226 + OutputType::Position => self.position().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 227 + OutputType::PositionWithDuration => self.position_with_duration().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 228 + OutputType::Rotate => self.rotate().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 229 + OutputType::RotateWithDirection => self.rotate_with_direction().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 230 + OutputType::Spray => self.spray().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 231 + OutputType::Unknown => None, 232 + OutputType::Vibrate => self.vibrate().as_ref().map(|x| x as &dyn DeviceFeatureOutputLimits), 233 + } 201 234 } 202 235 } 203 236 204 237 #[derive( 205 238 Clone, Debug, Default, PartialEq, Eq, Getters, MutGetters, Setters, Serialize, Deserialize, 206 239 )] 207 - pub struct DeviceFeatureInput { 240 + pub struct DeviceFeatureInputProperties { 208 241 #[getset(get = "pub", get_mut = "pub(super)")] 209 242 #[serde(rename = "ValueRange")] 210 243 #[serde(serialize_with = "range_sequence_serialize")] ··· 214 247 input_commands: HashSet<InputCommandType>, 215 248 } 216 249 217 - impl DeviceFeatureInput { 250 + impl DeviceFeatureInputProperties { 218 251 pub fn new( 219 252 value_range: &Vec<RangeInclusive<i32>>, 220 253 sensor_commands: &HashSet<InputCommandType>, ··· 225 258 } 226 259 } 227 260 } 261 + 262 + 263 + #[derive(Clone, Debug, Getters, Setters, Default, Serialize, Deserialize, Builder)] 264 + #[builder(setter(strip_option), default)] 265 + #[getset(get = "pub")] 266 + pub struct DeviceFeatureInput { 267 + battery: Option<DeviceFeatureInputProperties>, 268 + rssi: Option<DeviceFeatureInputProperties>, 269 + pressure: Option<DeviceFeatureInputProperties>, 270 + button: Option<DeviceFeatureInputProperties>, 271 + } 272 + 273 + impl DeviceFeatureInput { 274 + pub fn contains(&self, input_type: InputType) -> bool { 275 + match input_type { 276 + InputType::Battery => self.battery.is_some(), 277 + InputType::Rssi => self.rssi.is_some(), 278 + InputType::Pressure => self.pressure.is_some(), 279 + InputType::Button => self.button.is_some(), 280 + InputType::Unknown => false, 281 + } 282 + } 283 + 284 + pub fn get(&self, input_type: InputType) -> &Option<DeviceFeatureInputProperties> { 285 + match input_type { 286 + InputType::Battery => self.battery(), 287 + InputType::Rssi => self.rssi(), 288 + InputType::Pressure => self.pressure(), 289 + InputType::Button => self.button(), 290 + InputType::Unknown => &None, 291 + } 292 + } 293 + }
+1 -1
crates/buttplug_server/src/device/protocol_impl/kiiroo_prowand.rs
··· 11 11 }; 12 12 use buttplug_core::{ 13 13 errors::ButtplugDeviceError, 14 - message::{self, InputData, InputReadingV4, InputType, InputTypeData}, 14 + message::{self, InputData, InputReadingV4, InputTypeData}, 15 15 }; 16 16 use buttplug_server_device_config::Endpoint; 17 17 use futures::{future::BoxFuture, FutureExt};
+1 -1
crates/buttplug_server/src/device/protocol_impl/kiiroo_spot.rs
··· 11 11 }; 12 12 use buttplug_core::{ 13 13 errors::ButtplugDeviceError, 14 - message::{self, InputData, InputReadingV4, InputType, InputTypeData}, 14 + message::{self, InputData, InputReadingV4, InputTypeData}, 15 15 }; 16 16 use buttplug_server_device_config::Endpoint; 17 17 use futures::{future::BoxFuture, FutureExt};
+3 -6
crates/buttplug_server/src/device/protocol_impl/kiiroo_v21.rs
··· 11 11 hardware::{ 12 12 Hardware, 13 13 HardwareCommand, 14 - HardwareEvent, 15 14 HardwareReadCmd, 16 - HardwareSubscribeCmd, 17 - HardwareUnsubscribeCmd, 18 15 HardwareWriteCmd, 19 16 }, 20 17 protocol::{generic_protocol_setup, ProtocolHandler}, ··· 23 20 }; 24 21 use buttplug_core::{ 25 22 errors::ButtplugDeviceError, 26 - message::{InputData, InputReadingV4, InputType, InputTypeData}, 27 - util::{async_manager, stream::convert_broadcast_receiver_to_stream}, 23 + message::{InputData, InputReadingV4, InputTypeData}, 24 + util::{stream::convert_broadcast_receiver_to_stream}, 28 25 }; 29 26 use buttplug_server_device_config::Endpoint; 30 27 use dashmap::DashSet; 31 28 use futures::{ 32 - future::{self, BoxFuture}, 29 + future::BoxFuture, 33 30 FutureExt, 34 31 StreamExt, 35 32 };
+2 -2
crates/buttplug_server/src/device/server_device.rs
··· 529 529 .features() 530 530 .iter() 531 531 .enumerate() 532 - .map(|(i, x)| (i as u32, x.as_device_feature(i as u32))) 533 - .filter(|(_, x)| x.output().as_ref().is_some_and(|x| x.len() > 0) || x.input().as_ref().is_some_and(|x| x.len() > 0)) 532 + .map(|(i, x)| (i as u32, x.as_device_feature(i as u32).expect("Infallible"))) 533 + .filter(|(_, x)| x.output().as_ref().is_some() || x.input().as_ref().is_some()) 534 534 .collect::<BTreeMap<u32, DeviceFeature>>(), 535 535 ) 536 536 }
+72 -57
crates/buttplug_server/src/message/v3/client_device_message_attributes.rs
··· 9 9 v1::NullDeviceMessageAttributesV1, 10 10 v2::{ClientDeviceMessageAttributesV2, GenericDeviceMessageAttributesV2}, 11 11 }; 12 - use buttplug_core::message::{DeviceFeature, InputCommandType, InputType, OutputType}; 12 + use buttplug_core::message::{ 13 + DeviceFeature, 14 + DeviceFeatureOutputValueProperties, 15 + InputCommandType, 16 + InputType, 17 + OutputType, 18 + }; 13 19 use getset::{Getters, MutGetters, Setters}; 14 - use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer}; 20 + use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq}; 15 21 use std::ops::RangeInclusive; 16 22 17 23 // This will look almost exactly like ServerDeviceMessageAttributes. However, it will only contain ··· 22 28 // For many messages, client and server configurations may be exactly the same. If they are not, 23 29 // then we denote this by prefixing the type with Client/Server. Server attributes will usually be 24 30 // hosted in the server/device/configuration module. 25 - #[derive( 26 - Clone, Debug, Default, PartialEq, Getters, MutGetters, Setters, Serialize, Deserialize, 27 - )] 31 + #[derive(Clone, Debug, Default, Getters, MutGetters, Setters, Serialize, Deserialize)] 28 32 pub struct ClientDeviceMessageAttributesV3 { 29 33 // Generic commands 30 34 #[getset(get = "pub", get_mut = "pub(super)")] ··· 211 215 seq.end() 212 216 } 213 217 214 - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Getters, Setters)] 218 + #[derive(Clone, Debug, Serialize, Deserialize, Getters, Setters)] 215 219 pub struct SensorDeviceMessageAttributesV3 { 216 220 #[getset(get = "pub")] 217 221 #[serde(rename = "FeatureDescriptor")] ··· 243 247 .flat_map(|feature| { 244 248 let mut actuator_vec = vec![]; 245 249 if let Some(output_map) = feature.output() { 246 - for (actuator_type, actuator) in output_map { 247 - if ![ 248 - OutputType::PositionWithDuration, 249 - OutputType::RotateWithDirection, 250 - ] 251 - .contains(actuator_type) 252 - { 253 - let actuator_type = *actuator_type; 250 + let mut create_actuator = 251 + |actuator_type, actuator: &DeviceFeatureOutputValueProperties| { 254 252 let attrs = ClientGenericDeviceMessageAttributesV3 { 255 253 feature_descriptor: feature.description().to_owned(), 256 254 actuator_type, ··· 258 256 index: 0, 259 257 }; 260 258 actuator_vec.push(attrs) 261 - } 262 - } 259 + }; 260 + output_map 261 + .constrict() 262 + .as_ref() 263 + .map(|x| create_actuator(OutputType::Constrict, x)); 264 + output_map 265 + .heater() 266 + .as_ref() 267 + .map(|x| create_actuator(OutputType::Heater, x)); 268 + output_map 269 + .led() 270 + .as_ref() 271 + .map(|x| create_actuator(OutputType::Led, x)); 272 + output_map 273 + .oscillate() 274 + .as_ref() 275 + .map(|x| create_actuator(OutputType::Oscillate, x)); 276 + output_map 277 + .position() 278 + .as_ref() 279 + .map(|x| create_actuator(OutputType::Position, x)); 280 + output_map 281 + .rotate() 282 + .as_ref() 283 + .map(|x| create_actuator(OutputType::Rotate, x)); 284 + output_map 285 + .spray() 286 + .as_ref() 287 + .map(|x| create_actuator(OutputType::Spray, x)); 263 288 } 264 289 actuator_vec 265 290 }) ··· 272 297 .flat_map(|feature| { 273 298 let mut actuator_vec = vec![]; 274 299 if let Some(output_map) = feature.output() { 275 - for (actuator_type, actuator) in output_map { 276 - if *actuator_type == OutputType::RotateWithDirection { 277 - let actuator_type = OutputType::Rotate; 278 - let attrs = ClientGenericDeviceMessageAttributesV3 { 279 - feature_descriptor: feature.description().to_owned(), 280 - actuator_type, 281 - step_count: actuator.step_count(), 282 - index: 0, 283 - }; 284 - actuator_vec.push(attrs) 285 - } 300 + if let Some(actuator) = output_map.rotate_with_direction() { 301 + let actuator_type = OutputType::Rotate; 302 + let attrs = ClientGenericDeviceMessageAttributesV3 { 303 + feature_descriptor: feature.description().to_owned(), 304 + actuator_type, 305 + step_count: actuator.step_count(), 306 + index: 0, 307 + }; 308 + actuator_vec.push(attrs) 286 309 } 287 310 } 288 311 actuator_vec ··· 294 317 .flat_map(|feature| { 295 318 let mut actuator_vec = vec![]; 296 319 if let Some(output_map) = feature.output() { 297 - for (actuator_type, actuator) in output_map { 298 - if *actuator_type == OutputType::PositionWithDuration { 299 - let actuator_type = OutputType::Position; 300 - let attrs = ClientGenericDeviceMessageAttributesV3 { 301 - feature_descriptor: feature.description().to_owned(), 302 - actuator_type, 303 - step_count: actuator.step_count(), 304 - index: 0, 305 - }; 306 - actuator_vec.push(attrs) 307 - } 320 + if let Some(actuator) = output_map.position_with_duration() { 321 + let actuator_type = OutputType::Position; 322 + let attrs = ClientGenericDeviceMessageAttributesV3 { 323 + feature_descriptor: feature.description().to_owned(), 324 + actuator_type, 325 + step_count: actuator.step_count(), 326 + index: 0, 327 + }; 328 + actuator_vec.push(attrs) 308 329 } 309 330 } 310 331 actuator_vec ··· 317 338 .map(|feature| { 318 339 let mut sensor_vec = vec![]; 319 340 if let Some(sensor_map) = feature.input() { 320 - for (sensor_type, sensor) in sensor_map { 321 - // Only convert Battery backwards. Other sensors weren't really built for v3 and we 322 - // never recommended using them or implemented much for them. 323 - if *sensor_type == InputType::Battery 324 - && sensor.input_commands().contains(&InputCommandType::Read) 325 - { 326 - sensor_vec.push(SensorDeviceMessageAttributesV3 { 327 - feature_descriptor: feature.description().to_owned(), 328 - sensor_type: *sensor_type, 329 - sensor_range: sensor.value_range().clone(), 330 - feature: feature.clone(), 331 - index: 0, 332 - }); 333 - } 341 + // Only convert Battery backwards. Other sensors weren't really built for v3 and we 342 + // never recommended using them or implemented much for them. 343 + if let Some(battery) = sensor_map.battery() 344 + && battery.input_commands().contains(&InputCommandType::Read) 345 + { 346 + sensor_vec.push(SensorDeviceMessageAttributesV3 { 347 + feature_descriptor: feature.description().to_owned(), 348 + sensor_type: InputType::Battery, 349 + sensor_range: battery.value_range().clone(), 350 + feature: feature.clone(), 351 + index: 0, 352 + }); 334 353 } 335 354 } 336 355 sensor_vec 337 356 }) 338 357 .flatten() 339 358 .collect(); 340 - if !attrs.is_empty() { 341 - Some(attrs) 342 - } else { 343 - None 344 - } 359 + if !attrs.is_empty() { Some(attrs) } else { None } 345 360 }; 346 361 347 362 Self {
+1 -1
crates/buttplug_server/src/message/v3/device_added.rs
··· 23 23 24 24 /// Notification that a device has been found and connected to the server. 25 25 #[derive( 26 - ButtplugMessage, PartialEq, Clone, Debug, Getters, CopyGetters, Serialize, Deserialize, 26 + ButtplugMessage, Clone, Debug, Getters, CopyGetters, Serialize, Deserialize, 27 27 )] 28 28 pub struct DeviceAddedV3 { 29 29 #[serde(rename = "Id")]
+1 -1
crates/buttplug_server/src/message/v3/device_list.rs
··· 16 16 use super::DeviceMessageInfoV3; 17 17 18 18 /// List of all devices currently connected to the server. 19 - #[derive(Default, Clone, Debug, PartialEq, ButtplugMessage, Getters, Serialize, Deserialize)] 19 + #[derive(Default, Clone, Debug, ButtplugMessage, Getters, Serialize, Deserialize)] 20 20 pub struct DeviceListV3 { 21 21 #[serde(rename = "Id")] 22 22 id: u32,
+1 -1
crates/buttplug_server/src/message/v3/device_message_info.rs
··· 13 13 use serde::{Deserialize, Serialize}; 14 14 15 15 /// Substructure of device messages, used for attribute information (name, messages supported, etc...) 16 - #[derive(Clone, Debug, PartialEq, MutGetters, Getters, CopyGetters, Serialize, Deserialize)] 16 + #[derive(Clone, Debug, MutGetters, Getters, CopyGetters, Serialize, Deserialize)] 17 17 pub struct DeviceMessageInfoV3 { 18 18 #[serde(rename = "DeviceIndex")] 19 19 #[getset(get_copy = "pub")]
+39 -54
crates/buttplug_server/src/message/v3/server_device_message_attributes.rs
··· 59 59 .flat_map(|feature| { 60 60 let mut actuator_vec = vec![]; 61 61 if let Some(output_map) = feature.output() { 62 - for (actuator_type, actuator) in output_map { 63 - if ![ 64 - OutputType::PositionWithDuration, 65 - OutputType::RotateWithDirection, 66 - ] 67 - .contains(actuator_type) 68 - { 69 - let actuator_type = *actuator_type; 70 - let step_limit = actuator.step_limit(); 71 - let step_count = step_limit.end() - step_limit.start(); 72 - let attrs = ServerGenericDeviceMessageAttributesV3 { 73 - feature_descriptor: feature.description().to_owned(), 74 - actuator_type, 75 - step_count, 76 - feature: feature.clone(), 77 - index: 0, 78 - }; 79 - actuator_vec.push(attrs) 80 - } 81 - } 62 + let mut create_attribute = |actuator_type, step_count| { 63 + let actuator_type = actuator_type; 64 + let attrs = ServerGenericDeviceMessageAttributesV3 { 65 + feature_descriptor: feature.description().to_owned(), 66 + actuator_type, 67 + step_count, 68 + feature: feature.clone(), 69 + index: 0, 70 + }; 71 + actuator_vec.push(attrs) 72 + }; 73 + // TODO oh come on just make a fucking iterator here. At least, once we figure out the 74 + // unifying trait we can use to make an iterator on this. 75 + output_map.constrict().as_ref().map(|attr| create_attribute(OutputType::Constrict, attr.value().step_count())); 76 + output_map.oscillate().as_ref().map(|attr| create_attribute(OutputType::Oscillate, attr.value().step_count())); 77 + output_map.position().as_ref().map(|attr| create_attribute(OutputType::Position, attr.position().step_count())); 78 + output_map.rotate().as_ref().map(|attr| create_attribute(OutputType::Rotate, attr.value().step_count())); 82 79 } 83 80 actuator_vec 84 81 }) ··· 91 88 .flat_map(|feature| { 92 89 let mut actuator_vec = vec![]; 93 90 if let Some(output_map) = feature.output() { 94 - for (actuator_type, actuator) in output_map { 95 - if *actuator_type == OutputType::RotateWithDirection { 96 - let actuator_type = OutputType::Rotate; 97 - let step_limit = actuator.step_limit(); 98 - let step_count = step_limit.end() - step_limit.start(); 99 - let attrs = ServerGenericDeviceMessageAttributesV3 { 100 - feature_descriptor: feature.description().to_owned(), 101 - actuator_type, 102 - step_count, 103 - feature: feature.clone(), 104 - index: 0, 105 - }; 106 - actuator_vec.push(attrs) 107 - } 91 + if let Some(actuator) = output_map.rotate_with_direction() { 92 + let actuator_type = OutputType::Rotate; 93 + let step_count = actuator.value().step_count(); 94 + let attrs = ServerGenericDeviceMessageAttributesV3 { 95 + feature_descriptor: feature.description().to_owned(), 96 + actuator_type, 97 + step_count, 98 + feature: feature.clone(), 99 + index: 0, 100 + }; 101 + actuator_vec.push(attrs) 108 102 } 109 103 } 110 104 actuator_vec ··· 116 110 .flat_map(|feature| { 117 111 let mut actuator_vec = vec![]; 118 112 if let Some(output_map) = feature.output() { 119 - for (actuator_type, actuator) in output_map { 120 - if *actuator_type == OutputType::PositionWithDuration { 113 + if let Some(actuator) = output_map.position_with_duration() { 121 114 let actuator_type = OutputType::Position; 122 - let step_limit = actuator.step_limit(); 123 - let step_count = step_limit.end() - step_limit.start(); 115 + let step_count = actuator.position().step_count(); 124 116 let attrs = ServerGenericDeviceMessageAttributesV3 { 125 117 feature_descriptor: feature.description().to_owned(), 126 118 actuator_type, ··· 131 123 actuator_vec.push(attrs) 132 124 } 133 125 } 134 - } 135 126 actuator_vec 136 127 }) 137 128 .collect(); ··· 142 133 .map(|feature| { 143 134 let mut sensor_vec = vec![]; 144 135 if let Some(sensor_map) = feature.input() { 145 - for (sensor_type, sensor) in sensor_map { 136 + if let Some(battery) = sensor_map.battery() { 146 137 // Only convert Battery backwards. Other sensors weren't really built for v3 and we 147 138 // never recommended using them or implemented much for them. 148 - if *sensor_type == InputType::Battery { 149 - sensor_vec.push(ServerSensorDeviceMessageAttributesV3 { 150 - feature_descriptor: feature.description().to_owned(), 151 - sensor_type: *sensor_type, 152 - sensor_range: sensor.value_range().clone(), 153 - feature: feature.clone(), 154 - index: 0, 155 - }); 156 - } 139 + sensor_vec.push(ServerSensorDeviceMessageAttributesV3 { 140 + feature_descriptor: feature.description().to_owned(), 141 + sensor_type: InputType::Battery, 142 + sensor_range: battery.value_range().clone(), 143 + feature: feature.clone(), 144 + index: 0, 145 + }); 157 146 } 158 147 } 159 148 sensor_vec 160 149 }) 161 150 .flatten() 162 151 .collect(); 163 - if !attrs.is_empty() { 164 - Some(attrs) 165 - } else { 166 - None 167 - } 152 + if !attrs.is_empty() { Some(attrs) } else { None } 168 153 }; 169 154 170 155 Self {
-1
crates/buttplug_server/src/message/v3/spec_enums.rs
··· 116 116 #[derive( 117 117 Debug, 118 118 Clone, 119 - PartialEq, 120 119 ButtplugMessage, 121 120 ButtplugMessageValidator, 122 121 FromSpecificButtplugMessage,
+14 -40
crates/buttplug_server/src/message/v4/checked_output_cmd.rs
··· 15 15 ButtplugMessageValidator, 16 16 OutputCmdV4, 17 17 OutputCommand, 18 - OutputType, 19 18 }, 20 19 }; 21 20 ··· 107 106 // Check to make sure the feature has an actuator that handles the data we've been passed 108 107 if let Some(output_map) = feature.output() { 109 108 let output_type = cmd.command().as_output_type(); 110 - if let Some(actuator) = output_map.get(&output_type) { 111 - let value = cmd.command().value(); 112 - let step_count = actuator.step_count(); 113 - if value > step_count { 114 - Err(ButtplugError::from( 115 - ButtplugDeviceError::DeviceStepRangeError(step_count, value), 116 - )) 117 - } else { 118 - // Only set adjusted value if we haven't gotten zero, otherwise assume stop. 119 - let new_value = if [OutputType::Position, OutputType::PositionWithDuration] 120 - .contains(&output_type) 121 - && actuator.reverse_position() 122 - { 123 - actuator.step_limit().end() - value 124 - } else if step_count != 0 && value != 0 { 125 - actuator.step_limit().start() + value 126 - } else { 127 - 0 128 - }; 129 - let mut new_command = cmd.command(); 130 - new_command.set_value(new_value); 131 - // We can't make a private trait impl to turn a ValueCmd into a CheckedValueCmd, and this 132 - // is all about security, so we just copy. Silly, but it works for our needs in terms of 133 - // making this a barrier. 134 - Ok(Self { 135 - id: cmd.id(), 136 - feature_id: feature.id(), 137 - device_index: cmd.device_index(), 138 - feature_index: cmd.feature_index(), 139 - output_command: new_command, 140 - }) 141 - } 142 - } else { 143 - Err(ButtplugError::from( 144 - ButtplugDeviceError::MessageNotSupported( 145 - ButtplugDeviceMessageNameV4::OutputCmd.to_string(), 146 - ), 147 - )) 148 - } 109 + let value = cmd.command().value(); 110 + let new_value = output_map.calculate_from_value(output_type, value as i32).map_err(|_| ButtplugDeviceError::DeviceStepRangeError(0, value))?; 111 + let mut new_command = cmd.command(); 112 + new_command.set_value(new_value as u32); 113 + // We can't make a private trait impl to turn a ValueCmd into a CheckedValueCmd, and this 114 + // is all about security, so we just copy. Silly, but it works for our needs in terms of 115 + // making this a barrier. 116 + Ok(Self { 117 + id: cmd.id(), 118 + feature_id: feature.id(), 119 + device_index: cmd.device_index(), 120 + feature_index: cmd.feature_index(), 121 + output_command: new_command, 122 + }) 149 123 } else { 150 124 Err(ButtplugError::from( 151 125 ButtplugDeviceError::MessageNotSupported(
+52 -58
crates/buttplug_server/src/message/v4/checked_output_vec_cmd.rs
··· 6 6 // for full license information. 7 7 8 8 use crate::message::{ 9 - v0::SingleMotorVibrateCmdV0, 10 - v1::VibrateCmdV1, 11 - v3::ScalarCmdV3, 12 9 ButtplugDeviceMessageNameV3, 13 10 LinearCmdV1, 14 11 RotateCmdV1, 15 12 ServerDeviceAttributes, 16 13 TryFromDeviceAttributes, 14 + v0::SingleMotorVibrateCmdV0, 15 + v1::VibrateCmdV1, 16 + v3::ScalarCmdV3, 17 17 }; 18 18 use buttplug_core::{ 19 19 errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, ··· 85 85 .iter() 86 86 .enumerate() 87 87 .filter(|(_, feature)| { 88 - if let Some(output_map) = feature.output() { 89 - output_map.contains_key(&buttplug_core::message::OutputType::Vibrate) 90 - } else { 91 - false 92 - } 88 + feature 89 + .output() 90 + .as_ref() 91 + .map_or(false, |x| x.contains(OutputType::Vibrate)) 93 92 }) 94 93 .peekable(); 95 94 ··· 108 107 .output() 109 108 .as_ref() 110 109 .unwrap() 111 - .get(&OutputType::Vibrate) 112 - .unwrap(); 110 + .vibrate() 111 + .as_ref() 112 + .expect("Already confirmed we have vibrator for this feature"); 113 113 // This doesn't need to run through a security check because we have to construct it to be 114 114 // inherently secure anyways. 115 115 cmds.push(CheckedOutputCmdV4::new( ··· 118 118 index as u32, 119 119 feature.id(), 120 120 OutputCommand::Vibrate(OutputValue::new( 121 - (msg.speed() * ((*actuator.step_limit().end() - *actuator.step_limit().start()) as f64) 122 - + *actuator.step_limit().start() as f64) 123 - .ceil() as u32, 121 + actuator.calculate_scaled_float(msg.speed()).map_err( 122 + |e: buttplug_server_device_config::ButtplugDeviceConfigError| { 123 + ButtplugMessageError::InvalidMessageContents(e.to_string()) 124 + }, 125 + )? as u32, 124 126 )), 125 127 )) 126 128 } ··· 177 179 .ok_or(ButtplugDeviceError::DeviceConfigurationError( 178 180 "Device configuration does not have Vibrate actuator available.".to_owned(), 179 181 ))? 180 - .get(&OutputType::Vibrate) 182 + .vibrate() 183 + .as_ref() 181 184 .ok_or(ButtplugDeviceError::DeviceConfigurationError( 182 185 "Device configuration does not have Vibrate actuator available.".to_owned(), 183 186 ))?; ··· 187 190 idx as u32, 188 191 feature.id(), 189 192 OutputCommand::Vibrate(OutputValue::new( 190 - (vibrate_cmd.speed() 191 - * ((*actuator.step_limit().end() - *actuator.step_limit().start()) as f64) 192 - + *actuator.step_limit().start() as f64) 193 - .ceil() as u32, 193 + actuator 194 + .calculate_scaled_float(vibrate_cmd.speed()) 195 + .map_err(|e| ButtplugMessageError::InvalidMessageContents(e.to_string()))? 196 + as u32, 194 197 )), 195 198 )) 196 199 } ··· 238 241 .find(|(_, f)| f.id() == feature.feature().id()) 239 242 .expect("Already proved existence") 240 243 .0 as u32; 241 - let actuator = feature 244 + let output = feature 242 245 .feature() 243 246 .output() 244 247 .as_ref() 245 248 .ok_or(ButtplugError::from( 246 249 ButtplugDeviceError::DeviceNoActuatorError("ScalarCmdV3".to_owned()), 247 - ))? 248 - .get(&cmd.actuator_type()) 249 - .ok_or(ButtplugError::from( 250 - ButtplugDeviceError::DeviceNoActuatorError("ScalarCmdV3".to_owned()), 251 250 ))?; 252 - 253 - // This needs to take the user configured step limit into account, otherwise we'll hand back 254 - // the wrong placement and it won't be noticed. 255 - if cmd.scalar() > 0.000001 { 256 - cmds.push(CheckedOutputCmdV4::new( 257 - msg.id(), 258 - msg.device_index(), 259 - idx, 260 - feature.feature.id(), 261 - OutputCommand::from_output_type( 262 - cmd.actuator_type(), 263 - (cmd.scalar() 264 - * ((*actuator.step_limit().end() - *actuator.step_limit().start()) as f64) 265 - + *actuator.step_limit().start() as f64) 266 - .ceil() as u32, 267 - ) 268 - .unwrap(), 269 - )); 270 - } else { 271 - cmds.push(CheckedOutputCmdV4::new( 272 - msg.id(), 273 - msg.device_index(), 274 - idx, 275 - feature.feature.id(), 276 - OutputCommand::from_output_type(cmd.actuator_type(), 0).unwrap(), 277 - )); 278 - } 251 + let output_value = output 252 + .calculate_from_float(cmd.actuator_type(), cmd.scalar()) 253 + .map_err(|_| { 254 + ButtplugError::from(ButtplugDeviceError::DeviceNoActuatorError( 255 + "ScalarCmdV3".to_owned(), 256 + )) 257 + })?; 258 + cmds.push(CheckedOutputCmdV4::new( 259 + msg.id(), 260 + msg.device_index(), 261 + idx, 262 + feature.feature.id(), 263 + OutputCommand::from_output_type(cmd.actuator_type(), output_value as u32).unwrap(), 264 + )); 279 265 } 280 266 281 267 Ok(CheckedOutputVecCmdV4::new( ··· 318 304 "Device got LinearCmd command but has no actuators on Linear feature.".to_owned(), 319 305 ), 320 306 ))? 321 - .get(&buttplug_core::message::OutputType::PositionWithDuration) 307 + .position_with_duration() 308 + .as_ref() 322 309 .ok_or(ButtplugError::from( 323 310 ButtplugDeviceError::DeviceFeatureMismatch( 324 311 "Device got LinearCmd command but has no actuators on Linear feature.".to_owned(), ··· 330 317 0, 331 318 f.id(), 332 319 OutputCommand::PositionWithDuration(OutputPositionWithDuration::new( 333 - (x.position() * ((*actuator.step_limit().end() - *actuator.step_limit().start()) as f64) 334 - + *actuator.step_limit().start() as f64) 335 - .ceil() as u32, 320 + actuator.calculate_scaled_float(x.position()).map_err(|_| { 321 + ButtplugError::from(ButtplugMessageError::InvalidMessageContents( 322 + "Position should be 0.0 < x < 1.0" 323 + .to_owned(), 324 + )) 325 + })?, 336 326 x.duration().try_into().map_err(|_| { 337 327 ButtplugError::from(ButtplugMessageError::InvalidMessageContents( 338 328 "Duration should be under 2^31. You are not waiting 24 days to run this command." ··· 388 378 .ok_or(ButtplugError::from( 389 379 ButtplugDeviceError::DeviceNoActuatorError("RotateCmdV1".to_owned()), 390 380 ))? 391 - .get(&buttplug_core::message::OutputType::RotateWithDirection) 381 + .rotate_with_direction() 382 + .as_ref() 392 383 .ok_or(ButtplugError::from( 393 384 ButtplugDeviceError::DeviceNoActuatorError("RotateCmdV1".to_owned()), 394 385 ))?; ··· 398 389 idx, 399 390 feature.feature.id(), 400 391 OutputCommand::RotateWithDirection(OutputRotateWithDirection::new( 401 - (cmd.speed() * ((*actuator.step_limit().end() - *actuator.step_limit().start()) as f64) 402 - + *actuator.step_limit().start() as f64) 403 - .ceil() as u32, 392 + actuator.calculate_scaled_float(cmd.speed()).map_err(|_| { 393 + ButtplugError::from(ButtplugMessageError::InvalidMessageContents( 394 + "Position should be 0.0 < x < 1.0" 395 + .to_owned(), 396 + )) 397 + })? as u32, 404 398 cmd.clockwise(), 405 399 )), 406 400 ));
+34 -6
crates/buttplug_server_device_config/src/device_config_file/feature.rs
··· 2 2 3 3 use buttplug_core::message::InputCommandType; 4 4 use getset::{CopyGetters, Getters, MutGetters, Setters}; 5 - use serde::{Deserialize, Serialize}; 5 + use serde::{Deserialize, Serialize, Serializer, ser::{self, SerializeSeq}}; 6 6 use uuid::Uuid; 7 7 use crate::{ButtplugDeviceConfigError, RangeWithLimit, ServerDeviceFeature, ServerDeviceFeatureInput, ServerDeviceFeatureInputProperties, ServerDeviceFeatureOutput, ServerDeviceFeatureOutputPositionProperties, ServerDeviceFeatureOutputPositionWithDurationProperties, ServerDeviceFeatureOutputValueProperties}; 8 8 9 - use super::range_sequence_serialize; 9 + fn range_serialize<S>(range: &Option<RangeInclusive<u32>>, serializer: S) -> Result<S::Ok, S::Error> 10 + where 11 + S: Serializer, 12 + { 13 + if let Some(range) = range { 14 + let mut seq = serializer.serialize_seq(Some(2))?; 15 + seq.serialize_element(&range.start())?; 16 + seq.serialize_element(&range.end())?; 17 + seq.end() 18 + } else { 19 + Err(ser::Error::custom( 20 + "shouldn't be serializing if range is None", 21 + )) 22 + } 23 + } 24 + 25 + fn range_sequence_serialize<S>( 26 + range_vec: &Vec<RangeInclusive<i32>>, 27 + serializer: S, 28 + ) -> Result<S::Ok, S::Error> 29 + where 30 + S: Serializer, 31 + { 32 + let mut seq = serializer.serialize_seq(Some(range_vec.len()))?; 33 + for range in range_vec { 34 + seq.serialize_element(&vec![*range.start(), *range.end()])?; 35 + } 36 + seq.end() 37 + } 10 38 11 39 #[derive(Serialize, Deserialize, Clone, Debug)] 12 40 struct BaseDeviceFeatureOutputValueProperties { ··· 21 49 22 50 #[derive(Serialize, Deserialize, Clone, Debug)] 23 51 struct BaseDeviceFeatureOutputPositionProperties { 24 - value: RangeInclusive<u32> 52 + value: RangeInclusive<i32> 25 53 } 26 54 27 55 impl Into<ServerDeviceFeatureOutputPositionProperties> for BaseDeviceFeatureOutputPositionProperties { ··· 32 60 33 61 #[derive(Serialize, Deserialize, Clone, Debug)] 34 62 struct BaseDeviceFeatureOutputPositionWithDurationProperties { 35 - position: RangeInclusive<u32>, 36 - duration: RangeInclusive<u32>, 63 + position: RangeInclusive<i32>, 64 + duration: RangeInclusive<i32>, 37 65 } 38 66 39 67 impl Into<ServerDeviceFeatureOutputPositionWithDurationProperties> for BaseDeviceFeatureOutputPositionWithDurationProperties { ··· 107 135 #[derive(Serialize, Deserialize, Clone, Debug)] 108 136 struct UserDeviceFeatureOutputValueProperties { 109 137 #[serde(skip_serializing_if="Option::is_none")] 110 - value: Option<RangeInclusive<i32>>, 138 + value: Option<RangeInclusive<u32>>, 111 139 #[serde(default)] 112 140 disabled: bool, 113 141 }
+1 -1
crates/buttplug_server_device_config/src/device_definitions.rs
··· 1 1 use getset::{CopyGetters, Getters}; 2 2 use uuid::Uuid; 3 3 4 - use super::device_feature::{ 4 + use super::server_device_feature::{ 5 5 ServerDeviceFeature, 6 6 }; 7 7 #[derive(Debug, Clone, Getters, CopyGetters)]
-323
crates/buttplug_server_device_config/src/device_feature.rs
··· 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 - 8 - use crate::ButtplugDeviceConfigError; 9 - 10 - use buttplug_core::message::{ 11 - DeviceFeature, 12 - DeviceFeatureInput, 13 - InputCommandType, InputType, OutputType, 14 - }; 15 - use getset::{CopyGetters, Getters, Setters}; 16 - use serde::{ 17 - Deserialize, 18 - Serialize, 19 - Serializer, 20 - ser::{self, SerializeSeq}, 21 - }; 22 - use std::{ 23 - collections::{HashMap, HashSet}, 24 - ops::RangeInclusive, 25 - }; 26 - use uuid::Uuid; 27 - 28 - fn range_serialize<S>(range: &Option<RangeInclusive<u32>>, serializer: S) -> Result<S::Ok, S::Error> 29 - where 30 - S: Serializer, 31 - { 32 - if let Some(range) = range { 33 - let mut seq = serializer.serialize_seq(Some(2))?; 34 - seq.serialize_element(&range.start())?; 35 - seq.serialize_element(&range.end())?; 36 - seq.end() 37 - } else { 38 - Err(ser::Error::custom( 39 - "shouldn't be serializing if range is None", 40 - )) 41 - } 42 - } 43 - 44 - fn range_sequence_serialize<S>( 45 - range_vec: &Vec<RangeInclusive<i32>>, 46 - serializer: S, 47 - ) -> Result<S::Ok, S::Error> 48 - where 49 - S: Serializer, 50 - { 51 - let mut seq = serializer.serialize_seq(Some(range_vec.len()))?; 52 - for range in range_vec { 53 - seq.serialize_element(&vec![*range.start(), *range.end()])?; 54 - } 55 - seq.end() 56 - } 57 - 58 - #[derive(Debug, Clone, Getters)] 59 - #[getset(get = "pub")] 60 - pub struct RangeWithLimit<T: PartialOrd + Clone> { 61 - base: RangeInclusive<T>, 62 - user: Option<RangeInclusive<T>>, 63 - } 64 - 65 - impl<T: PartialOrd + Clone> From<RangeInclusive<T>> for RangeWithLimit<T> { 66 - fn from(value: RangeInclusive<T>) -> Self { 67 - Self::new(&value) 68 - } 69 - } 70 - 71 - impl<T: PartialOrd + Clone> RangeWithLimit<T> { 72 - pub fn new(base: &RangeInclusive<T>) -> Self { 73 - Self { 74 - base: base.clone(), 75 - user: None 76 - } 77 - } 78 - 79 - pub fn try_new( 80 - base: &RangeInclusive<T>, 81 - user: &Option<RangeInclusive<T>>, 82 - ) -> Result<Self, ButtplugDeviceConfigError> { 83 - if let Some(user) = user { 84 - if user.is_empty() { 85 - Err(ButtplugDeviceConfigError::InvalidUserRange) 86 - } else { 87 - if *user.start() < *base.start() 88 - || *user.end() > *base.end() 89 - || *user.start() > *base.end() 90 - || *user.end() < *base.start() 91 - { 92 - Err(ButtplugDeviceConfigError::InvalidUserRange) 93 - } else { 94 - Ok(Self { 95 - base: (*base).clone(), 96 - user: Some((*user).clone()), 97 - }) 98 - } 99 - } 100 - } else { 101 - if base.is_empty() { 102 - Err(ButtplugDeviceConfigError::BaseRangeRequired) 103 - } else { 104 - Ok(Self { 105 - base: (*base).clone(), 106 - user: None, 107 - }) 108 - } 109 - } 110 - } 111 - } 112 - 113 - #[derive(Debug, Clone, Getters, CopyGetters)] 114 - pub struct ServerDeviceFeatureOutputValueProperties { 115 - #[getset(get = "pub")] 116 - value: RangeWithLimit<i32>, 117 - #[getset(get_copy = "pub")] 118 - disabled: bool, 119 - } 120 - 121 - impl ServerDeviceFeatureOutputValueProperties { 122 - pub fn new(value: &RangeWithLimit<i32>, disabled: bool) -> Self { 123 - Self { 124 - value: value.clone(), 125 - disabled 126 - } 127 - } 128 - } 129 - 130 - #[derive(Debug, Clone, Getters, CopyGetters)] 131 - pub struct ServerDeviceFeatureOutputPositionProperties { 132 - #[getset(get = "pub")] 133 - position: RangeWithLimit<u32>, 134 - #[getset(get_copy = "pub")] 135 - disabled: bool, 136 - #[getset(get_copy = "pub")] 137 - reverse_position: bool, 138 - } 139 - 140 - impl ServerDeviceFeatureOutputPositionProperties { 141 - pub fn new(position: &RangeWithLimit<u32>, disabled: bool, reverse_position: bool) -> Self { 142 - Self { 143 - position: position.clone(), 144 - disabled, 145 - reverse_position 146 - } 147 - } 148 - } 149 - 150 - #[derive(Debug, Clone, Getters, CopyGetters)] 151 - pub struct ServerDeviceFeatureOutputPositionWithDurationProperties { 152 - #[getset(get = "pub")] 153 - position: RangeWithLimit<u32>, 154 - #[getset(get = "pub")] 155 - duration: RangeWithLimit<u32>, 156 - #[getset(get_copy = "pub")] 157 - disabled: bool, 158 - #[getset(get_copy = "pub")] 159 - reverse_position: bool, 160 - } 161 - 162 - impl ServerDeviceFeatureOutputPositionWithDurationProperties { 163 - pub fn new(position: &RangeWithLimit<u32>, duration: &RangeWithLimit<u32>, disabled: bool, reverse_position: bool) -> Self { 164 - Self { 165 - position: position.clone(), 166 - duration: duration.clone(), 167 - disabled, 168 - reverse_position 169 - } 170 - } 171 - } 172 - 173 - #[derive(Clone, Debug, Getters, Setters, Default)] 174 - #[getset(get = "pub", set = "pub(crate)")] 175 - pub struct ServerDeviceFeatureOutput { 176 - vibrate: Option<ServerDeviceFeatureOutputValueProperties>, 177 - rotate: Option<ServerDeviceFeatureOutputValueProperties>, 178 - rotate_with_direction: Option<ServerDeviceFeatureOutputValueProperties>, 179 - oscillate: Option<ServerDeviceFeatureOutputValueProperties>, 180 - constrict: Option<ServerDeviceFeatureOutputValueProperties>, 181 - heater: Option<ServerDeviceFeatureOutputValueProperties>, 182 - led: Option<ServerDeviceFeatureOutputValueProperties>, 183 - position: Option<ServerDeviceFeatureOutputPositionProperties>, 184 - position_with_duration: Option<ServerDeviceFeatureOutputPositionWithDurationProperties>, 185 - spray: Option<ServerDeviceFeatureOutputValueProperties>, 186 - } 187 - 188 - impl ServerDeviceFeatureOutput { 189 - pub fn contains(&self, output_type: OutputType) -> bool { 190 - match output_type { 191 - OutputType::Constrict => self.constrict.is_some(), 192 - OutputType::Heater => self.heater.is_some(), 193 - OutputType::Led => self.led.is_some(), 194 - OutputType::Oscillate => self.oscillate.is_some(), 195 - OutputType::Position => self.position.is_some(), 196 - OutputType::PositionWithDuration => self.position_with_duration.is_some(), 197 - OutputType::Rotate => self.rotate.is_some(), 198 - OutputType::RotateWithDirection => self.rotate_with_direction.is_some(), 199 - OutputType::Spray => self.spray.is_some(), 200 - OutputType::Unknown => false, 201 - OutputType::Vibrate => self.vibrate.is_some() 202 - } 203 - } 204 - 205 - pub fn output_types(&self) -> Vec<OutputType> { 206 - let mut types = vec!(); 207 - self.constrict.is_some().then(|| types.push(OutputType::Constrict)); 208 - self.heater.is_some().then(|| types.push(OutputType::Heater)); 209 - self.led.is_some().then(|| types.push(OutputType::Led)); 210 - self.oscillate.is_some().then(|| types.push(OutputType::Oscillate)); 211 - self.position.is_some().then(|| types.push(OutputType::Position)); 212 - self.position_with_duration.is_some().then(|| types.push(OutputType::PositionWithDuration)); 213 - self.rotate.is_some().then(|| types.push(OutputType::Rotate)); 214 - self.rotate_with_direction.is_some().then(|| types.push(OutputType::RotateWithDirection)); 215 - self.spray.is_some().then(|| types.push(OutputType::Spray)); 216 - self.vibrate.is_some().then(|| types.push(OutputType::Vibrate)); 217 - types 218 - } 219 - } 220 - 221 - #[derive(Clone, Debug, Getters)] 222 - #[getset(get = "pub")] 223 - pub struct ServerDeviceFeatureInputProperties { 224 - value_range: Vec<RangeInclusive<i32>>, 225 - input_commands: HashSet<InputCommandType>, 226 - } 227 - 228 - impl ServerDeviceFeatureInputProperties { 229 - pub fn new( 230 - value_range: &Vec<RangeInclusive<i32>>, 231 - sensor_commands: &HashSet<InputCommandType>, 232 - ) -> Self { 233 - Self { 234 - value_range: value_range.clone(), 235 - input_commands: sensor_commands.clone(), 236 - } 237 - } 238 - } 239 - 240 - #[derive(Clone, Debug, Getters, Setters, Default)] 241 - #[getset(get = "pub", set = "pub(crate)")] 242 - pub struct ServerDeviceFeatureInput { 243 - battery: Option<ServerDeviceFeatureInputProperties>, 244 - rssi: Option<ServerDeviceFeatureInputProperties>, 245 - pressure: Option<ServerDeviceFeatureInputProperties>, 246 - button: Option<ServerDeviceFeatureInputProperties>, 247 - } 248 - 249 - impl ServerDeviceFeatureInput { 250 - pub fn contains(&self, input_type: InputType) -> bool { 251 - match input_type { 252 - InputType::Battery => self.battery.is_some(), 253 - InputType::Rssi => self.rssi.is_some(), 254 - InputType::Pressure => self.pressure.is_some(), 255 - InputType::Button => self.button.is_some(), 256 - InputType::Unknown => false, 257 - } 258 - } 259 - } 260 - 261 - #[derive(Clone, Debug, Getters, CopyGetters)] 262 - pub struct ServerDeviceFeature { 263 - #[getset(get = "pub")] 264 - description: String, 265 - #[getset(get_copy = "pub")] 266 - id: Uuid, 267 - #[getset(get_copy = "pub")] 268 - base_id: Option<Uuid>, 269 - #[getset(get_copy = "pub")] 270 - alt_protocol_index: Option<u32>, 271 - #[getset(get = "pub")] 272 - output: Option<ServerDeviceFeatureOutput>, 273 - #[getset(get = "pub")] 274 - input: Option<ServerDeviceFeatureInput>, 275 - } 276 - 277 - impl PartialEq for ServerDeviceFeature { 278 - fn eq(&self, other: &Self) -> bool { 279 - self.id() == other.id() 280 - } 281 - } 282 - 283 - impl Eq for ServerDeviceFeature { 284 - } 285 - 286 - impl ServerDeviceFeature { 287 - pub fn new(description: &str, id: Uuid, base_id: Option<Uuid>, alt_protocol_index: Option<u32>, output: &Option<ServerDeviceFeatureOutput>, input: &Option<ServerDeviceFeatureInput>) -> Self { 288 - Self { 289 - description: description.to_owned(), 290 - id, 291 - base_id, 292 - alt_protocol_index, 293 - output: output.clone(), 294 - input: input.clone(), 295 - } 296 - } 297 - 298 - /* 299 - pub fn as_device_feature(&self, index: u32) -> Result<DeviceFeature, ButtplugDeviceConfigError> { 300 - // try_collect() is still unstable so we extract the fallible-map call into a loop. This sucks. 301 - let mut outputs = HashMap::new(); 302 - if let Some(output_map) = &self.output { 303 - for (output_type, server_output) in output_map { 304 - outputs.insert( 305 - *output_type, 306 - server_output.as_device_feature_output_variant(*output_type)?, 307 - ); 308 - } 309 - } 310 - 311 - Ok(DeviceFeature::new( 312 - index, 313 - self.description(), 314 - &outputs.is_empty().then_some(outputs).or(None), 315 - &self.base_feature.input().clone().map(|x| { 316 - x.iter() 317 - .map(|(t, a)| (*t, DeviceFeatureInput::from(a.clone()))) 318 - .collect() 319 - }), 320 - )) 321 - } 322 - */ 323 - }
+11 -3
crates/buttplug_server_device_config/src/lib.rs
··· 141 141 extern crate log; 142 142 143 143 mod device_config_file; 144 + 145 + use buttplug_core::message::OutputType; 144 146 pub use device_config_file::{load_protocol_configs}; //, save_user_config}; 145 147 mod device_config_manager; 146 148 pub use device_config_manager::*; ··· 150 152 pub use identifiers::*; 151 153 mod device_definitions; 152 154 pub use device_definitions::*; 153 - mod device_feature; 154 - pub use device_feature::*; 155 + mod server_device_feature; 156 + pub use server_device_feature::*; 155 157 mod endpoint; 156 158 pub use endpoint::*; 157 159 use uuid::Uuid; ··· 174 176 #[error("Device definition with base id {0} not found")] 175 177 BaseIdNotFound(Uuid), 176 178 #[error("Feature vectors between base and user device definitions do not match")] 177 - UserFeatureMismatch 179 + UserFeatureMismatch, 180 + #[error("Output value {0} not in range {1}")] 181 + InvalidOutputValue(i32, String), 182 + #[error("Output type {0} not available on device")] 183 + InvalidOutput(OutputType), 184 + #[error("Float value {0} is not 0 < x < 1")] 185 + InvalidFloatConversion(f64) 178 186 }
+572
crates/buttplug_server_device_config/src/server_device_feature.rs
··· 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 + 8 + use crate::ButtplugDeviceConfigError; 9 + 10 + use buttplug_core::message::{ 11 + DeviceFeature, DeviceFeatureInput, DeviceFeatureInputBuilder, DeviceFeatureInputProperties, DeviceFeatureOutput, DeviceFeatureOutputBuilder, DeviceFeatureOutputPositionWithDurationProperties, DeviceFeatureOutputValueProperties, InputCommandType, InputType, OutputType 12 + }; 13 + use getset::{CopyGetters, Getters, Setters}; 14 + use std::{collections::HashSet, ops::RangeInclusive}; 15 + use uuid::Uuid; 16 + 17 + /// Holds a combination of ranges. Base range is defined in the base device config, user range is 18 + /// defined by the user later to be a sub-range of the base range. User range only stores in u32, 19 + /// ranges with negatives (i.e. rotate with direction) are considered to be symettric around 0, we 20 + /// let the system handle that conversion. 21 + #[derive(Debug, Clone, Getters)] 22 + #[getset(get = "pub")] 23 + pub struct RangeWithLimit { 24 + base: RangeInclusive<i32>, 25 + internal_base: RangeInclusive<u32>, 26 + user: Option<RangeInclusive<u32>>, 27 + } 28 + 29 + impl From<RangeInclusive<i32>> for RangeWithLimit { 30 + fn from(value: RangeInclusive<i32>) -> Self { 31 + Self::new(&value) 32 + } 33 + } 34 + 35 + impl RangeWithLimit { 36 + pub fn new(base: &RangeInclusive<i32>) -> Self { 37 + Self { 38 + base: base.clone(), 39 + internal_base: RangeInclusive::new(0, *base.end() as u32), 40 + user: None, 41 + } 42 + } 43 + 44 + pub fn step_limit(&self) -> RangeInclusive<i32> { 45 + if *self.base.start() < 0 { 46 + RangeInclusive::new(-(self.step_count() as i32), self.step_count() as i32) 47 + } else { 48 + RangeInclusive::new(0, self.step_count() as i32) 49 + } 50 + } 51 + 52 + pub fn step_count(&self) -> u32 { 53 + if let Some(user) = &self.user { 54 + *user.end() - *user.start() 55 + } else { 56 + *self.base.end() as u32 57 + } 58 + } 59 + 60 + pub fn try_new( 61 + base: &RangeInclusive<i32>, 62 + user: &Option<RangeInclusive<u32>>, 63 + ) -> Result<Self, ButtplugDeviceConfigError> { 64 + let truncated_base = RangeInclusive::new(0, *base.end() as u32); 65 + if let Some(user) = user { 66 + if user.is_empty() { 67 + Err(ButtplugDeviceConfigError::InvalidUserRange) 68 + } else { 69 + if *user.start() < *truncated_base.start() 70 + || *user.end() > *truncated_base.end() 71 + || *user.start() > *truncated_base.end() 72 + || *user.end() < *truncated_base.start() 73 + { 74 + Err(ButtplugDeviceConfigError::InvalidUserRange) 75 + } else { 76 + Ok(Self { 77 + base: (*base).clone(), 78 + internal_base: truncated_base, 79 + user: Some((*user).clone()), 80 + }) 81 + } 82 + } 83 + } else { 84 + if base.is_empty() { 85 + Err(ButtplugDeviceConfigError::BaseRangeRequired) 86 + } else { 87 + Ok(Self { 88 + base: (*base).clone(), 89 + internal_base: truncated_base, 90 + user: None, 91 + }) 92 + } 93 + } 94 + } 95 + } 96 + 97 + #[derive(Debug, Clone, Getters, CopyGetters)] 98 + pub struct ServerDeviceFeatureOutputValueProperties { 99 + #[getset(get = "pub")] 100 + value: RangeWithLimit, 101 + #[getset(get_copy = "pub")] 102 + disabled: bool, 103 + } 104 + 105 + impl ServerDeviceFeatureOutputValueProperties { 106 + pub fn new(value: &RangeWithLimit, disabled: bool) -> Self { 107 + Self { 108 + value: value.clone(), 109 + disabled, 110 + } 111 + } 112 + 113 + pub fn calculate_scaled_float(&self, value: f64) -> Result<i32, ButtplugDeviceConfigError> { 114 + if value > 1.0 || value < 1.0 { 115 + Err(ButtplugDeviceConfigError::InvalidFloatConversion(value)) 116 + } else { 117 + let value = if value < 0.000001 { 0f64 } else { value }; 118 + self.calculate_scaled_value((self.value.step_count() as f64 * value) as i32) 119 + } 120 + } 121 + 122 + // We'll get a number from 0-x here. We'll need to calculate it with in the range we have. We'll 123 + // consider negative ranges symmetric. 124 + pub fn calculate_scaled_value(&self, value: i32) -> Result<i32, ButtplugDeviceConfigError> { 125 + let range = if let Some(user_range) = self.value.user() { 126 + user_range 127 + } else { 128 + self.value.internal_base() 129 + }; 130 + let current_value = value.abs() as u32; 131 + let mult = if value < 0 { -1 } else { 1 }; 132 + if value > 0 && range.contains(&(range.start() + current_value)) { 133 + Ok((range.start() + current_value) as i32 * mult) 134 + } else if value == 0 { 135 + Ok(0) 136 + } else { 137 + Err(ButtplugDeviceConfigError::InvalidOutputValue( 138 + value as i32, 139 + format!("{:?}", range), 140 + )) 141 + } 142 + } 143 + } 144 + 145 + impl Into<DeviceFeatureOutputValueProperties> for &ServerDeviceFeatureOutputValueProperties { 146 + fn into(self) -> DeviceFeatureOutputValueProperties { 147 + DeviceFeatureOutputValueProperties::new(&self.value().step_limit()) 148 + } 149 + } 150 + 151 + #[derive(Debug, Clone, Getters, CopyGetters)] 152 + pub struct ServerDeviceFeatureOutputPositionProperties { 153 + #[getset(get = "pub")] 154 + position: RangeWithLimit, 155 + #[getset(get_copy = "pub")] 156 + disabled: bool, 157 + #[getset(get_copy = "pub")] 158 + reverse_position: bool, 159 + } 160 + 161 + impl ServerDeviceFeatureOutputPositionProperties { 162 + pub fn new(position: &RangeWithLimit, disabled: bool, reverse_position: bool) -> Self { 163 + Self { 164 + position: position.clone(), 165 + disabled, 166 + reverse_position, 167 + } 168 + } 169 + 170 + pub fn calculate_scaled_float(&self, value: f64) -> Result<i32, ButtplugDeviceConfigError> { 171 + self 172 + .calculate_scaled_value((self.position.step_count() as f64 * value) as u32) 173 + .map(|x| x as i32) 174 + } 175 + 176 + // We'll get a number from 0-x here. We'll need to calculate it with in the range we have. 177 + pub fn calculate_scaled_value(&self, value: u32) -> Result<u32, ButtplugDeviceConfigError> { 178 + let range = if let Some(user_range) = self.position.user() { 179 + user_range 180 + } else { 181 + self.position.internal_base() 182 + }; 183 + if value > 0 && range.contains(&(range.start() + value)) { 184 + if self.reverse_position { 185 + Ok(range.end() - value) 186 + } else { 187 + Ok(range.start() + value) 188 + } 189 + } else if value == 0 { 190 + Ok(0) 191 + } else { 192 + Err(ButtplugDeviceConfigError::InvalidOutputValue( 193 + value as i32, 194 + format!("{:?}", range), 195 + )) 196 + } 197 + } 198 + } 199 + 200 + impl Into<DeviceFeatureOutputValueProperties> for &ServerDeviceFeatureOutputPositionProperties { 201 + fn into(self) -> DeviceFeatureOutputValueProperties { 202 + DeviceFeatureOutputValueProperties::new(&self.position().step_limit()) 203 + } 204 + } 205 + 206 + #[derive(Debug, Clone, Getters, CopyGetters)] 207 + pub struct ServerDeviceFeatureOutputPositionWithDurationProperties { 208 + #[getset(get = "pub")] 209 + position: RangeWithLimit, 210 + #[getset(get = "pub")] 211 + duration: RangeWithLimit, 212 + #[getset(get_copy = "pub")] 213 + disabled: bool, 214 + #[getset(get_copy = "pub")] 215 + reverse_position: bool, 216 + } 217 + 218 + impl ServerDeviceFeatureOutputPositionWithDurationProperties { 219 + pub fn new( 220 + position: &RangeWithLimit, 221 + duration: &RangeWithLimit, 222 + disabled: bool, 223 + reverse_position: bool, 224 + ) -> Self { 225 + Self { 226 + position: position.clone(), 227 + duration: duration.clone(), 228 + disabled, 229 + reverse_position, 230 + } 231 + } 232 + 233 + pub fn calculate_scaled_float(&self, value: f64) -> Result<u32, ButtplugDeviceConfigError> { 234 + self.calculate_scaled_value((self.position.step_count() as f64 * value) as u32) 235 + } 236 + 237 + // We'll get a number from 0-x here. We'll need to calculate it with in the range we have. 238 + pub fn calculate_scaled_value(&self, value: u32) -> Result<u32, ButtplugDeviceConfigError> { 239 + let range = if let Some(user_range) = self.position.user() { 240 + user_range 241 + } else { 242 + self.position.internal_base() 243 + }; 244 + if value > 0 && range.contains(&(range.start() + value)) { 245 + if self.reverse_position { 246 + Ok(range.end() - value) 247 + } else { 248 + Ok(range.start() + value) 249 + } 250 + } else if value == 0 { 251 + Ok(0) 252 + } else { 253 + Err(ButtplugDeviceConfigError::InvalidOutputValue( 254 + value as i32, 255 + format!("{:?}", range), 256 + )) 257 + } 258 + } 259 + } 260 + 261 + impl Into<DeviceFeatureOutputPositionWithDurationProperties> for &ServerDeviceFeatureOutputPositionWithDurationProperties { 262 + fn into(self) -> DeviceFeatureOutputPositionWithDurationProperties { 263 + DeviceFeatureOutputPositionWithDurationProperties::new(&self.position().step_limit(), &self.duration().step_limit()) 264 + } 265 + } 266 + 267 + #[derive(Clone, Debug, Getters, Setters, Default)] 268 + #[getset(get = "pub", set = "pub(crate)")] 269 + pub struct ServerDeviceFeatureOutput { 270 + vibrate: Option<ServerDeviceFeatureOutputValueProperties>, 271 + rotate: Option<ServerDeviceFeatureOutputValueProperties>, 272 + rotate_with_direction: Option<ServerDeviceFeatureOutputValueProperties>, 273 + oscillate: Option<ServerDeviceFeatureOutputValueProperties>, 274 + constrict: Option<ServerDeviceFeatureOutputValueProperties>, 275 + heater: Option<ServerDeviceFeatureOutputValueProperties>, 276 + led: Option<ServerDeviceFeatureOutputValueProperties>, 277 + position: Option<ServerDeviceFeatureOutputPositionProperties>, 278 + position_with_duration: Option<ServerDeviceFeatureOutputPositionWithDurationProperties>, 279 + spray: Option<ServerDeviceFeatureOutputValueProperties>, 280 + } 281 + 282 + impl ServerDeviceFeatureOutput { 283 + pub fn contains(&self, output_type: OutputType) -> bool { 284 + match output_type { 285 + OutputType::Constrict => self.constrict.is_some(), 286 + OutputType::Heater => self.heater.is_some(), 287 + OutputType::Led => self.led.is_some(), 288 + OutputType::Oscillate => self.oscillate.is_some(), 289 + OutputType::Position => self.position.is_some(), 290 + OutputType::PositionWithDuration => self.position_with_duration.is_some(), 291 + OutputType::Rotate => self.rotate.is_some(), 292 + OutputType::RotateWithDirection => self.rotate_with_direction.is_some(), 293 + OutputType::Spray => self.spray.is_some(), 294 + OutputType::Unknown => false, 295 + OutputType::Vibrate => self.vibrate.is_some(), 296 + } 297 + } 298 + 299 + pub fn output_types(&self) -> Vec<OutputType> { 300 + let mut types = vec![]; 301 + self 302 + .constrict 303 + .is_some() 304 + .then(|| types.push(OutputType::Constrict)); 305 + self 306 + .heater 307 + .is_some() 308 + .then(|| types.push(OutputType::Heater)); 309 + self.led.is_some().then(|| types.push(OutputType::Led)); 310 + self 311 + .oscillate 312 + .is_some() 313 + .then(|| types.push(OutputType::Oscillate)); 314 + self 315 + .position 316 + .is_some() 317 + .then(|| types.push(OutputType::Position)); 318 + self 319 + .position_with_duration 320 + .is_some() 321 + .then(|| types.push(OutputType::PositionWithDuration)); 322 + self 323 + .rotate 324 + .is_some() 325 + .then(|| types.push(OutputType::Rotate)); 326 + self 327 + .rotate_with_direction 328 + .is_some() 329 + .then(|| types.push(OutputType::RotateWithDirection)); 330 + self.spray.is_some().then(|| types.push(OutputType::Spray)); 331 + self 332 + .vibrate 333 + .is_some() 334 + .then(|| types.push(OutputType::Vibrate)); 335 + types 336 + } 337 + 338 + pub fn calculate_from_value( 339 + &self, 340 + output_type: OutputType, 341 + value: i32, 342 + ) -> Result<i32, ButtplugDeviceConfigError> { 343 + // TODO just fucking do some trait implementations for calculation methods and clean this up for fuck sake. :c 344 + match output_type { 345 + OutputType::Constrict => self.constrict.as_ref().map_or( 346 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 347 + |x| x.calculate_scaled_value(value), 348 + ), 349 + OutputType::Heater => self.heater.as_ref().map_or( 350 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 351 + |x| x.calculate_scaled_value(value), 352 + ), 353 + OutputType::Led => self.led.as_ref().map_or( 354 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 355 + |x| x.calculate_scaled_value(value), 356 + ), 357 + OutputType::Oscillate => self.oscillate.as_ref().map_or( 358 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 359 + |x| x.calculate_scaled_value(value), 360 + ), 361 + OutputType::Position => self.position.as_ref().map_or( 362 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 363 + |x| x.calculate_scaled_value(value as u32).map(|x| x as i32), 364 + ), 365 + OutputType::PositionWithDuration => self.position_with_duration.as_ref().map_or( 366 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 367 + |x| x.calculate_scaled_value(value as u32).map(|x| x as i32), 368 + ), 369 + OutputType::Rotate => self.rotate.as_ref().map_or( 370 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 371 + |x| x.calculate_scaled_value(value), 372 + ), 373 + OutputType::RotateWithDirection => self.rotate_with_direction.as_ref().map_or( 374 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 375 + |x| x.calculate_scaled_value(value), 376 + ), 377 + OutputType::Spray => self.spray.as_ref().map_or( 378 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 379 + |x| x.calculate_scaled_value(value), 380 + ), 381 + OutputType::Unknown => Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 382 + OutputType::Vibrate => self.vibrate.as_ref().map_or( 383 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 384 + |x| x.calculate_scaled_value(value), 385 + ), 386 + } 387 + } 388 + 389 + pub fn calculate_from_float( 390 + &self, 391 + output_type: OutputType, 392 + value: f64, 393 + ) -> Result<i32, ButtplugDeviceConfigError> { 394 + match output_type { 395 + OutputType::Constrict => self.constrict.as_ref().map_or( 396 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 397 + |x| x.calculate_scaled_float(value), 398 + ), 399 + OutputType::Heater => self.heater.as_ref().map_or( 400 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 401 + |x| x.calculate_scaled_float(value), 402 + ), 403 + OutputType::Led => self.led.as_ref().map_or( 404 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 405 + |x| x.calculate_scaled_float(value), 406 + ), 407 + OutputType::Oscillate => self.oscillate.as_ref().map_or( 408 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 409 + |x| x.calculate_scaled_float(value), 410 + ), 411 + OutputType::Position => self.position.as_ref().map_or( 412 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 413 + |x| x.calculate_scaled_float(value).map(|x| x as i32), 414 + ), 415 + OutputType::PositionWithDuration => self.position_with_duration.as_ref().map_or( 416 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 417 + |x| x.calculate_scaled_float(value).map(|x| x as i32), 418 + ), 419 + OutputType::Rotate => self.rotate.as_ref().map_or( 420 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 421 + |x| x.calculate_scaled_float(value), 422 + ), 423 + OutputType::RotateWithDirection => self.rotate_with_direction.as_ref().map_or( 424 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 425 + |x| x.calculate_scaled_float(value), 426 + ), 427 + OutputType::Spray => self.spray.as_ref().map_or( 428 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 429 + |x| x.calculate_scaled_float(value), 430 + ), 431 + OutputType::Unknown => Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 432 + OutputType::Vibrate => self.vibrate.as_ref().map_or( 433 + Err(ButtplugDeviceConfigError::InvalidOutput(output_type)), 434 + |x| x.calculate_scaled_float(value), 435 + ), 436 + } 437 + } 438 + } 439 + 440 + impl Into<DeviceFeatureOutput> for ServerDeviceFeatureOutput { 441 + fn into(self) -> DeviceFeatureOutput { 442 + let mut builder = DeviceFeatureOutputBuilder::default(); 443 + self.vibrate.as_ref().map(|x| builder.vibrate(x.into())); 444 + self.rotate.as_ref().map(|x| builder.rotate(x.into())); 445 + self 446 + .rotate_with_direction 447 + .as_ref() 448 + .map(|x| builder.rotate_with_direction(x.into())); 449 + self.oscillate.as_ref().map(|x| builder.oscillate(x.into())); 450 + self.constrict.as_ref().map(|x| builder.constrict(x.into())); 451 + self.heater.as_ref().map(|x| builder.heater(x.into())); 452 + self.led.as_ref().map(|x| builder.led(x.into())); 453 + self.position.as_ref().map(|x| builder.position(x.into())); 454 + self 455 + .position_with_duration 456 + .as_ref() 457 + .map(|x| builder.position_with_duration(x.into())); 458 + self.spray.as_ref().map(|x| builder.spray(x.into())); 459 + builder.build().expect("Infallible") 460 + } 461 + } 462 + 463 + #[derive(Clone, Debug, Getters)] 464 + #[getset(get = "pub")] 465 + pub struct ServerDeviceFeatureInputProperties { 466 + value_range: Vec<RangeInclusive<i32>>, 467 + input_commands: HashSet<InputCommandType>, 468 + } 469 + 470 + impl ServerDeviceFeatureInputProperties { 471 + pub fn new( 472 + value_range: &Vec<RangeInclusive<i32>>, 473 + sensor_commands: &HashSet<InputCommandType>, 474 + ) -> Self { 475 + Self { 476 + value_range: value_range.clone(), 477 + input_commands: sensor_commands.clone(), 478 + } 479 + } 480 + } 481 + 482 + impl Into<DeviceFeatureInputProperties> for &ServerDeviceFeatureInputProperties { 483 + fn into(self) -> DeviceFeatureInputProperties { 484 + DeviceFeatureInputProperties::new(&self.value_range, &self.input_commands) 485 + } 486 + } 487 + 488 + #[derive(Clone, Debug, Getters, Setters, Default)] 489 + #[getset(get = "pub", set = "pub(crate)")] 490 + pub struct ServerDeviceFeatureInput { 491 + battery: Option<ServerDeviceFeatureInputProperties>, 492 + rssi: Option<ServerDeviceFeatureInputProperties>, 493 + pressure: Option<ServerDeviceFeatureInputProperties>, 494 + button: Option<ServerDeviceFeatureInputProperties>, 495 + } 496 + 497 + impl ServerDeviceFeatureInput { 498 + pub fn contains(&self, input_type: InputType) -> bool { 499 + match input_type { 500 + InputType::Battery => self.battery.is_some(), 501 + InputType::Rssi => self.rssi.is_some(), 502 + InputType::Pressure => self.pressure.is_some(), 503 + InputType::Button => self.button.is_some(), 504 + InputType::Unknown => false, 505 + } 506 + } 507 + } 508 + 509 + impl Into<DeviceFeatureInput> for ServerDeviceFeatureInput { 510 + fn into(self) -> DeviceFeatureInput { 511 + let mut builder = DeviceFeatureInputBuilder::default(); 512 + self.battery.as_ref().map(|x| builder.battery(x.into())); 513 + self.rssi.as_ref().map(|x| builder.rssi(x.into())); 514 + self.pressure.as_ref().map(|x| builder.pressure(x.into())); 515 + self.button.as_ref().map(|x| builder.button(x.into())); 516 + builder.build().expect("Infallible") 517 + } 518 + } 519 + 520 + #[derive(Clone, Debug, Getters, CopyGetters)] 521 + pub struct ServerDeviceFeature { 522 + #[getset(get = "pub")] 523 + description: String, 524 + #[getset(get_copy = "pub")] 525 + id: Uuid, 526 + #[getset(get_copy = "pub")] 527 + base_id: Option<Uuid>, 528 + #[getset(get_copy = "pub")] 529 + alt_protocol_index: Option<u32>, 530 + #[getset(get = "pub")] 531 + output: Option<ServerDeviceFeatureOutput>, 532 + #[getset(get = "pub")] 533 + input: Option<ServerDeviceFeatureInput>, 534 + } 535 + 536 + impl PartialEq for ServerDeviceFeature { 537 + fn eq(&self, other: &Self) -> bool { 538 + self.id() == other.id() 539 + } 540 + } 541 + 542 + impl Eq for ServerDeviceFeature { 543 + } 544 + 545 + impl ServerDeviceFeature { 546 + pub fn new( 547 + description: &str, 548 + id: Uuid, 549 + base_id: Option<Uuid>, 550 + alt_protocol_index: Option<u32>, 551 + output: &Option<ServerDeviceFeatureOutput>, 552 + input: &Option<ServerDeviceFeatureInput>, 553 + ) -> Self { 554 + Self { 555 + description: description.to_owned(), 556 + id, 557 + base_id, 558 + alt_protocol_index, 559 + output: output.clone(), 560 + input: input.clone(), 561 + } 562 + } 563 + 564 + pub fn as_device_feature(&self, index: u32) -> Result<DeviceFeature, ButtplugDeviceConfigError> { 565 + Ok(DeviceFeature::new( 566 + index, 567 + self.description(), 568 + &self.output.as_ref().and_then(|x| Some(x.clone().into())), 569 + &self.input.as_ref().and_then(|x| Some(x.clone().into())), 570 + )) 571 + } 572 + }
+1 -1
crates/buttplug_tests/tests/test_client_device.rs
··· 12 12 message::{OutputType,}, 13 13 util::async_manager 14 14 }; 15 - use buttplug_server_device_config::{load_protocol_configs, UserDeviceCustomization, DeviceDefinition, UserDeviceIdentifier, ServerDeviceFeature, ServerDeviceFeatureOutput, Endpoint}; 15 + use buttplug_server_device_config::{load_protocol_configs, ServerDeviceDefinition, UserDeviceIdentifier, ServerDeviceFeature, ServerDeviceFeatureOutput, Endpoint}; 16 16 use buttplug_server::{ 17 17 device::{ 18 18 hardware::{HardwareCommand, HardwareWriteCmd},
+4 -4
crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs
··· 59 59 let vibe_features: Vec<&ClientDeviceFeature> = device 60 60 .device_features() 61 61 .iter() 62 - .filter(|f| f.1.feature().output().as_ref().is_some_and(|x| x.contains_key(&OutputType::Vibrate))) 62 + .filter(|f| f.1.feature().output().as_ref().is_some_and(|x| x.contains(OutputType::Vibrate))) 63 63 .map(|(_, x)| x) 64 64 .collect(); 65 65 let f = vibe_features[cmd.index() as usize].clone(); ··· 78 78 let rotate_features: Vec<&ClientDeviceFeature> = device 79 79 .device_features() 80 80 .iter() 81 - .filter(|f| f.1.feature().output().as_ref().is_some_and(|x| x.contains_key(&OutputType::RotateWithDirection))) 81 + .filter(|f| f.1.feature().output().as_ref().is_some_and(|x| x.contains(OutputType::RotateWithDirection))) 82 82 .map(|(_, x)| x) 83 83 .collect(); 84 84 let f = rotate_features[cmd.index() as usize].clone(); ··· 89 89 .output() 90 90 .as_ref() 91 91 .unwrap() 92 - .get(&OutputType::RotateWithDirection) 92 + .get(OutputType::RotateWithDirection) 93 93 .unwrap() 94 94 .step_count() as f64) 95 95 .ceil() as u32, ··· 111 111 .output() 112 112 .as_ref() 113 113 .unwrap() 114 - .get(&OutputType::PositionWithDuration) 114 + .get(OutputType::PositionWithDuration) 115 115 .unwrap() 116 116 .step_count() as f64) 117 117 .ceil() as u32,