diff --git a/crates/buttplug_core/src/connector/transport/mod.rs b/crates/buttplug_core/src/connector/transport/mod.rs index 94b04cfb4..1033838c3 100644 --- a/crates/buttplug_core/src/connector/transport/mod.rs +++ b/crates/buttplug_core/src/connector/transport/mod.rs @@ -8,7 +8,9 @@ pub mod stream; use crate::connector::{ - ButtplugConnectorError, ButtplugConnectorResultFuture, ButtplugSerializedMessage, + ButtplugConnectorError, + ButtplugConnectorResultFuture, + ButtplugSerializedMessage, }; use displaydoc::Display; use futures::future::BoxFuture; diff --git a/crates/buttplug_core/src/errors.rs b/crates/buttplug_core/src/errors.rs index 8162d624a..a12d1f8ed 100644 --- a/crates/buttplug_core/src/errors.rs +++ b/crates/buttplug_core/src/errors.rs @@ -57,7 +57,9 @@ pub enum ButtplugHandshakeError { #[error("Expected either a ServerInfo or Error message, received {0}")] UnexpectedHandshakeMessageReceived(String), /// Expected a RequestServerInfo message to start connection. - #[error("Expected a RequestServerInfo message to start connection. Message either not received or wrong message received.")] + #[error( + "Expected a RequestServerInfo message to start connection. Message either not received or wrong message received." + )] RequestServerInfoExpected, /// Handshake already happened, cannot run handshake again. #[error("Handshake already happened, cannot run handshake again.")] diff --git a/crates/buttplug_server/src/device/protocol_impl/itoys.rs b/crates/buttplug_server/src/device/protocol_impl/itoys.rs index 437eb3fe0..8cc34bda5 100644 --- a/crates/buttplug_server/src/device/protocol_impl/itoys.rs +++ b/crates/buttplug_server/src/device/protocol_impl/itoys.rs @@ -57,7 +57,7 @@ impl ProtocolHandler for IToys { ], false, ) - .into(), + .into(), ]) } @@ -81,7 +81,7 @@ impl ProtocolHandler for IToys { ], false, ) - .into(), + .into(), ]) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/luvmazer.rs b/crates/buttplug_server/src/device/protocol_impl/luvmazer.rs index a01b0dfb7..0cae38f47 100644 --- a/crates/buttplug_server/src/device/protocol_impl/luvmazer.rs +++ b/crates/buttplug_server/src/device/protocol_impl/luvmazer.rs @@ -35,7 +35,7 @@ impl ProtocolHandler for Luvmazer { vec![0xa0, 0x0c, 0x00, 0x00, 0x64, speed as u8], false, ) - .into(), + .into(), ]) } else { Ok(vec![ @@ -45,7 +45,7 @@ impl ProtocolHandler for Luvmazer { vec![0xa0, 0x01, 0x00, feature_index as u8, 0x64, speed as u8], false, ) - .into(), + .into(), ]) } } diff --git a/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs b/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs index f22b179b4..1a2f7c7cb 100644 --- a/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs +++ b/crates/buttplug_server/src/device/protocol_impl/satisfyer.rs @@ -157,7 +157,7 @@ impl Satisfyer { impl ProtocolHandler for Satisfyer { fn keepalive_strategy(&self) -> ProtocolKeepaliveStrategy { - ProtocolKeepaliveStrategy::RepeatLastPacketStrategyWithTiming(Duration::from_secs(3)) + ProtocolKeepaliveStrategy::RepeatLastPacketStrategyWithTiming(Duration::from_millis(500)) } fn handle_output_vibrate_cmd( diff --git a/crates/buttplug_server/src/message/v4/checked_output_vec_cmd.rs b/crates/buttplug_server/src/message/v4/checked_output_vec_cmd.rs index a0849c3c2..1912bec4a 100644 --- a/crates/buttplug_server/src/message/v4/checked_output_vec_cmd.rs +++ b/crates/buttplug_server/src/message/v4/checked_output_vec_cmd.rs @@ -381,10 +381,7 @@ impl TryFromDeviceAttributes for CheckedOutputVecCmdV4 { mod tests { use super::*; use crate::message::v1::VibrateSubcommandV1; - use buttplug_core::util::{ - range::RangeInclusive, - small_vec_enum_map::SmallVecEnumMap, - }; + use buttplug_core::util::{range::RangeInclusive, small_vec_enum_map::SmallVecEnumMap}; use buttplug_server_device_config::{ RangeWithLimit, ServerDeviceFeature, diff --git a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v5.json b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v5.json index eb78890b3..1200a328b 100644 --- a/crates/buttplug_server_device_config/build-config/buttplug-device-config-v5.json +++ b/crates/buttplug_server_device_config/build-config/buttplug-device-config-v5.json @@ -1,7 +1,7 @@ { "version": { "major": 5, - "minor": 5 + "minor": 7 }, "protocols": { "activejoy": { @@ -6076,7 +6076,10 @@ "J-Vortus", "J-Phantom", "J-Thelma", - "J-Mystor" + "J-Mystor", + "J-MutantX", + "J-Marino", + "J-Jason" ], "services": { "0000ffa0-0000-1000-8000-00805f9b34fb": { @@ -10493,6 +10496,117 @@ "J-Mystor" ], "name": "JoyHub Mystor" + }, + { + "features": [ + { + "id": "e7335111-8303-4731-8e99-fefdead2a4a8", + "index": 0, + "output": { + "vibrate": { + "value": [ + 0, + 255 + ] + } + } + }, + { + "id": "f5932afb-f7f0-40d5-abc6-a2040a7a85e3", + "index": 5, + "output": { + "constrict": { + "value": [ + 0, + 7 + ] + } + } + } + ], + "id": "53a105e1-da01-4f46-b51e-d65ba8d3e302", + "identifier": [ + "J-MutantX" + ], + "name": "JoyHub Mutant X" + }, + { + "features": [ + { + "id": "88577fa4-4cb8-4691-b5ba-194205b3c812", + "index": 0, + "output": { + "rotate": { + "value": [ + 0, + 255 + ] + } + } + }, + { + "id": "482f1b86-7392-4fb8-bfac-22be525ea243", + "index": 5, + "output": { + "constrict": { + "value": [ + 0, + 9 + ] + } + } + } + ], + "id": "9065a86a-0151-47d7-aca7-4bc78ec0fcd5", + "identifier": [ + "J-Marino" + ], + "name": "JoyHub Marino" + }, + { + "features": [ + { + "id": "5d1778f7-46c5-43b8-8a1b-6f99982f2c0c", + "index": 0, + "output": { + "oscillate": { + "value": [ + 0, + 255 + ] + } + } + }, + { + "id": "bc64740f-7b23-4c49-af90-d2d48fb8a475", + "index": 1, + "output": { + "vibrate": { + "value": [ + 0, + 255 + ] + } + } + }, + { + "id": "efffd1df-55cd-498e-bf3d-0c156dbbb51c", + "index": 6, + "output": { + "temperature": { + "value": [ + 0, + 1 + ] + } + } + } + ], + "id": "adf08126-a0e5-4d35-9083-73f7f1e34256", + "identifier": [ + "J-Jason" + ], + "name": "JoyHub Jason" } ], "defaults": { diff --git a/crates/buttplug_server_device_config/device-config/protocols/joyhub.yml b/crates/buttplug_server_device_config/device-config/protocols/joyhub.yml index d4e06eb4a..eebae3c58 100644 --- a/crates/buttplug_server_device_config/device-config/protocols/joyhub.yml +++ b/crates/buttplug_server_device_config/device-config/protocols/joyhub.yml @@ -2568,6 +2568,70 @@ configurations: - 10 index: 5 id: 0dad021f-8e31-46cd-8dc4-6a5d67a70539 +- identifier: + - J-MutantX + name: JoyHub Mutant X + features: + - id: e7335111-8303-4731-8e99-fefdead2a4a8 + output: + vibrate: + value: + - 0 + - 255 + index: 0 + - id: f5932afb-f7f0-40d5-abc6-a2040a7a85e3 + output: + constrict: + value: + - 0 + - 7 + index: 5 + id: 53a105e1-da01-4f46-b51e-d65ba8d3e302 +- identifier: + - J-Marino + name: JoyHub Marino + features: + - id: 88577fa4-4cb8-4691-b5ba-194205b3c812 + output: + rotate: + value: + - 0 + - 255 + index: 0 + - id: 482f1b86-7392-4fb8-bfac-22be525ea243 + output: + constrict: + value: + - 0 + - 9 + index: 5 + id: 9065a86a-0151-47d7-aca7-4bc78ec0fcd5 +- identifier: + - J-Jason + name: JoyHub Jason + features: + - id: 5d1778f7-46c5-43b8-8a1b-6f99982f2c0c + output: + oscillate: + value: + - 0 + - 255 + index: 0 + - id: bc64740f-7b23-4c49-af90-d2d48fb8a475 + output: + vibrate: + value: + - 0 + - 255 + index: 1 + - id: efffd1df-55cd-498e-bf3d-0c156dbbb51c + output: + temperature: + value: + - 0 + - 1 + index: 6 + id: adf08126-a0e5-4d35-9083-73f7f1e34256 communication: - btle: names: @@ -2719,6 +2783,9 @@ communication: - J-Phantom - J-Thelma - J-Mystor + - J-MutantX + - J-Marino + - J-Jason services: 0000ffa0-0000-1000-8000-00805f9b34fb: tx: 0000ffa1-0000-1000-8000-00805f9b34fb diff --git a/crates/buttplug_server_device_config/device-config/version.yaml b/crates/buttplug_server_device_config/device-config/version.yaml index a43561f80..b3031ae48 100644 --- a/crates/buttplug_server_device_config/device-config/version.yaml +++ b/crates/buttplug_server_device_config/device-config/version.yaml @@ -1,3 +1,3 @@ version: major: 5 - minor: 5 + minor: 7 diff --git a/crates/buttplug_tests/tests/test_device_config.rs b/crates/buttplug_tests/tests/test_device_config.rs index df473eb97..f3175fbc9 100644 --- a/crates/buttplug_tests/tests/test_device_config.rs +++ b/crates/buttplug_tests/tests/test_device_config.rs @@ -12,10 +12,7 @@ use buttplug_server_device_config::load_protocol_configs; use futures::StreamExt; use std::time::Duration; use tokio_test::assert_ok; -use util::{ - test_client_with_device_and_custom_dcm, - test_device_manager::TestDeviceIdentifier, -}; +use util::{test_client_with_device_and_custom_dcm, test_device_manager::TestDeviceIdentifier}; const BASE_CONFIG_JSON: &str = r#" { diff --git a/crates/buttplug_tests/tests/test_disabled_device_features.rs b/crates/buttplug_tests/tests/test_disabled_device_features.rs index c6a4a7137..ff4709cc0 100644 --- a/crates/buttplug_tests/tests/test_disabled_device_features.rs +++ b/crates/buttplug_tests/tests/test_disabled_device_features.rs @@ -33,17 +33,14 @@ const USER_CONFIG_DISABLED_HW_POSITION: &str = include_str!( "util/device_test/device_test_case/config/tcode_disabled_hw_position_user_config.json" ); -const USER_CONFIG_DISABLED_POSITION: &str = include_str!( - "util/device_test/device_test_case/config/tcode_disabled_position_user_config.json" -); +const USER_CONFIG_DISABLED_POSITION: &str = + include_str!("util/device_test/device_test_case/config/tcode_disabled_position_user_config.json"); const USER_CONFIG_DISABLED_BOTH: &str = include_str!( "util/device_test/device_test_case/config/tcode_disabled_both_outputs_user_config.json" ); -fn load_dcm_with_config( - config: &str, -) -> buttplug_server_device_config::DeviceConfigurationManager { +fn load_dcm_with_config(config: &str) -> buttplug_server_device_config::DeviceConfigurationManager { load_protocol_configs(&None, &Some(config.to_string()), false) .expect("Test, assuming infallible.") .finish() @@ -58,9 +55,7 @@ fn test_identifier() -> TestDeviceIdentifier { } /// Helper: connect a client, scan, and return the first DeviceAdded event. -async fn get_client_device_from_config( - config: &str, -) -> buttplug_client::ButtplugClientDevice { +async fn get_client_device_from_config(config: &str) -> buttplug_client::ButtplugClientDevice { let dcm = load_dcm_with_config(config); let (client, _device_channel) = test_client_with_device_and_custom_dcm(&test_identifier(), dcm).await; @@ -246,8 +241,7 @@ async fn test_disabled_position_not_in_device_list() { /// Verify the DeviceList structure when Position is disabled. #[tokio::test] async fn test_disabled_position_device_list_structure() { - let (_server, _device_index, list) = - get_server_device_list(USER_CONFIG_DISABLED_POSITION).await; + let (_server, _device_index, list) = get_server_device_list(USER_CONFIG_DISABLED_POSITION).await; let device_info = list.devices().values().next().expect("One device expected"); let feature = device_info @@ -268,8 +262,7 @@ async fn test_disabled_position_device_list_structure() { /// Verify that Position commands are rejected when Position is disabled. #[tokio::test] async fn test_disabled_position_command_rejected() { - let (server, device_index, _list) = - get_server_device_list(USER_CONFIG_DISABLED_POSITION).await; + let (server, device_index, _list) = get_server_device_list(USER_CONFIG_DISABLED_POSITION).await; let result = server .parse_message(ButtplugClientMessageVariant::V4( @@ -291,8 +284,7 @@ async fn test_disabled_position_command_rejected() { /// Verify that HwPositionWithDuration commands are still accepted when only Position is disabled. #[tokio::test] async fn test_disabled_position_allows_hw_position_commands() { - let (server, device_index, _list) = - get_server_device_list(USER_CONFIG_DISABLED_POSITION).await; + let (server, device_index, _list) = get_server_device_list(USER_CONFIG_DISABLED_POSITION).await; let result = server .parse_message(ButtplugClientMessageVariant::V4( @@ -320,8 +312,7 @@ async fn test_disabled_position_allows_hw_position_commands() { /// neither outputs nor inputs. #[tokio::test] async fn test_disabled_both_outputs_feature_absent() { - let (_server, _device_index, list) = - get_server_device_list(USER_CONFIG_DISABLED_BOTH).await; + let (_server, _device_index, list) = get_server_device_list(USER_CONFIG_DISABLED_BOTH).await; let device_info = list.devices().values().next().expect("One device expected"); assert!( diff --git a/crates/buttplug_tests/tests/test_lovense_solace_pro_stop.rs b/crates/buttplug_tests/tests/test_lovense_solace_pro_stop.rs index 7d77ac7f8..8824dc9f7 100644 --- a/crates/buttplug_tests/tests/test_lovense_solace_pro_stop.rs +++ b/crates/buttplug_tests/tests/test_lovense_solace_pro_stop.rs @@ -1,7 +1,8 @@ mod util; use buttplug_client::{ - ButtplugClient, ButtplugClientEvent, + ButtplugClient, + ButtplugClientEvent, device::{ClientDeviceCommandValue, ClientDeviceOutputCommand}, }; use buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder; @@ -16,7 +17,9 @@ use futures::StreamExt; use std::time::Duration; use tokio::time::timeout; use util::{ - TestDeviceChannelHost, TestDeviceCommunicationManagerBuilder, TestHardwareEvent, + TestDeviceChannelHost, + TestDeviceCommunicationManagerBuilder, + TestHardwareEvent, test_device_manager::TestDeviceIdentifier, }; diff --git a/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs b/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs index b7e69d625..aca38df61 100644 --- a/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs +++ b/crates/buttplug_transport_websocket_tungstenite/src/websocket_server.rs @@ -7,9 +7,11 @@ use buttplug_core::{ connector::{ - ButtplugConnectorError, ButtplugConnectorResultFuture, + ButtplugConnectorError, + ButtplugConnectorResultFuture, transport::{ - ButtplugConnectorTransport, ButtplugConnectorTransportSpecificError, + ButtplugConnectorTransport, + ButtplugConnectorTransportSpecificError, ButtplugTransportIncomingMessage, }, }, @@ -325,7 +327,8 @@ mod test { connector::{ ButtplugConnectorError, transport::{ - ButtplugConnectorTransport, ButtplugConnectorTransportSpecificError, + ButtplugConnectorTransport, + ButtplugConnectorTransportSpecificError, ButtplugTransportIncomingMessage, }, }, diff --git a/examples/src/bin/device_tester.rs b/examples/src/bin/device_tester.rs index a32ecde78..ca3d45f2d 100644 --- a/examples/src/bin/device_tester.rs +++ b/examples/src/bin/device_tester.rs @@ -87,290 +87,301 @@ async fn device_tester() { } let exercise_device = |dev: ButtplugClientDevice| async move { - let mut cmds = vec![]; - dev.device_features().iter().for_each(|(_, feature)| { - if let Some(out) = feature.feature().get_output_limits(OutputType::Vibrate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Vibrate( - (out.step_count() as i32).into(), - ))); - println!( - "{} ({}) should start vibrating on feature {}!", - dev.name(), - dev.index(), - feature.feature_index() - ); - } else if let Some(out) = feature.feature().get_output_limits(OutputType::Rotate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate( - out.step_limit().end().into(), - ))); - println!( - "{} ({}) should start rotating on feature {}!", - dev.name(), - dev.index(), - feature.feature_index() - ); - } else if let Some(out) = feature.feature().get_output_limits(OutputType::Oscillate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Oscillate( - out.step_count().into(), - ))); - println!( - "{} ({}) should start oscillating on feature {}!", - dev.name(), - dev.index(), - feature.feature_index() - ); - } else if let Some(out) = feature.feature().get_output_limits(OutputType::Constrict) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Constrict( - out.step_count().into(), - ))); - println!( - "{} ({}) should start constricting on feature {}!", - dev.name(), - dev.index(), - feature.feature_index() - ); - } else if let Some(out) = feature.feature().get_output_limits(OutputType::Temperature) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Temperature( - out.step_limit().end().into(), - ))); - println!( - "{} ({}) should start heating on feature {}!", - dev.name(), - dev.index(), - feature.feature_index() - ); - } - }); - if !cmds.is_empty() { - // If the device had any features send what used to be scalar commands async, - // dispatch all commands now in parallel, then go back and stop them in parallel. - futures::future::join_all(cmds) - .await - .iter() - .for_each(|cmd| { - if let Err(err) = cmd { - error!("{:?}", err); - } - }); - - sleep(Duration::from_secs(5)).await; - + let mut backoff = 1; + loop { let mut cmds = vec![]; dev.device_features().iter().for_each(|(_, feature)| { - if feature.feature().contains_output(OutputType::Vibrate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Vibrate(0.into()))); + if let Some(out) = feature.feature().get_output_limits(OutputType::Vibrate) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Vibrate( + (out.step_count() as i32).into(), + ))); println!( - "{} ({}) should stop vibrating on feature {}!", + "{} ({}) should start vibrating on feature {}!", dev.name(), dev.index(), feature.feature_index() ); - } else if feature.feature().contains_output(OutputType::Rotate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate(0.into()))); + } else if let Some(out) = feature.feature().get_output_limits(OutputType::Rotate) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate( + out.step_limit().end().into(), + ))); println!( - "{} ({}) should stop rotating on feature {}!", + "{} ({}) should start rotating on feature {}!", dev.name(), dev.index(), feature.feature_index() ); - } else if feature.feature().contains_output(OutputType::Oscillate) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Oscillate(0.into()))); + } else if let Some(out) = feature.feature().get_output_limits(OutputType::Oscillate) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Oscillate( + out.step_count().into(), + ))); println!( - "{} ({}) should stop oscillating on feature {}!", + "{} ({}) should start oscillating on feature {}!", dev.name(), dev.index(), feature.feature_index() ); - } else if feature.feature().contains_output(OutputType::Constrict) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Constrict(0.into()))); + } else if let Some(out) = feature.feature().get_output_limits(OutputType::Constrict) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Constrict( + out.step_count().into(), + ))); println!( - "{} ({}) should stop constricting on feature {}!", + "{} ({}) should start constricting on feature {}!", dev.name(), dev.index(), feature.feature_index() ); - } else if feature.feature().contains_output(OutputType::Temperature) { - cmds.push(feature.run_output(&ClientDeviceOutputCommand::Temperature(0.into()))); + } else if let Some(out) = feature.feature().get_output_limits(OutputType::Temperature) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Temperature( + out.step_limit().end().into(), + ))); println!( - "{} ({}) should stop heating on feature {}!", + "{} ({}) should start heating on feature {}!", dev.name(), dev.index(), feature.feature_index() ); } }); + if !cmds.is_empty() { + // If the device had any features send what used to be scalar commands async, + // dispatch all commands now in parallel, then go back and stop them in parallel. + futures::future::join_all(cmds) + .await + .iter() + .for_each(|cmd| { + if let Err(err) = cmd { + error!("{:?}", err); + } + }); - futures::future::join_all(cmds) - .await - .iter() - .for_each(|cmd| { - if let Err(err) = cmd { - error!("{:?}", err); - } - }); - - sleep(Duration::from_secs(2)).await; - } - - // Exercise each feature - for feature in dev.device_features().values() { - for output_type in [ - OutputType::Constrict, - OutputType::Temperature, - OutputType::Led, - OutputType::Oscillate, - OutputType::Position, - OutputType::HwPositionWithDuration, - OutputType::Rotate, - OutputType::Spray, - OutputType::Vibrate, - ] { - if !feature.feature().contains_output(output_type) { - continue; - } - match output_type { - OutputType::Vibrate - | OutputType::Constrict - | OutputType::Oscillate - | OutputType::Temperature - | OutputType::Spray - | OutputType::Led - | OutputType::Position => { - set_level_and_wait(&dev, feature, &output_type, 0.05).await; - set_level_and_wait(&dev, feature, &output_type, 0.10).await; - set_level_and_wait(&dev, feature, &output_type, 0.25).await; - set_level_and_wait(&dev, feature, &output_type, 0.5).await; - set_level_and_wait(&dev, feature, &output_type, 0.75).await; - set_level_and_wait(&dev, feature, &output_type, 1.0).await; - set_level_and_wait(&dev, feature, &output_type, 0.0).await; - } - OutputType::Rotate => { - if feature - .feature() - .get_output_limits(OutputType::Rotate) - .map(|l| l.step_limit().start() < 0) - .unwrap_or(false) - { - set_level_and_wait(&dev, feature, &output_type, 0.25).await; - set_level_and_wait(&dev, feature, &output_type, -0.25).await; - set_level_and_wait(&dev, feature, &output_type, 0.5).await; - set_level_and_wait(&dev, feature, &output_type, -0.5).await; - set_level_and_wait(&dev, feature, &output_type, 0.75).await; - set_level_and_wait(&dev, feature, &output_type, -0.75).await; - set_level_and_wait(&dev, feature, &output_type, 1.0).await; - set_level_and_wait(&dev, feature, &output_type, -1.0).await; - set_level_and_wait(&dev, feature, &output_type, 0.0).await; + sleep(Duration::from_secs(5)).await; - set_level_and_wait(&dev, feature, &output_type, 0.25).await; - set_level_and_wait(&dev, feature, &output_type, 0.5).await; - set_level_and_wait(&dev, feature, &output_type, 0.75).await; - set_level_and_wait(&dev, feature, &output_type, 1.0).await; - set_level_and_wait(&dev, feature, &output_type, -0.25).await; - set_level_and_wait(&dev, feature, &output_type, -0.5).await; - set_level_and_wait(&dev, feature, &output_type, -0.75).await; - set_level_and_wait(&dev, feature, &output_type, -1.0).await; - set_level_and_wait(&dev, feature, &output_type, 0.0).await; - } else { - set_level_and_wait(&dev, feature, &output_type, 0.25).await; - set_level_and_wait(&dev, feature, &output_type, 0.5).await; - set_level_and_wait(&dev, feature, &output_type, 0.75).await; - set_level_and_wait(&dev, feature, &output_type, 1.0).await; - set_level_and_wait(&dev, feature, &output_type, 0.0).await; - } - } - OutputType::HwPositionWithDuration => { - feature - .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( - 0.0f64.into(), - 10, - )) - .await - .unwrap(); + let mut cmds = vec![]; + dev.device_features().iter().for_each(|(_, feature)| { + if feature.feature().contains_output(OutputType::Vibrate) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Vibrate(0.into()))); println!( - "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + "{} ({}) should stop vibrating on feature {}!", dev.name(), dev.index(), - feature.feature().feature_index(), - feature.feature().description(), - "HwPositionWithDuration", - (0.0 * 100.0) as u8, - 10 + feature.feature_index() ); - sleep(Duration::from_secs(1)).await; - feature - .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( - 0.5f64.into(), - 1000, - )) - .await - .unwrap(); + } else if feature.feature().contains_output(OutputType::Rotate) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Rotate(0.into()))); println!( - "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + "{} ({}) should stop rotating on feature {}!", dev.name(), dev.index(), - feature.feature().feature_index(), - feature.feature().description(), - "HwPositionWithDuration", - (0.0 * 100.0) as u8, - 1000 + feature.feature_index() ); - sleep(Duration::from_secs(1)).await; - feature - .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( - 0.0f64.into(), - 10, - )) - .await - .unwrap(); + } else if feature.feature().contains_output(OutputType::Oscillate) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Oscillate(0.into()))); println!( - "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + "{} ({}) should stop oscillating on feature {}!", dev.name(), dev.index(), - feature.feature().feature_index(), - feature.feature().description(), - "HwPositionWithDuration", - (0.0 * 100.0) as u8, - 10 + feature.feature_index() ); - sleep(Duration::from_secs(1)).await; - feature - .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( - 1.0f64.into(), - 500, - )) - .await - .unwrap(); + } else if feature.feature().contains_output(OutputType::Constrict) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Constrict(0.into()))); println!( - "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + "{} ({}) should stop constricting on feature {}!", dev.name(), dev.index(), - feature.feature().feature_index(), - feature.feature().description(), - "HwPositionWithDuration", - (1.0 * 100.0) as u8, - 500 + feature.feature_index() ); - sleep(Duration::from_secs(1)).await; - feature - .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( - 0.0f64.into(), - 1500, - )) - .await - .unwrap(); + } else if feature.feature().contains_output(OutputType::Temperature) { + cmds.push(feature.run_output(&ClientDeviceOutputCommand::Temperature(0.into()))); println!( - "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + "{} ({}) should stop heating on feature {}!", dev.name(), dev.index(), - feature.feature().feature_index(), - feature.feature().description(), - "HwPositionWithDuration", - (0.0 * 100.0) as u8, - 1500 + feature.feature_index() ); } + }); + + futures::future::join_all(cmds) + .await + .iter() + .for_each(|cmd| { + if let Err(err) = cmd { + error!("{:?}", err); + } + }); + + sleep(Duration::from_secs(2)).await; + } + + // Exercise each feature + for feature in dev.device_features().values() { + for output_type in [ + OutputType::Constrict, + OutputType::Temperature, + OutputType::Led, + OutputType::Oscillate, + OutputType::Position, + OutputType::HwPositionWithDuration, + OutputType::Rotate, + OutputType::Spray, + OutputType::Vibrate, + ] { + if !feature.feature().contains_output(output_type) { + continue; + } + match output_type { + OutputType::Vibrate + | OutputType::Constrict + | OutputType::Oscillate + | OutputType::Temperature + | OutputType::Spray + | OutputType::Led + | OutputType::Position => { + set_level_and_wait(&dev, feature, &output_type, 0.05).await; + set_level_and_wait(&dev, feature, &output_type, 0.10).await; + set_level_and_wait(&dev, feature, &output_type, 0.25).await; + set_level_and_wait(&dev, feature, &output_type, 0.5).await; + set_level_and_wait(&dev, feature, &output_type, 0.75).await; + set_level_and_wait(&dev, feature, &output_type, 1.0).await; + set_level_and_wait(&dev, feature, &output_type, 0.0).await; + } + OutputType::Rotate => { + if feature + .feature() + .get_output_limits(OutputType::Rotate) + .map(|l| l.step_limit().start() < 0) + .unwrap_or(false) + { + set_level_and_wait(&dev, feature, &output_type, 0.25).await; + set_level_and_wait(&dev, feature, &output_type, -0.25).await; + set_level_and_wait(&dev, feature, &output_type, 0.5).await; + set_level_and_wait(&dev, feature, &output_type, -0.5).await; + set_level_and_wait(&dev, feature, &output_type, 0.75).await; + set_level_and_wait(&dev, feature, &output_type, -0.75).await; + set_level_and_wait(&dev, feature, &output_type, 1.0).await; + set_level_and_wait(&dev, feature, &output_type, -1.0).await; + set_level_and_wait(&dev, feature, &output_type, 0.0).await; + + set_level_and_wait(&dev, feature, &output_type, 0.25).await; + set_level_and_wait(&dev, feature, &output_type, 0.5).await; + set_level_and_wait(&dev, feature, &output_type, 0.75).await; + set_level_and_wait(&dev, feature, &output_type, 1.0).await; + set_level_and_wait(&dev, feature, &output_type, -0.25).await; + set_level_and_wait(&dev, feature, &output_type, -0.5).await; + set_level_and_wait(&dev, feature, &output_type, -0.75).await; + set_level_and_wait(&dev, feature, &output_type, -1.0).await; + set_level_and_wait(&dev, feature, &output_type, 0.0).await; + } else { + set_level_and_wait(&dev, feature, &output_type, 0.25).await; + set_level_and_wait(&dev, feature, &output_type, 0.5).await; + set_level_and_wait(&dev, feature, &output_type, 0.75).await; + set_level_and_wait(&dev, feature, &output_type, 1.0).await; + set_level_and_wait(&dev, feature, &output_type, 0.0).await; + } + } + OutputType::HwPositionWithDuration => { + feature + .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( + 0.0f64.into(), + 10, + )) + .await + .unwrap(); + println!( + "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + dev.name(), + dev.index(), + feature.feature().feature_index(), + feature.feature().description(), + "HwPositionWithDuration", + (0.0 * 100.0) as u8, + 10 + ); + sleep(Duration::from_secs(1)).await; + feature + .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( + 0.5f64.into(), + 1000, + )) + .await + .unwrap(); + println!( + "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + dev.name(), + dev.index(), + feature.feature().feature_index(), + feature.feature().description(), + "HwPositionWithDuration", + (0.0 * 100.0) as u8, + 1000 + ); + sleep(Duration::from_secs(1)).await; + feature + .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( + 0.0f64.into(), + 10, + )) + .await + .unwrap(); + println!( + "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + dev.name(), + dev.index(), + feature.feature().feature_index(), + feature.feature().description(), + "HwPositionWithDuration", + (0.0 * 100.0) as u8, + 10 + ); + sleep(Duration::from_secs(1)).await; + feature + .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( + 1.0f64.into(), + 500, + )) + .await + .unwrap(); + println!( + "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + dev.name(), + dev.index(), + feature.feature().feature_index(), + feature.feature().description(), + "HwPositionWithDuration", + (1.0 * 100.0) as u8, + 500 + ); + sleep(Duration::from_secs(1)).await; + feature + .run_output(&ClientDeviceOutputCommand::HwPositionWithDuration( + 0.0f64.into(), + 1500, + )) + .await + .unwrap(); + println!( + "{} ({}) Testing feature {}: {}, output {:?} - {}% {}ms", + dev.name(), + dev.index(), + feature.feature().feature_index(), + feature.feature().description(), + "HwPositionWithDuration", + (0.0 * 100.0) as u8, + 1500 + ); + } + } } } + sleep(Duration::from_mins(backoff)).await; + println!( + "{} ({}) rerun started after {} minutes", + dev.name(), + dev.index(), + backoff + ); + backoff *= 2; } };