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::device::{
9 hardware::{Hardware, HardwareCommand, HardwareWriteCmd},
10 protocol::{
11 ProtocolHandler,
12 ProtocolIdentifier,
13 ProtocolInitializer,
14 generic_protocol_initializer_setup,
15 },
16};
17use async_trait::async_trait;
18use buttplug_core::{errors::ButtplugDeviceError, util::async_manager};
19use buttplug_server_device_config::{
20 Endpoint,
21 ProtocolCommunicationSpecifier,
22 ServerDeviceDefinition,
23 UserDeviceIdentifier,
24};
25use std::{
26 sync::{
27 Arc,
28 atomic::{AtomicBool, AtomicU16, Ordering},
29 },
30 time::Duration,
31};
32use tokio::sync::Notify;
33use uuid::{Uuid, uuid};
34
35const NINTENDO_JOYCON_PROTOCOL_UUID: Uuid = uuid!("de9cce17-abb7-4ad5-9754-f1872733c197");
36
37/// Send command, sub-command, and data (sub-command's arguments) with u8 integers
38/// This returns ACK packet for the command or Error.
39async fn send_command_raw(
40 device: Arc<Hardware>,
41 packet_number: u8,
42 command: u8,
43 sub_command: u8,
44 data: &[u8],
45 rumble_r: Option<Rumble>,
46 rumble_l: Option<Rumble>,
47) -> Result<(), ButtplugDeviceError> {
48 let mut buf = [0x0; 0x40];
49 // set command
50 buf[0] = command;
51 // set packet number
52 buf[1] = packet_number;
53
54 // rumble
55 if let Some(rumble_l) = rumble_l {
56 let rumble_left: [u8; 4] = rumble_l.into();
57 buf[2..6].copy_from_slice(&rumble_left);
58 }
59 if let Some(rumble_r) = rumble_r {
60 let rumble_right: [u8; 4] = rumble_r.into();
61 buf[6..10].copy_from_slice(&rumble_right);
62 }
63
64 // set sub command
65 buf[10] = sub_command;
66 // set data
67 buf[11..11 + data.len()].copy_from_slice(data);
68
69 // send command
70 device
71 .write_value(&HardwareWriteCmd::new(
72 &[NINTENDO_JOYCON_PROTOCOL_UUID],
73 Endpoint::Tx,
74 buf.to_vec(),
75 false,
76 ))
77 .await
78}
79
80/// Send sub-command, and data (sub-command's arguments) with u8 integers
81/// This returns ACK packet for the command or Error.
82///
83/// # Notice
84/// If you are using non-blocking mode,
85/// it is more likely to fail to validate the sub command reply.
86async fn send_sub_command_raw(
87 device: Arc<Hardware>,
88 packet_number: u8,
89 sub_command: u8,
90 data: &[u8],
91) -> Result<(), ButtplugDeviceError> {
92 //use input_report_mode::sub_command_mode::AckByte;
93
94 send_command_raw(device, packet_number, 1, sub_command, data, None, None).await
95 /*
96 // check reply
97 if self.valid_reply() {
98 std::iter::repeat(())
99 .take(Self::ACK_TRY)
100 .flat_map(|()| {
101 let mut buf = [0u8; 362];
102 self.read(&mut buf).ok()?;
103 let ack_byte = AckByte::from(buf[13]);
104
105 match ack_byte {
106 AckByte::Ack { .. } => Some(buf),
107 AckByte::Nack => None
108 }
109 })
110 .next()
111 .map(SubCommandReply::Checked)
112 .ok_or_else(|| JoyConError::SubCommandError(sub_command, Vec::new()))
113 } else {
114 Ok(SubCommandReply::Unchecked)
115 }
116 */
117}
118
119/// Send sub-command, and data (sub-command's arguments) with `Command` and `SubCommand`
120/// This returns ACK packet for the command or Error.
121async fn send_sub_command(
122 device: Arc<Hardware>,
123 packet_number: u8,
124 sub_command: u8,
125 data: &[u8],
126) -> Result<(), ButtplugDeviceError> {
127 send_sub_command_raw(device, packet_number, sub_command, data).await
128}
129
130/// Rumble data for vibration.
131///
132/// # Notice
133/// Constraints exist.
134/// * frequency - 0.0 < freq < 1252.0
135/// * amplitude - 0.0 < amp < 1.799.0
136#[derive(Debug, Clone, Copy, PartialEq)]
137pub struct Rumble {
138 frequency: f32,
139 amplitude: f32,
140}
141
142impl Rumble {
143 pub fn frequency(self) -> f32 {
144 self.frequency
145 }
146
147 pub fn amplitude(self) -> f32 {
148 self.amplitude
149 }
150
151 /// Constructor of Rumble.
152 /// If arguments not in line with constraints, args will be saturated.
153 pub fn new(freq: f32, amp: f32) -> Self {
154 let freq = if freq < 0.0 {
155 0.0
156 } else if freq > 1252.0 {
157 1252.0
158 } else {
159 freq
160 };
161
162 let amp = if amp < 0.0 {
163 0.0
164 } else if amp > 1.799 {
165 1.799
166 } else {
167 amp
168 };
169
170 Self {
171 frequency: freq,
172 amplitude: amp,
173 }
174 }
175
176 /// The amplitudes over 1.003 are not safe for the integrity of the linear resonant actuators.
177 pub fn is_safe(self) -> bool {
178 self.amplitude < 1.003
179 }
180
181 /// Generates stopper of rumbling.
182 pub fn stop() -> Self {
183 Self {
184 frequency: 0.0,
185 amplitude: 0.0,
186 }
187 }
188}
189
190impl From<Rumble> for [u8; 4] {
191 fn from(val: Rumble) -> Self {
192 let encoded_hex_freq = f32::round(f32::log2(val.frequency / 10.0) * 32.0) as u8;
193
194 let hf_freq: u16 = (encoded_hex_freq as u16).saturating_sub(0x60) * 4;
195 let lf_freq: u8 = encoded_hex_freq.saturating_sub(0x41) + 1;
196
197 let encoded_hex_amp = if val.amplitude > 0.23 {
198 f32::round(f32::log2(val.amplitude * 8.7) * 32.0) as u8
199 } else if val.amplitude > 0.12 {
200 f32::round(f32::log2(val.amplitude * 17.0) * 16.0) as u8
201 } else {
202 f32::round(((f32::log2(val.amplitude) * 32.0) - 96.0) / (4.0 - 2.0 * val.amplitude)) as u8
203 };
204
205 let hf_amp: u16 = {
206 let hf_amp: u16 = encoded_hex_amp as u16 * 2;
207 if hf_amp > 0x01FC { 0x01FC } else { hf_amp }
208 }; // encoded_hex_amp<<1;
209 let lf_amp: u8 = {
210 let lf_amp = encoded_hex_amp / 2 + 64;
211 if lf_amp > 0x7F { 0x7F } else { lf_amp }
212 }; // (encoded_hex_amp>>1)+0x40;
213
214 let mut buf = [0u8; 4];
215
216 // HF: Byte swapping
217 buf[0] = (hf_freq & 0xFF) as u8;
218 // buf[1] = (hf_amp + ((hf_freq >> 8) & 0xFF)) as u8; //Add amp + 1st byte of frequency to amplitude byte
219 buf[1] = (hf_amp + (hf_freq.wrapping_shr(8) & 0xFF)) as u8; //Add amp + 1st byte of frequency to amplitude byte
220
221 // LF: Byte swapping
222 buf[2] = lf_freq.saturating_add(lf_amp.wrapping_shr(8));
223 buf[3] = lf_amp;
224
225 buf
226 }
227}
228
229generic_protocol_initializer_setup!(NintendoJoycon, "nintendo-joycon");
230
231#[derive(Default)]
232pub struct NintendoJoyconInitializer {}
233
234#[async_trait]
235impl ProtocolInitializer for NintendoJoyconInitializer {
236 async fn initialize(
237 &mut self,
238 hardware: Arc<Hardware>,
239 _: &ServerDeviceDefinition,
240 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
241 send_sub_command(hardware.clone(), 0, 72, &[0x01])
242 .await
243 .map_err(|_| {
244 ButtplugDeviceError::DeviceConnectionError("Cannot initialize joycon".to_owned())
245 })?;
246 Ok(Arc::new(NintendoJoycon::new(hardware)))
247 }
248}
249
250pub struct NintendoJoycon {
251 //packet_number: Arc<AtomicU8>,
252 speed_val: Arc<AtomicU16>,
253 notifier: Arc<Notify>,
254 is_stopped: Arc<AtomicBool>,
255}
256
257impl NintendoJoycon {
258 fn new(hardware: Arc<Hardware>) -> Self {
259 let speed_val = Arc::new(AtomicU16::new(0));
260 let speed_val_clone = speed_val.clone();
261 let notifier = Arc::new(Notify::new());
262 #[cfg(not(feature = "wasm"))]
263 let notifier_clone = notifier.clone();
264 let is_stopped = Arc::new(AtomicBool::new(false));
265 let is_stopped_clone = is_stopped.clone();
266 async_manager::spawn(async move {
267 #[cfg(feature = "wasm")]
268 use buttplug_core::util;
269 loop {
270 if is_stopped_clone.load(Ordering::Relaxed) {
271 return;
272 }
273 let amp = speed_val_clone.load(Ordering::Relaxed) as f32 / 1000f32;
274 let rumble = if amp > 0.001 {
275 Rumble::new(200.0f32, amp)
276 } else {
277 Rumble::stop()
278 };
279
280 if let Err(_) =
281 send_command_raw(hardware.clone(), 1, 16, 0, &[], Some(rumble), Some(rumble)).await
282 {
283 error!("Joycon command failed, exiting update loop");
284 break;
285 }
286 #[cfg(not(feature = "wasm"))]
287 let _ = tokio::time::timeout(Duration::from_millis(15), notifier_clone.notified()).await;
288
289 // If we're using WASM, we can't use tokio's timeout due to lack of time library in WASM.
290 // I'm also too lazy to make this a select. So, this'll do. We can't even access this
291 // protocol in a web context yet since there's no WebHID comm manager yet.
292 #[cfg(feature = "wasm")]
293 util::sleep(Duration::from_millis(15)).await;
294 }
295 });
296 Self {
297 //packet_number: Arc::new(AtomicU8::new(0)),
298 speed_val,
299 notifier,
300 is_stopped,
301 }
302 }
303}
304
305impl ProtocolHandler for NintendoJoycon {
306 fn handle_output_vibrate_cmd(
307 &self,
308 _feature_index: u32,
309 _feature_id: Uuid,
310 speed: u32,
311 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
312 self.speed_val.store(speed as u16, Ordering::Relaxed);
313 Ok(vec![])
314 }
315}
316
317impl Drop for NintendoJoycon {
318 fn drop(&mut self) {
319 self.is_stopped.store(false, Ordering::Relaxed);
320 self.notifier.notify_one();
321 }
322}