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