use crate::{
state::{
update_entire_msg,
update_msg_from_command,
update_service_struct,
update_state_on_message,
},
device_model::{self, *},
};
use serde_json::json;
use tokio;
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::test]
async fn test_update_state_on_message() {
let sample_message =
json!({
"session_id": 176,
"inverse3": [
{
"device_id": "04C3",
"config": {
"type": "inverse3",
"device_info": {
"major_version": 7,
"minor_version": 1,
"id": "04C3",
"device_type": 4,
"uuid": "9AB19C4B5E655E32B4F00F8A59FD04C3"
},
"port": "COM15",
"extended_device_id": "9AB19C4B5E655E32B4F00F8A59FD04C3",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 0.75
},
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": {
"enabled": true
},
"cursor_offset": {
"x": 0,
"y": 0,
"z": 0
},
"coordinate_origin": "device_base",
"basis": {
"permutation": "XYZ"
}
},
"state": {
"angular_position": {
"a0": -89.66179,
"a1": 222.81319,
"a2": -22.783606
},
"angular_velocity": {
"a0": 0,
"a1": 0,
"a2": 0
},
"body_orientation": {
"x": 0.026611328,
"y": 0.7089844,
"z": -0.00018310547,
"w": 0.7047119
},
"cursor_position": {
"x": 0.12794551,
"y": 0.038903017,
"z": -0.05089721
},
"cursor_velocity": {
"x": -0.00018158932,
"y": -0.000023230032,
"z": -0.00025131978
},
"mode": "idle",
"control_domain": "undefined",
"control_mode": "idle",
"transform": {
"position": {
"x": 0,
"y": 0,
"z": 0
},
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"scale": {
"x": 1,
"y": 1,
"z": 1
}
}
},
"status": {
"calibrated": true,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1102",
"config": {
"port": "COM12",
"type": "wireless_verse_grip",
"major_version": 0,
"minor_version": 0,
"hardware_version": 0,
"streaming_mode": "Radio",
"basis": {
"permutation": "XYZ"
}
},
"state": {
"battery_voltage": 3.695,
"battery_level": 0.1583334,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.0016784668,
"y": -0.037994385,
"z": -0.74890137,
"w": 0.65896606
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"status": {
"connected": true,
"awake": false,
"ready": true
}
}
],
"custom_verse_grip": [
{
"device_id": "1272",
"config": {
"port": "COM13",
"type": "custom_verse_grip",
"major_version": 2,
"minor_version": 1,
"hardware_version": 1,
"streaming_mode": "USB",
"basis": {
"permutation": "XYZ"
}
},
"state": {
"battery_voltage": 4.12,
"battery_level": 0.8666668,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": 0.6329651,
"y": -0.32418823,
"z": 0.60165405,
"w": 0.35888672
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"extension_data": [0,0,0,0,5,6,7,8,9,10,11,12]
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.inverse3.len(), 1);
assert_eq!(state_lock.inverse3[0].device_id, "04C3");
assert_eq!(state_lock.inverse3[0].state.cursor_position.as_ref().unwrap().x, 0.12794551);
assert_eq!(state_lock.inverse3[0].state.cursor_position.as_ref().unwrap().y, 0.038903017);
assert_eq!(state_lock.inverse3[0].state.cursor_position.as_ref().unwrap().z, -0.05089721);
assert_eq!(state_lock.inverse3[0].state.angular_position.as_ref().unwrap().0.x, -89.66179);
assert_eq!(state_lock.inverse3[0].state.angular_position.as_ref().unwrap().0.y, 222.81319);
assert_eq!(state_lock.inverse3[0].state.angular_position.as_ref().unwrap().0.z, -22.783606);
assert_eq!(state_lock.inverse3[0].state.angular_velocity.as_ref().unwrap().0.x, 0.0);
assert_eq!(state_lock.inverse3[0].state.body_orientation.as_ref().unwrap().w, 0.7047119);
assert_eq!(state_lock.inverse3[0].state.body_orientation.as_ref().unwrap().x, 0.026611328);
assert_eq!(state_lock.inverse3[0].state.cursor_velocity.as_ref().unwrap().x, -0.00018158932);
assert_eq!(state_lock.inverse3[0].state.mode, device_model::DeviceMode::Idle);
assert!(state_lock.inverse3[0].status.calibrated);
assert!(!state_lock.inverse3[0].status.in_use);
assert!(state_lock.inverse3[0].status.power_supply);
assert!(state_lock.inverse3[0].status.ready);
assert!(state_lock.inverse3[0].status.started);
assert_eq!(state_lock.verse_grip.len(), 0);
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.custom_verse_grip.len(), 1);
assert_eq!(state_lock.session_id, 176);
assert_eq!(*state_lock.inverse3[0].state.control_domain.as_ref().unwrap(), device_model::ControlDomain::Undefined);
assert_eq!(*state_lock.inverse3[0].state.control_mode.as_ref().unwrap(), device_model::ControlMode::Idle);
let inv_config = state_lock.inverse3[0].config.clone().unwrap();
assert_eq!(inv_config.type_, device_model::DeviceType::Inverse3);
assert_eq!(inv_config.device_info.id, "04C3");
assert_eq!(inv_config.device_info.major_version, 7);
assert_eq!(inv_config.device_info.minor_version, 1);
assert_eq!(inv_config.device_info.device_type, device_model::DeviceType::Inverse3);
assert_eq!(inv_config.device_info.uuid, "9AB19C4B5E655E32B4F00F8A59FD04C3");
assert_eq!(inv_config.port, "COM15");
assert_eq!(inv_config.handedness, device_model::Handedness::Right);
assert_eq!(&inv_config.extended_device_id, "9AB19C4B5E655E32B4F00F8A59FD04C3");
assert_eq!(&inv_config.extended_firmware_version, "8C20FDC8010AA1E15AA133CDA2534874");
assert_eq!(inv_config.gravity_compensation.enabled, true);
assert_eq!(inv_config.gravity_compensation.scaling_factor, 0.75);
assert_eq!(inv_config.torque_scaling.enabled, true);
assert_eq!(inv_config.cursor_offset.unwrap().x, 0.0);
assert_eq!(inv_config.coordinate_origin.unwrap(), device_model::CoordinateOrigin::DeviceBase);
assert_eq!(inv_config.coordinate_system.as_ref().unwrap().permutation, "XYZ");
assert_eq!(inv_config.streaming_mode.unwrap(), device_model::StreamingMode::USB);
assert_eq!(state_lock.custom_verse_grip[0].device_id, "1272");
let wvg_config_config = state_lock.custom_verse_grip[0].config.clone().unwrap();
assert_eq!(wvg_config_config.type_, device_model::DeviceType::CustomVerseGrip);
assert_eq!(wvg_config_config.port, "COM13");
assert_eq!(wvg_config_config.major_version, 2);
assert_eq!(wvg_config_config.minor_version, 1);
assert_eq!(wvg_config_config.hardware_version, 1);
assert_eq!(state_lock.custom_verse_grip[0].state.battery_voltage, Some(4.12));
assert_eq!(state_lock.custom_verse_grip[0].state.battery_level, Some(0.8666668));
assert_eq!(state_lock.custom_verse_grip[0].state.hall, Some(16));
assert_eq!(state_lock.custom_verse_grip[0].state.orientation.as_ref().unwrap().x, 0.6329651);
assert_eq!(state_lock.custom_verse_grip[0].state.transform.as_ref().unwrap().position.x, 0.0);
assert_eq!(state_lock.custom_verse_grip[0].state.transform.as_ref().unwrap().rotation.w, 1.0);
assert_eq!(state_lock.custom_verse_grip[0].state.transform.as_ref().unwrap().scale.x, 1.0);
assert_eq!(
state_lock.custom_verse_grip[0].state.extension_data.as_ref().unwrap(),
&[0, 0, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12]
);
}
#[tokio::test]
async fn test_update_service_struct() {
let mut service_data = ServiceData::default();
service_data.inverse3.push(Inverse3Device {
device_id: "device1".to_string(),
config: None,
state: Inverse3State::default(),
status: Inverse3Status::default(),
});
let mut input_data = ServiceData::default();
input_data.inverse3.push(Inverse3Device {
device_id: "device1".to_string(),
config: service_data.inverse3[0].config.clone(),
state: Inverse3State {
cursor_position: Some(Linear3D { x: 1.0, y: 2.0, z: 3.0 }),
..Default::default()
},
status: Inverse3Status {
calibrated: true,
in_use: true,
power_supply: true,
ready: true,
started: true,
..Default::default()
},
});
input_data.inverse3.push(Inverse3Device {
device_id: "device2".to_string(),
config: None,
state: Inverse3State::default(),
status: Inverse3Status::default(),
});
input_data.verse_grip.push(VerseGripDevice {
device_id: "device3".to_string(),
config: None,
state: VerseGripState {
button: Some(true),
hall: Some(100),
orientation: Some(Orientation4D { w: 1.0, x: 0.0, y: 0.0, z: 0.0 }),
..Default::default()
},
status: VerseGripStatus { error: Some(0), ready: Some(true) },
});
input_data.wireless_verse_grip.push(WirelessVerseGripDevice {
device_id: "device4".to_string(),
config: None,
state: WirelessVerseGripState {
battery_level: Some(75.0),
battery_voltage: None,
buttons: Some(Buttons { a: true, b: false, c: true, ..Default::default() }),
hall: Some(200),
orientation: Some(Orientation4D { w: 1.0, x: 0.0, y: 0.0, z: 0.0 }),
trigger: None,
wheel: None,
transform: None,
transform_velocity: None,
extension_data: None,
},
status: WirelessVerseGripStatus { ready: true, connected: true, awake: true },
});
update_service_struct(&mut service_data, &input_data, VerseGripDuplicateMode::PreferCustom).unwrap();
assert_eq!(service_data.inverse3.len(), 2);
assert_eq!(service_data.inverse3[0].device_id, "device1");
assert_eq!(service_data.inverse3[0].state.mode, device_model::DeviceMode::default());
assert_eq!(service_data.inverse3[1].device_id, "device2");
assert_eq!(service_data.verse_grip.len(), 1);
assert_eq!(service_data.verse_grip[0].device_id, "device3");
assert_eq!(service_data.wireless_verse_grip.len(), 1);
assert_eq!(service_data.wireless_verse_grip[0].device_id, "device4");
assert!(service_data.custom_verse_grip.is_empty());
assert_eq!(service_data.session_id, 0);
}
#[tokio::test]
async fn test_create_cmd_msgs_i3_set_cursor_force() {
let mut service_msg = ServiceMsg::default();
let command = Command::SetCursorForce {
device_id: "device1".to_string(),
vector: Force { x: 1.0, y: 2.0, z: 3.0 },
execute: true,
};
update_msg_from_command(command, &mut service_msg).unwrap();
assert_eq!(service_msg.inverse3.len(), 1);
assert_eq!(service_msg.inverse3[0].device_id, "device1");
let cmds = service_msg.inverse3[0].commands.as_ref().unwrap();
let force_cmd = cmds.set_cursor_force.as_ref().unwrap();
assert_eq!(force_cmd.vector.x, 1.0);
assert_eq!(force_cmd.vector.y, 2.0);
assert_eq!(force_cmd.vector.z, 3.0);
assert_eq!(force_cmd.execute, true);
assert!(service_msg.inverse3[0].configure.is_none());
}
#[tokio::test]
async fn test_create_cmd_msgs_probe_orientation() {
let mut service_msg = ServiceMsg::default();
let command = Command::ProbeOrientation {
device_id: "device2".to_string(),
};
update_msg_from_command(command, &mut service_msg).unwrap();
assert_eq!(service_msg.wireless_verse_grip.len(), 1);
assert_eq!(service_msg.wireless_verse_grip[0].device_id, "device2");
assert!(service_msg.wireless_verse_grip[0].commands.as_ref().unwrap().probe_orientation.is_some());
assert!(service_msg.verse_grip.is_empty());
}
#[tokio::test]
async fn test_probe_orientation_wire_format_has_no_custom_lane() {
let mut service_msg = ServiceMsg::default();
let command = Command::ProbeOrientation {
device_id: "device2".to_string(),
};
update_msg_from_command(command, &mut service_msg).unwrap();
let json = serde_json::to_value(&service_msg).unwrap();
assert!(json.get("custom_verse_grip").is_none());
assert_eq!(json["wireless_verse_grip"][0]["device_id"], "device2");
assert!(json["wireless_verse_grip"][0]["commands"]["probe_orientation"].is_object());
}
#[tokio::test]
async fn test_create_cmd_msgs_set_extension_data() {
let mut service_msg = ServiceMsg::default();
let command = Command::SetExtensionData {
device_id: "device3".to_string(),
extension_data: vec![1, 2, 3],
};
update_msg_from_command(command, &mut service_msg).unwrap();
assert_eq!(service_msg.wireless_verse_grip.len(), 1);
let ext_cmd = service_msg.wireless_verse_grip[0].commands.as_ref().unwrap()
.set_extension_data.as_ref().unwrap();
assert_eq!(ext_cmd.extension_data, vec![1, 2, 3]);
}
#[tokio::test]
async fn test_multiple_commands_same_device() {
let mut service_msg = ServiceMsg::default();
let commands = vec![
Command::SetCursorForce {
device_id: "dev1".to_string(),
vector: Force { x: 1.0, y: 0.0, z: 0.0 },
execute: true,
},
Command::ProbePosition {
device_id: "dev1".to_string(),
},
];
update_entire_msg(&commands, &mut service_msg).unwrap();
assert_eq!(service_msg.inverse3.len(), 1);
assert_eq!(service_msg.inverse3[0].device_id, "dev1");
let cmds = service_msg.inverse3[0].commands.as_ref().unwrap();
assert!(cmds.set_cursor_force.is_some());
assert!(cmds.probe_position.is_some());
assert_eq!(cmds.set_cursor_force.as_ref().unwrap().vector.x, 1.0);
}
#[tokio::test]
async fn test_update_entire_msg_multiple_devices() {
let mut service_msg = ServiceMsg::default();
let commands = vec![
Command::SetCursorForce {
device_id: "device1".to_string(),
vector: Force { x: 1.0, y: 2.0, z: 3.0 },
execute: true,
},
Command::ProbeOrientation {
device_id: "device3".to_string(),
},
];
update_entire_msg(&commands, &mut service_msg).unwrap();
assert_eq!(service_msg.inverse3.len(), 1);
assert_eq!(service_msg.inverse3[0].device_id, "device1");
assert!(service_msg.inverse3[0].commands.as_ref().unwrap().set_cursor_force.is_some());
assert_eq!(service_msg.wireless_verse_grip.len(), 1);
assert_eq!(service_msg.wireless_verse_grip[0].device_id, "device3");
assert!(service_msg.wireless_verse_grip[0].commands.as_ref().unwrap().probe_orientation.is_some());
}
#[tokio::test]
async fn test_configure_inverse3() {
let mut service_msg = ServiceMsg::default();
let command = Command::ConfigureInverse3 {
device_id: "dev1".to_string(),
config: Inverse3Configure {
damping: Some(DampingConfig { scalar: Some(0.5), vector: None }),
navigation: Some(NavigationConfigure {
mode: "bubble".to_string(),
bubble: Some(BubbleNavigationSettings {
center: Some(Linear3D { x: 0.0, y: -0.05, z: 0.18 }),
..Default::default()
}),
}),
..Default::default()
},
};
update_msg_from_command(command, &mut service_msg).unwrap();
assert_eq!(service_msg.inverse3.len(), 1);
let cfg = service_msg.inverse3[0].configure.as_ref().unwrap();
assert_eq!(cfg.damping.as_ref().unwrap().scalar, Some(0.5));
assert_eq!(cfg.navigation.as_ref().unwrap().mode, "bubble");
assert_eq!(cfg.navigation.as_ref().unwrap().bubble.as_ref().unwrap().center.as_ref().unwrap().z, 0.18);
}
#[tokio::test]
async fn test_configure_session() {
let mut service_msg = ServiceMsg::default();
let command = Command::ConfigureSession(SessionConfigure {
profile: Some(ProfileConfig {
name: "my_app".to_string(),
required_version: Some(">=3.5".to_string()),
}),
basis: Some(CoordinateSystem { permutation: "ZXY".to_string() }),
});
update_msg_from_command(command, &mut service_msg).unwrap();
let cfg = service_msg.session.configure.as_ref().unwrap();
assert_eq!(cfg.profile.as_ref().unwrap().name, "my_app");
assert_eq!(cfg.profile.as_ref().unwrap().required_version.as_ref().unwrap(), ">=3.5");
assert_eq!(cfg.basis.as_ref().unwrap().permutation, "ZXY");
}
#[tokio::test]
async fn test_wire_format_golden() {
let mut service_msg = ServiceMsg::default();
service_msg.session.force_render_full_state = None;
let commands = vec![
Command::SetCursorForce {
device_id: "A14".to_string(),
vector: Force { x: 1.0, y: 0.0, z: 0.0 },
execute: true,
},
Command::ConfigureSession(SessionConfigure {
profile: Some(ProfileConfig {
name: "test".to_string(),
required_version: None,
}),
basis: None,
}),
];
update_entire_msg(&commands, &mut service_msg).unwrap();
let json_str = serde_json::to_string(&service_msg).unwrap();
let v: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(v["session"]["configure"]["profile"]["name"], "test");
assert_eq!(v["inverse3"][0]["device_id"], "A14");
assert_eq!(v["inverse3"][0]["commands"]["set_cursor_force"]["vector"]["x"], 1.0);
assert_eq!(v["inverse3"][0]["commands"]["set_cursor_force"]["execute"], true);
assert!(v["inverse3"][0].get("configure").is_none());
assert!(v.get("verse_grip").is_none());
assert!(v.get("wireless_verse_grip").is_none());
assert!(v.get("custom_verse_grip").is_none());
}
#[tokio::test]
async fn test_deserialize_service_data() {
let sample_message =
json!({
"inverse3": [
{
"device_id": "048D",
"config": {
"type": "inverse3",
"device_info": {
"major_version": 7,
"minor_version": 1,
"id": "048D",
"device_type": "inverse3",
"uuid": "ED78730C8173560784AB4167961D048D"
},
"port": "COM16",
"extended_device_id": "ED78730C8173560784AB4167961D048D",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 0.85
},
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": {
"enabled": true
},
"cursor_offset": {
"x": 0,
"y": 0,
"z": 0
},
"coordinate_origin": "device_base",
"coordinate_system": {
"permutation": "XYZ"
}
},
"state": {
"angular_position": {
"a0": -88.81024,
"a1": 118.23612,
"a2": 3.7508194
},
"angular_velocity": { "a0": 0, "a1": 0, "a2": 0 },
"body_orientation": {
"x": 0.035461426,
"y": 0.6989746,
"z": 0.0025634766,
"w": 0.71429443
},
"cursor_position": {
"x": 0.025769988,
"y": -0.16684988,
"z": 0.09366201
},
"cursor_velocity": { "x": 0, "y": 0, "z": 0 },
"mode": "idle",
"control_domain": "undefined",
"control_mode": "idle",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"status": {
"calibrated": true,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": { "port": "COM3", "type": "verse_grip" },
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
}
},
"status": { "error": 0, "ready": true }
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"port": "COM6",
"type": "wireless_verse_grip",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1
},
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": { "a": false, "b": false, "c": false },
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
}
},
"status": { "connected": true, "awake": true, "ready": true }
}
],
"custom_verse_grip": [],
"session_id": 0
}).to_string();
let mut first = false;
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let timestamped_state = Arc::new(Mutex::new(timestamped_state));
update_state_on_message(timestamped_state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = timestamped_state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 0);
assert_eq!(state_lock.inverse3.len(), 1);
let inverse3 = &state_lock.inverse3[0];
assert_eq!(inverse3.device_id, "048D");
let config = &inverse3.config.clone().unwrap();
assert_eq!(config.type_, device_model::DeviceType::Inverse3);
assert_eq!(config.device_info.id, "048D");
assert_eq!(config.port, "COM16");
assert_eq!(config.gravity_compensation.scaling_factor, 0.85);
assert_eq!(config.coordinate_system.as_ref().unwrap().permutation, "XYZ");
assert_eq!(config.torque_scaling.enabled, true);
let state = &inverse3.state;
assert_eq!(state.angular_position.as_ref().unwrap().0.x, -88.81024);
assert_eq!(state.cursor_position.as_ref().unwrap().x, 0.025769988);
assert_eq!(state.body_orientation.as_ref().unwrap().w, 0.71429443);
assert_eq!(state.mode, device_model::DeviceMode::Idle);
assert!(inverse3.status.calibrated);
assert!(inverse3.status.ready);
assert_eq!(state_lock.verse_grip.len(), 1);
let vg = &state_lock.verse_grip[0];
assert_eq!(vg.device_id, "61548");
assert_eq!(vg.state.button, Some(false));
assert_eq!(vg.state.orientation.as_ref().unwrap().x, -0.5019531);
assert_eq!(vg.status.ready, Some(true));
let wvg = &state_lock.wireless_verse_grip[0];
assert_eq!(wvg.device_id, "0");
assert_eq!(wvg.state.battery_level, Some(0.816));
assert_eq!(wvg.state.orientation.as_ref().unwrap().w, -0.9963989);
assert!(wvg.status.connected);
assert!(state_lock.custom_verse_grip.is_empty());
}
#[tokio::test]
async fn test_parse_full_payload_without_custom_verse_grip_key() {
let sample_message = json!({
"session_id": 16,
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"device_info": {
"major_version": 7,
"minor_version": 5,
"id": "04BA",
"device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"port": "COM18",
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "DC35D00B594AD123B9561C2F095E6B9E",
"gravity_compensation": { "enabled": true, "scaling_factor": 0.0 },
"handedness": "right",
"torque_scaling": { "enabled": true }
},
"state": { "mode": "idle" },
"status": { "calibrated": true, "in_use": false, "power_supply": true, "ready": true, "started": true }
}
],
"verse_grip": [],
"wireless_verse_grip": []
})
.to_string();
let mut first = false;
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 16);
assert_eq!(state_lock.inverse3.len(), 1);
assert!(state_lock.custom_verse_grip.is_empty());
}
#[tokio::test]
async fn test_parse_full_payload_without_wireless_verse_grip_key() {
let sample_message = json!({
"session_id": 17,
"inverse3": [],
"verse_grip": [],
"custom_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46",
"type": "custom_verse_grip",
"sub_type": "ruko",
"major_version": 2,
"minor_version": 2,
"hardware_version": 1
},
"state": {
"battery_voltage": 4.135,
"battery_level": 0.89166737,
"buttons": { "a": false, "b": false, "c": false },
"hall": 15,
"orientation": { "x": 0.34646606, "y": 0.6608887, "z": -0.34802246, "w": 0.5644226 },
"extension_data": [0, 1, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12]
},
"status": { "connected": true, "awake": true, "ready": true }
}
]
})
.to_string();
let mut first = false;
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 17);
assert!(state_lock.wireless_verse_grip.is_empty());
assert_eq!(state_lock.custom_verse_grip.len(), 1);
}
#[tokio::test]
async fn test_parse_dev98_ruko_dual_view_payload() {
let sample_message = json!({
"session_id": 16,
"session": {
"session_id": 16,
"config": {
"profile": { "name": "default" },
"started_time": "2026-04-20 20:11:58.6121582"
}
},
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46",
"type": "ruko",
"sub_type": "ruko",
"major_version": 2,
"minor_version": 2,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {}
},
"state": {
"battery_voltage": 4.135,
"battery_level": 0.89166737,
"buttons": { "down": false, "up": false, "right": false, "left": false },
"trigger": 1,
"wheel": 0,
"hall": 15,
"orientation": { "x": 0.34646606, "y": 0.6608887, "z": -0.34802246, "w": 0.5644226 },
"transform": {},
"transform_velocity": {},
"extension_data": [0, 1, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12]
},
"status": { "awake": true }
}
],
"custom_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46",
"type": "custom_verse_grip",
"sub_type": "ruko",
"major_version": 2,
"minor_version": 2,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {}
},
"state": {
"battery_voltage": 4.135,
"battery_level": 0.89166737,
"buttons": { "a": false, "b": false, "c": false },
"hall": 15,
"orientation": { "x": 0.34646606, "y": 0.6608887, "z": -0.34802246, "w": 0.5644226 },
"transform": {},
"transform_velocity": {},
"extension_data": [0, 1, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12]
},
"status": { "connected": true, "awake": true, "ready": true }
}
]
})
.to_string();
let mut first = false;
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::KeepBoth).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 16);
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.custom_verse_grip.len(), 1);
let wvg = &state_lock.wireless_verse_grip[0];
assert_eq!(wvg.config.as_ref().unwrap().sub_type.as_deref(), Some("ruko"));
assert!(wvg.config.as_ref().unwrap().mount.is_none());
assert_eq!(
wvg.state.extension_data.as_ref().unwrap(),
&[0, 1, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12]
);
assert!(wvg.state.transform.is_none());
assert!(wvg.state.transform_velocity.is_none());
let cvg = &state_lock.custom_verse_grip[0];
assert_eq!(cvg.config.as_ref().unwrap().sub_type.as_deref(), Some("ruko"));
assert!(cvg.config.as_ref().unwrap().mount.is_none());
assert_eq!(
cvg.state.extension_data.as_ref().unwrap(),
&[0, 1, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12]
);
assert!(cvg.state.transform.is_none());
assert!(cvg.state.transform_velocity.is_none());
}
#[tokio::test]
async fn test_malformed_full_payload_falls_back_to_state_only() {
let sample_message = json!({
"session_id": 42,
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46",
"type": "ruko",
"major_version": "bad_type",
"minor_version": 2,
"hardware_version": 1
},
"state": {
"hall": 15,
"battery_level": 0.9,
"buttons": { "up": true }
},
"status": { "awake": true }
}
],
"custom_verse_grip": []
})
.to_string();
let mut first = false;
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::KeepBoth).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 42);
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].device_id, "1402");
assert_eq!(state_lock.wireless_verse_grip[0].state.hall, Some(15));
assert!(state_lock.wireless_verse_grip[0].state.buttons.unwrap().up);
}
#[tokio::test]
async fn test_bad_state_only_field_does_not_crash_or_clobber_state() {
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
{
let mut lock = state.lock().await;
lock.data.wireless_verse_grip.push(WirelessVerseGripDevice {
device_id: "1402".to_string(),
config: None,
state: WirelessVerseGripState {
hall: Some(7),
..Default::default()
},
status: WirelessVerseGripStatus::default(),
});
}
let bad_state_only_message = json!({
"session_id": 42,
"wireless_verse_grip": [
{
"device_id": "1402",
"state": { "hall": "oops" },
"status": { "awake": true }
}
],
"inverse3": [],
"verse_grip": [],
"custom_verse_grip": []
})
.to_string();
let mut first = false;
update_state_on_message(
state.clone(),
bad_state_only_message,
&mut first,
VerseGripDuplicateMode::KeepBoth,
)
.await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].state.hall, Some(7));
}
#[tokio::test]
async fn test_state_only_omitted_lanes_do_not_clear_cached_devices() {
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
{
let mut lock = state.lock().await;
lock.data.inverse3.push(Inverse3Device {
device_id: "04C3".to_string(),
config: None,
state: Inverse3State::default(),
status: Inverse3Status::default(),
});
lock.data.wireless_verse_grip.push(WirelessVerseGripDevice {
device_id: "1402".to_string(),
config: None,
state: WirelessVerseGripState {
hall: Some(1),
..Default::default()
},
status: WirelessVerseGripStatus::default(),
});
}
let state_only_message = json!({
"session_id": 55,
"wireless_verse_grip": [
{
"device_id": "1402",
"state": { "hall": 33 },
"status": { "awake": true }
}
]
})
.to_string();
let mut first = false;
update_state_on_message(
state.clone(),
state_only_message,
&mut first,
VerseGripDuplicateMode::KeepBoth,
)
.await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.inverse3.len(), 1);
assert_eq!(state_lock.inverse3[0].device_id, "04C3");
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].state.hall, Some(33));
}
#[tokio::test]
async fn test_best_effort_state_only_parses_good_entries_when_one_is_bad() {
let state = Arc::new(Mutex::new(TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
}));
{
let mut lock = state.lock().await;
lock.data.wireless_verse_grip.push(WirelessVerseGripDevice {
device_id: "1402".to_string(),
config: None,
state: WirelessVerseGripState {
hall: Some(7),
..Default::default()
},
status: WirelessVerseGripStatus::default(),
});
lock.data.wireless_verse_grip.push(WirelessVerseGripDevice {
device_id: "3000".to_string(),
config: None,
state: WirelessVerseGripState {
hall: Some(99),
..Default::default()
},
status: WirelessVerseGripStatus::default(),
});
}
let mixed_message = json!({
"wireless_verse_grip": [
{
"device_id": "1402",
"state": { "hall": 11 },
"status": { "awake": true }
},
{
"device_id": "BAD1",
"state": { "hall": "oops" },
"status": { "awake": true }
}
]
})
.to_string();
let mut first = false;
update_state_on_message(
state.clone(),
mixed_message,
&mut first,
VerseGripDuplicateMode::KeepBoth,
)
.await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 2);
assert_eq!(
state_lock
.wireless_verse_grip
.iter()
.find(|d| d.device_id == "1402")
.unwrap()
.state
.hall,
Some(11)
);
assert_eq!(
state_lock
.wireless_verse_grip
.iter()
.find(|d| d.device_id == "3000")
.unwrap()
.state
.hall,
Some(99)
);
}
#[tokio::test]
async fn test_parse_new_wireless_versegrip_types() {
let sample_message = json!({
"session_id": 114,
"session": {
"session_id": 114,
"config": {
"profile": { "name": "default" },
"started_time": "2026-04-14 21:03:41.8127122"
}
},
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1412",
"config": {
"port": "COM22",
"type": "ruko",
"major_version": 2,
"minor_version": 2,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"battery_voltage": 4.17,
"battery_level": 0.95000046,
"buttons": { "down": false, "up": false, "right": false, "left": false },
"trigger": 0,
"wheel": 210,
"hall": 15,
"orientation": {
"x": -0.45703125, "y": -0.48660278,
"z": -0.2996521, "w": 0.6790161
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": { "awake": true }
}
],
"custom_verse_grip": [
{
"device_id": "1412",
"config": {
"port": "COM22",
"type": "custom_verse_grip",
"major_version": 2,
"minor_version": 2,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"battery_voltage": 4.17,
"battery_level": 0.95000046,
"buttons": { "a": false, "b": false, "c": false },
"hall": 15,
"orientation": {
"x": -0.45703125, "y": -0.48660278,
"z": -0.2996521, "w": 0.6790161
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
},
"extension_data": [0, 0, 82, 79, 5, 6, 7, 8, 9, 10, 11, 12]
},
"status": { "connected": true, "awake": true, "ready": true }
}
]
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 114);
let session = state_lock.session.as_ref().unwrap();
assert_eq!(session.session_id, 114);
assert_eq!(session.config.as_ref().unwrap().profile.as_ref().unwrap().name, "default");
assert_eq!(state_lock.wireless_verse_grip.len(), 0);
assert_eq!(state_lock.custom_verse_grip.len(), 1);
assert_eq!(state_lock.custom_verse_grip[0].device_id, "1412");
let cvg_config = state_lock.custom_verse_grip[0].config.clone().unwrap();
assert_eq!(cvg_config.type_, device_model::DeviceType::CustomVerseGrip);
assert_eq!(cvg_config.preset.as_ref().unwrap(), "defaults");
assert_eq!(cvg_config.coordinate_system.as_ref().unwrap().permutation, "XYZ");
assert!(cvg_config.mount.is_some());
assert_eq!(
state_lock.custom_verse_grip[0].state.extension_data.as_ref().unwrap(),
&[0, 0, 82, 79, 5, 6, 7, 8, 9, 10, 11, 12]
);
let cvg_buttons = state_lock.custom_verse_grip[0].state.buttons.unwrap();
assert_eq!(cvg_buttons.a, false);
assert_eq!(cvg_buttons.b, false);
assert_eq!(cvg_buttons.c, false);
}
#[tokio::test]
async fn test_standalone_ruko_not_filtered() {
let sample_message = json!({
"session_id": 200,
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "2001",
"config": {
"port": "COM30",
"type": "ruko",
"major_version": 2,
"minor_version": 2,
"hardware_version": 1,
"streaming_mode": "Radio",
"basis": { "permutation": "XYZ" }
},
"state": {
"battery_voltage": 4.0,
"battery_level": 0.9,
"buttons": { "down": false, "up": true, "right": false, "left": false },
"hall": 10,
"orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }
},
"status": { "awake": true }
}
],
"custom_verse_grip": []
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].device_id, "2001");
let cfg = state_lock.wireless_verse_grip[0].config.clone().unwrap();
assert_eq!(cfg.type_, device_model::DeviceType::Ruko);
assert_eq!(state_lock.wireless_verse_grip[0].state.buttons.unwrap().up, true);
assert_eq!(state_lock.custom_verse_grip.len(), 0);
}
#[tokio::test]
async fn test_ruko_trigger_wheel_fields() {
let sample_message = json!({
"session_id": 26,
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46", "type": "ruko",
"major_version": 2, "minor_version": 2, "hardware_version": 1,
"streaming_mode": "Radio"
},
"state": {
"battery_voltage": 4.06, "battery_level": 0.77,
"buttons": { "down": false, "up": false, "right": false, "left": false },
"trigger": 4,
"wheel": 42,
"hall": 16,
"orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"transform": {}, "transform_velocity": {}
},
"status": { "awake": true }
}
],
"custom_verse_grip": []
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].state.trigger, Some(4));
assert_eq!(state_lock.wireless_verse_grip[0].state.wheel, Some(42));
assert_eq!(state_lock.wireless_verse_grip[0].state.hall, Some(16));
}
#[tokio::test]
async fn test_standard_wvg_no_trigger_wheel() {
let sample_message = json!({
"session_id": 27,
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "0001",
"config": {
"port": "COM6", "type": "wireless_verse_grip",
"major_version": 1, "minor_version": 4, "hardware_version": 1
},
"state": {
"battery_voltage": 3.8, "battery_level": 0.9,
"buttons": { "a": false, "b": false, "c": false },
"hall": 0,
"orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }
},
"status": { "connected": true, "awake": true, "ready": true }
}
],
"custom_verse_grip": []
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].state.trigger, None);
assert_eq!(state_lock.wireless_verse_grip[0].state.wheel, None);
assert_eq!(state_lock.wireless_verse_grip[0].state.hall, Some(0));
}
#[tokio::test]
async fn test_verse_grip_prefer_wireless_mode() {
let sample_message = json!({
"session_id": 28,
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1412",
"config": { "port": "COM22", "type": "ruko", "major_version": 2, "minor_version": 2, "hardware_version": 1 },
"state": { "battery_voltage": 4.0, "battery_level": 0.9, "buttons": { "down": false, "up": false, "right": false, "left": false }, "trigger": 3, "wheel": 100, "hall": 15, "orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 } },
"status": { "awake": true }
}
],
"custom_verse_grip": [
{
"device_id": "1412",
"config": { "port": "COM22", "type": "custom_verse_grip", "major_version": 2, "minor_version": 2, "hardware_version": 1 },
"state": { "battery_voltage": 4.0, "battery_level": 0.9, "buttons": { "a": false, "b": false, "c": false }, "hall": 15, "orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }, "extension_data": [0, 3, 100] },
"status": { "connected": true, "awake": true, "ready": true }
}
]
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferWireless).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].device_id, "1412");
assert_eq!(state_lock.wireless_verse_grip[0].state.trigger, Some(3));
assert_eq!(state_lock.custom_verse_grip.len(), 0);
}
#[tokio::test]
async fn test_verse_grip_keep_both_mode() {
let sample_message = json!({
"session_id": 29,
"inverse3": [],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1412",
"config": { "port": "COM22", "type": "ruko", "major_version": 2, "minor_version": 2, "hardware_version": 1 },
"state": { "battery_voltage": 4.0, "battery_level": 0.9, "buttons": { "down": false, "up": false, "right": false, "left": false }, "hall": 15, "orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 } },
"status": { "awake": true }
}
],
"custom_verse_grip": [
{
"device_id": "1412",
"config": { "port": "COM22", "type": "custom_verse_grip", "major_version": 2, "minor_version": 2, "hardware_version": 1 },
"state": { "battery_voltage": 4.0, "battery_level": 0.9, "buttons": { "a": false, "b": false, "c": false }, "hall": 15, "orientation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }, "extension_data": [1, 2, 3] },
"status": { "connected": true, "awake": true, "ready": true }
}
]
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::KeepBoth).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.wireless_verse_grip.len(), 1);
assert_eq!(state_lock.custom_verse_grip.len(), 1);
assert_eq!(state_lock.wireless_verse_grip[0].device_id, "1412");
assert_eq!(state_lock.custom_verse_grip[0].device_id, "1412");
}
#[tokio::test]
async fn test_parse_transformless_format() {
let sample_message = json!({
"session_id": 15,
"session": {
"session_id": 15,
"config": {
"profile": { "name": "default" },
"started_time": "2026-04-16 18:13:29.7875056"
}
},
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"device_info": {
"major_version": 7, "minor_version": 5,
"id": "04BA", "device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"port": "COM18",
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "F7BACE8FF058FF8206CD336A475CF81F",
"gravity_compensation": { "enabled": true, "scaling_factor": 0 },
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": { "enabled": true },
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {},
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": -1 }
}
},
"state": {
"angular_position": { "a0": 0, "a1": 0, "a2": 0 },
"angular_velocity": { "a0": 0, "a1": 0, "a2": 0 },
"body_orientation": { "x": -0.049804688, "y": 0.69537354, "z": -0.0021972656, "w": 0.71691895 },
"cursor_position": { "x": 0.020575874, "y": -0.02761598, "z": 0.19947645 },
"cursor_velocity": { "x": 0, "y": 0, "z": 0 },
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "a0": 0, "a1": 0, "a2": 0 },
"current_angular_position": { "a0": 0, "a1": 0, "a2": 0 },
"mode": "idle",
"control_domain": "undefined",
"control_mode": "idle",
"transform": {},
"transform_velocity": {}
},
"status": { "calibrated": true, "in_use": false, "power_supply": true, "ready": true, "started": true }
}
],
"verse_grip": [],
"wireless_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46", "type": "ruko",
"major_version": 2, "minor_version": 2, "hardware_version": 1,
"streaming_mode": "Radio",
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {}
},
"state": {
"battery_voltage": 4.075, "battery_level": 0.7916666,
"buttons": { "down": false, "up": false, "right": false, "left": false },
"trigger": 3, "wheel": 166, "hall": 15,
"orientation": { "x": 0.47711182, "y": 0.431427, "z": -0.5709839, "w": 0.50668335 },
"transform": {},
"transform_velocity": {}
},
"status": { "awake": true }
}
],
"custom_verse_grip": [
{
"device_id": "1402",
"config": {
"port": "COM46", "type": "custom_verse_grip",
"major_version": 2, "minor_version": 2, "hardware_version": 1,
"streaming_mode": "Radio",
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {}
},
"state": {
"battery_voltage": 4.075, "battery_level": 0.7916666,
"buttons": { "a": false, "b": false, "c": false },
"hall": 15,
"orientation": { "x": 0.47711182, "y": 0.431427, "z": -0.5709839, "w": 0.50668335 },
"transform": {},
"transform_velocity": {},
"extension_data": [0, 3, 65, 30, 5, 6, 7, 8, 9, 10, 11, 12]
},
"status": { "connected": true, "awake": true, "ready": true }
}
]
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.session_id, 15);
let session = state_lock.session.as_ref().unwrap();
assert_eq!(session.session_id, 15);
assert_eq!(session.config.as_ref().unwrap().profile.as_ref().unwrap().name, "default");
assert!(session.config.as_ref().unwrap().started_time.is_some());
let i3 = &state_lock.inverse3[0];
assert_eq!(i3.device_id, "04BA");
assert_eq!(i3.state.cursor_position.as_ref().unwrap().x, 0.020575874);
assert_eq!(i3.state.mode, device_model::DeviceMode::Idle);
assert!(i3.state.transform.is_none());
assert!(i3.state.transform_velocity.is_none());
assert!(i3.state.current_cursor_force.is_some());
assert_eq!(i3.state.current_cursor_force.as_ref().unwrap().x, 0.0);
assert!(i3.state.current_cursor_position.is_some());
assert_eq!(i3.state.current_cursor_position.as_ref().unwrap().x, 0.0);
assert!(i3.state.current_angular_torques.is_some());
assert_eq!(i3.state.current_angular_torques.as_ref().unwrap().0.x, 0.0);
assert!(i3.state.current_angular_position.is_some());
assert_eq!(i3.state.current_angular_position.as_ref().unwrap().0.x, 0.0);
let inv_config = i3.config.clone().unwrap();
assert_eq!(inv_config.preset.as_ref().unwrap(), "defaults");
assert_eq!(inv_config.coordinate_system.as_ref().unwrap().permutation, "XYZ");
assert!(inv_config.mount.is_none()); let filters = inv_config.filters.as_ref().unwrap();
assert_eq!(filters.force_gate.as_ref().unwrap().gain, 0.3);
assert_eq!(filters.damping.as_ref().unwrap().scalar, -1.0);
assert_eq!(state_lock.wireless_verse_grip.len(), 0); assert_eq!(state_lock.custom_verse_grip.len(), 1);
assert_eq!(state_lock.custom_verse_grip[0].device_id, "1402");
assert!(state_lock.custom_verse_grip[0].state.transform.is_none());
assert!(state_lock.custom_verse_grip[0].state.transform_velocity.is_none());
assert_eq!(
state_lock.custom_verse_grip[0].state.extension_data.as_ref().unwrap(),
&[0, 3, 65, 30, 5, 6, 7, 8, 9, 10, 11, 12]
);
}
#[tokio::test]
async fn test_parse_mixed_transform_format() {
let sample_message = json!({
"session_id": 16,
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"device_info": {
"major_version": 7, "minor_version": 5,
"id": "04BA", "device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"port": "COM18",
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "F7BACE8FF058FF8206CD336A475CF81F",
"gravity_compensation": { "enabled": true, "scaling_factor": 0 },
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": { "enabled": true },
"basis": { "permutation": "XYZ" }
},
"state": {
"angular_position": { "a0": 0, "a1": 0, "a2": 0 },
"angular_velocity": { "a0": 0, "a1": 0, "a2": 0 },
"body_orientation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"cursor_position": { "x": 0.1, "y": 0.2, "z": 0.3 },
"cursor_velocity": { "x": 0, "y": 0, "z": 0 },
"mode": "idle",
"control_domain": "undefined",
"control_mode": "idle",
"transform": {
"position": { "x": 1.0, "y": 2.0, "z": 3.0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {}
},
"status": { "calibrated": true, "in_use": false, "power_supply": true, "ready": true, "started": true }
}
],
"verse_grip": [],
"wireless_verse_grip": [],
"custom_verse_grip": []
}).to_string();
let timestamped_state = TimestampedServiceData {
timestamp: std::time::Instant::now(),
data: ServiceData::default(),
};
let state = Arc::new(Mutex::new(timestamped_state));
let mut first = false;
update_state_on_message(state.clone(), sample_message, &mut first, VerseGripDuplicateMode::PreferCustom).await;
let state_lock = state.lock().await.data.clone();
assert_eq!(state_lock.inverse3.len(), 1);
let i3_transform = state_lock.inverse3[0].state.transform.as_ref().unwrap();
assert_eq!(i3_transform.position.x, 1.0);
assert_eq!(i3_transform.position.y, 2.0);
assert_eq!(i3_transform.position.z, 3.0);
assert_eq!(i3_transform.rotation.w, 1.0);
assert_eq!(i3_transform.scale.x, 1.0);
assert!(state_lock.inverse3[0].state.transform_velocity.is_none());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::http::ApiResponse;
#[test]
fn test_force_render_full_state_command() {
let mut service_msg = ServiceMsg::default();
let command = vec![Command::ForceRenderFullState];
update_entire_msg(&command, &mut service_msg).unwrap();
assert!(service_msg.session.force_render_full_state.is_some());
}
#[test]
fn test_ping_service_command() {
let mut service_msg = ServiceMsg::default();
service_msg.session.force_render_full_state = None;
let default_msg = service_msg.clone();
let command = vec![Command::PingService];
update_entire_msg(&command, &mut service_msg).unwrap();
assert_eq!(service_msg, default_msg);
}
#[test]
fn test_api_response_ok_deserialization() {
let json_str = r#"{ "ok": true, "data": { "name": "default" } }"#;
let resp: ApiResponse = serde_json::from_str(json_str).unwrap();
assert!(resp.ok);
assert!(resp.data.is_some());
assert!(resp.error.is_none());
let profile: ProfileInfo = serde_json::from_value(resp.data.unwrap()).unwrap();
assert_eq!(profile.name, "default");
}
#[test]
fn test_api_response_error_deserialization() {
let json_str = r#"{ "ok": false, "error": "device not found" }"#;
let resp: ApiResponse = serde_json::from_str(json_str).unwrap();
assert!(!resp.ok);
assert!(resp.data.is_none());
assert_eq!(resp.error.as_ref().unwrap(), "device not found");
}
#[test]
fn test_api_response_ok_no_data() {
let json_str = r#"{ "ok": true }"#;
let resp: ApiResponse = serde_json::from_str(json_str).unwrap();
assert!(resp.ok);
assert!(resp.data.is_none());
}
fn assert_json_f32(val: &serde_json::Value, expected: f32) {
let actual = val.as_f64().expect("expected number");
assert!((actual - expected as f64).abs() < 1e-5,
"expected ~{}, got {}", expected, actual);
}
#[test]
fn test_sdf_primitive_sphere_serialization() {
let prim = SdfPrimitive::Sphere { r: 0.05 };
let json = serde_json::to_value(&prim).unwrap();
assert_eq!(json["primitive"], "sphere");
assert_json_f32(&json["parameters"]["r"], 0.05);
let json_str = serde_json::to_string(&prim).unwrap();
let back: SdfPrimitive = serde_json::from_str(&json_str).unwrap();
assert_eq!(back, prim);
}
#[test]
fn test_sdf_primitive_box_serialization() {
let prim = SdfPrimitive::Box {
b: Linear3D { x: 0.04, y: 0.02, z: 0.04 },
};
let json = serde_json::to_value(&prim).unwrap();
assert_eq!(json["primitive"], "box");
assert_json_f32(&json["parameters"]["b"]["x"], 0.04);
assert_json_f32(&json["parameters"]["b"]["y"], 0.02);
}
#[test]
fn test_sdf_primitive_plane_serialization() {
let prim = SdfPrimitive::Plane {
n: Some(Linear3D { x: 0.0, y: 0.0, z: -1.0 }),
h: 0.0,
};
let json = serde_json::to_value(&prim).unwrap();
assert_eq!(json["primitive"], "plane");
assert_json_f32(&json["parameters"]["h"], 0.0);
assert_json_f32(&json["parameters"]["n"]["z"], -1.0);
}
#[test]
fn test_sdf_primitive_capsule_serialization() {
let prim = SdfPrimitive::Capsule {
a: Linear3D { x: 0.0, y: -0.03, z: 0.0 },
b: Linear3D { x: 0.0, y: 0.03, z: 0.0 },
r: 0.04,
};
let json = serde_json::to_value(&prim).unwrap();
assert_eq!(json["primitive"], "capsule");
assert_json_f32(&json["parameters"]["a"]["y"], -0.03);
assert_json_f32(&json["parameters"]["b"]["y"], 0.03);
assert_json_f32(&json["parameters"]["r"], 0.04);
let json_str = serde_json::to_string(&prim).unwrap();
let back: SdfPrimitive = serde_json::from_str(&json_str).unwrap();
assert_eq!(back, prim);
}
#[test]
fn test_sdf_primitive_ellipsoid_deserialization() {
let json_str = r#"{ "primitive": "ellipsoid", "parameters": { "a": { "x": 0.06, "y": 0.03, "z": 0.06 } } }"#;
let prim: SdfPrimitive = serde_json::from_str(json_str).unwrap();
match prim {
SdfPrimitive::Ellipsoid { a } => {
assert_eq!(a.x, 0.06);
assert_eq!(a.y, 0.03);
}
_ => panic!("Expected Ellipsoid"),
}
}
#[test]
fn test_sdf_command_set_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::SdfSet {
device_id: "049D".to_string(),
objects: vec![SdfHfxObject {
id: "boundary".to_string(),
shape: Some(SdfPrimitive::Sphere { r: 1.0 }),
force_scale: Some(-2.0),
range: Some(0.1),
ease: Some(EasingType::CubicInOut),
symmetry: Some(SymmetryMode::Single),
..Default::default()
}],
from_space: Some("application".to_string()),
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
let sdf_cmd = &json["inverse3"][0]["commands"]["sdf"];
assert_eq!(sdf_cmd["mode"], "set");
assert_eq!(sdf_cmd["from_space"], "application");
assert_eq!(sdf_cmd["objects"][0]["id"], "boundary");
assert_eq!(sdf_cmd["objects"][0]["shape"]["primitive"], "sphere");
assert_eq!(sdf_cmd["objects"][0]["force_scale"], -2.0);
assert_eq!(sdf_cmd["objects"][0]["ease"], "cubic_in_out");
assert_eq!(sdf_cmd["objects"][0]["symmetry"], "single");
}
#[test]
fn test_sdf_command_update_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::SdfUpdate {
device_id: "049D".to_string(),
objects: vec![SdfHfxObject {
id: "boundary".to_string(),
force_scale: Some(-3.0),
..Default::default()
}],
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
let sdf_cmd = &json["inverse3"][0]["commands"]["sdf"];
assert_eq!(sdf_cmd["mode"], "update");
assert_eq!(sdf_cmd["objects"][0]["id"], "boundary");
assert_eq!(sdf_cmd["objects"][0]["force_scale"], -3.0);
assert!(sdf_cmd["objects"][0].get("shape").is_none());
}
#[test]
fn test_sdf_command_remove_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::SdfRemove {
device_id: "049D".to_string(),
ids: vec!["boundary".to_string(), "wall".to_string()],
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
let sdf_cmd = &json["inverse3"][0]["commands"]["sdf"];
assert_eq!(sdf_cmd["mode"], "remove");
assert_eq!(sdf_cmd["objects"][0]["id"], "boundary");
assert_eq!(sdf_cmd["objects"][1]["id"], "wall");
}
#[test]
fn test_sdf_output_configure_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::ConfigureSdfOutput {
device_id: "049D".to_string(),
state_output: true,
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
assert_eq!(json["inverse3"][0]["configure"]["sdf"]["state_output"], true);
}
#[test]
fn test_navigation_full_configure_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::ConfigureInverse3 {
device_id: "04C3".to_string(),
config: Inverse3Configure {
navigation: Some(NavigationConfigure {
mode: "bubble".to_string(),
bubble: Some(BubbleNavigationSettings {
shape: Some(SdfPrimitive::Sphere { r: 0.05 }),
velocity_zone_width: Some(0.03),
max_velocity: Some(1.0),
velocity_ease: Some(EasingType::Linear),
spring_inner: Some(4.0),
spring_surface: Some(7.0),
spring_outer: Some(12.0),
workspace_bounded: Some(true),
center_mode: Some(CenterMode::AutoFollow),
avatar_boundary_enabled: Some(true),
avatar_boundary: Some(SdfPrimitive::Box {
b: Linear3D { x: 5.0, y: 3.0, z: 5.0 },
}),
..Default::default()
}),
}),
..Default::default()
},
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
let nav = &json["inverse3"][0]["configure"]["navigation"];
assert_eq!(nav["mode"], "bubble");
assert_eq!(nav["bubble"]["shape"]["primitive"], "sphere");
assert_json_f32(&nav["bubble"]["shape"]["parameters"]["r"], 0.05);
assert_json_f32(&nav["bubble"]["velocity_zone_width"], 0.03);
assert_json_f32(&nav["bubble"]["spring_surface"], 7.0);
assert_eq!(nav["bubble"]["workspace_bounded"], true);
assert_eq!(nav["bubble"]["center_mode"], "auto_follow");
assert_eq!(nav["bubble"]["avatar_boundary_enabled"], true);
assert_eq!(nav["bubble"]["avatar_boundary"]["primitive"], "box");
assert_json_f32(&nav["bubble"]["avatar_boundary"]["parameters"]["b"]["x"], 5.0);
}
#[test]
fn test_navigation_minimal_configure_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::ConfigureInverse3 {
device_id: "04C3".to_string(),
config: Inverse3Configure {
navigation: Some(NavigationConfigure {
mode: "bubble".to_string(),
bubble: None,
}),
..Default::default()
},
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
let nav = &json["inverse3"][0]["configure"]["navigation"];
assert_eq!(nav["mode"], "bubble");
assert!(nav.get("bubble").is_none());
}
#[test]
fn test_navigation_disable_wire_format() {
let mut msg = ServiceMsg::default();
msg.session.force_render_full_state = None;
let cmd = Command::ConfigureInverse3 {
device_id: "04C3".to_string(),
config: Inverse3Configure {
navigation: Some(NavigationConfigure {
mode: "disabled".to_string(),
bubble: None,
}),
..Default::default()
},
};
update_msg_from_command(cmd, &mut msg).unwrap();
let json = serde_json::to_value(&msg).unwrap();
let nav = &json["inverse3"][0]["configure"]["navigation"];
assert_eq!(nav["mode"], "disabled");
}
#[test]
fn test_sdf_session_state_deserialization() {
let json_str = r#"{
"objects": [
{
"id": "boundary_049D",
"shape": { "primitive": "sphere", "parameters": { "r": 1.0 } },
"transform": { "position": { "x": 0, "y": -0.05, "z": -0.04 } },
"force_scale": -2.0,
"range": 0.1,
"ease": "cubic_in_out",
"reverse_easing": false,
"symmetry": "single",
"session_id": 7,
"devices": ["049D"]
}
]
}"#;
let state: SdfSessionState = serde_json::from_str(json_str).unwrap();
assert_eq!(state.objects.len(), 1);
let obj = &state.objects[0];
assert_eq!(obj.object.id, "boundary_049D");
assert_eq!(obj.session_id, Some(7));
assert_eq!(obj.devices.as_ref().unwrap(), &["049D"]);
assert_eq!(obj.object.force_scale, Some(-2.0));
assert_eq!(obj.object.range, Some(0.1));
assert_eq!(obj.object.ease, Some(EasingType::CubicInOut));
assert_eq!(obj.object.symmetry, Some(SymmetryMode::Single));
match obj.object.shape.as_ref().unwrap() {
SdfPrimitive::Sphere { r } => assert_eq!(*r, 1.0),
_ => panic!("Expected Sphere"),
}
}
#[test]
fn test_readme_sdf_positioning_experimental_only() {
let readme = include_str!("../README.md");
assert!(
!readme.contains("**SDF haptic effects**"),
"README must not advertise SDF as a supported feature"
);
assert!(
!readme.contains("| SDF effects |"),
"README WS coverage must not advertise SDF methods"
);
assert!(
!readme.contains("| SDF | 8 device-scoped + 4 flat-route methods |"),
"README HTTP coverage must not advertise SDF endpoints"
);
let has_sdf_demo_row = readme.contains("| `sdf_demo` |");
if has_sdf_demo_row {
assert!(
readme.contains("Experimental/internal-only"),
"If README includes sdf_demo, it must be marked experimental/internal-only"
);
}
}
}