Buttplug sex toy control library
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
8use crate::{message::InputCommandType, util::range_serialize::*};
9use derive_builder::Builder;
10use getset::{CopyGetters, Getters, MutGetters, Setters};
11use serde::{Deserialize, Serialize, Serializer, ser::SerializeSeq};
12use std::{collections::HashSet, hash::Hash, ops::RangeInclusive};
13
14#[derive(
15 Debug, Display, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, EnumIter, EnumString,
16)]
17pub enum OutputType {
18 Unknown,
19 #[serde(alias = "vibrate")]
20 Vibrate,
21 #[serde(alias = "rotate")]
22 Rotate,
23 #[serde(alias = "oscillate")]
24 Oscillate,
25 #[serde(alias = "constrict")]
26 Constrict,
27 #[serde(alias = "temperature")]
28 Temperature,
29 #[serde(alias = "led")]
30 Led,
31 // For instances where we specify a position to move to ASAP. Usually servos, probably for the
32 // OSR-2/SR-6.
33 #[serde(alias = "position")]
34 Position,
35 #[serde(alias = "position_with_duration")]
36 PositionWithDuration,
37 // Lube shooters
38 #[serde(alias = "spray")]
39 Spray,
40 // Things we might add in the future
41 // Inflate,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, Hash, EnumIter)]
45pub enum InputType {
46 Unknown,
47 #[serde(alias = "battery")]
48 Battery,
49 #[serde(alias = "rssi")]
50 Rssi,
51 #[serde(alias = "button")]
52 Button,
53 #[serde(alias = "pressure")]
54 Pressure,
55 // Temperature,
56 // Accelerometer,
57 // Gyro,
58}
59
60// This will look almost exactly like ServerDeviceFeature. However, it will only contain
61// information we want the client to know, i.e. step counts versus specific step ranges. This is
62// what will be sent to the client as part of DeviceAdded/DeviceList messages. It should not be used
63// for outside configuration/serialization, rather it should be a subset of that information.
64//
65// For many messages, client and server configurations may be exactly the same. If they are not,
66// then we denote this by prefixing the type with Client/Server. Server attributes will usually be
67// hosted in the server/device/configuration module.
68#[derive(
69 Clone, Debug, Default, Getters, MutGetters, CopyGetters, Setters, Serialize, Deserialize,
70)]
71#[serde(rename_all = "PascalCase")]
72pub struct DeviceFeature {
73 // Index of the feature on the device. This was originally implicit as the position in the feature
74 // array. We now make it explicit even though it's still just array position, because implicit
75 // array positions have made life hell in so many different ways.
76 #[getset(get_copy = "pub")]
77 feature_index: u32,
78 #[getset(get = "pub", get_mut = "pub(super)")]
79 #[serde(default, rename = "FeatureDescription")]
80 description: String,
81 // TODO Maybe make this its own object instead of a HashMap?
82 #[getset(get = "pub")]
83 #[serde(skip_serializing_if = "Option::is_none")]
84 output: Option<DeviceFeatureOutput>,
85 #[getset(get = "pub")]
86 #[serde(skip_serializing_if = "Option::is_none")]
87 input: Option<DeviceFeatureInput>,
88}
89
90impl DeviceFeature {
91 pub fn new(
92 index: u32,
93 description: &str,
94 output: &Option<DeviceFeatureOutput>,
95 input: &Option<DeviceFeatureInput>,
96 ) -> Self {
97 Self {
98 feature_index: index,
99 description: description.to_owned(),
100 output: output.clone(),
101 input: input.clone(),
102 }
103 }
104}
105
106fn range_sequence_serialize<S>(
107 range_vec: &Vec<RangeInclusive<i32>>,
108 serializer: S,
109) -> Result<S::Ok, S::Error>
110where
111 S: Serializer,
112{
113 let mut seq = serializer.serialize_seq(Some(range_vec.len()))?;
114 for range in range_vec {
115 seq.serialize_element(&vec![*range.start(), *range.end()])?;
116 }
117 seq.end()
118}
119
120pub trait DeviceFeatureOutputLimits {
121 fn step_count(&self) -> u32;
122 fn step_limit(&self) -> RangeInclusive<i32>;
123}
124
125#[derive(Serialize, Deserialize, Clone, Debug, Getters)]
126#[serde(rename_all = "PascalCase")]
127pub struct DeviceFeatureOutputValueProperties {
128 #[getset(get = "pub")]
129 #[serde(serialize_with = "range_serialize")]
130 value: RangeInclusive<i32>,
131}
132
133impl DeviceFeatureOutputValueProperties {
134 pub fn new(value: &RangeInclusive<i32>) -> Self {
135 DeviceFeatureOutputValueProperties {
136 value: value.clone(),
137 }
138 }
139
140 pub fn step_count(&self) -> u32 {
141 *self.value.end() as u32
142 }
143}
144
145impl DeviceFeatureOutputLimits for DeviceFeatureOutputValueProperties {
146 fn step_count(&self) -> u32 {
147 self.step_count()
148 }
149 fn step_limit(&self) -> RangeInclusive<i32> {
150 self.value.clone()
151 }
152}
153
154#[derive(Serialize, Deserialize, Clone, Debug, Getters)]
155#[serde(rename_all = "PascalCase")]
156pub struct DeviceFeatureOutputPositionWithDurationProperties {
157 #[getset(get = "pub")]
158 #[serde(serialize_with = "range_serialize")]
159 position: RangeInclusive<i32>,
160 #[getset(get = "pub")]
161 #[serde(serialize_with = "range_serialize")]
162 duration: RangeInclusive<i32>,
163}
164
165impl DeviceFeatureOutputPositionWithDurationProperties {
166 pub fn new(position: &RangeInclusive<i32>, duration: &RangeInclusive<i32>) -> Self {
167 DeviceFeatureOutputPositionWithDurationProperties {
168 position: position.clone(),
169 duration: duration.clone(),
170 }
171 }
172
173 pub fn step_count(&self) -> u32 {
174 *self.position.end() as u32
175 }
176}
177
178impl 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()
184 }
185}
186
187#[derive(Clone, Debug, Getters, Setters, Default, Serialize, Deserialize, Builder)]
188#[builder(setter(strip_option), default)]
189#[getset(get = "pub")]
190#[serde(rename_all = "PascalCase")]
191pub struct DeviceFeatureOutput {
192 #[serde(skip_serializing_if = "Option::is_none")]
193 vibrate: Option<DeviceFeatureOutputValueProperties>,
194 #[serde(skip_serializing_if = "Option::is_none")]
195 rotate: Option<DeviceFeatureOutputValueProperties>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 oscillate: Option<DeviceFeatureOutputValueProperties>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 constrict: Option<DeviceFeatureOutputValueProperties>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 temperature: Option<DeviceFeatureOutputValueProperties>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 led: Option<DeviceFeatureOutputValueProperties>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 position: Option<DeviceFeatureOutputValueProperties>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 position_with_duration: Option<DeviceFeatureOutputPositionWithDurationProperties>,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 spray: Option<DeviceFeatureOutputValueProperties>,
210}
211
212impl DeviceFeatureOutput {
213 pub fn contains(&self, output_type: OutputType) -> bool {
214 match output_type {
215 OutputType::Constrict => self.constrict.is_some(),
216 OutputType::Temperature => self.temperature.is_some(),
217 OutputType::Led => self.led.is_some(),
218 OutputType::Oscillate => self.oscillate.is_some(),
219 OutputType::Position => self.position.is_some(),
220 OutputType::PositionWithDuration => self.position_with_duration.is_some(),
221 OutputType::Rotate => self.rotate.is_some(),
222 OutputType::Spray => self.spray.is_some(),
223 OutputType::Unknown => false,
224 OutputType::Vibrate => self.vibrate.is_some(),
225 }
226 }
227
228 pub fn get(&self, output_type: OutputType) -> Option<&dyn DeviceFeatureOutputLimits> {
229 match output_type {
230 OutputType::Constrict => self
231 .constrict()
232 .as_ref()
233 .map(|x| x as &dyn DeviceFeatureOutputLimits),
234 OutputType::Temperature => self
235 .temperature()
236 .as_ref()
237 .map(|x| x as &dyn DeviceFeatureOutputLimits),
238 OutputType::Led => self
239 .led()
240 .as_ref()
241 .map(|x| x as &dyn DeviceFeatureOutputLimits),
242 OutputType::Oscillate => self
243 .oscillate()
244 .as_ref()
245 .map(|x| x as &dyn DeviceFeatureOutputLimits),
246 OutputType::Position => self
247 .position()
248 .as_ref()
249 .map(|x| x as &dyn DeviceFeatureOutputLimits),
250 OutputType::PositionWithDuration => self
251 .position_with_duration()
252 .as_ref()
253 .map(|x| x as &dyn DeviceFeatureOutputLimits),
254 OutputType::Rotate => self
255 .rotate()
256 .as_ref()
257 .map(|x| x as &dyn DeviceFeatureOutputLimits),
258 OutputType::Spray => self
259 .spray()
260 .as_ref()
261 .map(|x| x as &dyn DeviceFeatureOutputLimits),
262 OutputType::Unknown => None,
263 OutputType::Vibrate => self
264 .vibrate()
265 .as_ref()
266 .map(|x| x as &dyn DeviceFeatureOutputLimits),
267 }
268 }
269}
270
271#[derive(
272 Clone, Debug, Default, PartialEq, Eq, Getters, MutGetters, Setters, Serialize, Deserialize,
273)]
274#[serde(rename_all = "PascalCase")]
275pub struct DeviceFeatureInputProperties {
276 #[getset(get = "pub", get_mut = "pub(super)")]
277 #[serde(serialize_with = "range_sequence_serialize")]
278 value_range: Vec<RangeInclusive<i32>>,
279 #[getset(get = "pub")]
280 input_commands: HashSet<InputCommandType>,
281}
282
283impl DeviceFeatureInputProperties {
284 pub fn new(
285 value_range: &Vec<RangeInclusive<i32>>,
286 sensor_commands: &HashSet<InputCommandType>,
287 ) -> Self {
288 Self {
289 value_range: value_range.clone(),
290 input_commands: sensor_commands.clone(),
291 }
292 }
293}
294
295#[derive(Clone, Debug, Getters, Setters, Default, Serialize, Deserialize, Builder)]
296#[builder(setter(strip_option), default)]
297#[getset(get = "pub")]
298#[serde(rename_all = "PascalCase")]
299pub struct DeviceFeatureInput {
300 #[serde(skip_serializing_if = "Option::is_none")]
301 battery: Option<DeviceFeatureInputProperties>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 rssi: Option<DeviceFeatureInputProperties>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pressure: Option<DeviceFeatureInputProperties>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 button: Option<DeviceFeatureInputProperties>,
308}
309
310impl DeviceFeatureInput {
311 pub fn contains(&self, input_type: InputType) -> bool {
312 match input_type {
313 InputType::Battery => self.battery.is_some(),
314 InputType::Rssi => self.rssi.is_some(),
315 InputType::Pressure => self.pressure.is_some(),
316 InputType::Button => self.button.is_some(),
317 InputType::Unknown => false,
318 }
319 }
320
321 pub fn get(&self, input_type: InputType) -> &Option<DeviceFeatureInputProperties> {
322 match input_type {
323 InputType::Battery => self.battery(),
324 InputType::Rssi => self.rssi(),
325 InputType::Pressure => self.pressure(),
326 InputType::Button => self.button(),
327 InputType::Unknown => &None,
328 }
329 }
330}