use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Clone, Default)]
pub struct QueryHeadsetsOptions {
pub id: Option<String>,
pub include_flex_mappings: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct HeadsetInfo {
pub id: String,
pub status: String,
#[serde(rename = "connectedBy")]
pub connected_by: Option<String>,
#[serde(rename = "dongle")]
pub dongle_serial: Option<String>,
pub firmware: Option<String>,
#[serde(rename = "motionSensors")]
pub motion_sensors: Option<Vec<String>>,
pub sensors: Option<Vec<String>>,
pub settings: Option<serde_json::Value>,
#[serde(rename = "flexMappings", alias = "flexMapping")]
pub flex_mapping: Option<serde_json::Value>,
#[serde(rename = "headbandPosition")]
pub headband_position: Option<String>,
#[serde(rename = "customName")]
pub custom_name: Option<String>,
#[serde(rename = "isVirtual")]
pub is_virtual: Option<bool>,
pub mode: Option<String>,
#[serde(rename = "batteryPercent")]
pub battery_percent: Option<u32>,
#[serde(rename = "signalStrength")]
pub signal_strength: Option<u32>,
pub power: Option<String>,
#[serde(rename = "virtualHeadsetId")]
pub virtual_headset_id: Option<String>,
#[serde(rename = "firmwareDisplay")]
pub firmware_display: Option<String>,
#[serde(rename = "isDfuMode")]
pub is_dfu_mode: Option<bool>,
#[serde(rename = "dfuTypes")]
pub dfu_types: Option<Vec<String>>,
#[serde(rename = "systemUpTime")]
pub system_up_time: Option<u64>,
pub uptime: Option<u64>,
#[serde(rename = "bluetoothUpTime")]
pub bluetooth_up_time: Option<u64>,
pub counter: Option<u64>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct HeadsetClockSyncResult {
pub adjustment: f64,
pub headset: String,
}
#[derive(Debug, Clone, Copy)]
pub enum ConfigMappingMode {
Create,
Get,
Read,
Update,
Delete,
}
impl ConfigMappingMode {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
ConfigMappingMode::Create => "create",
ConfigMappingMode::Get => "get",
ConfigMappingMode::Read => "read",
ConfigMappingMode::Update => "update",
ConfigMappingMode::Delete => "delete",
}
}
}
#[derive(Debug, Clone)]
pub enum ConfigMappingRequest {
Create {
name: String,
mappings: serde_json::Value,
},
Get,
Read { uuid: String },
Update {
uuid: String,
name: Option<String>,
mappings: Option<serde_json::Value>,
},
Delete { uuid: String },
}
impl ConfigMappingRequest {
#[must_use]
pub fn mode(&self) -> ConfigMappingMode {
match self {
ConfigMappingRequest::Create { .. } => ConfigMappingMode::Create,
ConfigMappingRequest::Get => ConfigMappingMode::Get,
ConfigMappingRequest::Read { .. } => ConfigMappingMode::Read,
ConfigMappingRequest::Update { .. } => ConfigMappingMode::Update,
ConfigMappingRequest::Delete { .. } => ConfigMappingMode::Delete,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ConfigMappingValue {
pub label: Option<serde_json::Value>,
pub mappings: serde_json::Value,
pub name: String,
pub uuid: String,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ConfigMappingListValue {
#[serde(default)]
pub config: Vec<ConfigMappingValue>,
pub updated: Option<String>,
pub version: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub enum ConfigMappingResponse {
Value {
message: String,
value: ConfigMappingValue,
},
List {
message: String,
value: ConfigMappingListValue,
},
Deleted { message: String, uuid: String },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_headset_info() {
let json = r#"{
"id": "INSIGHT-A1B2C3D4",
"dongle": "6ff",
"firmware": "925",
"status": "connected",
"connectedBy": "dongle",
"motionSensors": ["GYROX", "GYROY", "GYROZ", "ACCX", "ACCY", "ACCZ"],
"sensors": ["AF3", "AF4", "T7", "T8", "Pz"]
}"#;
let info: HeadsetInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.id, "INSIGHT-A1B2C3D4");
assert_eq!(info.status, "connected");
assert_eq!(info.sensors.as_ref().unwrap().len(), 5);
assert!(info.extra.is_empty());
}
#[test]
fn test_deserialize_headset_info_flex_mappings_aliases() {
let new_json = r#"{
"id": "EPOCFLEX-ABCD1234",
"status": "connected",
"flexMappings": {"AF3":"C1"}
}"#;
let old_json = r#"{
"id": "EPOCFLEX-ABCD1234",
"status": "connected",
"flexMapping": {"AF3":"C1"}
}"#;
let new_info: HeadsetInfo = serde_json::from_str(new_json).unwrap();
let old_info: HeadsetInfo = serde_json::from_str(old_json).unwrap();
assert!(new_info.flex_mapping.is_some());
assert!(old_info.flex_mapping.is_some());
}
#[test]
fn test_deserialize_headset_info_extended_fields() {
let json = r#"{
"id": "EPOCX-11223344",
"status": "connected",
"mode": "EPOC X",
"batteryPercent": 87,
"signalStrength": 2,
"power": "on",
"virtualHeadsetId": "VH-001",
"firmwareDisplay": "3.7.1",
"isDfuMode": false,
"dfuTypes": ["firmware"],
"systemUpTime": 12345,
"uptime": 12300,
"bluetoothUpTime": 12000,
"counter": 91,
"futureField": "future"
}"#;
let info: HeadsetInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.mode.as_deref(), Some("EPOC X"));
assert_eq!(info.battery_percent, Some(87));
assert_eq!(info.signal_strength, Some(2));
assert_eq!(info.power.as_deref(), Some("on"));
assert_eq!(info.virtual_headset_id.as_deref(), Some("VH-001"));
assert_eq!(info.firmware_display.as_deref(), Some("3.7.1"));
assert_eq!(info.is_dfu_mode, Some(false));
assert_eq!(info.dfu_types, Some(vec!["firmware".to_string()]));
assert_eq!(info.system_up_time, Some(12345));
assert_eq!(info.uptime, Some(12300));
assert_eq!(info.bluetooth_up_time, Some(12000));
assert_eq!(info.counter, Some(91));
assert_eq!(
info.extra.get("futureField"),
Some(&serde_json::json!("future"))
);
}
#[test]
fn test_deserialize_headset_clock_sync_result() {
let json = r#"{
"adjustment": 0.0123,
"headset": "INSIGHT-A1B2C3D4"
}"#;
let sync: HeadsetClockSyncResult = serde_json::from_str(json).unwrap();
assert!((sync.adjustment - 0.0123).abs() < f64::EPSILON);
assert_eq!(sync.headset, "INSIGHT-A1B2C3D4");
}
#[test]
fn test_deserialize_config_mapping_value_response_shapes() {
let value_json = r#"{
"message": "Create flex mapping config successful",
"value": {
"label": {},
"mappings": {"CMS":"TP9"},
"name": "config1",
"uuid": "4416dc1b-3a7c-4d20-9ec6-aacdb9930071"
}
}"#;
let list_json = r#"{
"message": "Get flex mapping config successful",
"value": {
"config": [{
"label": {},
"mappings": {"CMS":"TP10"},
"name": "Default Configuration",
"uuid": "f4296b2d-d6e7-45cf-9569-7bc2a1bd56e4"
}],
"updated": "2025-10-08T06:16:30.521+07:00",
"version": "2018-05-08"
}
}"#;
let delete_json = r#"{
"message": "Delete flex mapping config successful",
"uuid": "effa621f-49d6-4c46-95f3-28f43813a6e9"
}"#;
#[derive(serde::Deserialize)]
struct ValueEnvelope {
message: String,
value: ConfigMappingValue,
}
#[derive(serde::Deserialize)]
struct ListEnvelope {
message: String,
value: ConfigMappingListValue,
}
#[derive(serde::Deserialize)]
struct DeleteEnvelope {
message: String,
uuid: String,
}
let value: ValueEnvelope = serde_json::from_str(value_json).unwrap();
assert_eq!(value.message, "Create flex mapping config successful");
assert_eq!(value.value.name, "config1");
let list: ListEnvelope = serde_json::from_str(list_json).unwrap();
assert_eq!(list.message, "Get flex mapping config successful");
assert_eq!(list.value.config.len(), 1);
assert_eq!(list.value.version.as_deref(), Some("2018-05-08"));
let deleted: DeleteEnvelope = serde_json::from_str(delete_json).unwrap();
assert_eq!(deleted.message, "Delete flex mapping config successful");
assert_eq!(deleted.uuid, "effa621f-49d6-4c46-95f3-28f43813a6e9");
}
}