Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2023 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::device::{
9 hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareSubscribeCmd, HardwareWriteCmd},
10 protocol::{
11 ProtocolHandler,
12 ProtocolIdentifier,
13 ProtocolInitializer,
14 generic_protocol_initializer_setup,
15 },
16};
17use async_trait::async_trait;
18use buttplug_core::{
19 errors::ButtplugDeviceError,
20 util::{async_manager, sleep},
21};
22use buttplug_server_device_config::{
23 Endpoint,
24 ProtocolCommunicationSpecifier,
25 ServerDeviceDefinition,
26 UserDeviceIdentifier,
27};
28use futures::FutureExt;
29use std::{
30 sync::{
31 Arc,
32 atomic::{AtomicU8, Ordering},
33 },
34 time::Duration,
35};
36use tokio::select;
37use uuid::{Uuid, uuid};
38
39const FREDORCH_COMMAND_TIMEOUT_MS: u64 = 100;
40const FREDORCH_ROTORY_PROTOCOL_UUID: Uuid = uuid!("0ec6598a-bfd1-4f47-9738-e8cd8ace6473");
41generic_protocol_initializer_setup!(FredorchRotary, "fredorch-rotary");
42
43#[derive(Default)]
44pub struct FredorchRotaryInitializer {}
45
46#[async_trait]
47impl ProtocolInitializer for FredorchRotaryInitializer {
48 async fn initialize(
49 &mut self,
50 hardware: Arc<Hardware>,
51 _: &ServerDeviceDefinition,
52 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
53 warn!(
54 "FredorchRotary device doesn't provide state feedback. If the device beeps twice, it is powered off and must be reconnected before it can be controlled!"
55 );
56
57 let mut event_receiver = hardware.event_stream();
58 hardware
59 .subscribe(&HardwareSubscribeCmd::new(
60 FREDORCH_ROTORY_PROTOCOL_UUID,
61 Endpoint::Rx,
62 ))
63 .await?;
64
65 let init: Vec<(String, Vec<u8>)> = vec![
66 (
67 "Start the handshake".to_owned(),
68 vec![0x55, 0x03, 0x99, 0x9c, 0xaa],
69 ),
70 (
71 "Send the password".to_owned(),
72 vec![
73 0x55, 0x09, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xaa,
74 ],
75 ),
76 (
77 "Power up the device".to_owned(),
78 vec![0x55, 0x03, 0x1f, 0x22, 0xaa],
79 ),
80 (
81 "Stop the device".to_owned(),
82 vec![0x55, 0x03, 0x24, 0x27, 0xaa],
83 ),
84 ];
85
86 for data in init {
87 debug!("FredorchRotary: {} - sent {:?}", data.0, data.1);
88 hardware
89 .write_value(&HardwareWriteCmd::new(
90 &[FREDORCH_ROTORY_PROTOCOL_UUID],
91 Endpoint::Tx,
92 data.1.clone(),
93 false,
94 ))
95 .await?;
96
97 select! {
98 event = event_receiver.recv().fuse() => {
99 if let Ok(HardwareEvent::Notification(_, _, n)) = event {
100 debug!("FredorchRotary: {} - received {:?}", data.0, n);
101 } else {
102 return Err(
103 ButtplugDeviceError::ProtocolSpecificError(
104 "FredorchRotary".to_owned(),
105 "FredorchRotary Device disconnected while initialising.".to_owned(),
106 )
107 );
108 }
109 }
110 _ = sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).fuse() => {
111 // The after the password check, we won't get anything
112 }
113 }
114 }
115
116 Ok(Arc::new(FredorchRotary::new(hardware)))
117 }
118}
119
120pub struct FredorchRotary {
121 current_speed: Arc<AtomicU8>,
122 target_speed: Arc<AtomicU8>,
123}
124
125async fn speed_update_handler(
126 device: Arc<Hardware>,
127 current_speed: Arc<AtomicU8>,
128 target_speed: Arc<AtomicU8>,
129) {
130 info!("Entering FredorchRotary Control Loop");
131
132 loop {
133 let ts = target_speed.load(Ordering::Relaxed);
134 let cs = current_speed.load(Ordering::Relaxed);
135
136 trace!("FredorchRotary: {}c vs {}t", cs, ts);
137
138 if ts != cs {
139 let cmd: u8 = if ts == 0 {
140 0x24
141 } else if ts > cs {
142 0x01
143 } else {
144 0x02
145 };
146 let update = device
147 .write_value(&HardwareWriteCmd::new(
148 &[FREDORCH_ROTORY_PROTOCOL_UUID],
149 Endpoint::Tx,
150 vec![0x55u8, 0x03, cmd, cmd + 3, 0xaa],
151 false,
152 ))
153 .await;
154 if update.is_ok() {
155 debug!(
156 "FredorchRotary: {}c vs {}t - speed {:?}",
157 cs,
158 ts,
159 if ts == 0 {
160 "STOP"
161 } else if ts > cs {
162 "+1"
163 } else {
164 "-1"
165 }
166 );
167 current_speed.store(
168 u8::max(
169 if ts == 0 {
170 0
171 } else if ts > cs {
172 cs + 1
173 } else {
174 cs - 1
175 },
176 0,
177 ),
178 Ordering::Relaxed,
179 );
180 continue;
181 } else {
182 info!("FredorchRotary update error: {:?}", update.err());
183 break;
184 }
185 }
186
187 sleep(Duration::from_millis(FREDORCH_COMMAND_TIMEOUT_MS)).await;
188 }
189 info!("FredorchRotary control loop exiting, most likely due to device disconnection.");
190}
191
192impl FredorchRotary {
193 fn new(device: Arc<Hardware>) -> Self {
194 let current_speed = Arc::new(AtomicU8::new(0));
195 let target_speed = Arc::new(AtomicU8::new(0));
196 let current_speed_clone = current_speed.clone();
197 let target_speed_clone = target_speed.clone();
198 async_manager::spawn(async move {
199 speed_update_handler(device, current_speed_clone, target_speed_clone).await
200 });
201 Self {
202 current_speed,
203 target_speed,
204 }
205 }
206}
207
208impl ProtocolHandler for FredorchRotary {
209 fn handle_output_oscillate_cmd(
210 &self,
211 _feature_index: u32,
212 _feature_id: Uuid,
213 speed: u32,
214 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
215 let speed: u8 = speed as u8;
216 self.target_speed.store(speed, Ordering::Relaxed);
217 if speed == 0 {
218 self.current_speed.store(speed, Ordering::Relaxed);
219 Ok(vec![
220 HardwareWriteCmd::new(
221 &[FREDORCH_ROTORY_PROTOCOL_UUID],
222 Endpoint::Tx,
223 vec![0x55, 0x03, 0x24, 0x27, 0xaa],
224 false,
225 )
226 .into(),
227 ])
228 } else {
229 Ok(vec![])
230 }
231 }
232}