Buttplug sex toy control library
at dev 13 kB view raw
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}