From e96afcac14256d286103c8ac916825ed5dbd24e5 Mon Sep 17 00:00:00 2001 From: blackspherefollower Date: Fri, 6 Feb 2026 17:10:32 +0000 Subject: [PATCH 1/4] chore: cargo fmt --- crates/buttplug_client/src/device/device.rs | 16 ++--- crates/buttplug_client/src/device/feature.rs | 19 ++++-- crates/buttplug_client/src/lib.rs | 25 +++++--- .../src/message/serializer/json_serializer.rs | 4 +- .../buttplug_core/src/message/v4/stop_cmd.rs | 15 ++--- crates/buttplug_core/src/util/json.rs | 4 +- .../src/device/device_handle.rs | 27 ++++++--- .../buttplug_server/src/device/device_task.rs | 6 +- crates/buttplug_server/src/device/mod.rs | 2 +- .../src/device/protocol_impl/joyhub.rs | 4 +- .../src/device/server_device_manager.rs | 9 ++- .../server_device_manager_event_loop.rs | 10 ++-- crates/buttplug_server/src/message/mod.rs | 58 ++++++++++++++----- .../src/message/server_device_attributes.rs | 1 - .../src/message/v0/spec_enums.rs | 6 +- .../src/message/v1/spec_enums.rs | 6 +- .../src/message/v2/spec_enums.rs | 6 +- .../src/message/v3/spec_enums.rs | 3 +- .../src/message/v4/spec_enums.rs | 15 ++++- crates/buttplug_server/src/server.rs | 12 ++-- .../src/device_config_file/device.rs | 4 +- .../src/device_config_file/feature.rs | 6 +- .../src/server_device_feature.rs | 12 +++- .../tests/test_client_device.rs | 9 ++- .../util/device_test/client/client_v0/mod.rs | 2 - .../util/device_test/client/client_v1/mod.rs | 2 - .../device_test/client/client_v2/client.rs | 23 ++++---- .../client/client_v2/client_event_loop.rs | 2 +- .../device_test/client/client_v2/device.rs | 6 +- .../device_test/client/client_v3/client.rs | 29 +++++----- .../client/client_v3/client_event_loop.rs | 2 +- .../util/device_test/client/client_v4/mod.rs | 11 ++-- crates/intiface_engine/src/backdoor_server.rs | 9 ++- crates/intiface_engine/src/buttplug_server.rs | 28 ++++----- examples/src/bin/application.rs | 16 +++-- examples/src/bin/device_control.rs | 24 ++++++-- examples/src/bin/device_tester.rs | 26 +++++---- examples/src/bin/errors.rs | 4 +- 38 files changed, 290 insertions(+), 173 deletions(-) diff --git a/crates/buttplug_client/src/device/device.rs b/crates/buttplug_client/src/device/device.rs index ee0a3277..9fc5798f 100644 --- a/crates/buttplug_client/src/device/device.rs +++ b/crates/buttplug_client/src/device/device.rs @@ -10,15 +10,8 @@ use crate::ButtplugClientError; use crate::device::ClientDeviceOutputCommand; -use crate::{ - ButtplugClientMessageSender, - ButtplugClientResultFuture, - device::ClientDeviceFeature, -}; -use buttplug_core::message::{ - InputType, - InputTypeReading, -}; +use crate::{ButtplugClientMessageSender, ButtplugClientResultFuture, device::ClientDeviceFeature}; +use buttplug_core::message::{InputType, InputTypeReading}; use buttplug_core::{ errors::ButtplugDeviceError, message::{ButtplugServerMessageV4, DeviceFeature, DeviceMessageInfoV4, OutputType, StopCmdV4}, @@ -249,7 +242,10 @@ impl ButtplugClientDevice { } } - pub fn run_input_read(&self, input_type: InputType) -> ButtplugClientResultFuture { + pub fn run_input_read( + &self, + input_type: InputType, + ) -> ButtplugClientResultFuture { match self.input_feature(input_type) { Ok(dev) => dev.run_input_read(input_type).boxed(), Err(e) => future::ready(Err(e)).boxed(), diff --git a/crates/buttplug_client/src/device/feature.rs b/crates/buttplug_client/src/device/feature.rs index d81fd560..b805143a 100644 --- a/crates/buttplug_client/src/device/feature.rs +++ b/crates/buttplug_client/src/device/feature.rs @@ -96,7 +96,10 @@ impl ClientDeviceFeature { ) -> Result { if !(-1.0f64..=1.0f64).contains(&float_amt) { Err(ButtplugClientError::ButtplugOutputCommandConversionError( - format!("Float values must be between 0.0 and 1.0, received value was {}", float_amt), + format!( + "Float values must be between 0.0 and 1.0, received value was {}", + float_amt + ), )) } else { let mut val = float_amt * feature_output.step_count() as f64; @@ -158,9 +161,12 @@ impl ClientDeviceFeature { ClientDeviceOutputCommand::Position(v) => { OutputCommand::Position(OutputValue::new(self.check_step_value(output, v)?)) } - ClientDeviceOutputCommand::HwPositionWithDuration(v, d) => OutputCommand::HwPositionWithDuration( - OutputHwPositionWithDuration::new(self.check_step_value(output, v)? as u32, *d), - ), + ClientDeviceOutputCommand::HwPositionWithDuration(v, d) => { + OutputCommand::HwPositionWithDuration(OutputHwPositionWithDuration::new( + self.check_step_value(output, v)? as u32, + *d, + )) + } }; Ok(OutputCmdV4::new( self.device_index, @@ -219,7 +225,10 @@ impl ClientDeviceFeature { ) } - pub fn run_input_read(&self, sensor_type: InputType) -> ButtplugClientResultFuture { + pub fn run_input_read( + &self, + sensor_type: InputType, + ) -> ButtplugClientResultFuture { if let Some(sensor_map) = self.feature.input() && let Some(sensor) = sensor_map.get(sensor_type) && sensor.command().contains(&InputCommandType::Read) diff --git a/crates/buttplug_client/src/lib.rs b/crates/buttplug_client/src/lib.rs index c0ad8415..e72e751b 100644 --- a/crates/buttplug_client/src/lib.rs +++ b/crates/buttplug_client/src/lib.rs @@ -20,7 +20,17 @@ use buttplug_core::{ connector::{ButtplugConnector, ButtplugConnectorError}, errors::{ButtplugError, ButtplugHandshakeError}, message::{ - BUTTPLUG_CURRENT_API_MAJOR_VERSION, BUTTPLUG_CURRENT_API_MINOR_VERSION, ButtplugClientMessageV4, ButtplugServerMessageV4, InputType, PingV0, RequestDeviceListV0, RequestServerInfoV4, StartScanningV0, StopCmdV4, StopScanningV0 + BUTTPLUG_CURRENT_API_MAJOR_VERSION, + BUTTPLUG_CURRENT_API_MINOR_VERSION, + ButtplugClientMessageV4, + ButtplugServerMessageV4, + InputType, + PingV0, + RequestDeviceListV0, + RequestServerInfoV4, + StartScanningV0, + StopCmdV4, + StopScanningV0, }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; @@ -103,7 +113,7 @@ pub enum ButtplugClientError { /// Error converting output command: {} ButtplugOutputCommandConversionError(String), /// Multiple inputs available for {}, must use specific feature - ButtplugMultipleInputAvailableError(InputType) + ButtplugMultipleInputAvailableError(InputType), } /// Enum representing different events that can be emitted by a client. @@ -286,14 +296,11 @@ impl ButtplugClient { // Take the request receiver - if None, a previous connection consumed it and we can't reconnect // without creating a new client (the sender is tied to this receiver) - let request_receiver = self - .request_receiver - .lock() - .await - .take() - .ok_or(ButtplugConnectorError::ConnectorGenericError( + let request_receiver = self.request_receiver.lock().await.take().ok_or( + ButtplugConnectorError::ConnectorGenericError( "Cannot reconnect - request channel already consumed. Create a new client.".to_string(), - ))?; + ), + )?; info!("Connecting to server."); let (connector_sender, connector_receiver) = mpsc::channel(256); diff --git a/crates/buttplug_core/src/message/serializer/json_serializer.rs b/crates/buttplug_core/src/message/serializer/json_serializer.rs index 5d4233e2..f1516d1c 100644 --- a/crates/buttplug_core/src/message/serializer/json_serializer.rs +++ b/crates/buttplug_core/src/message/serializer/json_serializer.rs @@ -22,8 +22,8 @@ pub fn create_message_validator() -> Validator { // SAFETY: MESSAGE_JSON_SCHEMA is embedded at compile time via include_str!() and validated by // build.rs before compilation. These expects can only fail if there's a build/packaging error, // not at runtime. - let schema: serde_json::Value = - serde_json::from_str(MESSAGE_JSON_SCHEMA).expect("schema must be valid JSON (validated by build.rs)"); + let schema: serde_json::Value = serde_json::from_str(MESSAGE_JSON_SCHEMA) + .expect("schema must be valid JSON (validated by build.rs)"); Validator::new(&schema).expect("schema must be valid JSON Schema (validated by build.rs)") } diff --git a/crates/buttplug_core/src/message/v4/stop_cmd.rs b/crates/buttplug_core/src/message/v4/stop_cmd.rs index 55d91a7f..33d0b1b3 100644 --- a/crates/buttplug_core/src/message/v4/stop_cmd.rs +++ b/crates/buttplug_core/src/message/v4/stop_cmd.rs @@ -5,11 +5,7 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. -use crate::message::{ - ButtplugMessage, - ButtplugMessageError, - ButtplugMessageValidator, -}; +use crate::message::{ButtplugMessage, ButtplugMessageError, ButtplugMessageValidator}; use getset::CopyGetters; use serde::{Deserialize, Serialize}; @@ -36,7 +32,12 @@ pub struct StopCmdV4 { } impl StopCmdV4 { - pub fn new(device_index: Option, feature_index: Option, inputs: bool, outputs: bool) -> Self { + pub fn new( + device_index: Option, + feature_index: Option, + inputs: bool, + outputs: bool, + ) -> Self { Self { id: 1, device_index, @@ -55,7 +56,7 @@ impl Default for StopCmdV4 { device_index: None, feature_index: None, inputs: true, - outputs: true + outputs: true, } } } diff --git a/crates/buttplug_core/src/util/json.rs b/crates/buttplug_core/src/util/json.rs index 52bce040..f8c7253e 100644 --- a/crates/buttplug_core/src/util/json.rs +++ b/crates/buttplug_core/src/util/json.rs @@ -28,8 +28,8 @@ impl JSONValidator { // These expects can only fail if there's a build/packaging error, not at runtime. let schema_json: serde_json::Value = serde_json::from_str(schema).expect("schema must be valid JSON (validated by build.rs)"); - let schema = - Validator::new(&schema_json).expect("schema must be valid JSON Schema (validated by build.rs)"); + let schema = Validator::new(&schema_json) + .expect("schema must be valid JSON Schema (validated by build.rs)"); Self { schema } } diff --git a/crates/buttplug_server/src/device/device_handle.rs b/crates/buttplug_server/src/device/device_handle.rs index e2f711fb..b6376ceb 100644 --- a/crates/buttplug_server/src/device/device_handle.rs +++ b/crates/buttplug_server/src/device/device_handle.rs @@ -16,17 +16,30 @@ use buttplug_core::{ ButtplugResultFuture, errors::{ButtplugDeviceError, ButtplugError}, message::{ - self, ButtplugMessage, ButtplugServerMessageV4, DeviceFeature, DeviceMessageInfoV4, - InputCommandType, InputType, OutputType, OutputValue, StopCmdV4, + self, + ButtplugMessage, + ButtplugServerMessageV4, + DeviceFeature, + DeviceMessageInfoV4, + InputCommandType, + InputType, + OutputType, + OutputValue, + StopCmdV4, }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; use buttplug_server_device_config::{ - DeviceConfigurationManager, ServerDeviceDefinition, UserDeviceIdentifier, + DeviceConfigurationManager, + ServerDeviceDefinition, + UserDeviceIdentifier, }; use dashmap::DashMap; use futures::future::{self, BoxFuture, FutureExt}; -use tokio::sync::{mpsc::{channel, Sender}, oneshot}; +use tokio::sync::{ + mpsc::{Sender, channel}, + oneshot, +}; use tokio_stream::StreamExt; use uuid::Uuid; @@ -43,7 +56,7 @@ use crate::{ use super::{ InternalDeviceEvent, - device_task::{spawn_device_task, DeviceTaskConfig}, + device_task::{DeviceTaskConfig, spawn_device_task}, hardware::{Hardware, HardwareCommand, HardwareConnector, HardwareEvent}, protocol::{ProtocolHandler, ProtocolKeepaliveStrategy, ProtocolSpecializer}, }; @@ -554,9 +567,7 @@ pub(super) async fn build_device_handle( strategy, ProtocolKeepaliveStrategy::RepeatLastPacketStrategyWithTiming(_) )) - && let Err(e) = device_handle - .stop(&StopCmdV4::default()) - .await + && let Err(e) = device_handle.stop(&StopCmdV4::default()).await { return Err(ButtplugDeviceError::DeviceConnectionError(format!( "Error setting up keepalive: {e}" diff --git a/crates/buttplug_server/src/device/device_task.rs b/crates/buttplug_server/src/device/device_task.rs index 3ed84f3f..f2884d1c 100644 --- a/crates/buttplug_server/src/device/device_task.rs +++ b/crates/buttplug_server/src/device/device_task.rs @@ -16,11 +16,7 @@ use std::{collections::VecDeque, sync::Arc, time::Duration}; use buttplug_core::util::{self, async_manager}; use futures::future; -use tokio::{ - select, - sync::mpsc::Receiver, - time::Instant, -}; +use tokio::{select, sync::mpsc::Receiver, time::Instant}; use super::{ hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareWriteCmd}, diff --git a/crates/buttplug_server/src/device/mod.rs b/crates/buttplug_server/src/device/mod.rs index 93e3d587..967cb6b7 100644 --- a/crates/buttplug_server/src/device/mod.rs +++ b/crates/buttplug_server/src/device/mod.rs @@ -105,8 +105,8 @@ mod server_device_manager_event_loop; pub use device_handle::{DeviceCommand, DeviceEvent, DeviceHandle}; -use buttplug_server_device_config::UserDeviceIdentifier; use crate::message::ButtplugServerDeviceMessage; +use buttplug_server_device_config::UserDeviceIdentifier; /// Internal event enum for device manager communication. /// Used by DeviceHandle to send events to the device manager event loop. diff --git a/crates/buttplug_server/src/device/protocol_impl/joyhub.rs b/crates/buttplug_server/src/device/protocol_impl/joyhub.rs index 87364106..dac81225 100644 --- a/crates/buttplug_server/src/device/protocol_impl/joyhub.rs +++ b/crates/buttplug_server/src/device/protocol_impl/joyhub.rs @@ -133,7 +133,7 @@ impl ProtocolHandler for JoyHub { }, false, ) - .into(), + .into(), ]) } @@ -154,7 +154,7 @@ impl ProtocolHandler for JoyHub { }, false, ) - .into(), + .into(), ]) } } diff --git a/crates/buttplug_server/src/device/server_device_manager.rs b/crates/buttplug_server/src/device/server_device_manager.rs index c065ef6c..90e3bec8 100644 --- a/crates/buttplug_server/src/device/server_device_manager.rs +++ b/crates/buttplug_server/src/device/server_device_manager.rs @@ -230,9 +230,12 @@ impl ServerDeviceManager { .iter() .map(|dev| { let device = dev.value(); - device.stop( - &message::StopCmdV4::new(None, None, msg.inputs(), msg.outputs()), - ) + device.stop(&message::StopCmdV4::new( + None, + None, + msg.inputs(), + msg.outputs(), + )) }) .collect(); future::join_all(fut_vec).await; diff --git a/crates/buttplug_server/src/device/server_device_manager_event_loop.rs b/crates/buttplug_server/src/device/server_device_manager_event_loop.rs index 75430d79..d568612d 100644 --- a/crates/buttplug_server/src/device/server_device_manager_event_loop.rs +++ b/crates/buttplug_server/src/device/server_device_manager_event_loop.rs @@ -185,9 +185,7 @@ impl ServerDeviceManagerEventLoop { } ScanningState::BringupInProgress => { // Comm manager finished before we completed bringup - ignore for now - debug!( - "Hardware Comm Manager finished before scanning was fully started, ignoring" - ); + debug!("Hardware Comm Manager finished before scanning was fully started, ignoring"); } ScanningState::Active => { // Check if all hardware has actually stopped @@ -364,7 +362,11 @@ impl ServerDeviceManagerEventLoop { // Note: The device event forwarding task is now spawned in build_device_handle(), // so we no longer need to create it here. - info!("Assigning index {} to {}", device_index, device_handle.name()); + info!( + "Assigning index {} to {}", + device_index, + device_handle.name() + ); self.device_map.insert(device_index, device_handle.clone()); let device_update_message: ButtplugServerMessageV4 = self.generate_device_list().into(); diff --git a/crates/buttplug_server/src/message/mod.rs b/crates/buttplug_server/src/message/mod.rs index 9b203bdc..774caed1 100644 --- a/crates/buttplug_server/src/message/mod.rs +++ b/crates/buttplug_server/src/message/mod.rs @@ -5,7 +5,6 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. - use buttplug_core::{ errors::{ButtplugError, ButtplugMessageError}, message::{ @@ -84,7 +83,8 @@ pub enum ButtplugClientMessageVariant { } impl_message_enum_traits!(ButtplugClientMessageVariant { V0, V1, V2, V3, V4 }); -impl ButtplugMessageFinalizer for ButtplugClientMessageVariant {} +impl ButtplugMessageFinalizer for ButtplugClientMessageVariant { +} impl ButtplugClientMessageVariant { pub fn version(&self) -> ButtplugMessageSpecVersion { @@ -99,16 +99,44 @@ impl ButtplugClientMessageVariant { pub fn device_index(&self) -> Option { match self { - Self::V0(msg) => extract_device_index!(msg, ButtplugClientMessageV0, - [FleshlightLaunchFW12Cmd, SingleMotorVibrateCmd, VorzeA10CycloneCmd]), - Self::V1(msg) => extract_device_index!(msg, ButtplugClientMessageV1, - [FleshlightLaunchFW12Cmd, SingleMotorVibrateCmd, VorzeA10CycloneCmd, VibrateCmd]), - Self::V2(msg) => extract_device_index!(msg, ButtplugClientMessageV2, - [VibrateCmd, RotateCmd, LinearCmd, BatteryLevelCmd]), - Self::V3(msg) => extract_device_index!(msg, ButtplugClientMessageV3, - [VibrateCmd, SensorSubscribeCmd, SensorUnsubscribeCmd, ScalarCmd, RotateCmd, LinearCmd, SensorReadCmd]), - Self::V4(msg) => extract_device_index!(msg, ButtplugClientMessageV4, - [OutputCmd, InputCmd]), + Self::V0(msg) => extract_device_index!( + msg, + ButtplugClientMessageV0, + [ + FleshlightLaunchFW12Cmd, + SingleMotorVibrateCmd, + VorzeA10CycloneCmd + ] + ), + Self::V1(msg) => extract_device_index!( + msg, + ButtplugClientMessageV1, + [ + FleshlightLaunchFW12Cmd, + SingleMotorVibrateCmd, + VorzeA10CycloneCmd, + VibrateCmd + ] + ), + Self::V2(msg) => extract_device_index!( + msg, + ButtplugClientMessageV2, + [VibrateCmd, RotateCmd, LinearCmd, BatteryLevelCmd] + ), + Self::V3(msg) => extract_device_index!( + msg, + ButtplugClientMessageV3, + [ + VibrateCmd, + SensorSubscribeCmd, + SensorUnsubscribeCmd, + ScalarCmd, + RotateCmd, + LinearCmd, + SensorReadCmd + ] + ), + Self::V4(msg) => extract_device_index!(msg, ButtplugClientMessageV4, [OutputCmd, InputCmd]), } } } @@ -123,7 +151,8 @@ pub enum ButtplugServerMessageVariant { } impl_message_enum_traits!(ButtplugServerMessageVariant { V0, V1, V2, V3, V4 }); -impl ButtplugMessageFinalizer for ButtplugServerMessageVariant {} +impl ButtplugMessageFinalizer for ButtplugServerMessageVariant { +} impl ButtplugServerMessageVariant { pub fn version(&self) -> ButtplugMessageSpecVersion { @@ -148,7 +177,8 @@ pub enum ButtplugServerDeviceMessage { } impl_message_enum_traits!(ButtplugServerDeviceMessage { SensorReading }); -impl ButtplugMessageFinalizer for ButtplugServerDeviceMessage {} +impl ButtplugMessageFinalizer for ButtplugServerDeviceMessage { +} impl From for ButtplugServerMessageV4 { fn from(other: ButtplugServerDeviceMessage) -> Self { diff --git a/crates/buttplug_server/src/message/server_device_attributes.rs b/crates/buttplug_server/src/message/server_device_attributes.rs index 014f7326..76489d77 100644 --- a/crates/buttplug_server/src/message/server_device_attributes.rs +++ b/crates/buttplug_server/src/message/server_device_attributes.rs @@ -5,7 +5,6 @@ // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. - use super::ServerDeviceMessageAttributesV3; use super::v2::ServerDeviceMessageAttributesV2; use buttplug_core::errors::ButtplugError; diff --git a/crates/buttplug_server/src/message/v0/spec_enums.rs b/crates/buttplug_server/src/message/v0/spec_enums.rs index 01dd34f0..702e6413 100644 --- a/crates/buttplug_server/src/message/v0/spec_enums.rs +++ b/crates/buttplug_server/src/message/v0/spec_enums.rs @@ -48,7 +48,8 @@ impl_message_enum_traits!(ButtplugClientMessageV0 { FleshlightLaunchFW12Cmd, VorzeA10CycloneCmd, }); -impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugClientMessageV0 {} +impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugClientMessageV0 { +} /// Represents all server-to-client messages in v0 of the Buttplug Spec #[derive(Debug, Clone, PartialEq, derive_more::From, Serialize, Deserialize)] @@ -74,7 +75,8 @@ impl_message_enum_traits!(ButtplugServerMessageV0 { DeviceRemoved, ScanningFinished, }); -impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugServerMessageV0 {} +impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugServerMessageV0 { +} #[derive(Copy, Debug, Clone, PartialEq, Eq, Hash, Display, Serialize, Deserialize)] pub enum ButtplugDeviceMessageNameV0 { diff --git a/crates/buttplug_server/src/message/v1/spec_enums.rs b/crates/buttplug_server/src/message/v1/spec_enums.rs index b8b997f2..d58667cd 100644 --- a/crates/buttplug_server/src/message/v1/spec_enums.rs +++ b/crates/buttplug_server/src/message/v1/spec_enums.rs @@ -78,7 +78,8 @@ impl_message_enum_traits!(ButtplugClientMessageV1 { FleshlightLaunchFW12Cmd, VorzeA10CycloneCmd, }); -impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugClientMessageV1 {} +impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugClientMessageV1 { +} // No messages were changed or deprecated before v2, so we can convert all v0 messages to v1. impl From for ButtplugClientMessageV1 { @@ -132,7 +133,8 @@ impl_message_enum_traits!(ButtplugServerMessageV1 { DeviceRemoved, ScanningFinished, }); -impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugServerMessageV1 {} +impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugServerMessageV1 { +} impl From for ButtplugServerMessageV0 { fn from(value: ButtplugServerMessageV1) -> Self { diff --git a/crates/buttplug_server/src/message/v2/spec_enums.rs b/crates/buttplug_server/src/message/v2/spec_enums.rs index 824223d6..84ebef98 100644 --- a/crates/buttplug_server/src/message/v2/spec_enums.rs +++ b/crates/buttplug_server/src/message/v2/spec_enums.rs @@ -67,7 +67,8 @@ impl_message_enum_traits!(ButtplugClientMessageV2 { StopDeviceCmd, BatteryLevelCmd, }); -impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugClientMessageV2 {} +impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugClientMessageV2 { +} // For v1 to v2, several messages were deprecated. Throw errors when trying to convert those. impl TryFrom for ButtplugClientMessageV2 { @@ -136,7 +137,8 @@ impl_message_enum_traits!(ButtplugServerMessageV2 { ScanningFinished, BatteryLevelReading, }); -impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugServerMessageV2 {} +impl buttplug_core::message::ButtplugMessageFinalizer for ButtplugServerMessageV2 { +} impl From for ButtplugServerMessageV1 { fn from(value: ButtplugServerMessageV2) -> Self { diff --git a/crates/buttplug_server/src/message/v3/spec_enums.rs b/crates/buttplug_server/src/message/v3/spec_enums.rs index 2bd4d604..c737ad01 100644 --- a/crates/buttplug_server/src/message/v3/spec_enums.rs +++ b/crates/buttplug_server/src/message/v3/spec_enums.rs @@ -77,7 +77,8 @@ impl_message_enum_traits!(ButtplugClientMessageV3 { SensorSubscribeCmd, SensorUnsubscribeCmd, }); -impl ButtplugMessageFinalizer for ButtplugClientMessageV3 {} +impl ButtplugMessageFinalizer for ButtplugClientMessageV3 { +} // For v2 to v3, all deprecations should be treated as conversions, but will require current // connected device state, meaning they'll need to be implemented where they can also access the diff --git a/crates/buttplug_server/src/message/v4/spec_enums.rs b/crates/buttplug_server/src/message/v4/spec_enums.rs index 4f649bb8..6b0f6514 100644 --- a/crates/buttplug_server/src/message/v4/spec_enums.rs +++ b/crates/buttplug_server/src/message/v4/spec_enums.rs @@ -23,7 +23,15 @@ use crate::message::{ use buttplug_core::{ errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, message::{ - ButtplugClientMessageV4, ButtplugDeviceMessage, ButtplugMessage, PingV0, RequestDeviceListV0, RequestServerInfoV4, StartScanningV0, StopCmdV4, StopScanningV0 + ButtplugClientMessageV4, + ButtplugDeviceMessage, + ButtplugMessage, + PingV0, + RequestDeviceListV0, + RequestServerInfoV4, + StartScanningV0, + StopCmdV4, + StopScanningV0, }, }; @@ -93,7 +101,10 @@ impl TryFromClientMessage for ButtplugCheckedClientMess } // Messages that need device index checking ButtplugClientMessageV4::StopCmd(m) => { - if m.device_index().map_or(true,|x| feature_map.get(&x).is_some()) { + if m + .device_index() + .map_or(true, |x| feature_map.get(&x).is_some()) + { Ok(ButtplugCheckedClientMessageV4::StopCmd(m)) } else { Err(ButtplugError::from( diff --git a/crates/buttplug_server/src/server.rs b/crates/buttplug_server/src/server.rs index 3debf7ef..409fc98a 100644 --- a/crates/buttplug_server/src/server.rs +++ b/crates/buttplug_server/src/server.rs @@ -43,10 +43,7 @@ use futures::{ }; use std::{ fmt, - sync::{ - Arc, - RwLock, - }, + sync::{Arc, RwLock}, }; use tokio::sync::broadcast; use tokio_stream::StreamExt; @@ -215,9 +212,8 @@ impl ButtplugServer { let stop_scanning_fut = self.parse_checked_message( ButtplugCheckedClientMessageV4::StopScanning(StopScanningV0::default()), ); - let stop_fut = self.parse_checked_message(ButtplugCheckedClientMessageV4::StopCmd( - StopCmdV4::default(), - )); + let stop_fut = + self.parse_checked_message(ButtplugCheckedClientMessageV4::StopCmd(StopCmdV4::default())); let state = self.state.clone(); async move { { @@ -255,7 +251,7 @@ impl ButtplugServer { current_version } else { info!("Setting Buttplug Server Message Spec version to {}", v); - v + v }; match msg { ButtplugClientMessageVariant::V4(msg) => { diff --git a/crates/buttplug_server_device_config/src/device_config_file/device.rs b/crates/buttplug_server_device_config/src/device_config_file/device.rs index 85b6324d..dc30f953 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/device.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/device.rs @@ -143,7 +143,9 @@ impl TryFrom<&ServerDeviceDefinition> for ConfigUserDeviceDefinition { fn try_from(value: &ServerDeviceDefinition) -> Result { Ok(Self { id: value.id(), - base_id: value.base_id().ok_or(ButtplugDeviceConfigError::MissingBaseId)?, + base_id: value + .base_id() + .ok_or(ButtplugDeviceConfigError::MissingBaseId)?, features: value .features() .values() diff --git a/crates/buttplug_server_device_config/src/device_config_file/feature.rs b/crates/buttplug_server_device_config/src/device_config_file/feature.rs index 0dcbac47..2acdcef7 100644 --- a/crates/buttplug_server_device_config/src/device_config_file/feature.rs +++ b/crates/buttplug_server_device_config/src/device_config_file/feature.rs @@ -13,8 +13,8 @@ use crate::{ ServerDeviceFeature, ServerDeviceFeatureInput, ServerDeviceFeatureOutput, - ServerDeviceFeatureOutputPositionProperties, ServerDeviceFeatureOutputHwPositionWithDurationProperties, + ServerDeviceFeatureOutputPositionProperties, ServerDeviceFeatureOutputValueProperties, }; use buttplug_core::util::range_serialize::option_range_serialize; @@ -313,7 +313,9 @@ impl TryFrom<&ServerDeviceFeature> for ConfigUserDeviceFeature { fn try_from(value: &ServerDeviceFeature) -> Result { Ok(Self { id: value.id(), - base_id: value.base_id().ok_or(ButtplugDeviceConfigError::MissingBaseId)?, + base_id: value + .base_id() + .ok_or(ButtplugDeviceConfigError::MissingBaseId)?, output: value.output().as_ref().map(|x| x.into()), }) } diff --git a/crates/buttplug_server_device_config/src/server_device_feature.rs b/crates/buttplug_server_device_config/src/server_device_feature.rs index 74163ad8..49a8f735 100644 --- a/crates/buttplug_server_device_config/src/server_device_feature.rs +++ b/crates/buttplug_server_device_config/src/server_device_feature.rs @@ -22,9 +22,11 @@ use buttplug_core::message::{ }; use getset::{CopyGetters, Getters, Setters}; use serde::{ + Deserialize, + Serialize, + Serializer, de::{self, Deserializer, SeqAccess, Visitor}, ser::SerializeSeq, - Deserialize, Serialize, Serializer, }; use std::{collections::HashSet, fmt, ops::RangeInclusive}; use uuid::Uuid; @@ -429,7 +431,10 @@ impl ServerDeviceFeatureOutput { (self.temperature.is_some(), OutputType::Temperature), (self.led.is_some(), OutputType::Led), (self.position.is_some(), OutputType::Position), - (self.hw_position_with_duration.is_some(), OutputType::HwPositionWithDuration), + ( + self.hw_position_with_duration.is_some(), + OutputType::HwPositionWithDuration, + ), (self.spray.is_some(), OutputType::Spray), ] .into_iter() @@ -673,7 +678,8 @@ impl PartialEq for ServerDeviceFeature { } } -impl Eq for ServerDeviceFeature {} +impl Eq for ServerDeviceFeature { +} impl Default for ServerDeviceFeature { fn default() -> Self { diff --git a/crates/buttplug_tests/tests/test_client_device.rs b/crates/buttplug_tests/tests/test_client_device.rs index 7b69a8af..496a5494 100644 --- a/crates/buttplug_tests/tests/test_client_device.rs +++ b/crates/buttplug_tests/tests/test_client_device.rs @@ -144,7 +144,14 @@ async fn test_client_device_invalid_command() { let test_device = client_device.expect("Test, assuming infallible."); assert!(matches!( - test_device.run_output(&buttplug_client::device::ClientDeviceOutputCommand::Vibrate(buttplug_client::device::ClientDeviceCommandValue::Steps(1000))).await.unwrap_err(), + test_device + .run_output( + &buttplug_client::device::ClientDeviceOutputCommand::Vibrate( + buttplug_client::device::ClientDeviceCommandValue::Steps(1000) + ) + ) + .await + .unwrap_err(), ButtplugClientError::ButtplugOutputCommandConversionError(_) )); } diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v0/mod.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v0/mod.rs index c95efebd..da280491 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v0/mod.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v0/mod.rs @@ -4,5 +4,3 @@ // // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. - - diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v1/mod.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v1/mod.rs index c95efebd..da280491 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v1/mod.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v1/mod.rs @@ -4,5 +4,3 @@ // // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. - - diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs index 5bc54712..4a53a674 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client.rs @@ -20,7 +20,6 @@ use buttplug_core::{ }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; -use futures::channel::oneshot; use buttplug_server::message::{ ButtplugClientMessageV2, ButtplugServerMessageV2, @@ -28,6 +27,7 @@ use buttplug_server::message::{ StopAllDevicesV0, }; use dashmap::DashMap; +use futures::channel::oneshot; use futures::{ Stream, future::{self, BoxFuture}, @@ -168,15 +168,18 @@ impl ButtplugClient { pub fn new(name: &str) -> (Self, mpsc::Receiver) { let (message_sender, message_receiver) = mpsc::channel(256); let (event_stream, _) = broadcast::channel(256); - (Self { - client_name: name.to_owned(), - server_name: Arc::new(Mutex::new(None)), - event_stream, - message_sender, - _client_span: Arc::new(Mutex::new(None)), - connected: Arc::new(AtomicBool::new(false)), - device_map: Arc::new(DashMap::new()), - }, message_receiver) + ( + Self { + client_name: name.to_owned(), + server_name: Arc::new(Mutex::new(None)), + event_stream, + message_sender, + _client_span: Arc::new(Mutex::new(None)), + connected: Arc::new(AtomicBool::new(false)), + device_map: Arc::new(DashMap::new()), + }, + message_receiver, + ) } pub async fn connect( diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client_event_loop.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client_event_loop.rs index f1f77fd2..000c1899 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/client_event_loop.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/client_event_loop.rs @@ -15,7 +15,6 @@ use buttplug_core::{ connector::ButtplugConnector, errors::{ButtplugDeviceError, ButtplugError}, }; -use futures::channel::oneshot; use buttplug_server::message::{ ButtplugClientMessageV2, ButtplugServerMessageV2, @@ -23,6 +22,7 @@ use buttplug_server::message::{ DeviceMessageInfoV2, }; use dashmap::DashMap; +use futures::channel::oneshot; use log::*; use std::sync::{ Arc, diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs index aab576ac..c0ffc0bb 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v2/device.rs @@ -15,7 +15,6 @@ use super::{ }, client_event_loop::ButtplugClientRequest, }; -use futures::channel::oneshot; use buttplug_core::{ connector::ButtplugConnectorError, errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError}, @@ -37,6 +36,7 @@ use buttplug_server::message::{ VibrateCmdV1, VibrateSubcommandV1, }; +use futures::channel::oneshot; use futures::{Stream, future}; use getset::Getters; use log::*; @@ -240,7 +240,9 @@ impl ButtplugClientDevice { ButtplugConnectorError::ConnectorChannelClosed, ) })?; - let msg = rx.await.map_err(|_| ButtplugConnectorError::ConnectorChannelClosed)??; + let msg = rx + .await + .map_err(|_| ButtplugConnectorError::ConnectorChannelClosed)??; if let ButtplugServerMessageV2::Error(_err) = msg { Err(ButtplugError::from(_err).into()) } else { diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs index 9f2f251b..c2666b71 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v3/client.rs @@ -19,7 +19,6 @@ use buttplug_core::{ }, util::{async_manager, stream::convert_broadcast_receiver_to_stream}, }; -use futures::channel::oneshot; use buttplug_server::message::{ ButtplugClientMessageV3, ButtplugServerMessageV3, @@ -27,6 +26,7 @@ use buttplug_server::message::{ StopAllDevicesV0, }; use dashmap::DashMap; +use futures::channel::oneshot; use futures::{ Stream, future::{self, BoxFuture, FutureExt}, @@ -146,10 +146,7 @@ pub(super) struct ButtplugClientMessageSender { } impl ButtplugClientMessageSender { - fn new( - message_sender: mpsc::Sender, - connected: &Arc, - ) -> Self { + fn new(message_sender: mpsc::Sender, connected: &Arc) -> Self { Self { message_sender, connected: connected.clone(), @@ -248,17 +245,17 @@ impl ButtplugClient { let (message_sender, message_receiver) = mpsc::channel(256); let (event_stream, _) = broadcast::channel(256); let connected = Arc::new(AtomicBool::new(false)); - (Self { - client_name: name.to_owned(), - server_name: Arc::new(Mutex::new(None)), - event_stream, - message_sender: Arc::new(ButtplugClientMessageSender::new( - message_sender, - &connected, - )), - connected, - device_map: Arc::new(DashMap::new()), - }, message_receiver) + ( + Self { + client_name: name.to_owned(), + server_name: Arc::new(Mutex::new(None)), + event_stream, + message_sender: Arc::new(ButtplugClientMessageSender::new(message_sender, &connected)), + connected, + device_map: Arc::new(DashMap::new()), + }, + message_receiver, + ) } pub async fn connect( diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v3/client_event_loop.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v3/client_event_loop.rs index b99fccc9..9ffcfffa 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v3/client_event_loop.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v3/client_event_loop.rs @@ -18,7 +18,6 @@ use buttplug_core::{ errors::{ButtplugDeviceError, ButtplugError}, message::ButtplugDeviceMessage, }; -use futures::channel::oneshot; use buttplug_server::message::{ ButtplugClientMessageV3, ButtplugServerMessageV3, @@ -26,6 +25,7 @@ use buttplug_server::message::{ DeviceMessageInfoV3, }; use dashmap::DashMap; +use futures::channel::oneshot; use log::*; use std::sync::{ Arc, diff --git a/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs b/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs index e9e89a57..1f3ab01c 100644 --- a/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs +++ b/crates/buttplug_tests/tests/util/device_test/client/client_v4/mod.rs @@ -131,9 +131,11 @@ async fn run_test_client_command(command: &TestClientCommand, device: &ButtplugC .map(|(_, x)| x) .collect(); let f = rotate_features[cmd.index() as usize].clone(); - f.run_output(&ClientDeviceOutputCommand::Rotate(ClientDeviceCommandValue::Percent( - cmd.speed() * if cmd.clockwise() { 1f64 } else { -1f64 }, - ))) + f.run_output(&ClientDeviceOutputCommand::Rotate( + ClientDeviceCommandValue::Percent( + cmd.speed() * if cmd.clockwise() { 1f64 } else { -1f64 }, + ), + )) }) .collect(); futures::future::try_join_all(fut_vec).await.unwrap(); @@ -153,7 +155,8 @@ async fn run_test_client_command(command: &TestClientCommand, device: &ButtplugC .get(OutputType::HwPositionWithDuration) .unwrap() .step_count() as f64) - .ceil() as u32).into(), + .ceil() as u32) + .into(), cmd.duration(), )) }) diff --git a/crates/intiface_engine/src/backdoor_server.rs b/crates/intiface_engine/src/backdoor_server.rs index 577ed9b8..8a819162 100644 --- a/crates/intiface_engine/src/backdoor_server.rs +++ b/crates/intiface_engine/src/backdoor_server.rs @@ -46,11 +46,10 @@ impl BackdoorServer { tokio::spawn(async move { // Backdoor server can always use latest spec. if let Err(e) = server - .start( - ButtplugRemoteServerConnector::<_, ButtplugServerJSONSerializer>::new( - ButtplugStreamTransport::new(s_out, r_in), - ) - ) + .start(ButtplugRemoteServerConnector::< + _, + ButtplugServerJSONSerializer, + >::new(ButtplugStreamTransport::new(s_out, r_in))) .await { // We can't do much if the server fails, but we *can* yell into the logs! diff --git a/crates/intiface_engine/src/buttplug_server.rs b/crates/intiface_engine/src/buttplug_server.rs index 76323d17..8cd501a5 100644 --- a/crates/intiface_engine/src/buttplug_server.rs +++ b/crates/intiface_engine/src/buttplug_server.rs @@ -160,22 +160,24 @@ pub async fn run_server( ) -> Result<(), ButtplugServerConnectorError> { if let Some(port) = options.websocket_port() { server - .start( - ButtplugRemoteServerConnector::<_, ButtplugServerJSONSerializer>::new( - ButtplugWebsocketServerTransportBuilder::default() - .port(port) - .listen_on_all_interfaces(options.websocket_use_all_interfaces()) - .finish(), - ) - ) + .start(ButtplugRemoteServerConnector::< + _, + ButtplugServerJSONSerializer, + >::new( + ButtplugWebsocketServerTransportBuilder::default() + .port(port) + .listen_on_all_interfaces(options.websocket_use_all_interfaces()) + .finish(), + )) .await } else if let Some(addr) = options.websocket_client_address() { server - .start( - ButtplugRemoteServerConnector::<_, ButtplugServerJSONSerializer>::new( - ButtplugWebsocketClientTransport::new_insecure_connector(addr), - ) - ) + .start(ButtplugRemoteServerConnector::< + _, + ButtplugServerJSONSerializer, + >::new( + ButtplugWebsocketClientTransport::new_insecure_connector(addr), + )) .await } else { panic!( diff --git a/examples/src/bin/application.rs b/examples/src/bin/application.rs index 711df62c..41b91999 100644 --- a/examples/src/bin/application.rs +++ b/examples/src/bin/application.rs @@ -9,7 +9,13 @@ // 3. Run: cargo run --bin application use buttplug_client::{ - ButtplugClient, ButtplugClientDevice, ButtplugClientError, ButtplugClientEvent, connector::ButtplugRemoteClientConnector, device::ClientDeviceOutputCommand, serializer::ButtplugClientJSONSerializer + ButtplugClient, + ButtplugClientDevice, + ButtplugClientError, + ButtplugClientEvent, + connector::ButtplugRemoteClientConnector, + device::ClientDeviceOutputCommand, + serializer::ButtplugClientJSONSerializer, }; use buttplug_core::message::{InputType, OutputType}; use buttplug_transport_websocket_tungstenite::ButtplugWebsocketClientTransport; @@ -137,8 +143,7 @@ async fn main() -> anyhow::Result<()> { client.stop_scanning().await?; // Step 5: Check what devices we found - let devices: Vec = - client.devices().into_values().collect(); + let devices: Vec = client.devices().into_values().collect(); if devices.is_empty() { println!("No devices found. Make sure your device is:"); @@ -183,7 +188,10 @@ async fn main() -> anyhow::Result<()> { let intensity = percent as f64 / 100.0; for device in &devices { if !device.output_available(OutputType::Vibrate) { - match device.run_output(&ClientDeviceOutputCommand::Vibrate(intensity.into())).await { + match device + .run_output(&ClientDeviceOutputCommand::Vibrate(intensity.into())) + .await + { Ok(_) => println!(" {}: vibrating at {}%", device.name(), percent), Err(e) => println!(" {}: error - {}", device.name(), e), } diff --git a/examples/src/bin/device_control.rs b/examples/src/bin/device_control.rs index 4d5655a8..436ec561 100644 --- a/examples/src/bin/device_control.rs +++ b/examples/src/bin/device_control.rs @@ -1,5 +1,9 @@ use buttplug_client::{ - ButtplugClient, ButtplugClientError, connector::ButtplugRemoteClientConnector, device::{ClientDeviceCommandValue, ClientDeviceOutputCommand}, serializer::ButtplugClientJSONSerializer + ButtplugClient, + ButtplugClientError, + connector::ButtplugRemoteClientConnector, + device::{ClientDeviceCommandValue, ClientDeviceOutputCommand}, + serializer::ButtplugClientJSONSerializer, }; use buttplug_core::message::OutputType; @@ -68,7 +72,11 @@ async fn main() -> anyhow::Result<()> { // We can use the convenience functions on ButtplugClientDevice to // send the message. This version sets all of the motors on a // vibrating device to the same speed. - test_client_device.run_output(&ClientDeviceOutputCommand::Vibrate(ClientDeviceCommandValue::Percent(0.5f64))).await?; + test_client_device + .run_output(&ClientDeviceOutputCommand::Vibrate( + ClientDeviceCommandValue::Percent(0.5f64), + )) + .await?; // If we wanted to just set one motor on and the other off, we could // try this version that uses an array. It'll throw an exception if @@ -87,7 +95,11 @@ async fn main() -> anyhow::Result<()> { // Just set all of the vibrators to full speed. if vibrator_count > 0 { - test_client_device.run_output(&ClientDeviceOutputCommand::Vibrate(ClientDeviceCommandValue::Steps(10))).await?; + test_client_device + .run_output(&ClientDeviceOutputCommand::Vibrate( + ClientDeviceCommandValue::Steps(10), + )) + .await?; } else { println!("Device does not have > 1 vibrators, not running multiple vibrator test."); } @@ -99,7 +111,11 @@ async fn main() -> anyhow::Result<()> { println!("Trying error"); // If we try to send a command to a device after the client has // disconnected, we'll get an exception thrown. - let vibrate_result = test_client_device.run_output(&ClientDeviceOutputCommand::Vibrate(ClientDeviceCommandValue::Steps(30))).await; + let vibrate_result = test_client_device + .run_output(&ClientDeviceOutputCommand::Vibrate( + ClientDeviceCommandValue::Steps(30), + )) + .await; if let Err(ButtplugClientError::ButtplugConnectorError(error)) = vibrate_result { println!("Tried to send after disconnection! Error: "); println!("{}", error); diff --git a/examples/src/bin/device_tester.rs b/examples/src/bin/device_tester.rs index 0480560e..f23bb0c5 100644 --- a/examples/src/bin/device_tester.rs +++ b/examples/src/bin/device_tester.rs @@ -10,9 +10,7 @@ use tracing::Level; use buttplug_client::device::{ClientDeviceFeature, ClientDeviceOutputCommand}; use buttplug_client::{ButtplugClient, ButtplugClientDevice, ButtplugClientEvent}; use buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder; -use buttplug_core::message::{ - OutputType, -}; +use buttplug_core::message::OutputType; use buttplug_server::ButtplugServerBuilder; use buttplug_server::device::ServerDeviceManagerBuilder; use buttplug_server_device_config::load_protocol_configs; @@ -96,7 +94,9 @@ async fn device_tester() { dev.device_features().iter().for_each(|(_, feature)| { let outs = feature.feature().output().clone().unwrap_or_default(); if let Some(out) = outs.get(OutputType::Vibrate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Vibrate((out.step_count() as i32).into()))); + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Vibrate( + (out.step_count() as i32).into(), + ))); println!( "{} ({}) should start vibrating on feature {}!", dev.name(), @@ -104,7 +104,9 @@ async fn device_tester() { feature.feature_index() ); } else if let Some(out) = outs.get(OutputType::Rotate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate((*out.step_limit().end()).into()))); + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate( + (*out.step_limit().end()).into(), + ))); println!( "{} ({}) should start rotating on feature {}!", dev.name(), @@ -112,7 +114,9 @@ async fn device_tester() { feature.feature_index() ); } else if let Some(out) = outs.get(OutputType::Oscillate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Oscillate(out.step_count().into()))); + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Oscillate( + out.step_count().into(), + ))); println!( "{} ({}) should start oscillating on feature {}!", dev.name(), @@ -120,7 +124,9 @@ async fn device_tester() { feature.feature_index() ); } else if let Some(out) = outs.get(OutputType::Constrict) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Constrict(out.step_count().into()))); + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Constrict( + out.step_count().into(), + ))); println!( "{} ({}) should start constricting on feature {}!", dev.name(), @@ -128,9 +134,9 @@ async fn device_tester() { feature.feature_index() ); } else if let Some(out) = outs.get(OutputType::Temperature) { - cmds.push( - feature.run_output(&ClientDeviceOutputCommand::Temperature((*out.step_limit().end()).into())), - ); + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Temperature( + (*out.step_limit().end()).into(), + ))); println!( "{} ({}) should start heating on feature {}!", dev.name(), diff --git a/examples/src/bin/errors.rs b/examples/src/bin/errors.rs index a9ce3d9f..984c7186 100644 --- a/examples/src/bin/errors.rs +++ b/examples/src/bin/errors.rs @@ -12,8 +12,8 @@ fn handle_error(error: ButtplugClientError) { ButtplugError::ButtplugPingError(_details) => {} ButtplugError::ButtplugUnknownError(_details) => {} }, - ButtplugClientError::ButtplugOutputCommandConversionError(_details) => {}, - ButtplugClientError::ButtplugMultipleInputAvailableError(_details) => {}, + ButtplugClientError::ButtplugOutputCommandConversionError(_details) => {} + ButtplugClientError::ButtplugMultipleInputAvailableError(_details) => {} } } From 77de8015c8d720aba12118f0af4ec42780db3110 Mon Sep 17 00:00:00 2001 From: blackspherefollower Date: Fri, 6 Feb 2026 17:07:55 +0000 Subject: [PATCH 2/4] fix: Fix a few more JoyHubs --- .../buttplug-device-config-v4.json | 63 ++++++++++++++----- .../device-config-v4/protocols/joyhub.yml | 31 +++++++-- .../device-config-v4/version.yaml | 2 +- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json index 5070c42e..12079334 100644 --- a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json +++ b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json @@ -1,7 +1,7 @@ { "version": { "major": 4, - "minor": 167 + "minor": 173 }, "protocols": { "activejoy": { @@ -5135,7 +5135,9 @@ "J-Mirage3", "J-Maelstrom", "J-Dodge", - "J-Pyt" + "J-Pyt", + "J-Perseus", + "J-Pogo" ], "services": { "0000ffa0-0000-1000-8000-00805f9b34fb": { @@ -5740,15 +5742,11 @@ { "features": [ { - "id": "7ccaa3e6-d5d3-441b-9b2d-4b97eeccbb1e", - "index": 3 - }, - { - "description": "Air Pump", + "description": "Suction Vibrator", "id": "0d3b3010-d438-4899-b1c2-d81bff0c6714", - "index": 4, + "index": 0, "output": { - "constrict": { + "vibrate": { "value": [ 0, 255 @@ -8618,7 +8616,7 @@ }, { "id": "94025679-badf-49bb-a247-1dab022d9204", - "index": 1, + "index": 2, "output": { "vibrate": { "value": [ @@ -8891,6 +8889,39 @@ "J-Pyt" ], "name": "JoyHub Pyt" + }, + { + "features": [ + { + "id": "96f2b666-1663-4b59-83a3-5b9f95097095", + "index": 0, + "output": { + "vibrate": { + "value": [ + 0, + 255 + ] + } + } + }, + { + "id": "ddff38ef-a553-4e6a-85cd-40a1143452f5", + "index": 1, + "output": { + "oscillate": { + "value": [ + 0, + 255 + ] + } + } + } + ], + "id": "53ce5d2f-d0c9-448b-8df5-4f739bd44a4b", + "identifier": [ + "J-Pogo" + ], + "name": "JoyHub Pogo" } ], "defaults": { @@ -12207,12 +12238,6 @@ "id": "c4b2855d-5ecc-4010-8a8d-17fd3e51cc57", "index": 0, "output": { - "oscillate": { - "value": [ - 0, - 20 - ] - }, "hw_position_with_duration": { "duration": [ 0, @@ -12222,6 +12247,12 @@ 0, 100 ] + }, + "oscillate": { + "value": [ + 0, + 20 + ] } } }, diff --git a/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml b/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml index 43b4518f..515fe594 100644 --- a/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml +++ b/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml @@ -356,16 +356,14 @@ configurations: - J-ROSELLA3 name: JoyHub Rose Love features: - - id: 7ccaa3e6-d5d3-441b-9b2d-4b97eeccbb1e - index: 3 - - description: Air Pump + - description: Suction Vibrator id: 0d3b3010-d438-4899-b1c2-d81bff0c6714 output: - constrict: + vibrate: value: - 0 - 255 - index: 4 + index: 0 id: ca36d3a7-c305-45e3-b8f7-3106b36b233a - identifier: - J-DukeDazzle2 @@ -2034,7 +2032,7 @@ configurations: value: - 0 - 255 - index: 1 + index: 2 id: c31f74b8-c859-47ab-8b1c-66745cb11355 - identifier: - J-Melody @@ -2185,6 +2183,25 @@ configurations: - 255 index: 1 id: d5bd0e69-7bdc-4ad7-80ab-6c7a2255208f +- identifier: + - J-Pogo + name: JoyHub Pogo + features: + - id: 96f2b666-1663-4b59-83a3-5b9f95097095 + output: + vibrate: + value: + - 0 + - 255 + index: 0 + - id: ddff38ef-a553-4e6a-85cd-40a1143452f5 + output: + oscillate: + value: + - 0 + - 255 + index: 1 + id: 53ce5d2f-d0c9-448b-8df5-4f739bd44a4b communication: - btle: names: @@ -2313,6 +2330,8 @@ communication: - J-Maelstrom - J-Dodge - J-Pyt + - J-Perseus + - J-Pogo services: 0000ffa0-0000-1000-8000-00805f9b34fb: tx: 0000ffa1-0000-1000-8000-00805f9b34fb diff --git a/crates/buttplug_server_device_config/device-config-v4/version.yaml b/crates/buttplug_server_device_config/device-config-v4/version.yaml index 74f89363..e4fa1f99 100644 --- a/crates/buttplug_server_device_config/device-config-v4/version.yaml +++ b/crates/buttplug_server_device_config/device-config-v4/version.yaml @@ -1,3 +1,3 @@ version: major: 4 - minor: 167 + minor: 173 From e460c8142e76acb5ed0d3bb04848e6c7a94d99d6 Mon Sep 17 00:00:00 2001 From: blackspherefollower Date: Mon, 9 Feb 2026 10:47:19 +0000 Subject: [PATCH 3/4] feat: add JoyHub Pyro --- .../buttplug-device-config-v4.json | 38 ++++++++++++++++++- .../device-config-v4/protocols/joyhub.yml | 20 ++++++++++ .../device-config-v4/version.yaml | 2 +- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json index 12079334..6bf9e21e 100644 --- a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json +++ b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json @@ -1,7 +1,7 @@ { "version": { "major": 4, - "minor": 173 + "minor": 174 }, "protocols": { "activejoy": { @@ -5137,7 +5137,8 @@ "J-Dodge", "J-Pyt", "J-Perseus", - "J-Pogo" + "J-Pogo", + "J-Pyro" ], "services": { "0000ffa0-0000-1000-8000-00805f9b34fb": { @@ -8922,6 +8923,39 @@ "J-Pogo" ], "name": "JoyHub Pogo" + }, + { + "features": [ + { + "id": "304f0407-7de2-4c6f-adfc-079c61044315", + "index": 0, + "output": { + "vibrate": { + "value": [ + 0, + 255 + ] + } + } + }, + { + "id": "b7471ca3-cbd9-4caf-affd-54865699f439", + "index": 1, + "output": { + "rotate": { + "value": [ + 0, + 255 + ] + } + } + } + ], + "id": "682796f5-1855-4f5c-ac58-c7a41b9f80dd", + "identifier": [ + "J-Pyro" + ], + "name": "JoyHub Pyro" } ], "defaults": { diff --git a/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml b/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml index 515fe594..b038d368 100644 --- a/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml +++ b/crates/buttplug_server_device_config/device-config-v4/protocols/joyhub.yml @@ -2202,6 +2202,25 @@ configurations: - 255 index: 1 id: 53ce5d2f-d0c9-448b-8df5-4f739bd44a4b +- identifier: + - J-Pyro + name: JoyHub Pyro + features: + - id: 304f0407-7de2-4c6f-adfc-079c61044315 + output: + vibrate: + value: + - 0 + - 255 + index: 0 + - id: b7471ca3-cbd9-4caf-affd-54865699f439 + output: + rotate: + value: + - 0 + - 255 + index: 1 + id: 682796f5-1855-4f5c-ac58-c7a41b9f80dd communication: - btle: names: @@ -2332,6 +2351,7 @@ communication: - J-Pyt - J-Perseus - J-Pogo + - J-Pyro services: 0000ffa0-0000-1000-8000-00805f9b34fb: tx: 0000ffa1-0000-1000-8000-00805f9b34fb diff --git a/crates/buttplug_server_device_config/device-config-v4/version.yaml b/crates/buttplug_server_device_config/device-config-v4/version.yaml index e4fa1f99..c48a50b0 100644 --- a/crates/buttplug_server_device_config/device-config-v4/version.yaml +++ b/crates/buttplug_server_device_config/device-config-v4/version.yaml @@ -1,3 +1,3 @@ version: major: 4 - minor: 173 + minor: 174 From 629a43cf67293dd3306dbccc9ebb60ca0144655e Mon Sep 17 00:00:00 2001 From: blackspherefollower Date: Thu, 5 Feb 2026 15:59:34 +0000 Subject: [PATCH 4/4] feat: add HoneyPlayBox devices support --- crates/buttplug_server/Cargo.toml | 1 + .../src/device/protocol_impl/honeyplaybox.rs | 538 ++++++++++++++ .../src/device/protocol_impl/mod.rs | 5 + .../buttplug-device-config-v4.json | 674 ++++++++++++++++++ .../protocols/honeyplaybox.yml | 391 ++++++++++ .../tests/test_device_protocols.rs | 4 + .../test_honeyplaybox_protocol.yaml | 47 ++ 7 files changed, 1660 insertions(+) create mode 100644 crates/buttplug_server/src/device/protocol_impl/honeyplaybox.rs create mode 100644 crates/buttplug_server_device_config/device-config-v4/protocols/honeyplaybox.yml create mode 100644 crates/buttplug_tests/tests/util/device_test/device_test_case/test_honeyplaybox_protocol.yaml diff --git a/crates/buttplug_server/Cargo.toml b/crates/buttplug_server/Cargo.toml index f8c15313..6919e0e4 100644 --- a/crates/buttplug_server/Cargo.toml +++ b/crates/buttplug_server/Cargo.toml @@ -52,6 +52,7 @@ paste = "1.0.15" aes = { version = "0.8.4" } ecb = { version = "0.1.2", features = ["std"] } sha2 = { version = "0.10.9", features = ["std"] } +md-5 = "0.10.6" byteorder = "1.5.0" # Used by several packages, but we need to bring in the JS feature for wasm. Pinned at 0.8 until # dependencies update diff --git a/crates/buttplug_server/src/device/protocol_impl/honeyplaybox.rs b/crates/buttplug_server/src/device/protocol_impl/honeyplaybox.rs new file mode 100644 index 00000000..9561523e --- /dev/null +++ b/crates/buttplug_server/src/device/protocol_impl/honeyplaybox.rs @@ -0,0 +1,538 @@ +// Buttplug Rust Source Code File - See https://buttplug.io for more info. +// +// Copyright 2016-2026 Nonpolynomial Labs LLC. All rights reserved. +// +// Licensed under the BSD 3-Clause license. See LICENSE file in the project root +// for full license information. + +use crate::device::hardware::HardwareReadCmd; +use crate::device::{ + hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareSubscribeCmd, HardwareWriteCmd}, + protocol::{ + ProtocolHandler, + ProtocolIdentifier, + ProtocolInitializer, + generic_protocol_initializer_setup, + }, +}; +use async_trait::async_trait; +use buttplug_core::message::{InputReadingV4, InputTypeReading, InputValue}; +use buttplug_core::util::async_manager; +use buttplug_core::{errors::ButtplugDeviceError, util::sleep}; +use buttplug_server_device_config::Endpoint; +use buttplug_server_device_config::{ + ProtocolCommunicationSpecifier, + ServerDeviceDefinition, + UserDeviceIdentifier, +}; +use futures::FutureExt; +use futures_util::future::BoxFuture; +use md5::{Digest, Md5}; +use std::time::Instant; +use std::{ + sync::{ + Arc, + atomic::{AtomicU8, Ordering}, + }, + time::Duration, +}; +use tokio::select; +use tokio::sync::RwLock; +use uuid::{Uuid, uuid}; + +const HONEY_PLAYBOX_KEEPALIVE_INTERVAL: u64 = 100; +const HONEY_PLAYBOX_KEEPALIVE_DELAY: u64 = 500; +const HONEY_PLAYBOX_COMMAND_RETRY: u64 = 3; +const HONEY_PLAYBOX_PROTOCOL_UUID: Uuid = uuid!("0d1598bd-6845-4950-8aa0-416b1115fc7c"); + +// The secret key used for MD5 signing. +const SECRET: [u8; 16] = [ + 0x8b, 0xe3, 0xfd, 0x04, 0x68, 0x35, 0x09, 0x86, 0x12, 0x1a, 0xbf, 0x03, 0x30, 0xe9, 0xe3, 0xc5, +]; + +generic_protocol_initializer_setup!(HoneyPlayBox, "honeyplaybox"); + +#[derive(Default)] +pub struct HoneyPlayBoxInitializer {} + +#[async_trait] +impl ProtocolInitializer for HoneyPlayBoxInitializer { + async fn initialize( + &mut self, + hardware: Arc, + device_definition: &ServerDeviceDefinition, + ) -> Result, ButtplugDeviceError> { + let feature_count = device_definition + .features() + .iter() + .filter(|x| x.1.output().is_some()) + .count(); + + let mut event_receiver = hardware.event_stream(); + hardware + .subscribe(&HardwareSubscribeCmd::new( + HONEY_PLAYBOX_PROTOCOL_UUID, + Endpoint::Rx, + )) + .await?; + + let mut collector = FrameCollector::new(); + let mut count = 0; + let mut counter = 0; + + loop { + hardware + .write_value(&HardwareWriteCmd::new( + &[HONEY_PLAYBOX_PROTOCOL_UUID], + Endpoint::Tx, + FrameCodec::build_frame(0xB1, 0x01, &[0x00, 0x10], counter), + true, + )) + .await?; + counter = counter.wrapping_add(1); + + // loop reads because the response is over multiple bluetooth packets + for _ in 0..3 { + select! { + event = event_receiver.recv().fuse() => { + if let Ok(HardwareEvent::Notification(_, _, payload)) = event { + let frames = collector.push_bytes(&payload); + for frame in frames { + if let Some(packet) = FrameCodec::parse_response_frame(&frame) { + match packet.response_code { + Some(0x9000) => { + trace!("HoneyPlayBox handshake success on attempt {}", count+1); + if let Some(rand) = FrameCodec::extract_random(&frame) { + info!("HoneyPlayBox handshake random token {:?}", rand.clone()); + return Ok(Arc::new(HoneyPlayBox::new(hardware, rand, feature_count, counter))); + } + warn!("HoneyPlayBox: No random token in frame (handshake attempt {})", count+1); + } + Some(code) => { + let msg = format!("Handshake failed, code={:04X}", code); + error!("HoneyPlayBox: {}", msg); + return Err(ButtplugDeviceError::ProtocolSpecificError( + "HoneyPlayBox".into(), + msg, + )); + } + None => warn!("HoneyPlayBox: No response code in frame (handshake attempt {})", count+1), + } + } + } + } else { + return Err( + ButtplugDeviceError::ProtocolSpecificError( + "HoneyPlayBox".to_owned(), + "HoneyPlayBox Device disconnected during handshake.".to_owned(), + ), + ); + } + } + _ = sleep(Duration::from_secs(count+1)).fuse() => { + count += 1; + if count > HONEY_PLAYBOX_COMMAND_RETRY { + error!("HoneyPlayBox Device timed out while waiting for handshake. ({} retries)", HONEY_PLAYBOX_COMMAND_RETRY); + return Err(ButtplugDeviceError::ProtocolSpecificError( + "HoneyPlayBox".into(), + format!("Handshake failed after {} retries", HONEY_PLAYBOX_COMMAND_RETRY), + )); + } + } + } + } + } + } +} + +pub struct HoneyPlayBox { + random_key: [u8; 16], + last_command: Arc>, + packet_id: Arc, + last_send: Arc>, +} + +async fn hpb_keepalive( + device: Arc, + random_key: [u8; 16], + last_command: Arc>, + packet_id: Arc, + last_send: Arc>, +) { + loop { + { + if let Ok(mut last_time) = last_send.try_write() { + if last_time.elapsed().as_millis() >= HONEY_PLAYBOX_KEEPALIVE_DELAY as u128 { + *last_time = Instant::now(); + let mut groups = vec![]; + for i in 0..last_command.len() { + groups.push(VibrateGroup { + work_mode: 1, + motor_pos: i as u8, + time_100ms: 60, + freq: 0, + strength: last_command[i].load(Ordering::Relaxed), + }); + } + + let payload = build_vibrate_data(&random_key, &groups) + .map_err(|e| ButtplugDeviceError::ProtocolSpecificError("HoneyPlayBox".into(), e)); + packet_id.store( + packet_id.load(Ordering::Relaxed).wrapping_add(1), + Ordering::Relaxed, + ); + let data = FrameCodec::build_frame( + 0xB1, + 0x03, + &payload.unwrap(), + packet_id.load(Ordering::Relaxed), + ); + + trace!("HoneyPlayBox: sending keepalive"); + if device + .write_value(&HardwareWriteCmd::new( + &[HONEY_PLAYBOX_PROTOCOL_UUID], + Endpoint::Tx, + data, + true, + )) + .await + .is_err() + { + return; + } + } + } + } + sleep(Duration::from_millis(HONEY_PLAYBOX_KEEPALIVE_INTERVAL)).await; + } +} + +impl HoneyPlayBox { + fn new(hardware: Arc, random_key: [u8; 16], feature_count: usize, count: u8) -> Self { + let last_command = Arc::new( + (0..feature_count) + .map(|_| AtomicU8::new(0)) + .collect::>(), + ); + let packet_id = Arc::new(AtomicU8::new(count)); + let last_send = Arc::new(RwLock::new(Instant::now())); + async_manager::spawn(hpb_keepalive( + hardware.clone(), + random_key, + last_command.clone(), + packet_id.clone(), + last_send.clone(), + )); + + Self { + random_key, + last_command, + packet_id, + last_send, + } + } + + fn send_command( + &self, + feature_index: u32, + speed: u32, + ) -> Result, ButtplugDeviceError> { + // Best effort last command time update + let last_send = self.last_send.clone(); + async_manager::spawn(async move { + if let Ok(mut last_time) = last_send.try_write() { + *last_time = Instant::now(); + } + }); + + self.last_command[feature_index as usize].store(speed as u8, Ordering::Relaxed); + let mut groups = vec![]; + for i in 0..self.last_command.len() { + groups.push(VibrateGroup { + work_mode: 1, + motor_pos: i as u8, + time_100ms: 60, + freq: 0, + strength: self.last_command[i].load(Ordering::Relaxed), + }); + } + + let payload = build_vibrate_data(&self.random_key, &groups) + .map_err(|e| ButtplugDeviceError::ProtocolSpecificError("HoneyPlayBox".into(), e))?; + self.packet_id.store( + self.packet_id.load(Ordering::Relaxed).wrapping_add(1), + Ordering::Relaxed, + ); + let data = + FrameCodec::build_frame(0xB1, 0x03, &payload, self.packet_id.load(Ordering::Relaxed)); + + Ok(vec![ + HardwareWriteCmd::new(&[HONEY_PLAYBOX_PROTOCOL_UUID], Endpoint::Tx, data, true).into(), + ]) + } +} + +#[async_trait] +impl ProtocolHandler for HoneyPlayBox { + fn handle_output_vibrate_cmd( + &self, + feature_index: u32, + _feature_id: Uuid, + speed: u32, + ) -> Result, ButtplugDeviceError> { + self.send_command(feature_index, speed) + } + + fn handle_output_oscillate_cmd( + &self, + feature_index: u32, + _feature_id: Uuid, + speed: u32, + ) -> Result, ButtplugDeviceError> { + self.send_command(feature_index, speed) + } + + fn handle_output_rotate_cmd( + &self, + feature_index: u32, + _feature_id: Uuid, + speed: i32, + ) -> Result, ButtplugDeviceError> { + self.send_command(feature_index, speed.abs() as u32) + } + + fn handle_battery_level_cmd( + &self, + device_index: u32, + device: Arc, + feature_index: u32, + feature_id: Uuid, + ) -> BoxFuture<'_, Result> { + debug!("Trying to get battery reading."); + let msg = HardwareReadCmd::new(feature_id, Endpoint::RxBLEBattery, 20, 0); + let fut = device.read_value(&msg); + async move { + let hw_msg = fut.await?; + let data = hw_msg.data(); + let battery_reading = InputReadingV4::new( + device_index, + feature_index, + InputTypeReading::Battery(InputValue::new(data[0])), + ); + debug!("Got battery reading: {}", data[0]); + Ok(battery_reading) + } + .boxed() + } +} + +#[derive(Clone, Debug)] +pub struct VibrateGroup { + pub work_mode: u8, + pub motor_pos: u8, + pub time_100ms: u16, + pub freq: u16, + pub strength: u8, +} + +fn encode_vibrate_group(g: &VibrateGroup) -> [u8; 8] { + let mode_pos: u8 = ((g.motor_pos & 0x0F) << 4) | (g.work_mode & 0x0F); + [ + mode_pos, + 0x00, + 0x05, + (g.time_100ms >> 8) as u8, + (g.time_100ms & 0xFF) as u8, + (g.freq >> 8) as u8, + (g.freq & 0xFF) as u8, + g.strength, + ] +} + +fn build_vibrate_data(random: &[u8], groups: &[VibrateGroup]) -> Result, String> { + if random.len() != 16 { + return Err("random len not valid".into()); + } + let mut data: Vec = Vec::new(); + for g in groups { + data.extend_from_slice(&encode_vibrate_group(g)); + } + let data_len: u16 = (data.len() as u16).saturating_add(8); + let type_byte: u8 = 0xB1; + let cmd_byte: u8 = 0x03; + let mut hasher = Md5::new(); + hasher.update([type_byte]); + hasher.update([cmd_byte]); + hasher.update([(data_len >> 8) as u8]); + hasher.update([(data_len & 0xFF) as u8]); + hasher.update(&data); + hasher.update(&SECRET); + hasher.update(random); + let digest = hasher.finalize(); + let md58 = &digest.as_slice()[..8]; + let mut payload = data.clone(); + payload.extend_from_slice(md58); + Ok(payload) +} + +// Represents a complete decoded response frame. +pub struct ResponsePacket { + pub cmd_type: u8, + pub cmd: u8, + pub length: u16, + pub data: Vec, + pub response_code: Option, +} + +// Provides static helpers for encoding and decoding BLE frames. +pub struct FrameCodec; + +impl FrameCodec { + pub fn build_frame(cmd_type: u8, cmd: u8, data: &[u8], counter: u8) -> Vec { + let stx = 0x02u8; + let flag = [0xA5u8, 0x5A, 0x55, 0xAA, 0xF0]; + let len = data.len() as u16; + let etx = 0x03u8; + + let mut crc_data = vec![cmd_type, cmd, (len >> 8) as u8, (len & 0xFF) as u8]; + crc_data.extend_from_slice(data); + let crc16 = Self::crc16_modbus(&crc_data); + + let mut frame = Vec::new(); + frame.push(stx); + frame.extend_from_slice(&flag); + frame.push(counter); + frame.push(cmd_type); + frame.push(cmd); + frame.push((len >> 8) as u8); + frame.push((len & 0xFF) as u8); + frame.extend_from_slice(data); + frame.push(etx); + frame.push((crc16 >> 8) as u8); + frame.push((crc16 & 0xFF) as u8); + frame + } + + pub fn crc16_modbus(data: &[u8]) -> u16 { + let mut crc: u16 = 0xFFFF; + for &b in data { + crc ^= b as u16; + for _ in 0..8 { + crc = if crc & 1 != 0 { + (crc >> 1) ^ 0xA001 + } else { + crc >> 1 + }; + } + } + crc + } + + pub fn extract_random(frame: &[u8]) -> Option<[u8; 16]> { + let data_len = ((frame[9] as usize) << 8) | (frame[10] as usize); + let data_start = 11; + let data_end = data_start + data_len; + if frame.len() < data_end || data_len < 18 { + return None; + } + let rand_start = data_start + 2; + let rand_end = rand_start + 16; + if rand_end <= data_end { + let mut r = [0u8; 16]; + r.copy_from_slice(&frame[rand_start..rand_end]); + Some(r) + } else { + None + } + } + + pub fn parse_response_frame(frame: &[u8]) -> Option { + if frame.len() < 15 { + return None; + } + let cmd_type = frame[7]; + let cmd = frame[8]; + let len = ((frame[9] as u16) << 8) | frame[10] as u16; + let data_start = 11; + let data_end = data_start + len as usize; + if frame.len() < data_end + 3 { + return None; + } + let data = frame[data_start..data_end].to_vec(); + let response_code = if data.len() >= 2 { + Some(((data[0] as u16) << 8) | data[1] as u16) + } else { + None + }; + Some(ResponsePacket { + cmd_type, + cmd, + length: len, + data, + response_code, + }) + } +} + +pub struct FrameCollector { + buffer: Vec, +} + +impl FrameCollector { + pub fn new() -> Self { + Self { buffer: Vec::new() } + } + + pub fn push_bytes(&mut self, data: &[u8]) -> Vec> { + self.buffer.extend_from_slice(data); + let mut frames = Vec::new(); + + loop { + let start_pos = match self + .buffer + .windows(6) + .position(|w| w == [0x02, 0xA5, 0x5A, 0x55, 0xAA, 0xF0]) + { + Some(pos) => pos, + None => { + self.buffer.clear(); + break; + } + }; + // Discard the garbage bytes before the frame header. + if start_pos > 0 { + self.buffer.drain(0..start_pos); + } + // If the buffer length is shorter than the minimum frame length (header + flags + len + etc.) + if self.buffer.len() < 11 { + break; + } + + let len_hi = *self.buffer.get(9).unwrap_or(&0) as usize; + let len_lo = *self.buffer.get(10).unwrap_or(&0) as usize; + let data_len = (len_hi << 8) | len_lo; + // Calculate the complete frame length: STX + FLAG + CNT + TYPE/CMD/LEN(4B) + DATA + ETX + CRC16 + let frame_len = 1 + 5 + 1 + 1 + 1 + 2 + data_len + 1 + 2; + + if self.buffer.len() < frame_len { + break; + } + let etx_pos = 1 + 5 + 1 + 1 + 1 + 2 + data_len; + if self.buffer.get(etx_pos) != Some(&0x03) { + // If it's not a valid frame tail, discard the first byte and continue. + self.buffer.drain(0..1); + continue; + } + // Full frame + let frame: Vec = self.buffer.drain(0..frame_len).collect(); + trace!("FrameCollector: assembled frame (len={})", frame.len()); + frames.push(frame); + // If the remaining data is too short to construct a new frame, exit and wait. + if self.buffer.len() < 11 { + break; + } + } + frames + } +} diff --git a/crates/buttplug_server/src/device/protocol_impl/mod.rs b/crates/buttplug_server/src/device/protocol_impl/mod.rs index ae0e7a5a..2999de80 100644 --- a/crates/buttplug_server/src/device/protocol_impl/mod.rs +++ b/crates/buttplug_server/src/device/protocol_impl/mod.rs @@ -36,6 +36,7 @@ pub mod galaku_pump; pub mod hgod; pub mod hismith; pub mod hismith_mini; +pub mod honeyplaybox; pub mod htk_bm; pub mod itoys; pub mod jejoue; @@ -180,6 +181,10 @@ pub fn get_default_protocol_map() -> HashMap DeviceTestCase { #[test_case("test_hismith_thrusting_cup.yaml" ; "Hismith Protocol - Thrusting Cup")] #[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")] #[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")] +#[test_case("test_honeyplaybox_protocol.yaml" ; "HoneyPlayBox Protocol - VIBROSA")] #[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")] #[test_case("test_itoys_twinklingstars.yaml" ; "iToys Protocol - Twinkling Stars")] #[test_case("test_joyhub_moonhorn.yaml" ; "JoyHub Protocol - Moonhorn")] @@ -178,6 +179,7 @@ async fn test_device_protocols_embedded_v4(test_file: &str) { #[test_case("test_hismith_thrusting_cup.yaml" ; "Hismith Protocol - Thrusting Cup")] #[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")] #[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")] +#[test_case("test_honeyplaybox_protocol.yaml" ; "HoneyPlayBox Protocol - VIBROSA")] #[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")] #[test_case("test_itoys_twinklingstars.yaml" ; "iToys Protocol - Twinkling Stars")] #[test_case("test_joyhub_moonhorn.yaml" ; "JoyHub Protocol - Moonhorn")] @@ -303,6 +305,7 @@ async fn test_device_protocols_json_v4(test_file: &str) { #[test_case("test_hismith_thrusting_cup.yaml" ; "Hismith Protocol - Thrusting Cup")] #[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")] #[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")] +#[test_case("test_honeyplaybox_protocol.yaml" ; "HoneyPlayBox Protocol - VIBROSA")] #[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")] #[test_case("test_itoys_twinklingstars.yaml" ; "iToys Protocol - Twinkling Stars")] #[test_case("test_joyhub_moonhorn.yaml" ; "JoyHub Protocol - Moonhorn")] @@ -429,6 +432,7 @@ async fn test_device_protocols_embedded_v3(test_file: &str) { #[test_case("test_hismith_thrusting_cup.yaml" ; "Hismith Protocol - Thrusting Cup")] #[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")] #[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")] +#[test_case("test_honeyplaybox_protocol.yaml" ; "HoneyPlayBox Protocol - VIBROSA")] #[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")] #[test_case("test_itoys_twinklingstars.yaml" ; "iToys Protocol - Twinkling Stars")] #[test_case("test_joyhub_moonhorn.yaml" ; "JoyHub Protocol - Moonhorn")] diff --git a/crates/buttplug_tests/tests/util/device_test/device_test_case/test_honeyplaybox_protocol.yaml b/crates/buttplug_tests/tests/util/device_test/device_test_case/test_honeyplaybox_protocol.yaml new file mode 100644 index 00000000..e66c67c7 --- /dev/null +++ b/crates/buttplug_tests/tests/util/device_test/device_test_case/test_honeyplaybox_protocol.yaml @@ -0,0 +1,47 @@ +devices: + - identifier: + name: "JXT002" + expected_name: "VIBROSA" +device_init: + # Initialization + - !Commands + device_index: 0 + commands: + - !Subscribe + endpoint: rx + - !Write + endpoint: tx + data: [ 0x02, 0xA5, 0x5A, 0x55, 0xaa, 0xf0, 0x0b, 0xb1, 0x01, 0x00, 0x02, 0x00, 0x10, 0x03, 0x36, 0x86 ] + write_with_response: true + - !Events + device_index: 0 + events: + - !Notifications + - endpoint: rx + data: [ 90, 58, 49, 49, 58, 48, 48, 56, 50, 48, 53, 57, 65, 68, 51, 66, 68, 59 ] +device_commands: + - !Messages + device_index: 0 + messages: + - !Scalar + - Index: 0 + Scalar: 0.5 + ActuatorType: Vibrate + - !Commands + device_index: 0 + commands: + - !Write + endpoint: tx + data: [0x02,0xA5,0x5A,0x55,0xAA,0xF0,0x0B,0xB1,0x01,0x00,0x02,0x00,0x10,0x03,0x36,0x86] + write_with_response: true + - !Messages + device_index: 0 + messages: + - !Stop + - !Commands + device_index: 0 + commands: + - !Write + endpoint: tx + data: [0x02,0xA5,0x5A,0x55,0xAA,0xF0,0x0B,0xB1,0x01,0x00,0x02,0x00,0x10,0x03,0x36,0x86] + write_with_response: true