1use std::collections::HashMap;
4
5use serde::Deserialize;
6
7#[derive(Debug, Clone, Default)]
9pub struct QueryHeadsetsOptions {
10 pub id: Option<String>,
12 pub include_flex_mappings: bool,
14}
15
16#[derive(Debug, Clone, Deserialize)]
18pub struct HeadsetInfo {
19 pub id: String,
21
22 pub status: String,
24
25 #[serde(rename = "connectedBy")]
27 pub connected_by: Option<String>,
28
29 #[serde(rename = "dongle")]
31 pub dongle_serial: Option<String>,
32
33 pub firmware: Option<String>,
35
36 #[serde(rename = "motionSensors")]
38 pub motion_sensors: Option<Vec<String>>,
39
40 pub sensors: Option<Vec<String>>,
42
43 pub settings: Option<serde_json::Value>,
45
46 #[serde(rename = "flexMappings", alias = "flexMapping")]
51 pub flex_mapping: Option<serde_json::Value>,
52
53 #[serde(rename = "headbandPosition")]
55 pub headband_position: Option<String>,
56
57 #[serde(rename = "customName")]
59 pub custom_name: Option<String>,
60
61 #[serde(rename = "isVirtual")]
63 pub is_virtual: Option<bool>,
64
65 pub mode: Option<String>,
67
68 #[serde(rename = "batteryPercent")]
70 pub battery_percent: Option<u32>,
71
72 #[serde(rename = "signalStrength")]
74 pub signal_strength: Option<u32>,
75
76 pub power: Option<String>,
78
79 #[serde(rename = "virtualHeadsetId")]
81 pub virtual_headset_id: Option<String>,
82
83 #[serde(rename = "firmwareDisplay")]
85 pub firmware_display: Option<String>,
86
87 #[serde(rename = "isDfuMode")]
89 pub is_dfu_mode: Option<bool>,
90
91 #[serde(rename = "dfuTypes")]
93 pub dfu_types: Option<Vec<String>>,
94
95 #[serde(rename = "systemUpTime")]
97 pub system_up_time: Option<u64>,
98
99 pub uptime: Option<u64>,
101
102 #[serde(rename = "bluetoothUpTime")]
104 pub bluetooth_up_time: Option<u64>,
105
106 pub counter: Option<u64>,
108
109 #[serde(flatten)]
111 pub extra: HashMap<String, serde_json::Value>,
112}
113
114#[derive(Debug, Clone, Deserialize)]
116pub struct HeadsetClockSyncResult {
117 pub adjustment: f64,
119 pub headset: String,
121}
122
123#[derive(Debug, Clone, Copy)]
125pub enum ConfigMappingMode {
126 Create,
127 Get,
128 Read,
129 Update,
130 Delete,
131}
132
133impl ConfigMappingMode {
134 #[must_use]
136 pub fn as_str(&self) -> &'static str {
137 match self {
138 ConfigMappingMode::Create => "create",
139 ConfigMappingMode::Get => "get",
140 ConfigMappingMode::Read => "read",
141 ConfigMappingMode::Update => "update",
142 ConfigMappingMode::Delete => "delete",
143 }
144 }
145}
146
147#[derive(Debug, Clone)]
149pub enum ConfigMappingRequest {
150 Create {
152 name: String,
153 mappings: serde_json::Value,
154 },
155 Get,
157 Read { uuid: String },
159 Update {
161 uuid: String,
162 name: Option<String>,
163 mappings: Option<serde_json::Value>,
164 },
165 Delete { uuid: String },
167}
168
169impl ConfigMappingRequest {
170 #[must_use]
172 pub fn mode(&self) -> ConfigMappingMode {
173 match self {
174 ConfigMappingRequest::Create { .. } => ConfigMappingMode::Create,
175 ConfigMappingRequest::Get => ConfigMappingMode::Get,
176 ConfigMappingRequest::Read { .. } => ConfigMappingMode::Read,
177 ConfigMappingRequest::Update { .. } => ConfigMappingMode::Update,
178 ConfigMappingRequest::Delete { .. } => ConfigMappingMode::Delete,
179 }
180 }
181}
182
183#[derive(Debug, Clone, Deserialize)]
185pub struct ConfigMappingValue {
186 pub label: Option<serde_json::Value>,
188 pub mappings: serde_json::Value,
190 pub name: String,
192 pub uuid: String,
194 #[serde(flatten)]
196 pub extra: HashMap<String, serde_json::Value>,
197}
198
199#[derive(Debug, Clone, Deserialize)]
201pub struct ConfigMappingListValue {
202 #[serde(default)]
204 pub config: Vec<ConfigMappingValue>,
205 pub updated: Option<String>,
207 pub version: Option<String>,
209 #[serde(flatten)]
211 pub extra: HashMap<String, serde_json::Value>,
212}
213
214#[derive(Debug, Clone)]
216pub enum ConfigMappingResponse {
217 Value {
219 message: String,
220 value: ConfigMappingValue,
221 },
222 List {
224 message: String,
225 value: ConfigMappingListValue,
226 },
227 Deleted { message: String, uuid: String },
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_deserialize_headset_info() {
237 let json = r#"{
238 "id": "INSIGHT-A1B2C3D4",
239 "dongle": "6ff",
240 "firmware": "925",
241 "status": "connected",
242 "connectedBy": "dongle",
243 "motionSensors": ["GYROX", "GYROY", "GYROZ", "ACCX", "ACCY", "ACCZ"],
244 "sensors": ["AF3", "AF4", "T7", "T8", "Pz"]
245 }"#;
246
247 let info: HeadsetInfo = serde_json::from_str(json).unwrap();
248 assert_eq!(info.id, "INSIGHT-A1B2C3D4");
249 assert_eq!(info.status, "connected");
250 assert_eq!(info.sensors.as_ref().unwrap().len(), 5);
251 assert!(info.extra.is_empty());
252 }
253
254 #[test]
255 fn test_deserialize_headset_info_flex_mappings_aliases() {
256 let new_json = r#"{
257 "id": "EPOCFLEX-ABCD1234",
258 "status": "connected",
259 "flexMappings": {"AF3":"C1"}
260 }"#;
261 let old_json = r#"{
262 "id": "EPOCFLEX-ABCD1234",
263 "status": "connected",
264 "flexMapping": {"AF3":"C1"}
265 }"#;
266
267 let new_info: HeadsetInfo = serde_json::from_str(new_json).unwrap();
268 let old_info: HeadsetInfo = serde_json::from_str(old_json).unwrap();
269
270 assert!(new_info.flex_mapping.is_some());
271 assert!(old_info.flex_mapping.is_some());
272 }
273
274 #[test]
275 fn test_deserialize_headset_info_extended_fields() {
276 let json = r#"{
277 "id": "EPOCX-11223344",
278 "status": "connected",
279 "mode": "EPOC X",
280 "batteryPercent": 87,
281 "signalStrength": 2,
282 "power": "on",
283 "virtualHeadsetId": "VH-001",
284 "firmwareDisplay": "3.7.1",
285 "isDfuMode": false,
286 "dfuTypes": ["firmware"],
287 "systemUpTime": 12345,
288 "uptime": 12300,
289 "bluetoothUpTime": 12000,
290 "counter": 91,
291 "futureField": "future"
292 }"#;
293
294 let info: HeadsetInfo = serde_json::from_str(json).unwrap();
295 assert_eq!(info.mode.as_deref(), Some("EPOC X"));
296 assert_eq!(info.battery_percent, Some(87));
297 assert_eq!(info.signal_strength, Some(2));
298 assert_eq!(info.power.as_deref(), Some("on"));
299 assert_eq!(info.virtual_headset_id.as_deref(), Some("VH-001"));
300 assert_eq!(info.firmware_display.as_deref(), Some("3.7.1"));
301 assert_eq!(info.is_dfu_mode, Some(false));
302 assert_eq!(info.dfu_types, Some(vec!["firmware".to_string()]));
303 assert_eq!(info.system_up_time, Some(12345));
304 assert_eq!(info.uptime, Some(12300));
305 assert_eq!(info.bluetooth_up_time, Some(12000));
306 assert_eq!(info.counter, Some(91));
307 assert_eq!(
308 info.extra.get("futureField"),
309 Some(&serde_json::json!("future"))
310 );
311 }
312
313 #[test]
314 fn test_deserialize_headset_clock_sync_result() {
315 let json = r#"{
316 "adjustment": 0.0123,
317 "headset": "INSIGHT-A1B2C3D4"
318 }"#;
319
320 let sync: HeadsetClockSyncResult = serde_json::from_str(json).unwrap();
321 assert!((sync.adjustment - 0.0123).abs() < f64::EPSILON);
322 assert_eq!(sync.headset, "INSIGHT-A1B2C3D4");
323 }
324 #[test]
325 fn test_deserialize_config_mapping_value_response_shapes() {
326 let value_json = r#"{
327 "message": "Create flex mapping config successful",
328 "value": {
329 "label": {},
330 "mappings": {"CMS":"TP9"},
331 "name": "config1",
332 "uuid": "4416dc1b-3a7c-4d20-9ec6-aacdb9930071"
333 }
334 }"#;
335 let list_json = r#"{
336 "message": "Get flex mapping config successful",
337 "value": {
338 "config": [{
339 "label": {},
340 "mappings": {"CMS":"TP10"},
341 "name": "Default Configuration",
342 "uuid": "f4296b2d-d6e7-45cf-9569-7bc2a1bd56e4"
343 }],
344 "updated": "2025-10-08T06:16:30.521+07:00",
345 "version": "2018-05-08"
346 }
347 }"#;
348 let delete_json = r#"{
349 "message": "Delete flex mapping config successful",
350 "uuid": "effa621f-49d6-4c46-95f3-28f43813a6e9"
351 }"#;
352
353 #[derive(serde::Deserialize)]
354 struct ValueEnvelope {
355 message: String,
356 value: ConfigMappingValue,
357 }
358 #[derive(serde::Deserialize)]
359 struct ListEnvelope {
360 message: String,
361 value: ConfigMappingListValue,
362 }
363 #[derive(serde::Deserialize)]
364 struct DeleteEnvelope {
365 message: String,
366 uuid: String,
367 }
368
369 let value: ValueEnvelope = serde_json::from_str(value_json).unwrap();
370 assert_eq!(value.message, "Create flex mapping config successful");
371 assert_eq!(value.value.name, "config1");
372
373 let list: ListEnvelope = serde_json::from_str(list_json).unwrap();
374 assert_eq!(list.message, "Get flex mapping config successful");
375 assert_eq!(list.value.config.len(), 1);
376 assert_eq!(list.value.version.as_deref(), Some("2018-05-08"));
377
378 let deleted: DeleteEnvelope = serde_json::from_str(delete_json).unwrap();
379 assert_eq!(deleted.message, "Delete flex mapping config successful");
380 assert_eq!(deleted.uuid, "effa621f-49d6-4c46-95f3-28f43813a6e9");
381 }
382}