Buttplug sex toy control library

chore: Reimplement rest of joyhub protocols

Pass what few tests we have so whatevs

Fixes #709

+263 -766
+2 -116
crates/buttplug_server/src/device/protocol_impl/joyhub/joyhub.rs
··· 14 14 15 15 use crate::device::{ 16 16 hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 17 - protocol::{generic_protocol_initializer_setup, ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 17 + protocol::{generic_protocol_setup, ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 18 18 }; 19 19 use async_trait::async_trait; 20 20 use std::sync::{atomic::{AtomicU8, Ordering}, Arc, RwLock}; ··· 22 22 23 23 const JOYHUB_PROTOCOL_UUID: Uuid = uuid!("c0f6785a-0056-4a2a-a2a9-dc7ca4ae2a0d"); 24 24 25 - generic_protocol_initializer_setup!(JoyHub, "joyhub"); 26 - 27 - async fn delayed_constrict_handler(device: Arc<Hardware>, scalar: u8) { 28 - sleep(Duration::from_millis(25)).await; 29 - let res = device 30 - .write_value(&HardwareWriteCmd::new( 31 - &[JOYHUB_PROTOCOL_UUID], 32 - Endpoint::Tx, 33 - vec![ 34 - 0xa0, 35 - 0x07, 36 - if scalar == 0 { 0x00 } else { 0x01 }, 37 - 0x00, 38 - scalar, 39 - 0xff, 40 - ], 41 - false, 42 - )) 43 - .await; 44 - if res.is_err() { 45 - error!("Delayed JoyHub Constrict command error: {:?}", res.err()); 46 - } 47 - } 48 - 49 - 50 - 51 - #[derive(Default)] 52 - pub struct JoyHubInitializer {} 53 - 54 - #[async_trait] 55 - impl ProtocolInitializer for JoyHubInitializer { 56 - async fn initialize( 57 - &mut self, 58 - hardware: Arc<Hardware>, 59 - _: &DeviceDefinition, 60 - ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 61 - //Ok(Arc::new(JoyHub::new(hardware))) 62 - Ok(Arc::new(JoyHub::default())) 63 - } 64 - } 25 + generic_protocol_setup!(JoyHub, "joyhub"); 65 26 66 27 #[derive(Default)] 67 28 pub struct JoyHub { 68 - //device: Arc<Hardware>, 69 - //last_cmds: RwLock<Vec<Option<(ActuatorType, i32)>>>, 70 29 last_cmds: [AtomicU8; 3] 71 30 } 72 31 73 32 impl JoyHub { 74 - /* 75 - fn new(device: Arc<Hardware>) -> Self { 76 - //let last_cmds = RwLock::new(vec![]); 77 - //Self { device, last_cmds } 78 - } 79 - */ 80 - 81 33 fn form_hardware_command(&self, index: u32, speed: u32) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 82 34 self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed); 83 35 Ok(vec![HardwareWriteCmd::new( ··· 98 50 } 99 51 100 52 impl ProtocolHandler for JoyHub { 101 - 102 53 fn handle_output_vibrate_cmd( 103 54 &self, 104 55 feature_index: u32, ··· 147 98 ) 148 99 .into()]) 149 100 } 150 - 151 - /* 152 - fn handle_value_cmd( 153 - &self, 154 - commands: &[Option<(ActuatorType, i32)>], 155 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 156 - let cmd1 = commands[0]; 157 - let mut cmd2 = if commands.len() > 1 { 158 - commands[1] 159 - } else { 160 - None 161 - }; 162 - let cmd3 = if commands.len() > 2 { 163 - commands[2] 164 - } else { 165 - None 166 - }; 167 - 168 - if let Some(cmd) = cmd2 { 169 - if cmd.0 == ActuatorType::Constrict { 170 - cmd2 = None; 171 - if !scalar_changed(&self.last_cmds, commands, 1usize) { 172 - // no-op 173 - } else if vibes_changed(&self.last_cmds, commands, vec![1usize]) { 174 - let dev = self.device.clone(); 175 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 176 - } else { 177 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 178 - *command_writer = commands.to_vec(); 179 - 180 - return Ok(vec![HardwareWriteCmd::new( 181 - Endpoint::Tx, 182 - vec![ 183 - 0xa0, 184 - 0x07, 185 - if cmd.1 == 0 { 0x00 } else { 0x01 }, 186 - 0x00, 187 - cmd.1 as u8, 188 - 0xff, 189 - ], 190 - false, 191 - ) 192 - .into()]); 193 - } 194 - } 195 - } 196 - 197 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 198 - *command_writer = commands.to_vec(); 199 - Ok(vec![HardwareWriteCmd::new( 200 - Endpoint::Tx, 201 - vec![ 202 - 0xa0, 203 - 0x03, 204 - cmd1.unwrap_or((ActuatorType::Oscillate, 0)).1 as u8, 205 - cmd3.unwrap_or((ActuatorType::Rotate, 0)).1 as u8, 206 - cmd2.unwrap_or((ActuatorType::Oscillate, 0)).1 as u8, 207 - 0x00, 208 - 0xaa, 209 - ], 210 - false, 211 - ) 212 - .into()]) 213 - } 214 - */ 215 101 }
+64 -166
crates/buttplug_server/src/device/protocol_impl/joyhub/joyhub_v2.rs
··· 5 5 // Licensed under the BSD 3-Clause license. See LICENSE file in the project root 6 6 // for full license information. 7 7 8 - use crate::device::configuration::ProtocolCommunicationSpecifier; 9 - use crate::{ 10 - core::{ 11 - errors::ButtplugDeviceError, 12 - message::{ActuatorType, Endpoint}, 13 - }, 14 - generic_protocol_initializer_setup, 8 + 9 + use buttplug_server_device_config::{ProtocolCommunicationSpecifier, DeviceDefinition, UserDeviceIdentifier, Endpoint}; 10 + use buttplug_core::{ 11 + errors::ButtplugDeviceError, 12 + util::{async_manager, sleep}, 13 + }; 14 + use uuid::{uuid, Uuid}; 15 + 15 16 use crate::device::{ 16 - configuration::{UserDeviceDefinition, UserDeviceIdentifier}, 17 - hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 18 - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, 19 - }, 20 - util::{async_manager, sleep}, 17 + hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 18 + protocol::{generic_protocol_setup, ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 21 19 }; 22 20 use async_trait::async_trait; 23 - use uuid::{uuid, Uuid}; 24 - use std::sync::{Arc, RwLock}; 21 + use std::sync::{atomic::{AtomicU8, Ordering}, Arc, RwLock}; 25 22 use std::time::Duration; 26 23 27 24 const JOYHUB_V2_PROTOCOL_UUID: Uuid = uuid!("3144b936-99c8-47f3-b85d-defa5fac9e6d"); 28 - generic_protocol_initializer_setup!(JoyHubV2, "joyhub-v2"); 29 - 30 - async fn delayed_constrict_handler(device: Arc<Hardware>, scalar: u8) { 31 - sleep(Duration::from_millis(50)).await; 32 - let res = device 33 - .write_value(&HardwareWriteCmd::new( 34 - JOYHUB_V2_PROTOCOL_UUID, 35 - Endpoint::Tx, 36 - vec![0xa0, 0x0d, 0x00, 0x00, scalar, 0xff], 37 - false, 38 - )) 39 - .await; 40 - if res.is_err() { 41 - error!("Delayed JoyHub Constrict command error: {:?}", res.err()); 42 - } 43 - } 44 - 45 - fn vibes_changed( 46 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 47 - new_commands: &[Option<(ActuatorType, i32)>], 48 - exclude: Vec<usize>, 49 - ) -> bool { 50 - let old_commands = old_commands_lock.read().expect("locks should work"); 51 - if old_commands.len() != new_commands.len() { 52 - return true; 53 - } 54 - 55 - for i in 0..old_commands.len() { 56 - if exclude.contains(&i) { 57 - continue; 58 - } 59 - if let Some(ocmd) = old_commands[i] { 60 - if let Some(ncmd) = new_commands[i] { 61 - if ocmd.1 != ncmd.1 { 62 - return true; 63 - } 64 - } 65 - } 66 - } 67 - false 68 - } 69 - 70 - fn scalar_changed( 71 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 72 - new_commands: &[Option<(ActuatorType, i32)>], 73 - index: usize, 74 - ) -> bool { 75 - let old_commands = old_commands_lock.read().expect("locks should work"); 76 - if old_commands.len() != new_commands.len() { 77 - return true; 78 - } 79 - 80 - if index < old_commands.len() { 81 - if let Some(ocmd) = old_commands[index] { 82 - if let Some(ncmd) = new_commands[index] { 83 - if ocmd.1 != ncmd.1 { 84 - return true; 85 - } 86 - } 87 - } 88 - } 89 - false 90 - } 25 + generic_protocol_setup!(JoyHubV2, "joyhub-v2"); 91 26 92 27 #[derive(Default)] 93 - pub struct JoyHubV2Initializer {} 94 - 95 - #[async_trait] 96 - impl ProtocolInitializer for JoyHubV2Initializer { 97 - async fn initialize( 98 - &mut self, 99 - hardware: Arc<Hardware>, 100 - _: &UserDeviceDefinition, 101 - ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 102 - Ok(Arc::new(JoyHubV2::new(hardware))) 103 - } 104 - } 105 - 106 28 pub struct JoyHubV2 { 107 - device: Arc<Hardware>, 108 - last_cmds: RwLock<Vec<Option<(ActuatorType, i32)>>>, 29 + last_cmds: [AtomicU8; 3] 109 30 } 110 31 111 32 impl JoyHubV2 { 112 - fn new(device: Arc<Hardware>) -> Self { 113 - let last_cmds = RwLock::new(vec![]); 114 - Self { device, last_cmds } 33 + fn form_hardware_command(&self, index: u32, speed: u32) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 34 + info!("GOT JOYHUB COMMAND"); 35 + self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed); 36 + Ok(vec![HardwareWriteCmd::new( 37 + &[JOYHUB_V2_PROTOCOL_UUID], 38 + Endpoint::Tx, 39 + vec![ 40 + 0xa0, 41 + 0x03, 42 + self.last_cmds[0].load(Ordering::Relaxed), 43 + self.last_cmds[1].load(Ordering::Relaxed), 44 + self.last_cmds[2].load(Ordering::Relaxed), 45 + 0x00, 46 + 0xaa, 47 + ], 48 + false, 49 + ).into()]) 115 50 } 116 51 } 117 52 118 53 impl ProtocolHandler for JoyHubV2 { 119 - fn keepalive_strategy(&self) -> super::ProtocolKeepaliveStrategy { 120 - super::ProtocolKeepaliveStrategy::RepeatLastPacketStrategy 54 + fn handle_output_vibrate_cmd( 55 + &self, 56 + feature_index: u32, 57 + _feature_id: Uuid, 58 + speed: u32, 59 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 60 + self.form_hardware_command(feature_index, speed) 121 61 } 122 62 123 - fn outputs_full_command_set(&self) -> bool { 124 - true 63 + fn handle_output_rotate_cmd( 64 + &self, 65 + feature_index: u32, 66 + _feature_id: Uuid, 67 + speed: u32, 68 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 69 + self.form_hardware_command(feature_index, speed) 125 70 } 126 71 127 - fn handle_value_cmd( 128 - &self, 129 - commands: &[Option<(ActuatorType, i32)>], 130 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 131 - let cmd1 = commands[0]; 132 - let mut cmd2 = if commands.len() > 1 { 133 - commands[1] 134 - } else { 135 - None 136 - }; 137 - let mut cmd3 = if commands.len() > 2 { 138 - commands[2] 139 - } else { 140 - None 141 - }; 72 + fn handle_output_oscillate_cmd( 73 + &self, 74 + feature_index: u32, 75 + _feature_id: Uuid, 76 + speed: u32, 77 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 78 + self.form_hardware_command(feature_index, speed) 79 + } 142 80 143 - if let Some(cmd) = cmd2 { 144 - if cmd.0 == ActuatorType::Constrict { 145 - cmd2 = None; 146 - if !scalar_changed(&self.last_cmds, commands, 1usize) { 147 - // no-op 148 - } else if vibes_changed(&self.last_cmds, commands, vec![1usize]) { 149 - let dev = self.device.clone(); 150 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 151 - } else { 152 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 153 - *command_writer = commands.to_vec(); 154 - 155 - return Ok(vec![HardwareWriteCmd::new( 156 - Endpoint::Tx, 157 - vec![0xa0, 0x0d, 0x00, 0x00, cmd.1 as u8, 0xff], 158 - false, 159 - ) 160 - .into()]); 161 - } 162 - } 163 - } 164 - 165 - if let Some(cmd) = cmd3 { 166 - if cmd.0 == ActuatorType::Constrict { 167 - cmd3 = None; 168 - if !scalar_changed(&self.last_cmds, commands, 2usize) { 169 - // no-op 170 - } else if vibes_changed(&self.last_cmds, commands, vec![2usize]) { 171 - let dev = self.device.clone(); 172 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 173 - } else { 174 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 175 - *command_writer = commands.to_vec(); 176 - 177 - return Ok(vec![HardwareWriteCmd::new( 178 - Endpoint::Tx, 179 - vec![0xa0, 0x0d, 0x00, 0x00, cmd.1 as u8, 0xff], 180 - false, 181 - ) 182 - .into()]); 183 - } 184 - } 185 - } 186 - 187 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 188 - *command_writer = commands.to_vec(); 81 + fn handle_output_constrict_cmd( 82 + &self, 83 + _feature_index: u32, 84 + feature_id: Uuid, 85 + level: u32, 86 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 189 87 Ok(vec![HardwareWriteCmd::new( 88 + &[feature_id], 190 89 Endpoint::Tx, 191 90 vec![ 192 - 0xa0, 193 - 0x03, 194 - cmd1.unwrap_or((ActuatorType::Vibrate, 0)).1 as u8, 195 - cmd2.unwrap_or((ActuatorType::Rotate, 0)).1 as u8, 196 - cmd3.unwrap_or((ActuatorType::Oscillate, 0)).1 as u8, 91 + 0xa0, 92 + 0x0d, 197 93 0x00, 198 - 0xaa, 94 + 0x00, 95 + level as u8, 96 + 0xff 199 97 ], 200 98 false, 201 99 )
+62 -155
crates/buttplug_server/src/device/protocol_impl/joyhub/joyhub_v4.rs
··· 5 5 // Licensed under the BSD 3-Clause license. See LICENSE file in the project root 6 6 // for full license information. 7 7 8 - use crate::{ 9 - core::{ 10 - errors::ButtplugDeviceError, 11 - message::{ActuatorType, Endpoint}, 12 - }, 13 - generic_protocol_initializer_setup, 14 - use crate::device::{ 15 - configuration::UserDeviceIdentifier, 16 - configuration::{ProtocolCommunicationSpecifier, UserDeviceDefinition}, 17 - hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 18 - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, 19 - }, 8 + 9 + use buttplug_server_device_config::{ProtocolCommunicationSpecifier, DeviceDefinition, UserDeviceIdentifier, Endpoint}; 10 + use buttplug_core::{ 11 + errors::ButtplugDeviceError, 20 12 util::{async_manager, sleep}, 21 13 }; 22 - use async_trait::async_trait; 23 - use std::sync::{Arc, RwLock}; 14 + use uuid::{uuid, Uuid}; 15 + 16 + use crate::device::{ 17 + hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 18 + protocol::{generic_protocol_setup, ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 19 + }; 20 + use std::sync::{atomic::{AtomicU8, Ordering}, Arc, RwLock}; 24 21 use std::time::Duration; 25 22 26 - generic_protocol_initializer_setup!(JoyHubV4, "joyhub-v4"); 23 + const JOYHUB_V4_PROTOCOL_UUID: Uuid = uuid!("c99e8979-6f13-4556-9b6b-2061f527042b"); 24 + generic_protocol_setup!(JoyHubV4, "joyhub-v4"); 25 + 26 + #[derive(Default)] 27 + pub struct JoyHubV4 { 28 + last_cmds: [AtomicU8; 3] 29 + } 27 30 28 - async fn delayed_constrict_handler(device: Arc<Hardware>, scalar: u8) { 29 - sleep(Duration::from_millis(25)).await; 30 - let res = device 31 - .write_value(&HardwareWriteCmd::new( 31 + impl JoyHubV4 { 32 + fn form_hardware_command(&self, index: u32, speed: u32) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 33 + self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed); 34 + Ok(vec![HardwareWriteCmd::new( 35 + &[JOYHUB_V4_PROTOCOL_UUID], 32 36 Endpoint::Tx, 33 37 vec![ 34 38 0xa0, 35 - 0x07, 36 - if scalar == 0 { 0x00 } else { 0x01 }, 39 + 0x03, 40 + self.last_cmds[0].load(Ordering::Relaxed), 37 41 0x00, 38 - scalar, 39 - 0xff, 42 + self.last_cmds[2].load(Ordering::Relaxed), 43 + self.last_cmds[1].load(Ordering::Relaxed), 44 + 0xaa, 40 45 ], 41 46 false, 42 - )) 43 - .await; 44 - if res.is_err() { 45 - error!("Delayed JoyHub Constrict command error: {:?}", res.err()); 47 + ).into()]) 46 48 } 47 49 } 48 - fn vibes_changed( 49 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 50 - new_commands: &[Option<(ActuatorType, i32)>], 51 - exclude: Vec<usize>, 52 - ) -> bool { 53 - let old_commands = old_commands_lock.read().expect("locks should work"); 54 - if old_commands.len() != new_commands.len() { 55 - return true; 56 - } 57 50 58 - for i in 0..old_commands.len() { 59 - if exclude.contains(&i) { 60 - continue; 61 - } 62 - if let Some(ocmd) = old_commands[i] { 63 - if let Some(ncmd) = new_commands[i] { 64 - if ocmd.1 != ncmd.1 { 65 - return true; 66 - } 67 - } 68 - } 51 + impl ProtocolHandler for JoyHubV4 { 52 + fn handle_output_vibrate_cmd( 53 + &self, 54 + feature_index: u32, 55 + _feature_id: Uuid, 56 + speed: u32, 57 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 58 + self.form_hardware_command(feature_index, speed) 69 59 } 70 - false 71 - } 72 60 73 - fn scalar_changed( 74 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 75 - new_commands: &[Option<(ActuatorType, i32)>], 76 - index: usize, 77 - ) -> bool { 78 - let old_commands = old_commands_lock.read().expect("locks should work"); 79 - if old_commands.len() != new_commands.len() { 80 - return true; 61 + fn handle_output_rotate_cmd( 62 + &self, 63 + feature_index: u32, 64 + _feature_id: Uuid, 65 + speed: u32, 66 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 67 + self.form_hardware_command(feature_index, speed) 81 68 } 82 69 83 - if index < old_commands.len() { 84 - if let Some(ocmd) = old_commands[index] { 85 - if let Some(ncmd) = new_commands[index] { 86 - if ocmd.1 != ncmd.1 { 87 - return true; 88 - } 89 - } 90 - } 91 - } 92 - false 93 - } 94 - 95 - #[derive(Default)] 96 - pub struct JoyHubV4Initializer {} 97 - 98 - #[async_trait] 99 - impl ProtocolInitializer for JoyHubV4Initializer { 100 - async fn initialize( 101 - &mut self, 102 - hardware: Arc<Hardware>, 103 - _: &UserDeviceDefinition, 104 - ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 105 - Ok(Arc::new(JoyHubV4::new(hardware))) 106 - } 107 - } 108 - 109 - pub struct JoyHubV4 { 110 - device: Arc<Hardware>, 111 - last_cmds: RwLock<Vec<Option<(ActuatorType, i32)>>>, 112 - } 113 - 114 - impl JoyHubV4 { 115 - fn new(device: Arc<Hardware>) -> Self { 116 - let last_cmds = RwLock::new(vec![]); 117 - Self { device, last_cmds } 118 - } 119 - } 120 - 121 - impl ProtocolHandler for JoyHubV4 { 122 - fn keepalive_strategy(&self) -> super::ProtocolKeepaliveStrategy { 123 - super::ProtocolKeepaliveStrategy::RepeatLastPacketStrategy 70 + fn handle_output_oscillate_cmd( 71 + &self, 72 + feature_index: u32, 73 + _feature_id: Uuid, 74 + speed: u32, 75 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 76 + self.form_hardware_command(feature_index, speed) 124 77 } 125 78 126 - fn outputs_full_command_set(&self) -> bool { 127 - true 128 - } 129 - 130 - fn handle_value_cmd( 131 - &self, 132 - commands: &[Option<(ActuatorType, i32)>], 133 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 134 - let cmd1 = commands[0]; 135 - let cmd2 = if commands.len() > 1 { 136 - commands[1] 137 - } else { 138 - None 139 - }; 140 - let mut cmd3 = if commands.len() > 2 { 141 - commands[2] 142 - } else { 143 - None 144 - }; 145 - 146 - if let Some(cmd) = cmd3 { 147 - if cmd.0 == ActuatorType::Constrict { 148 - cmd3 = None; 149 - if !scalar_changed(&self.last_cmds, commands, 2usize) { 150 - // no-op 151 - } else if vibes_changed(&self.last_cmds, commands, vec![2usize]) { 152 - let dev = self.device.clone(); 153 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 154 - } else { 155 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 156 - *command_writer = commands.to_vec(); 157 - 158 - return Ok(vec![HardwareWriteCmd::new( 159 - Endpoint::Tx, 160 - vec![ 161 - 0xa0, 162 - 0x07, 163 - if cmd.1 == 0 { 0x00 } else { 0x01 }, 164 - 0x00, 165 - cmd.1 as u8, 166 - 0xff, 167 - ], 168 - false, 169 - ) 170 - .into()]); 171 - } 172 - } 173 - } 174 - 175 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 176 - *command_writer = commands.to_vec(); 177 - 79 + fn handle_output_constrict_cmd( 80 + &self, 81 + _feature_index: u32, 82 + feature_id: Uuid, 83 + level: u32, 84 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 178 85 Ok(vec![HardwareWriteCmd::new( 86 + &[feature_id], 179 87 Endpoint::Tx, 180 88 vec![ 181 89 0xa0, 182 - 0x03, 183 - cmd1.unwrap_or((ActuatorType::Vibrate, 0)).1 as u8, 90 + 0x07, 91 + if level == 0 { 0x00 } else { 0x01 }, 184 92 0x00, 185 - cmd3.unwrap_or((ActuatorType::Vibrate, 0)).1 as u8, 186 - cmd2.unwrap_or((ActuatorType::Rotate, 0)).1 as u8, 187 - 0xaa, 93 + level as u8, 94 + 0xff, 188 95 ], 189 96 false, 190 97 )
+62 -151
crates/buttplug_server/src/device/protocol_impl/joyhub/joyhub_v5.rs
··· 5 5 // Licensed under the BSD 3-Clause license. See LICENSE file in the project root 6 6 // for full license information. 7 7 8 - use crate::{ 9 - core::{ 10 - errors::ButtplugDeviceError, 11 - message::{ActuatorType, Endpoint}, 12 - }, 13 - generic_protocol_initializer_setup, 8 + use buttplug_server_device_config::{ProtocolCommunicationSpecifier, DeviceDefinition, UserDeviceIdentifier, Endpoint}; 9 + use buttplug_core::{ 10 + errors::ButtplugDeviceError, 11 + util::{async_manager, sleep}, 12 + }; 13 + use uuid::{uuid, Uuid}; 14 + 14 15 use crate::device::{ 15 - configuration::UserDeviceIdentifier, 16 - configuration::{ProtocolCommunicationSpecifier, UserDeviceDefinition}, 17 - hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 18 - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, 19 - }, 20 - util::{async_manager, sleep}, 16 + hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 17 + protocol::{generic_protocol_setup, ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 21 18 }; 22 - use async_trait::async_trait; 23 - use std::sync::{Arc, RwLock}; 19 + use std::sync::{atomic::{AtomicU8, Ordering}, Arc, RwLock}; 24 20 use std::time::Duration; 25 21 26 - generic_protocol_initializer_setup!(JoyHubV5, "joyhub-v5"); 27 22 28 - async fn delayed_constrict_handler(device: Arc<Hardware>, scalar: u8) { 29 - sleep(Duration::from_millis(25)).await; 30 - let res = device 31 - .write_value(&HardwareWriteCmd::new( 23 + const JOYHUB_V5_PROTOCOL_UUID: Uuid = uuid!("c99e8979-6f13-4556-9b6b-2061f527042b"); 24 + generic_protocol_setup!(JoyHubV5, "joyhub-v5"); 25 + 26 + #[derive(Default)] 27 + pub struct JoyHubV5 { 28 + last_cmds: [AtomicU8; 3] 29 + } 30 + 31 + impl JoyHubV5 { 32 + fn form_hardware_command(&self, index: u32, speed: u32) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 33 + self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed); 34 + Ok(vec![HardwareWriteCmd::new( 35 + &[JOYHUB_V5_PROTOCOL_UUID], 32 36 Endpoint::Tx, 33 37 vec![ 34 38 0xa0, 35 - 0x07, 36 - if scalar == 0 { 0x00 } else { 0x01 }, 39 + 0x03, 40 + self.last_cmds[1].load(Ordering::Relaxed), 41 + 0x00, 42 + self.last_cmds[0].load(Ordering::Relaxed), 37 43 0x00, 38 - scalar, 39 - 0xff, 44 + 0xaa, 40 45 ], 41 46 false, 42 - )) 43 - .await; 44 - if res.is_err() { 45 - error!("Delayed JoyHub Constrict command error: {:?}", res.err()); 47 + ).into()]) 46 48 } 47 49 } 48 50 49 - fn vibes_changed( 50 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 51 - new_commands: &[Option<(ActuatorType, i32)>], 52 - exclude: Vec<usize>, 53 - ) -> bool { 54 - let old_commands = old_commands_lock.read().expect("locks should work"); 55 - if old_commands.len() != new_commands.len() { 56 - return true; 51 + impl ProtocolHandler for JoyHubV5 { 52 + fn handle_output_vibrate_cmd( 53 + &self, 54 + feature_index: u32, 55 + _feature_id: Uuid, 56 + speed: u32, 57 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 58 + self.form_hardware_command(feature_index, speed) 57 59 } 58 60 59 - for i in 0..old_commands.len() { 60 - if exclude.contains(&i) { 61 - continue; 62 - } 63 - if let Some(ocmd) = old_commands[i] { 64 - if let Some(ncmd) = new_commands[i] { 65 - if ocmd.1 != ncmd.1 { 66 - return true; 67 - } 68 - } 69 - } 61 + fn handle_output_rotate_cmd( 62 + &self, 63 + feature_index: u32, 64 + _feature_id: Uuid, 65 + speed: u32, 66 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 67 + self.form_hardware_command(feature_index, speed) 70 68 } 71 - false 72 - } 73 69 74 - fn scalar_changed( 75 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 76 - new_commands: &[Option<(ActuatorType, i32)>], 77 - index: usize, 78 - ) -> bool { 79 - let old_commands = old_commands_lock.read().expect("locks should work"); 80 - if old_commands.len() != new_commands.len() { 81 - return true; 70 + fn handle_output_oscillate_cmd( 71 + &self, 72 + feature_index: u32, 73 + _feature_id: Uuid, 74 + speed: u32, 75 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 76 + self.form_hardware_command(feature_index, speed) 82 77 } 83 78 84 - if index < old_commands.len() { 85 - if let Some(ocmd) = old_commands[index] { 86 - if let Some(ncmd) = new_commands[index] { 87 - if ocmd.1 != ncmd.1 { 88 - return true; 89 - } 90 - } 91 - } 92 - } 93 - false 94 - } 95 - 96 - #[derive(Default)] 97 - pub struct JoyHubV5Initializer {} 98 - 99 - #[async_trait] 100 - impl ProtocolInitializer for JoyHubV5Initializer { 101 - async fn initialize( 102 - &mut self, 103 - hardware: Arc<Hardware>, 104 - _: &UserDeviceDefinition, 105 - ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 106 - Ok(Arc::new(JoyHubV5::new(hardware))) 107 - } 108 - } 109 - 110 - pub struct JoyHubV5 { 111 - device: Arc<Hardware>, 112 - last_cmds: RwLock<Vec<Option<(ActuatorType, i32)>>>, 113 - } 114 - 115 - impl JoyHubV5 { 116 - fn new(device: Arc<Hardware>) -> Self { 117 - let last_cmds = RwLock::new(vec![]); 118 - Self { device, last_cmds } 119 - } 120 - } 121 - 122 - impl ProtocolHandler for JoyHubV5 { 123 - fn keepalive_strategy(&self) -> super::ProtocolKeepaliveStrategy { 124 - super::ProtocolKeepaliveStrategy::RepeatLastPacketStrategy 125 - } 126 - 127 - fn outputs_full_command_set(&self) -> bool { 128 - true 129 - } 130 - 131 - fn handle_value_cmd( 132 - &self, 133 - commands: &[Option<(ActuatorType, i32)>], 134 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 135 - let cmd1 = commands[0]; 136 - let mut cmd2 = if commands.len() > 1 { 137 - commands[1] 138 - } else { 139 - None 140 - }; 141 - 142 - if let Some(cmd) = cmd2 { 143 - if cmd.0 == ActuatorType::Constrict { 144 - cmd2 = None; 145 - if !scalar_changed(&self.last_cmds, commands, 1usize) { 146 - // no-op 147 - } else if vibes_changed(&self.last_cmds, commands, vec![1usize]) { 148 - let dev = self.device.clone(); 149 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 150 - } else { 151 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 152 - *command_writer = commands.to_vec(); 153 - 154 - return Ok(vec![HardwareWriteCmd::new( 155 - Endpoint::Tx, 156 - vec![ 157 - 0xa0, 158 - 0x07, 159 - if cmd.1 == 0 { 0x00 } else { 0x01 }, 160 - 0x00, 161 - cmd.1 as u8, 162 - 0xff, 163 - ], 164 - false, 165 - ) 166 - .into()]); 167 - } 168 - } 169 - } 170 - 171 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 172 - *command_writer = commands.to_vec(); 173 - 79 + fn handle_output_constrict_cmd( 80 + &self, 81 + _feature_index: u32, 82 + feature_id: Uuid, 83 + level: u32, 84 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 174 85 Ok(vec![HardwareWriteCmd::new( 86 + &[feature_id], 175 87 Endpoint::Tx, 176 88 vec![ 177 89 0xa0, 178 - 0x03, 179 - cmd2.unwrap_or((ActuatorType::Vibrate, 0)).1 as u8, 180 - 0x00, 181 - cmd1.unwrap_or((ActuatorType::Vibrate, 0)).1 as u8, 90 + 0x07, 91 + if level == 0 { 0x00 } else { 0x01 }, 182 92 0x00, 183 - 0xaa, 93 + level as u8, 94 + 0xff, 184 95 ], 185 96 false, 186 97 )
+62 -163
crates/buttplug_server/src/device/protocol_impl/joyhub/joyhub_v6.rs
··· 5 5 // Licensed under the BSD 3-Clause license. See LICENSE file in the project root 6 6 // for full license information. 7 7 8 - use crate::device::configuration::ProtocolCommunicationSpecifier; 9 - use crate::{ 10 - core::{ 11 - errors::ButtplugDeviceError, 12 - message::{ActuatorType, Endpoint}, 13 - }, 14 - generic_protocol_initializer_setup, 8 + use buttplug_server_device_config::{ProtocolCommunicationSpecifier, DeviceDefinition, UserDeviceIdentifier, Endpoint}; 9 + use buttplug_core::{ 10 + errors::ButtplugDeviceError, 11 + util::{async_manager, sleep}, 12 + }; 13 + use uuid::{uuid, Uuid}; 14 + 15 15 use crate::device::{ 16 - configuration::{UserDeviceDefinition, UserDeviceIdentifier}, 17 - hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 18 - protocol::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer}, 19 - }, 20 - util::{async_manager, sleep}, 16 + hardware::{Hardware, HardwareCommand, HardwareWriteCmd}, 17 + protocol::{generic_protocol_setup, ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy}, 21 18 }; 22 - use async_trait::async_trait; 23 - use std::sync::{Arc, RwLock}; 19 + use std::sync::{atomic::{AtomicU8, Ordering}, Arc, RwLock}; 24 20 use std::time::Duration; 25 21 26 - generic_protocol_initializer_setup!(JoyHubV6, "joyhub-v6"); 27 22 28 - async fn delayed_constrict_handler(device: Arc<Hardware>, scalar: u8) { 29 - sleep(Duration::from_millis(50)).await; 30 - let res = device 31 - .write_value(&HardwareWriteCmd::new( 32 - Endpoint::Tx, 33 - vec![0xa0, 0x0d, 0x00, 0x00, scalar, 0xff], 34 - false, 35 - )) 36 - .await; 37 - if res.is_err() { 38 - error!("Delayed JoyHub Constrict command error: {:?}", res.err()); 39 - } 40 - } 41 - 42 - fn vibes_changed( 43 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 44 - new_commands: &[Option<(ActuatorType, i32)>], 45 - exclude: Vec<usize>, 46 - ) -> bool { 47 - let old_commands = old_commands_lock.read().expect("locks should work"); 48 - if old_commands.len() != new_commands.len() { 49 - return true; 50 - } 51 - 52 - for i in 0..old_commands.len() { 53 - if exclude.contains(&i) { 54 - continue; 55 - } 56 - if let Some(ocmd) = old_commands[i] { 57 - if let Some(ncmd) = new_commands[i] { 58 - if ocmd.1 != ncmd.1 { 59 - return true; 60 - } 61 - } 62 - } 63 - } 64 - false 65 - } 66 - 67 - fn scalar_changed( 68 - old_commands_lock: &RwLock<Vec<Option<(ActuatorType, i32)>>>, 69 - new_commands: &[Option<(ActuatorType, i32)>], 70 - index: usize, 71 - ) -> bool { 72 - let old_commands = old_commands_lock.read().expect("locks should work"); 73 - if old_commands.len() != new_commands.len() { 74 - return true; 75 - } 76 - 77 - if index < old_commands.len() { 78 - if let Some(ocmd) = old_commands[index] { 79 - if let Some(ncmd) = new_commands[index] { 80 - if ocmd.1 != ncmd.1 { 81 - return true; 82 - } 83 - } 84 - } 85 - } 86 - false 87 - } 23 + const JOYHUB_V6_PROTOCOL_UUID: Uuid = uuid!("c089952e-cb80-462b-8eeb-526f7ba21ff2"); 24 + generic_protocol_setup!(JoyHubV6, "joyhub-v6"); 88 25 89 26 #[derive(Default)] 90 - pub struct JoyHubV6Initializer {} 91 - 92 - #[async_trait] 93 - impl ProtocolInitializer for JoyHubV6Initializer { 94 - async fn initialize( 95 - &mut self, 96 - hardware: Arc<Hardware>, 97 - _: &UserDeviceDefinition, 98 - ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> { 99 - Ok(Arc::new(JoyHubV6::new(hardware))) 100 - } 101 - } 102 - 103 27 pub struct JoyHubV6 { 104 - device: Arc<Hardware>, 105 - last_cmds: RwLock<Vec<Option<(ActuatorType, i32)>>>, 28 + last_cmds: [AtomicU8; 3] 106 29 } 107 30 108 31 impl JoyHubV6 { 109 - fn new(device: Arc<Hardware>) -> Self { 110 - let last_cmds = RwLock::new(vec![]); 111 - Self { device, last_cmds } 32 + fn form_hardware_command(&self, index: u32, speed: u32) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 33 + self.last_cmds[index as usize].store(speed as u8, Ordering::Relaxed); 34 + Ok(vec![HardwareWriteCmd::new( 35 + &[JOYHUB_V6_PROTOCOL_UUID], 36 + Endpoint::Tx, 37 + vec![ 38 + 0xa0, 39 + 0x03, 40 + self.last_cmds[1].load(Ordering::Relaxed), 41 + self.last_cmds[0].load(Ordering::Relaxed), 42 + self.last_cmds[2].load(Ordering::Relaxed), 43 + 0x00, 44 + 0xaa, 45 + ], 46 + false, 47 + ).into()]) 112 48 } 113 49 } 114 50 115 51 impl ProtocolHandler for JoyHubV6 { 116 - fn keepalive_strategy(&self) -> super::ProtocolKeepaliveStrategy { 117 - super::ProtocolKeepaliveStrategy::RepeatLastPacketStrategy 52 + fn handle_output_vibrate_cmd( 53 + &self, 54 + feature_index: u32, 55 + _feature_id: Uuid, 56 + speed: u32, 57 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 58 + self.form_hardware_command(feature_index, speed) 118 59 } 119 60 120 - fn outputs_full_command_set(&self) -> bool { 121 - true 61 + fn handle_output_rotate_cmd( 62 + &self, 63 + feature_index: u32, 64 + _feature_id: Uuid, 65 + speed: u32, 66 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 67 + self.form_hardware_command(feature_index, speed) 122 68 } 123 69 124 - fn handle_value_cmd( 125 - &self, 126 - commands: &[Option<(ActuatorType, i32)>], 127 - ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 128 - let cmd1 = commands[0]; 129 - let mut cmd2 = if commands.len() > 1 { 130 - commands[1] 131 - } else { 132 - None 133 - }; 134 - let mut cmd3 = if commands.len() > 2 { 135 - commands[2] 136 - } else { 137 - None 138 - }; 139 - 140 - if let Some(cmd) = cmd2 { 141 - if cmd.0 == ActuatorType::Constrict { 142 - cmd2 = None; 143 - if !scalar_changed(&self.last_cmds, commands, 1usize) { 144 - // no-op 145 - } else if vibes_changed(&self.last_cmds, commands, vec![1usize]) { 146 - let dev = self.device.clone(); 147 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 148 - } else { 149 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 150 - *command_writer = commands.to_vec(); 151 - 152 - return Ok(vec![HardwareWriteCmd::new( 153 - Endpoint::Tx, 154 - vec![0xa0, 0x0d, 0x00, 0x00, cmd.1 as u8, 0xff], 155 - false, 156 - ) 157 - .into()]); 158 - } 159 - } 160 - } 161 - 162 - if let Some(cmd) = cmd3 { 163 - if cmd.0 == ActuatorType::Constrict { 164 - cmd3 = None; 165 - if !scalar_changed(&self.last_cmds, commands, 2usize) { 166 - // no-op 167 - } else if vibes_changed(&self.last_cmds, commands, vec![2usize]) { 168 - let dev = self.device.clone(); 169 - async_manager::spawn(async move { delayed_constrict_handler(dev, cmd.1 as u8).await }); 170 - } else { 171 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 172 - *command_writer = commands.to_vec(); 173 - 174 - return Ok(vec![HardwareWriteCmd::new( 175 - Endpoint::Tx, 176 - vec![0xa0, 0x0d, 0x00, 0x00, cmd.1 as u8, 0xff], 177 - false, 178 - ) 179 - .into()]); 180 - } 181 - } 182 - } 70 + fn handle_output_oscillate_cmd( 71 + &self, 72 + feature_index: u32, 73 + _feature_id: Uuid, 74 + speed: u32, 75 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 76 + self.form_hardware_command(feature_index, speed) 77 + } 183 78 184 - let mut command_writer = self.last_cmds.write().expect("Locks should work"); 185 - *command_writer = commands.to_vec(); 79 + fn handle_output_constrict_cmd( 80 + &self, 81 + _feature_index: u32, 82 + feature_id: Uuid, 83 + level: u32, 84 + ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> { 186 85 Ok(vec![HardwareWriteCmd::new( 86 + &[feature_id], 187 87 Endpoint::Tx, 188 88 vec![ 189 89 0xa0, 190 - 0x03, 191 - cmd2.unwrap_or((ActuatorType::Rotate, 0)).1 as u8, 192 - cmd1.unwrap_or((ActuatorType::Vibrate, 0)).1 as u8, 193 - cmd3.unwrap_or((ActuatorType::Oscillate, 0)).1 as u8, 90 + 0x07, 91 + if level == 0 { 0x00 } else { 0x01 }, 194 92 0x00, 195 - 0xaa, 93 + level as u8, 94 + 0xff, 196 95 ], 197 96 false, 198 97 )
-2
crates/buttplug_server/src/device/protocol_impl/joyhub/mod.rs
··· 1 1 pub mod joyhub; 2 - /* 3 2 pub mod joyhub_v2; 4 3 pub mod joyhub_v3; 5 4 pub mod joyhub_v4; 6 5 pub mod joyhub_v5; 7 6 pub mod joyhub_v6; 8 - */
-2
crates/buttplug_server/src/device/protocol_impl/mod.rs
··· 210 210 add_to_protocol_map(&mut map, itoys::setup::IToysIdentifierFactory::default()); 211 211 add_to_protocol_map(&mut map, jejoue::setup::JeJoueIdentifierFactory::default()); 212 212 add_to_protocol_map(&mut map, joyhub::joyhub::setup::JoyHubIdentifierFactory::default()); 213 - /* 214 213 add_to_protocol_map( 215 214 &mut map, 216 215 joyhub::joyhub_v2::setup::JoyHubV2IdentifierFactory::default(), ··· 232 231 &mut map, 233 232 joyhub::joyhub_v6::setup::JoyHubV6IdentifierFactory::default(), 234 233 ); 235 - */ 236 234 add_to_protocol_map( 237 235 &mut map, 238 236 kiiroo_prowand::setup::KiirooProWandIdentifierFactory::default(),
+3 -3
crates/buttplug_tests/tests/test_device_protocols.rs
··· 44 44 #[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")] 45 45 #[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")] 46 46 #[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")] 47 - //#[test_case("test_joyhub_moonhorn.yaml" ; "JoyHub Protocol - Moonhorn")] 48 - //#[test_case("test_joyhub_petalwish_compat.yaml" ; "JoyHub Protocol - Petalwish Compat")] 47 + #[test_case("test_joyhub_moonhorn.yaml" ; "JoyHub Protocol - Moonhorn")] 48 + #[test_case("test_joyhub_petalwish_compat.yaml" ; "JoyHub Protocol - Petalwish Compat")] 49 49 #[test_case("test_joyhub_petalwish.yaml" ; "JoyHub Protocol - Petalwish")] 50 - //#[test_case("test_joyhub_roselin.yaml" ; "JoyHub Protocol - RoseLin")] 50 + #[test_case("test_joyhub_roselin.yaml" ; "JoyHub Protocol - RoseLin")] 51 51 #[test_case("test_kiiroo_prowand.yaml" ; "Kiiroo ProWand Protocol")] 52 52 #[test_case("test_kiiroo_spot.yaml" ; "Kiiroo Spot Protocol")] 53 53 #[test_case("test_lelo_f1sv1.yaml" ; "Lelo F1s V1 Protocol")]
+4 -4
crates/buttplug_tests/tests/util/device_test/device_test_case/test_joyhub_moonhorn.yaml
··· 17 17 endpoint: tx 18 18 data: [0xa0, 0x03, 0x80, 0x00, 0x00, 0x00, 0xaa] 19 19 write_with_response: false 20 - - !Write 21 - endpoint: tx 22 - data: [0xa0, 0x0d, 0x00, 0x00, 0x00, 0xff] # First 0 is free 23 - write_with_response: false 20 + # - !Write 21 + # endpoint: tx 22 + # data: [0xa0, 0x0d, 0x00, 0x00, 0x00, 0xff] # First 0 is free 23 + # write_with_response: false 24 24 - !Messages 25 25 device_index: 0 26 26 messages:
+4 -4
crates/buttplug_tests/tests/util/device_test/device_test_case/test_joyhub_roselin.yaml
··· 17 17 endpoint: tx 18 18 data: [0xa0, 0x03, 0x80, 0x00, 0x00, 0x00, 0xaa] 19 19 write_with_response: false 20 - - !Write 21 - endpoint: tx 22 - data: [0xa0, 0x07, 0x00, 0x00, 0x00, 0xff] 23 - write_with_response: false 20 + # - !Write 21 + # endpoint: tx 22 + # data: [0xa0, 0x07, 0x00, 0x00, 0x00, 0xff] 23 + # write_with_response: false 24 24 - !Messages 25 25 device_index: 0 26 26 messages: