Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2025 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 std::sync::Arc;
9use std::sync::atomic::{AtomicU8, Ordering};
10
11use async_trait::async_trait;
12use uuid::{Uuid, uuid};
13
14use buttplug_core::{errors::ButtplugDeviceError, message::OutputType};
15use buttplug_server_device_config::{
16 Endpoint,
17 ProtocolCommunicationSpecifier,
18 ServerDeviceDefinition,
19 UserDeviceIdentifier,
20};
21
22use crate::device::{
23 hardware::{Hardware, HardwareCommand, HardwareWriteCmd},
24 protocol::{
25 ProtocolHandler,
26 ProtocolIdentifier,
27 ProtocolInitializer,
28 generic_protocol_initializer_setup,
29 },
30};
31
32generic_protocol_initializer_setup!(SexverseV1, "sexverse-v1");
33
34#[derive(Default)]
35pub struct SexverseV1Initializer {}
36
37#[async_trait]
38impl ProtocolInitializer for SexverseV1Initializer {
39 async fn initialize(
40 &mut self,
41 _: Arc<Hardware>,
42 def: &ServerDeviceDefinition,
43 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
44 let mut commands = vec![];
45 def.features().iter().for_each(|x| {
46 if let Some(m) = x.output() {
47 for output in m.output_types() {
48 commands.push((output, AtomicU8::default()))
49 }
50 }
51 });
52 Ok(Arc::new(SexverseV1::new(commands)))
53 }
54}
55
56const SEXVERSE_PROTOCOL_UUID: Uuid = uuid!("6485a762-2ea7-48c1-a4ba-ab724e618348");
57
58#[derive(Default)]
59pub struct SexverseV1 {
60 commands: Vec<(OutputType, AtomicU8)>,
61}
62
63impl SexverseV1 {
64 fn new(commands: Vec<(OutputType, AtomicU8)>) -> Self {
65 Self { commands }
66 }
67
68 fn form_command(
69 &self,
70 feature_index: u32,
71 speed: u32,
72 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
73 self.commands[feature_index as usize]
74 .1
75 .store(speed as u8, Ordering::Relaxed);
76 let mut data: Vec<u8> = vec![0x23, 0x07];
77 data.push((self.commands.len() * 3) as u8);
78
79 for (i, (output_type, speed)) in self.commands.iter().enumerate() {
80 // motor number
81 data.push(0x80 | ((i + 1) as u8));
82 // motor type: 03=vibe 04=pump 06=rotate
83 data.push(if *output_type == OutputType::Rotate {
84 0x06
85 } else if *output_type == OutputType::Constrict || *output_type == OutputType::Oscillate {
86 0x04
87 } else {
88 // Vibrate
89 0x03
90 });
91 data.push(speed.load(Ordering::Relaxed));
92 }
93
94 let mut crc: u8 = 0;
95 for b in data.clone() {
96 crc ^= b;
97 }
98 data.push(crc);
99
100 Ok(vec![
101 HardwareWriteCmd::new(&[SEXVERSE_PROTOCOL_UUID], Endpoint::Tx, data, false).into(),
102 ])
103 }
104}
105
106impl ProtocolHandler for SexverseV1 {
107 fn handle_output_vibrate_cmd(
108 &self,
109 feature_index: u32,
110 _feature_id: Uuid,
111 speed: u32,
112 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
113 self.form_command(feature_index, speed)
114 }
115
116 fn handle_output_oscillate_cmd(
117 &self,
118 feature_index: u32,
119 _feature_id: Uuid,
120 speed: u32,
121 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
122 self.form_command(feature_index, speed)
123 }
124
125 fn handle_output_rotate_cmd(
126 &self,
127 feature_index: u32,
128 _feature_id: Uuid,
129 speed: i32,
130 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
131 self.form_command(feature_index, speed as u32)
132 }
133
134 fn handle_output_constrict_cmd(
135 &self,
136 feature_index: u32,
137 _feature_id: Uuid,
138 level: u32,
139 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
140 self.form_command(feature_index, level)
141 }
142}