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 super::fleshlight_launch_helper::calculate_speed;
9use crate::{
10 device::{
11 hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd},
12 protocol::{ProtocolHandler, generic_protocol_setup},
13 },
14 message::ButtplugServerDeviceMessage,
15};
16use buttplug_core::{
17 errors::ButtplugDeviceError,
18 message::{InputData, InputReadingV4, InputTypeData},
19 util::stream::convert_broadcast_receiver_to_stream,
20};
21use buttplug_server_device_config::Endpoint;
22// use dashmap::DashSet;
23use futures::{FutureExt, StreamExt, future::BoxFuture};
24use std::{
25 default::Default,
26 pin::Pin,
27 sync::{
28 Arc,
29 atomic::{AtomicU8, Ordering::Relaxed},
30 },
31};
32use tokio::sync::broadcast;
33use uuid::Uuid;
34
35generic_protocol_setup!(KiirooV21, "kiiroo-v21");
36
37pub struct KiirooV21 {
38 previous_position: Arc<AtomicU8>,
39 // Set of sensors we've subscribed to for updates.
40 // subscribed_sensors: Arc<DashSet<u32>>,
41 event_stream: broadcast::Sender<ButtplugServerDeviceMessage>,
42}
43
44impl Default for KiirooV21 {
45 fn default() -> Self {
46 let (sender, _) = broadcast::channel(256);
47 Self {
48 previous_position: Default::default(),
49 // subscribed_sensors: Arc::new(DashSet::new()),
50 event_stream: sender,
51 }
52 }
53}
54
55impl ProtocolHandler for KiirooV21 {
56 fn handle_output_vibrate_cmd(
57 &self,
58 _feature_index: u32,
59 feature_id: Uuid,
60 speed: u32,
61 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
62 Ok(vec![
63 HardwareWriteCmd::new(&[feature_id], Endpoint::Tx, vec![0x01, speed as u8], false).into(),
64 ])
65 }
66
67 fn handle_position_with_duration_cmd(
68 &self,
69 _feature_index: u32,
70 feature_id: Uuid,
71 position: u32,
72 duration: u32,
73 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
74 // In the protocol, we know max speed is 99, so convert here. We have to
75 // use AtomicU8 because there's no AtomicF64 yet.
76 let previous_position = self.previous_position.load(Relaxed);
77 let distance = (previous_position as f64 - (position as f64)).abs() / 99f64;
78 let position = position as u8;
79 let speed = (calculate_speed(distance, duration) * 99f64) as u8;
80 self.previous_position.store(position, Relaxed);
81 Ok(vec![
82 HardwareWriteCmd::new(
83 &[feature_id],
84 Endpoint::Tx,
85 [0x03, 0x00, speed, position].to_vec(),
86 false,
87 )
88 .into(),
89 ])
90 }
91
92 fn handle_battery_level_cmd(
93 &self,
94 device_index: u32,
95 device: Arc<Hardware>,
96 feature_index: u32,
97 feature_id: Uuid,
98 ) -> BoxFuture<'_, Result<InputReadingV4, ButtplugDeviceError>> {
99 debug!("Trying to get battery reading.");
100 // Reading the "whitelist" endpoint for this device retrieves the battery level,
101 // which is byte 5. All other bytes of the 20-byte result are unknown.
102 let msg = HardwareReadCmd::new(feature_id, Endpoint::Whitelist, 20, 0);
103 let fut = device.read_value(&msg);
104 async move {
105 let hw_msg = fut.await?;
106 let data = hw_msg.data();
107 if data.len() != 20 {
108 // Maybe not the Kiiroo Pearl 2.1?
109 return Err(ButtplugDeviceError::DeviceCommunicationError(
110 "Kiiroo battery data not expected length!".to_owned(),
111 ));
112 }
113 let battery_level = data[5];
114 let battery_reading = InputReadingV4::new(
115 device_index,
116 feature_index,
117 InputTypeData::Battery(InputData::new(battery_level)),
118 );
119 debug!("Got battery reading: {}", battery_level);
120 Ok(battery_reading)
121 }
122 .boxed()
123 }
124
125 fn event_stream(
126 &self,
127 ) -> Pin<Box<dyn futures::Stream<Item = ButtplugServerDeviceMessage> + Send>> {
128 convert_broadcast_receiver_to_stream(self.event_stream.subscribe()).boxed()
129 }
130
131 /*
132 fn handle_input_subscribe_cmd(
133 &self,
134 device_index: u32,
135 device: Arc<Hardware>,
136 feature_index: u32,
137 feature_id: Uuid,
138 _sensor_type: InputType,
139 ) -> BoxFuture<Result<(), ButtplugDeviceError>> {
140 if self.subscribed_sensors.contains(&feature_index) {
141 return future::ready(Ok(())).boxed();
142 }
143 let sensors = self.subscribed_sensors.clone();
144 // Format for the Kiiroo Pearl 2.1:
145 // Byte 0-1: Raw u16be pressure sensor, smaller values indicate more pressure, channel 1.
146 // Zero values differ even between sensors on same device.
147 // Legal range is not known (might even be i16le),
148 // actual range on one device is around 850±50.
149 // Byte 2-3: Same, channel 2.
150 // Byte 4-5: Same, channel 3.
151 // Byte 6-7: Same, channel 4.
152 // Byte 8: Flags corresponding to pressure regions, thresholded on device:
153 // LSB is channel 1 pressed, next least significant bit is channel 2, etc.
154 async move {
155 // If we have no sensors we're currently subscribed to, we'll need to bring up our BLE
156 // characteristic subscription.
157 if sensors.is_empty() {
158 device
159 .subscribe(&HardwareSubscribeCmd::new(feature_id, Endpoint::Rx))
160 .await?;
161 let sender = self.event_stream.clone();
162 let mut hardware_stream = device.event_stream();
163 let stream_sensors = sensors.clone();
164 // If we subscribe successfully, we need to set up our event handler.
165 async_manager::spawn(async move {
166 while let Ok(info) = hardware_stream.recv().await {
167 // If we have no receivers, quit.
168 if sender.receiver_count() == 0 || stream_sensors.is_empty() {
169 return;
170 }
171 if let HardwareEvent::Notification(_, endpoint, data) = info {
172 if endpoint == Endpoint::Rx {
173 if data.len() != 9 {
174 // Maybe not the Kiiroo Pearl 2.1?
175 error!("Kiiroo sensor data not expected length!");
176 continue;
177 }
178 // Extract our pressure values.
179 // Invert analog values so that the value increases with pressure.
180 let analog: Vec<i32> = (0..4)
181 .map(|i| {
182 (u16::MAX as i32) - ((data[2 * i] as i32) << 8 | (data[2 * i + 1] as i32))
183 })
184 .collect();
185 let digital: Vec<i32> = (0..4).map(|i| ((data[8] as i32) >> i) & 1).collect();
186 for ((sensor_index, sensor_type), sensor_data) in (0u32..)
187 .zip([InputType::Pressure, InputType::Button])
188 .zip([analog, digital])
189 {
190 if stream_sensors.contains(&sensor_index)
191 && sender
192 .send(
193 InputReadingV4::new(device_index, sensor_index, sensor_type, sensor_data)
194 .into(),
195 )
196 .is_err()
197 {
198 debug!(
199 "Hardware device listener for Kiiroo 2.1 device shut down, returning from task."
200 );
201 return;
202 }
203 }
204 }
205 }
206 }
207 });
208 }
209 sensors.insert(feature_index);
210 Ok(())
211 }
212 .boxed()
213 }
214
215 fn handle_input_unsubscribe_cmd(
216 &self,
217 device: Arc<Hardware>,
218 feature_index: u32,
219 feature_id: Uuid,
220 _sensor_type: InputType,
221 ) -> BoxFuture<Result<(), ButtplugDeviceError>> {
222 if !self.subscribed_sensors.contains(&feature_index) {
223 return future::ready(Ok(())).boxed();
224 }
225 let sensors = self.subscribed_sensors.clone();
226 async move {
227 // If we have no sensors we're currently subscribed to, we'll need to end our BLE
228 // characteristic subscription.
229 sensors.remove(&feature_index);
230 if sensors.is_empty() {
231 device
232 .unsubscribe(&HardwareUnsubscribeCmd::new(feature_id, Endpoint::Rx))
233 .await?;
234 }
235 Ok(())
236 }
237 .boxed()
238 }
239 */
240}