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 tracing::Level;
9
10/*
11use buttplug_client::device::{ClientDeviceFeature, ClientDeviceOutputCommand};
12use buttplug_client::{ButtplugClient, ButtplugClientDevice, ButtplugClientEvent};
13use buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder;
14use buttplug_core::message::ButtplugDeviceMessageNameV4::OutputCmd;
15use buttplug_core::message::{
16 DeviceFeature,
17 DeviceFeatureOutput,
18 OutputCommand,
19 OutputType,
20 OutputValue,
21};
22use buttplug_server::ButtplugServerBuilder;
23use buttplug_server::device::ServerDeviceManagerBuilder;
24use buttplug_server_device_config::load_protocol_configs;
25use buttplug_server_hwmgr_btleplug::BtlePlugCommunicationManagerBuilder;
26use futures::StreamExt;
27use futures::future::try_join;
28use log::error;
29use std::collections::{HashMap, HashSet};
30use std::{fs, sync::Arc, time::Duration};
31use tokio::time::sleep;
32
33async fn set_level_and_wait(
34 dev: &ButtplugClientDevice,
35 feature: &ClientDeviceFeature,
36 output: &DeviceFeatureOutput,
37 output_type: &OutputType,
38 level: f64,
39) {
40 let cmd = match (output_type) {
41 OutputType::Vibrate => Ok(ClientDeviceOutputCommand::VibrateFloat(level)),
42 OutputType::Rotate => Ok(ClientDeviceOutputCommand::RotateFloat(level)),
43 OutputType::Oscillate => Ok(ClientDeviceOutputCommand::OscillateFloat(level)),
44 OutputType::Constrict => Ok(ClientDeviceOutputCommand::ConstrictFloat(level)),
45 OutputType::Heater => Ok(ClientDeviceOutputCommand::HeaterFloat(level)),
46 OutputType::Led => Ok(ClientDeviceOutputCommand::LedFloat(level)),
47 OutputType::Position => Ok(ClientDeviceOutputCommand::PositionFloat(level)),
48 OutputType::Spray => Ok(ClientDeviceOutputCommand::SprayFloat(level)),
49 _ => Err(format!("Unknown output type {:?}", output_type)),
50 }
51 .unwrap();
52 feature.send_command(&cmd).await.unwrap();
53 println!(
54 "{} ({}) Testing feature {}: {}, output {:?} - {}%",
55 dev.name(),
56 dev.index(),
57 feature.feature().feature_index(),
58 feature.feature().description(),
59 output_type,
60 (level * 100.0) as u8
61 );
62 sleep(Duration::from_secs(1)).await;
63}
64
65async fn device_tester() {
66 let mut dc = None;
67 let mut uc = None;
68
69 dc = None; //Some(fs::read_to_string("C:\\Users\\NickPoole\\AppData\\Roaming\\com.nonpolynomial\\intiface_central\\config\\buttplug-device-config-v3.json").expect("Should have been able to read dc"));
70 uc = None; //Some(fs::read_to_string("C:\\Users\\NickPoole\\AppData\\Roaming\\com.nonpolynomial\\intiface_central\\config\\buttplug-user-device-config-v3.json").expect("Should have been able to read uc"));
71
72 let dcm = load_protocol_configs(&dc, &uc, false)
73 .unwrap()
74 .finish()
75 .unwrap();
76
77 let mut server_builder = ServerDeviceManagerBuilder::new(dcm);
78 server_builder.comm_manager(BtlePlugCommunicationManagerBuilder::default());
79 //server_builder.comm_manager(LovenseConnectServiceCommunicationManagerBuilder::default());
80 //server_builder.comm_manager(LovenseHIDDongleCommunicationManagerBuilder::default());
81 //server_builder.comm_manager(LovenseSerialDongleCommunicationManagerBuilder::default());
82 //server_builder.comm_manager(WebsocketServerDeviceCommunicationManagerBuilder::default());
83 //server_builder.comm_manager(HidCommunicationManagerBuilder::default());
84 //server_builder.comm_manager(SerialPortCommunicationManagerBuilder::default());
85
86 let sb = ButtplugServerBuilder::new(server_builder.finish().unwrap());
87 let server = sb.finish().unwrap();
88 let connector = ButtplugInProcessClientConnectorBuilder::default()
89 .server(server)
90 .finish();
91 let client = ButtplugClient::new("device-tester");
92 client.connect(connector).await.unwrap();
93
94 let mut event_stream = client.event_stream();
95
96 // We'll mostly be doing the same thing we did in example #3, up until we get
97 // a device.
98 if let Err(err) = client.start_scanning().await {
99 println!("Client errored when starting scan! {}", err);
100 return;
101 }
102
103 let exercise_device = |dev: ButtplugClientDevice| async move {
104 let mut cmds = vec![];
105 dev.device_features().iter().for_each(|(_, feature)| {
106 let outs = feature.feature().output().clone().unwrap_or_default();
107 if let Some(out) = outs.get(&OutputType::Vibrate) {
108 cmds.push(feature.vibrate(out.step_count()));
109 println!(
110 "{} ({}) should start vibrating on feature {}!",
111 dev.name(),
112 dev.index(),
113 feature.feature_index()
114 );
115 } else if let Some(out) = outs.get(&OutputType::Rotate) {
116 cmds.push(feature.rotate(out.step_count()));
117 println!(
118 "{} ({}) should start rotating on feature {}!",
119 dev.name(),
120 dev.index(),
121 feature.feature_index()
122 );
123 } else if let Some(out) = outs.get(&OutputType::Oscillate) {
124 cmds.push(feature.oscillate(out.step_count()));
125 println!(
126 "{} ({}) should start oscillating on feature {}!",
127 dev.name(),
128 dev.index(),
129 feature.feature_index()
130 );
131 } else if let Some(out) = outs.get(&OutputType::Constrict) {
132 cmds.push(feature.constrict(out.step_count()));
133 println!(
134 "{} ({}) should start constricting on feature {}!",
135 dev.name(),
136 dev.index(),
137 feature.feature_index()
138 );
139 } else if let Some(out) = outs.get(&OutputType::Heater) {
140 cmds.push(feature.send_command(&ClientDeviceOutputCommand::Heater(out.step_count())));
141 println!(
142 "{} ({}) should start heating on feature {}!",
143 dev.name(),
144 dev.index(),
145 feature.feature_index()
146 );
147 } else if let Some(out) = outs.get(&OutputType::Position) {
148 cmds.push(feature.position(out.step_count()));
149 println!(
150 "{} ({}) should start moving to position {} on feature {}!",
151 dev.name(),
152 dev.index(),
153 out.step_count(),
154 feature.feature_index()
155 );
156 }
157 });
158 futures::future::join_all(cmds)
159 .await
160 .iter()
161 .for_each(|cmd| {
162 if let Err(err) = cmd {
163 error!("{:?}", err);
164 }
165 });
166
167 sleep(Duration::from_secs(5)).await;
168
169 let mut cmds = vec![];
170 dev.device_features().iter().for_each(|(_, feature)| {
171 let outs = feature.feature().output().clone().unwrap_or_default();
172 if let Some(out) = outs.get(&OutputType::Vibrate) {
173 cmds.push(feature.vibrate(0));
174 println!(
175 "{} ({}) should stop vibrating on feature {}!",
176 dev.name(),
177 dev.index(),
178 feature.feature_index()
179 );
180 } else if let Some(out) = outs.get(&OutputType::Rotate) {
181 cmds.push(feature.rotate(0));
182 println!(
183 "{} ({}) should stop rotating on feature {}!",
184 dev.name(),
185 dev.index(),
186 feature.feature_index()
187 );
188 } else if let Some(out) = outs.get(&OutputType::Oscillate) {
189 cmds.push(feature.oscillate(0));
190 println!(
191 "{} ({}) should stop oscillating on feature {}!",
192 dev.name(),
193 dev.index(),
194 feature.feature_index()
195 );
196 } else if let Some(out) = outs.get(&OutputType::Constrict) {
197 cmds.push(feature.constrict(0));
198 println!(
199 "{} ({}) should stop constricting on feature {}!",
200 dev.name(),
201 dev.index(),
202 feature.feature_index()
203 );
204 } else if let Some(out) = outs.get(&OutputType::Heater) {
205 cmds.push(feature.send_command(&ClientDeviceOutputCommand::Heater(0)));
206 println!(
207 "{} ({}) should stop heating on feature {}!",
208 dev.name(),
209 dev.index(),
210 feature.feature_index()
211 );
212 } else if let Some(out) = outs.get(&OutputType::Position) {
213 cmds.push(feature.position(0));
214 println!(
215 "{} ({}) should start moving to position 0 on feature {}!",
216 dev.name(),
217 dev.index(),
218 feature.feature_index()
219 );
220 }
221 });
222
223 futures::future::join_all(cmds)
224 .await
225 .iter()
226 .for_each(|cmd| {
227 if let Err(err) = cmd {
228 error!("{:?}", err);
229 }
230 });
231
232 sleep(Duration::from_secs(2)).await;
233
234 for (_, feature) in dev.device_features() {
235 for outputs in feature.feature().output() {
236 for otype in outputs.keys() {
237 let output = outputs.get(otype).unwrap();
238 let test_feature = async |command, output_str| {
239 feature.send_command(&command).await;
240 println!(
241 "{} ({}) Testing feature {} ({}), output {:?} - {}",
242 dev.name(),
243 dev.index(),
244 feature.feature().feature_index(),
245 feature.feature().description(),
246 otype,
247 output_str
248 );
249 sleep(Duration::from_secs(1)).await;
250 };
251 match otype {
252 OutputType::Vibrate
253 | OutputType::Rotate
254 | OutputType::Constrict
255 | OutputType::Oscillate
256 | OutputType::Heater
257 | OutputType::Spray
258 | OutputType::Led
259 | OutputType::Position => {
260 set_level_and_wait(&dev, feature, &output, otype, 0.25).await;
261 set_level_and_wait(&dev, feature, &output, otype, 0.5).await;
262 set_level_and_wait(&dev, feature, &output, otype, 0.75).await;
263 set_level_and_wait(&dev, feature, &output, otype, 1.0).await;
264 set_level_and_wait(&dev, feature, &output, otype, 0.0).await;
265 }
266 OutputType::Unknown => {
267 error!(
268 "{} ({}) Can't test unknown feature {} ({}), output {:?}",
269 dev.name(),
270 dev.index(),
271 feature.feature().feature_index(),
272 feature.feature().description(),
273 otype
274 );
275 }
276 OutputType::RotateWithDirection => {
277 test_feature(
278 ClientDeviceOutputCommand::RotateWithDirection(output.step_count() / 4, true),
279 "25% clockwise",
280 )
281 .await;
282 test_feature(
283 ClientDeviceOutputCommand::RotateWithDirection(output.step_count() / 4, false),
284 "25% anti-clockwise",
285 )
286 .await;
287 test_feature(
288 ClientDeviceOutputCommand::RotateWithDirection(output.step_count() / 2, true),
289 "50% clockwise",
290 )
291 .await;
292 test_feature(
293 ClientDeviceOutputCommand::RotateWithDirection(output.step_count() / 2, false),
294 "50% anti-clockwise",
295 )
296 .await;
297 test_feature(
298 ClientDeviceOutputCommand::RotateWithDirection((output.step_count() / 4) * 3, true),
299 "75% clockwise",
300 )
301 .await;
302 test_feature(
303 ClientDeviceOutputCommand::RotateWithDirection(
304 (output.step_count() / 4) * 3,
305 false,
306 ),
307 "75% anti-clockwise",
308 )
309 .await;
310 test_feature(
311 ClientDeviceOutputCommand::RotateWithDirection(output.step_count(), true),
312 "100% clockwise",
313 )
314 .await;
315 test_feature(
316 ClientDeviceOutputCommand::RotateWithDirection(output.step_count(), false),
317 "100% anti-clockwise",
318 )
319 .await;
320 test_feature(
321 ClientDeviceOutputCommand::RotateWithDirection(0, false),
322 "stop",
323 )
324 .await;
325 }
326 OutputType::PositionWithDuration => {}
327 }
328 }
329 }
330 }
331 };
332
333 loop {
334 match event_stream
335 .next()
336 .await
337 .expect("We own the client so the event stream shouldn't die.")
338 {
339 ButtplugClientEvent::DeviceAdded(dev) => {
340 println!("We got a device: {}", dev.name());
341 let fut = exercise_device(dev);
342 tokio::spawn(async move {
343 fut.await;
344 });
345 // break;
346 }
347 ButtplugClientEvent::ServerDisconnect => {
348 // The server disconnected, which means we're done here, so just
349 // break up to the top level.
350 println!("Server disconnected!");
351 break;
352 }
353 _ => {
354 // Something else happened, like scanning finishing, devices
355 // getting removed, etc... Might as well say something about it.
356 println!("Got some other kind of event we don't care about");
357 }
358 }
359 }
360
361 // And now we're done!
362 println!("Exiting example");
363}
364*/
365#[tokio::main(flavor = "current_thread")]
366async fn main() {
367 tracing_subscriber::fmt()
368 .with_max_level(Level::DEBUG)
369 .init();
370 panic!("Reimplement me!");
371 /*
372 device_tester().await;
373 */
374}