Buttplug sex toy control library

feat: Add support for Kiiroo PowerShot

authored by

blackspherefollower and committed by qdot.tngl.sh 29d79c28 03cfd400

+237 -2
+82
crates/buttplug_server/src/device/protocol_impl/kiiroo_powershot.rs
··· 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 + 8 + use crate::device::{ 9 + hardware::{Hardware, HardwareCommand, HardwareReadCmd, HardwareWriteCmd}, 10 + protocol::{generic_protocol_setup, ProtocolHandler}, 11 + }; 12 + use buttplug_core::{ 13 + errors::ButtplugDeviceError, 14 + message::{self, InputData, InputReadingV4, InputType, InputTypeData}, 15 + }; 16 + use buttplug_server_device_config::Endpoint; 17 + use futures::{future::BoxFuture, FutureExt}; 18 + use std::{default::Default, sync::Arc}; 19 + use std::sync::atomic::{AtomicU8, Ordering}; 20 + use uuid::{uuid, Uuid}; 21 + const KIIROO_POWERSHUOT_PROTOCOL_UUID: Uuid = uuid!("06f49eb9-0dca-42a8-92f0-58634cc017d0"); 22 + 23 + generic_protocol_setup!(KiirooPowerShot, "kiiroo-powershot"); 24 + 25 + #[derive(Default)] 26 + pub struct KiirooPowerShot { 27 + last_cmds: [AtomicU8; 2] 28 + } 29 + 30 + impl KiirooPowerShot { 31 + fn form_hardware_command(&self, index: u32, speed: u32) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 32 + self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed); 33 + Ok(vec![HardwareWriteCmd::new( 34 + &[KIIROO_POWERSHUOT_PROTOCOL_UUID], 35 + Endpoint::Tx, 36 + vec![ 37 + 0x01, 38 + 0x00, 39 + 0x00, 40 + self.last_cmds[0].load(Ordering::Relaxed), 41 + self.last_cmds[1].load(Ordering::Relaxed), 42 + 0x00, 43 + ], 44 + true, 45 + ).into()]) 46 + } 47 + } 48 + 49 + impl ProtocolHandler for KiirooPowerShot { 50 + fn handle_output_vibrate_cmd( 51 + &self, 52 + feature_index: u32, 53 + _feature_id: Uuid, 54 + speed: u32, 55 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 56 + self.form_hardware_command(feature_index, speed) 57 + } 58 + 59 + fn handle_battery_level_cmd( 60 + &self, 61 + device_index: u32, 62 + device: Arc<Hardware>, 63 + feature_index: u32, 64 + feature_id: Uuid, 65 + ) -> BoxFuture<Result<InputReadingV4, ButtplugDeviceError>> { 66 + debug!("Trying to get battery reading."); 67 + let msg = HardwareReadCmd::new(feature_id, Endpoint::RxBLEBattery, 20, 0); 68 + let fut = device.read_value(&msg); 69 + async move { 70 + let hw_msg = fut.await?; 71 + let data = hw_msg.data(); 72 + let battery_reading = message::InputReadingV4::new( 73 + device_index, 74 + feature_index, 75 + InputTypeData::Battery(InputData::new(data[0])) 76 + ); 77 + debug!("Got battery reading: {}", data[0]); 78 + Ok(battery_reading) 79 + } 80 + .boxed() 81 + } 82 + }
+5
crates/buttplug_server/src/device/protocol_impl/mod.rs
··· 33 33 pub mod jejoue; 34 34 pub mod joyhub; 35 35 pub mod kgoal_boost; 36 + pub mod kiiroo_powershot; 36 37 pub mod kiiroo_prowand; 37 38 pub mod kiiroo_spot; 38 39 pub mod kiiroo_v2; ··· 230 231 add_to_protocol_map( 231 232 &mut map, 232 233 joyhub::joyhub_v6::setup::JoyHubV6IdentifierFactory::default(), 234 + ); 235 + add_to_protocol_map( 236 + &mut map, 237 + kiiroo_powershot::setup::KiirooPowerShotIdentifierFactory::default(), 233 238 ); 234 239 add_to_protocol_map( 235 240 &mut map,
+66 -1
crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json
··· 1 1 { 2 2 "version": { 3 3 "major": 4, 4 - "minor": 70 4 + "minor": 72 5 5 }, 6 6 "protocols": { 7 7 "activejoy": { ··· 7858 7858 ], 7859 7859 "id": "1835b668-d778-4552-b75a-95053e06cd5c", 7860 7860 "name": "KGoal Boost" 7861 + } 7862 + }, 7863 + "kiiroo-powershot": { 7864 + "communication": [ 7865 + { 7866 + "btle": { 7867 + "names": [ 7868 + "PowerShot" 7869 + ], 7870 + "services": { 7871 + "00001400-0000-1000-8000-00805f9b34fb": { 7872 + "rxblebattery": "00002a19-0000-1000-8000-00805f9b34fb", 7873 + "tx": "00001801-0000-1000-8000-00805f9b34fb" 7874 + } 7875 + } 7876 + } 7877 + } 7878 + ], 7879 + "defaults": { 7880 + "features": [ 7881 + { 7882 + "feature-type": "Vibrate", 7883 + "id": "8829c78b-420c-42e0-bc3e-280f84c89362", 7884 + "output": { 7885 + "Vibrate": { 7886 + "step-range": [ 7887 + 0, 7888 + 100 7889 + ] 7890 + } 7891 + } 7892 + }, 7893 + { 7894 + "feature-type": "Vibrate", 7895 + "id": "019e23e5-bcba-41c8-913d-03a7da63feee", 7896 + "output": { 7897 + "Vibrate": { 7898 + "step-range": [ 7899 + 0, 7900 + 100 7901 + ] 7902 + } 7903 + } 7904 + }, 7905 + { 7906 + "description": "Battery Level", 7907 + "feature-type": "Battery", 7908 + "id": "eba92a5d-4284-4170-b5e9-3e0700e4529e", 7909 + "input": { 7910 + "Battery": { 7911 + "input-commands": [ 7912 + "Read" 7913 + ], 7914 + "value-range": [ 7915 + [ 7916 + 0, 7917 + 100 7918 + ] 7919 + ] 7920 + } 7921 + } 7922 + } 7923 + ], 7924 + "id": "1929575e-bc47-4db8-bee2-f9fc60c438b5", 7925 + "name": "Kiiroo PowerShot" 7861 7926 } 7862 7927 }, 7863 7928 "kiiroo-prowand": {
+36
crates/buttplug_server_device_config/device-config-v4/protocols/kiiroo-powershot.yml
··· 1 + defaults: 2 + name: Kiiroo PowerShot 3 + features: 4 + - feature-type: Vibrate 5 + id: 8829c78b-420c-42e0-bc3e-280f84c89362 6 + output: 7 + Vibrate: 8 + step-range: 9 + - 0 10 + - 100 11 + - feature-type: Vibrate 12 + id: 019e23e5-bcba-41c8-913d-03a7da63feee 13 + output: 14 + Vibrate: 15 + step-range: 16 + - 0 17 + - 100 18 + - feature-type: Battery 19 + description: Battery Level 20 + id: eba92a5d-4284-4170-b5e9-3e0700e4529e 21 + input: 22 + Battery: 23 + value-range: 24 + - - 0 25 + - 100 26 + input-commands: 27 + - Read 28 + id: 1929575e-bc47-4db8-bee2-f9fc60c438b5 29 + communication: 30 + - btle: 31 + names: 32 + - PowerShot 33 + services: 34 + 00001400-0000-1000-8000-00805f9b34fb: 35 + tx: 00001801-0000-1000-8000-00805f9b34fb 36 + rxblebattery: 00002a19-0000-1000-8000-00805f9b34fb
+1 -1
crates/buttplug_server_device_config/device-config-v4/version.yaml
··· 1 1 version: 2 2 major: 4 3 - minor: 70 3 + minor: 72
+4
crates/buttplug_tests/tests/test_device_protocols.rs
··· 50 50 #[test_case("test_joyhub_petalwish_compat.yaml" ; "JoyHub Protocol - Petalwish Compat")] 51 51 #[test_case("test_joyhub_petalwish.yaml" ; "JoyHub Protocol - Petalwish")] 52 52 #[test_case("test_joyhub_roselin.yaml" ; "JoyHub Protocol - RoseLin")] 53 + #[test_case("test_kiiroo_powershot.yaml" ; "Kiiroo PowerShot Protocol")] 53 54 #[test_case("test_kiiroo_prowand.yaml" ; "Kiiroo ProWand Protocol")] 54 55 #[test_case("test_kiiroo_spot.yaml" ; "Kiiroo Spot Protocol")] 55 56 #[test_case("test_lelo_f1sv1.yaml" ; "Lelo F1s V1 Protocol")] ··· 172 173 #[test_case("test_joyhub_petalwish_compat.yaml" ; "JoyHub Protocol - Petalwish Compat")] 173 174 #[test_case("test_joyhub_petalwish.yaml" ; "JoyHub Protocol - Petalwish")] 174 175 #[test_case("test_joyhub_roselin.yaml" ; "JoyHub Protocol - RoseLin")] 176 + #[test_case("test_kiiroo_powershot.yaml" ; "Kiiroo PowerShot Protocol")] 175 177 #[test_case("test_kiiroo_prowand.yaml" ; "Kiiroo ProWand Protocol")] 176 178 #[test_case("test_kiiroo_spot.yaml" ; "Kiiroo Spot Protocol")] 177 179 #[test_case("test_lelo_f1sv1.yaml" ; "Lelo F1s V1 Protocol")] ··· 293 295 #[test_case("test_joyhub_petalwish_compat.yaml" ; "JoyHub Protocol - Petalwish Compat")] 294 296 #[test_case("test_joyhub_petalwish.yaml" ; "JoyHub Protocol - Petalwish")] 295 297 #[test_case("test_joyhub_roselin.yaml" ; "JoyHub Protocol - RoseLin")] 298 + #[test_case("test_kiiroo_powershot.yaml" ; "Kiiroo PowerShot Protocol")] 296 299 #[test_case("test_kiiroo_prowand.yaml" ; "Kiiroo ProWand Protocol")] 297 300 #[test_case("test_kiiroo_spot.yaml" ; "Kiiroo Spot Protocol")] 298 301 #[test_case("test_lelo_f1sv1.yaml" ; "Lelo F1s V1 Protocol")] ··· 415 418 #[test_case("test_joyhub_petalwish_compat.yaml" ; "JoyHub Protocol - Petalwish Compat")] 416 419 #[test_case("test_joyhub_petalwish.yaml" ; "JoyHub Protocol - Petalwish")] 417 420 #[test_case("test_joyhub_roselin.yaml" ; "JoyHub Protocol - RoseLin")] 421 + #[test_case("test_kiiroo_powershot.yaml" ; "Kiiroo PowerShot Protocol")] 418 422 #[test_case("test_kiiroo_prowand.yaml" ; "Kiiroo ProWand Protocol")] 419 423 #[test_case("test_kiiroo_spot.yaml" ; "Kiiroo Spot Protocol")] 420 424 #[test_case("test_lelo_f1sv1.yaml" ; "Lelo F1s V1 Protocol")]
+43
crates/buttplug_tests/tests/util/device_test/device_test_case/test_kiiroo_powershot.yaml
··· 1 + devices: 2 + - identifier: 3 + name: "PowerShot" 4 + expected_name: "Kiiroo PowerShot" 5 + device_commands: 6 + # Commands 7 + - !Messages 8 + device_index: 0 9 + messages: 10 + - !Vibrate 11 + - Index: 0 12 + Speed: 0.5 13 + - !Commands 14 + device_index: 0 15 + commands: 16 + - !Write 17 + endpoint: tx 18 + data: [0x01, 0x00, 0x00, 0x32, 0x00, 0x00] 19 + write_with_response: true 20 + - !Messages 21 + device_index: 0 22 + messages: 23 + - !Vibrate 24 + - Index: 1 25 + Speed: 1.0 26 + - !Commands 27 + device_index: 0 28 + commands: 29 + - !Write 30 + endpoint: tx 31 + data: [0x01, 0x00, 0x00, 0x32, 0x64, 0x00] 32 + write_with_response: true 33 + - !Messages 34 + device_index: 0 35 + messages: 36 + - !Stop 37 + - !Commands 38 + device_index: 0 39 + commands: 40 + - !Write 41 + endpoint: tx 42 + data: [0x01, 0x00, 0x00, 0x00, 0x00, 0x00] 43 + write_with_response: true