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 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}