Something went wrong. Try again.
Buttplug sex toy control library
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410use futures::{FutureExt, future};use getset::{CopyGetters, Getters};
use buttplug_core::{ errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, message::{ ButtplugDeviceMessageNameV4, ButtplugServerMessageV4, DeviceFeature, DeviceFeatureOutputLimits, InputCmdV4, InputCommandType, InputType, InputTypeData, OutputCmdV4, OutputCommand, OutputPositionWithDuration, OutputType, OutputValue, },};
use super::ClientDeviceOutputCommand;
use crate::{ ButtplugClientError, ButtplugClientMessageSender, ButtplugClientResultFuture, create_boxed_future_client_error, device::ClientDeviceCommandValue,};
#[derive(Getters, CopyGetters, Clone)]pub struct ClientDeviceFeature { #[getset(get_copy = "pub")] device_index: u32, #[getset(get_copy = "pub")] feature_index: u32, #[getset(get = "pub")] feature: DeviceFeature, /// Sends commands from the [ButtplugClientDevice] instance to the /// [ButtplugClient][super::ButtplugClient]'s event loop, which will then send /// the message on to the [ButtplugServer][crate::server::ButtplugServer] /// through the connector. event_loop_sender: ButtplugClientMessageSender,}
impl ClientDeviceFeature { pub(super) fn new( device_index: u32, feature_index: u32, feature: &DeviceFeature, event_loop_sender: &ButtplugClientMessageSender, ) -> Self { Self { device_index, feature_index, feature: feature.clone(), event_loop_sender: event_loop_sender.clone(), } }
fn check_step_value( &self, feature_output: &dyn DeviceFeatureOutputLimits, steps: i32, ) -> Result<i32, ButtplugClientError> { if feature_output.step_limit().contains(&(steps)) { Ok(steps) } else { Err(ButtplugClientError::ButtplugOutputCommandConversionError( format!( "{} is larger than the maximum number of steps ({}).", steps, feature_output.step_count() ), )) } }
fn convert_float_value( &self, feature_output: &dyn DeviceFeatureOutputLimits, float_amt: f64, ) -> Result<i32, ButtplugClientError> { if !(-1.0f64..=1.0f64).contains(&float_amt) { Err(ButtplugClientError::ButtplugOutputCommandConversionError( "Float values must be between 0.0 and 1.0".to_owned(), )) } else { let mut val = float_amt * feature_output.step_count() as f64; val = if val > 0.000001f64 { val.ceil() } else { val.floor() }; Ok(val as i32) } }
pub(super) fn convert_client_cmd_to_output_cmd( &self, client_cmd: &ClientDeviceOutputCommand, ) -> Result<OutputCmdV4, ButtplugClientError> { let output_type: OutputType = client_cmd.into(); // First off, make sure we support this output. let output = self .feature .output() .as_ref() .ok_or(ButtplugClientError::ButtplugOutputCommandConversionError( format!( "Device feature does not support output type {}", output_type ), ))? .get(output_type) .ok_or(ButtplugClientError::ButtplugOutputCommandConversionError( format!( "Device feature does not support output type {}", output_type ), ))?;
let output_cmd = match client_cmd { ClientDeviceOutputCommand::VibrateFloat(v) => { OutputCommand::Vibrate(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::OscillateFloat(v) => { OutputCommand::Oscillate(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::RotateFloat(v) => { OutputCommand::Rotate(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::ConstrictFloat(v) => { OutputCommand::Constrict(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::TemperatureFloat(v) => { OutputCommand::Temperature(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::LedFloat(v) => { OutputCommand::Led(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::SprayFloat(v) => { OutputCommand::Spray(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::PositionFloat(v) => { OutputCommand::Position(OutputValue::new(self.convert_float_value(output, *v)?)) } ClientDeviceOutputCommand::PositionWithDurationFloat(v, d) => { OutputCommand::PositionWithDuration(OutputPositionWithDuration::new( self.convert_float_value(output, *v)? as u32, *d, )) } ClientDeviceOutputCommand::Vibrate(v) => { OutputCommand::Vibrate(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::Oscillate(v) => { OutputCommand::Oscillate(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::Rotate(v) => { OutputCommand::Rotate(OutputValue::new(self.check_step_value(output, *v)?)) } ClientDeviceOutputCommand::Constrict(v) => { OutputCommand::Constrict(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::Temperature(v) => { OutputCommand::Temperature(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::Led(v) => { OutputCommand::Led(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::Spray(v) => { OutputCommand::Spray(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::Position(v) => { OutputCommand::Position(OutputValue::new(self.check_step_value(output, *v as i32)?)) } ClientDeviceOutputCommand::PositionWithDuration(v, d) => OutputCommand::PositionWithDuration( OutputPositionWithDuration::new(self.check_step_value(output, *v as i32)? as u32, *d), ), }; Ok(OutputCmdV4::new( self.device_index, self.feature_index, output_cmd, )) }
pub fn send_command( &self, client_device_command: &ClientDeviceOutputCommand, ) -> ButtplugClientResultFuture { match self.convert_client_cmd_to_output_cmd(client_device_command) { Ok(cmd) => self.event_loop_sender.send_message_expect_ok(cmd.into()), Err(e) => future::ready(Err(e)).boxed(), } }
pub fn vibrate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { let val = level.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Vibrate(v as u32), ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::VibrateFloat(f), }) }
pub fn oscillate( &self, level: impl Into<ClientDeviceCommandValue>, ) -> ButtplugClientResultFuture { let val = level.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Oscillate(v as u32), ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::OscillateFloat(f), }) }
pub fn rotate(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { let val = level.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Rotate(v), ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::RotateFloat(f), }) }
pub fn spray(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { let val = level.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Spray(v as u32), ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::SprayFloat(f), }) }
pub fn constrict( &self, level: impl Into<ClientDeviceCommandValue>, ) -> ButtplugClientResultFuture { let val = level.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Constrict(v as u32), ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::ConstrictFloat(f), }) }
pub fn position(&self, level: impl Into<ClientDeviceCommandValue>) -> ButtplugClientResultFuture { let val = level.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => ClientDeviceOutputCommand::Position(v as u32), ClientDeviceCommandValue::Float(f) => ClientDeviceOutputCommand::PositionFloat(f), }) }
pub fn position_with_duration( &self, position: impl Into<ClientDeviceCommandValue>, duration_in_ms: u32, ) -> ButtplugClientResultFuture { let val = position.into(); self.send_command(&match val { ClientDeviceCommandValue::Int(v) => { ClientDeviceOutputCommand::PositionWithDuration(v as u32, duration_in_ms) } ClientDeviceCommandValue::Float(f) => { ClientDeviceOutputCommand::PositionWithDurationFloat(f, duration_in_ms) } }) }
pub fn subscribe_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) && sensor .input_commands() .contains(&InputCommandType::Subscribe) { let msg = InputCmdV4::new( self.device_index, self.feature_index, sensor_type, InputCommandType::Subscribe, ) .into(); return self.event_loop_sender.send_message_expect_ok(msg); } create_boxed_future_client_error( ButtplugDeviceError::MessageNotSupported(ButtplugDeviceMessageNameV4::InputCmd.to_string()) .into(), ) }
pub fn unsubscribe_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) && sensor .input_commands() .contains(&InputCommandType::Subscribe) { let msg = InputCmdV4::new( self.device_index, self.feature_index, sensor_type, InputCommandType::Unsubscribe, ) .into(); return self.event_loop_sender.send_message_expect_ok(msg); } create_boxed_future_client_error( ButtplugDeviceError::MessageNotSupported(ButtplugDeviceMessageNameV4::InputCmd.to_string()) .into(), ) }
fn read_sensor(&self, sensor_type: InputType) -> ButtplugClientResultFuture<InputTypeData> { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) && sensor.input_commands().contains(&InputCommandType::Read) { let msg = InputCmdV4::new( self.device_index, self.feature_index, sensor_type, InputCommandType::Read, ) .into(); let reply = self.event_loop_sender.send_message(msg); return async move { if let ButtplugServerMessageV4::InputReading(data) = reply.await? { if sensor_type == data.data().as_input_type() { Ok(data.data()) } else { Err( ButtplugError::ButtplugMessageError(ButtplugMessageError::UnexpectedMessageType( "InputReading".to_owned(), )) .into(), ) } } else { Err( ButtplugError::ButtplugMessageError(ButtplugMessageError::UnexpectedMessageType( "InputReading".to_owned(), )) .into(), ) } } .boxed(); } create_boxed_future_client_error( ButtplugDeviceError::MessageNotSupported(ButtplugDeviceMessageNameV4::InputCmd.to_string()) .into(), ) }
pub fn battery_level(&self) -> ButtplugClientResultFuture<u32> { if self .feature() .input() .as_ref() .ok_or(false) .unwrap() .contains(InputType::Battery) { let send_fut = self.read_sensor(InputType::Battery); Box::pin(async move { let data = send_fut.await?; let battery_level = if let InputTypeData::Battery(level) = data { level.data() } else { 0 }; Ok(battery_level as u32) }) } else { create_boxed_future_client_error( ButtplugDeviceError::DeviceFeatureMismatch("Device feature is not battery".to_owned()) .into(), ) } }
pub fn rssi_level(&self) -> ButtplugClientResultFuture<i8> { if self .feature() .input() .as_ref() .ok_or(false) .unwrap() .contains(InputType::Rssi) { let send_fut = self.read_sensor(InputType::Rssi); Box::pin(async move { let data = send_fut.await?; let rssi_level = if let InputTypeData::Rssi(level) = data { level.data() } else { 0 }; Ok(rssi_level) }) } else { create_boxed_future_client_error( ButtplugDeviceError::DeviceFeatureMismatch("Device feature is not RSSI".to_owned()).into(), ) } }}