phytrace_sdk/models/domains/
audio.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use validator::Validate;
6
7use crate::models::enums::{SensorStatus, SoundType};
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct AudioDomain {
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub microphones: Option<Vec<Microphone>>,
15
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub speakers: Option<Vec<Speaker>>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub sound_detection: Option<Vec<SoundDetection>>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub voice_output: Option<VoiceOutput>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub ambient_noise_db: Option<f64>,
31}
32
33#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
35pub struct Microphone {
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub mic_id: Option<String>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub status: Option<SensorStatus>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub level_db: Option<f64>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub is_recording: Option<bool>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub sample_rate_hz: Option<u32>,
55}
56
57#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
59pub struct Speaker {
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub speaker_id: Option<String>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub status: Option<SensorStatus>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub is_playing: Option<bool>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 #[validate(range(min = 0.0, max = 100.0))]
75 pub volume_pct: Option<f64>,
76}
77
78#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
80pub struct SoundDetection {
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub detection_id: Option<String>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub sound_type: Option<SoundType>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 #[validate(range(min = 0.0, max = 1.0))]
92 pub confidence: Option<f64>,
93
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub direction_deg: Option<f64>,
97
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub distance_m: Option<f64>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub level_db: Option<f64>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub timestamp: Option<DateTime<Utc>>,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub description: Option<String>,
113}
114
115#[derive(Debug, Clone, Default, Serialize, Deserialize)]
117pub struct VoiceOutput {
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub is_speaking: Option<bool>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub current_text: Option<String>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub tts_engine: Option<String>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub language: Option<String>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub voice: Option<String>,
137}
138
139impl AudioDomain {
140 pub fn with_microphone(mut self, mic: Microphone) -> Self {
142 let mics = self.microphones.get_or_insert_with(Vec::new);
143 mics.push(mic);
144 self
145 }
146
147 pub fn with_speaker(mut self, speaker: Speaker) -> Self {
149 let speakers = self.speakers.get_or_insert_with(Vec::new);
150 speakers.push(speaker);
151 self
152 }
153
154 pub fn with_detection(mut self, detection: SoundDetection) -> Self {
156 let detections = self.sound_detection.get_or_insert_with(Vec::new);
157 detections.push(detection);
158 self
159 }
160
161 pub fn with_ambient_noise(mut self, db: f64) -> Self {
163 self.ambient_noise_db = Some(db);
164 self
165 }
166}
167
168impl SoundDetection {
169 pub fn new(sound_type: SoundType, confidence: f64) -> Self {
171 Self {
172 detection_id: Some(uuid::Uuid::new_v4().to_string()),
173 sound_type: Some(sound_type),
174 confidence: Some(confidence),
175 timestamp: Some(Utc::now()),
176 ..Default::default()
177 }
178 }
179
180 pub fn with_direction(mut self, deg: f64) -> Self {
182 self.direction_deg = Some(deg);
183 self
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_sound_detection() {
193 let detection = SoundDetection::new(SoundType::Speech, 0.9).with_direction(45.0);
194
195 assert_eq!(detection.sound_type, Some(SoundType::Speech));
196 assert!(detection.timestamp.is_some());
197 }
198
199 #[test]
204 fn test_audio_domain_default() {
205 let audio = AudioDomain::default();
206 assert!(audio.microphones.is_none());
207 assert!(audio.speakers.is_none());
208 assert!(audio.sound_detection.is_none());
209 assert!(audio.voice_output.is_none());
210 assert!(audio.ambient_noise_db.is_none());
211 }
212
213 #[test]
214 fn test_audio_domain_with_microphone() {
215 let mic = Microphone {
216 mic_id: Some("mic-001".to_string()),
217 status: Some(SensorStatus::Ok),
218 level_db: Some(-20.0),
219 is_recording: Some(true),
220 sample_rate_hz: Some(44100),
221 };
222
223 let audio = AudioDomain::default().with_microphone(mic);
224 assert!(audio.microphones.is_some());
225 assert_eq!(audio.microphones.as_ref().unwrap().len(), 1);
226 assert_eq!(
227 audio.microphones.as_ref().unwrap()[0].mic_id,
228 Some("mic-001".to_string())
229 );
230 }
231
232 #[test]
233 fn test_audio_domain_with_multiple_microphones() {
234 let mic1 = Microphone {
235 mic_id: Some("mic-001".to_string()),
236 ..Default::default()
237 };
238 let mic2 = Microphone {
239 mic_id: Some("mic-002".to_string()),
240 ..Default::default()
241 };
242
243 let audio = AudioDomain::default()
244 .with_microphone(mic1)
245 .with_microphone(mic2);
246
247 assert_eq!(audio.microphones.as_ref().unwrap().len(), 2);
248 }
249
250 #[test]
251 fn test_audio_domain_with_speaker() {
252 let speaker = Speaker {
253 speaker_id: Some("spk-001".to_string()),
254 status: Some(SensorStatus::Ok),
255 is_playing: Some(true),
256 volume_pct: Some(75.0),
257 };
258
259 let audio = AudioDomain::default().with_speaker(speaker);
260 assert!(audio.speakers.is_some());
261 assert_eq!(audio.speakers.as_ref().unwrap()[0].volume_pct, Some(75.0));
262 }
263
264 #[test]
265 fn test_audio_domain_with_detection() {
266 let detection = SoundDetection::new(SoundType::Alarm, 0.95);
267 let audio = AudioDomain::default().with_detection(detection);
268
269 assert!(audio.sound_detection.is_some());
270 assert_eq!(
271 audio.sound_detection.as_ref().unwrap()[0].sound_type,
272 Some(SoundType::Alarm)
273 );
274 }
275
276 #[test]
277 fn test_audio_domain_with_ambient_noise() {
278 let audio = AudioDomain::default().with_ambient_noise(65.5);
279 assert_eq!(audio.ambient_noise_db, Some(65.5));
280 }
281
282 #[test]
283 fn test_audio_domain_chained_builders() {
284 let audio = AudioDomain::default()
285 .with_microphone(Microphone::default())
286 .with_speaker(Speaker::default())
287 .with_detection(SoundDetection::new(SoundType::Speech, 0.8))
288 .with_ambient_noise(50.0);
289
290 assert!(audio.microphones.is_some());
291 assert!(audio.speakers.is_some());
292 assert!(audio.sound_detection.is_some());
293 assert!(audio.ambient_noise_db.is_some());
294 }
295
296 #[test]
301 fn test_microphone_default() {
302 let mic = Microphone::default();
303 assert!(mic.mic_id.is_none());
304 assert!(mic.status.is_none());
305 assert!(mic.level_db.is_none());
306 assert!(mic.is_recording.is_none());
307 assert!(mic.sample_rate_hz.is_none());
308 }
309
310 #[test]
311 fn test_microphone_full() {
312 let mic = Microphone {
313 mic_id: Some("mic-front".to_string()),
314 status: Some(SensorStatus::Ok),
315 level_db: Some(-15.5),
316 is_recording: Some(true),
317 sample_rate_hz: Some(48000),
318 };
319
320 assert_eq!(mic.mic_id, Some("mic-front".to_string()));
321 assert_eq!(mic.status, Some(SensorStatus::Ok));
322 assert_eq!(mic.level_db, Some(-15.5));
323 assert_eq!(mic.is_recording, Some(true));
324 assert_eq!(mic.sample_rate_hz, Some(48000));
325 }
326
327 #[test]
328 fn test_microphone_serialization() {
329 let mic = Microphone {
330 mic_id: Some("mic-001".to_string()),
331 level_db: Some(-20.0),
332 ..Default::default()
333 };
334
335 let json = serde_json::to_string(&mic).unwrap();
336 assert!(json.contains("mic-001"));
337 assert!(json.contains("-20"));
338
339 let deserialized: Microphone = serde_json::from_str(&json).unwrap();
340 assert_eq!(deserialized.mic_id, Some("mic-001".to_string()));
341 }
342
343 #[test]
348 fn test_speaker_default() {
349 let speaker = Speaker::default();
350 assert!(speaker.speaker_id.is_none());
351 assert!(speaker.status.is_none());
352 assert!(speaker.is_playing.is_none());
353 assert!(speaker.volume_pct.is_none());
354 }
355
356 #[test]
357 fn test_speaker_full() {
358 let speaker = Speaker {
359 speaker_id: Some("spk-main".to_string()),
360 status: Some(SensorStatus::Ok),
361 is_playing: Some(true),
362 volume_pct: Some(80.0),
363 };
364
365 assert_eq!(speaker.volume_pct, Some(80.0));
366 assert_eq!(speaker.is_playing, Some(true));
367 }
368
369 #[test]
370 fn test_speaker_serialization() {
371 let speaker = Speaker {
372 speaker_id: Some("spk-001".to_string()),
373 volume_pct: Some(50.0),
374 ..Default::default()
375 };
376
377 let json = serde_json::to_string(&speaker).unwrap();
378 let deserialized: Speaker = serde_json::from_str(&json).unwrap();
379 assert_eq!(deserialized.volume_pct, Some(50.0));
380 }
381
382 #[test]
387 fn test_sound_detection_new() {
388 let detection = SoundDetection::new(SoundType::Alarm, 0.85);
389
390 assert!(detection.detection_id.is_some());
391 assert_eq!(detection.sound_type, Some(SoundType::Alarm));
392 assert_eq!(detection.confidence, Some(0.85));
393 assert!(detection.timestamp.is_some());
394 }
395
396 #[test]
397 fn test_sound_detection_with_direction() {
398 let detection = SoundDetection::new(SoundType::Speech, 0.9).with_direction(135.0);
399
400 assert_eq!(detection.direction_deg, Some(135.0));
401 }
402
403 #[test]
404 fn test_sound_detection_default() {
405 let detection = SoundDetection::default();
406 assert!(detection.detection_id.is_none());
407 assert!(detection.sound_type.is_none());
408 assert!(detection.confidence.is_none());
409 assert!(detection.direction_deg.is_none());
410 assert!(detection.distance_m.is_none());
411 assert!(detection.level_db.is_none());
412 assert!(detection.timestamp.is_none());
413 assert!(detection.description.is_none());
414 }
415
416 #[test]
417 fn test_sound_detection_full() {
418 let detection = SoundDetection {
419 detection_id: Some("det-001".to_string()),
420 sound_type: Some(SoundType::Machine),
421 confidence: Some(0.92),
422 direction_deg: Some(270.0),
423 distance_m: Some(5.5),
424 level_db: Some(72.0),
425 timestamp: Some(Utc::now()),
426 description: Some("Conveyor belt noise".to_string()),
427 };
428
429 assert_eq!(detection.distance_m, Some(5.5));
430 assert_eq!(detection.level_db, Some(72.0));
431 }
432
433 #[test]
434 fn test_sound_detection_serialization() {
435 let detection = SoundDetection::new(SoundType::Speech, 0.9);
436 let json = serde_json::to_string(&detection).unwrap();
437 let deserialized: SoundDetection = serde_json::from_str(&json).unwrap();
438
439 assert_eq!(deserialized.sound_type, Some(SoundType::Speech));
440 assert_eq!(deserialized.confidence, Some(0.9));
441 }
442
443 #[test]
448 fn test_voice_output_default() {
449 let voice = VoiceOutput::default();
450 assert!(voice.is_speaking.is_none());
451 assert!(voice.current_text.is_none());
452 assert!(voice.tts_engine.is_none());
453 assert!(voice.language.is_none());
454 assert!(voice.voice.is_none());
455 }
456
457 #[test]
458 fn test_voice_output_full() {
459 let voice = VoiceOutput {
460 is_speaking: Some(true),
461 current_text: Some("Hello, how can I help you?".to_string()),
462 tts_engine: Some("pico".to_string()),
463 language: Some("en-US".to_string()),
464 voice: Some("default".to_string()),
465 };
466
467 assert_eq!(voice.is_speaking, Some(true));
468 assert_eq!(
469 voice.current_text,
470 Some("Hello, how can I help you?".to_string())
471 );
472 }
473
474 #[test]
475 fn test_voice_output_serialization() {
476 let voice = VoiceOutput {
477 is_speaking: Some(false),
478 language: Some("en-GB".to_string()),
479 ..Default::default()
480 };
481
482 let json = serde_json::to_string(&voice).unwrap();
483 let deserialized: VoiceOutput = serde_json::from_str(&json).unwrap();
484 assert_eq!(deserialized.language, Some("en-GB".to_string()));
485 }
486
487 #[test]
492 fn test_audio_domain_serialization_roundtrip() {
493 let audio = AudioDomain::default()
494 .with_microphone(Microphone {
495 mic_id: Some("mic-001".to_string()),
496 level_db: Some(-25.0),
497 ..Default::default()
498 })
499 .with_ambient_noise(55.0);
500
501 let json = serde_json::to_string(&audio).unwrap();
502 let deserialized: AudioDomain = serde_json::from_str(&json).unwrap();
503
504 assert_eq!(deserialized.ambient_noise_db, Some(55.0));
505 assert!(deserialized.microphones.is_some());
506 assert_eq!(
507 deserialized.microphones.as_ref().unwrap()[0].level_db,
508 Some(-25.0)
509 );
510 }
511}