Buttplug sex toy control library
at dev 4.8 kB view raw
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::{ 10 Hardware, 11 HardwareCommand, 12 HardwareEvent, 13 HardwareSubscribeCmd, 14 HardwareUnsubscribeCmd, 15 HardwareWriteCmd, 16 }, 17 protocol::{ 18 ProtocolHandler, 19 ProtocolIdentifier, 20 ProtocolInitializer, 21 generic_protocol_initializer_setup, 22 }, 23}; 24use async_trait::async_trait; 25use buttplug_core::errors::ButtplugDeviceError; 26use buttplug_server_device_config::Endpoint; 27use buttplug_server_device_config::{ 28 ProtocolCommunicationSpecifier, 29 ServerDeviceDefinition, 30 UserDeviceIdentifier, 31}; 32use std::sync::Arc; 33use uuid::{Uuid, uuid}; 34 35const LELO_HARMONY_PROTOCOL_UUID: Uuid = uuid!("220e180a-e6d5-4fd1-963e-43a6f990b717"); 36generic_protocol_initializer_setup!(LeloHarmony, "lelo-harmony"); 37 38#[derive(Default)] 39pub struct LeloHarmonyInitializer {} 40 41#[async_trait] 42impl ProtocolInitializer for LeloHarmonyInitializer { 43 async fn initialize( 44 &mut self, 45 hardware: Arc<Hardware>, 46 _: &ServerDeviceDefinition, 47 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 48 // The Lelo Harmony has a very specific pairing flow: 49 // * First the device is turned on in BLE mode (long press) 50 // * Then the security endpoint (Whitelist) needs to be read (which we can do via subscribe) 51 // * If it returns 0x00,00,00,00,00,00,00,00 the connection isn't not authorised 52 // * To authorize, the password must be writen to the characteristic. 53 // * If the password is unknown (buttplug lacks a storage mechanism right now), the power button 54 // must be pressed to send the password 55 // * The password must not be sent whilst subscribed to the endpoint 56 // * Once the password has been sent, the endpoint can be read for status again 57 // * If it returns 0x00,00,00,00,00,00,00,00 the connection is authorised 58 let mut event_receiver = hardware.event_stream(); 59 hardware 60 .subscribe(&HardwareSubscribeCmd::new( 61 LELO_HARMONY_PROTOCOL_UUID, 62 Endpoint::Whitelist, 63 )) 64 .await?; 65 66 loop { 67 let event = event_receiver.recv().await; 68 if let Ok(HardwareEvent::Notification(_, _, n)) = event { 69 if n.iter().all(|b| *b == 0u8) { 70 info!( 71 "Lelo Harmony isn't authorised: Tap the device's power button to complete connection." 72 ) 73 } else if !n.is_empty() && n[0] == 1u8 && n[1..].iter().all(|b| *b == 0u8) { 74 debug!("Lelo Harmony is authorised!"); 75 return Ok(Arc::new(LeloHarmony::default())); 76 } else { 77 debug!("Lelo Harmony gave us a password: {:?}", n); 78 // Can't send whilst subscribed 79 hardware 80 .unsubscribe(&HardwareUnsubscribeCmd::new( 81 LELO_HARMONY_PROTOCOL_UUID, 82 Endpoint::Whitelist, 83 )) 84 .await?; 85 // Send with response 86 hardware 87 .write_value(&HardwareWriteCmd::new( 88 &[LELO_HARMONY_PROTOCOL_UUID], 89 Endpoint::Whitelist, 90 n, 91 true, 92 )) 93 .await?; 94 // Get back to the loop 95 hardware 96 .subscribe(&HardwareSubscribeCmd::new( 97 LELO_HARMONY_PROTOCOL_UUID, 98 Endpoint::Whitelist, 99 )) 100 .await?; 101 } 102 } else { 103 return Err(ButtplugDeviceError::ProtocolSpecificError( 104 "LeloHarmony".to_owned(), 105 "Lelo Harmony didn't provided valid security handshake".to_owned(), 106 )); 107 } 108 } 109 } 110} 111 112#[derive(Default)] 113pub struct LeloHarmony {} 114 115impl LeloHarmony { 116 fn handle_input_cmd( 117 &self, 118 feature_index: u32, 119 feature_id: Uuid, 120 speed: u32, 121 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 122 Ok(vec![ 123 HardwareWriteCmd::new( 124 &[feature_id], 125 Endpoint::Tx, 126 vec![ 127 0x0a, 128 0x12, 129 feature_index as u8 + 1, 130 0x08, 131 0x00, 132 0x00, 133 0x00, 134 0x00, 135 speed as u8, 136 0x00, 137 ], 138 false, 139 ) 140 .into(), 141 ]) 142 } 143} 144 145impl ProtocolHandler for LeloHarmony { 146 fn handle_output_rotate_cmd( 147 &self, 148 feature_index: u32, 149 feature_id: Uuid, 150 speed: i32, 151 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 152 self.handle_input_cmd(feature_index, feature_id, speed as u32) 153 } 154 155 fn handle_output_vibrate_cmd( 156 &self, 157 feature_index: u32, 158 feature_id: Uuid, 159 speed: u32, 160 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 161 self.handle_input_cmd(feature_index, feature_id, speed) 162 } 163}