1use serde::{Deserialize, Serialize};
7use tokio::sync::broadcast;
8
9use aranet_types::{CurrentReading, DeviceInfo, DeviceType};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct DeviceId {
14 pub id: String,
16 pub name: Option<String>,
18 pub device_type: Option<DeviceType>,
20}
21
22impl DeviceId {
23 pub fn new(id: impl Into<String>) -> Self {
25 Self {
26 id: id.into(),
27 name: None,
28 device_type: None,
29 }
30 }
31
32 pub fn with_name(id: impl Into<String>, name: impl Into<String>) -> Self {
34 Self {
35 id: id.into(),
36 name: Some(name.into()),
37 device_type: None,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(tag = "type", rename_all = "snake_case")]
50#[non_exhaustive]
51pub enum DeviceEvent {
52 Discovered { device: DeviceId, rssi: Option<i16> },
54 Connected {
56 device: DeviceId,
57 info: Option<DeviceInfo>,
58 },
59 Disconnected {
61 device: DeviceId,
62 reason: DisconnectReason,
63 },
64 Reading {
66 device: DeviceId,
67 reading: CurrentReading,
68 },
69 Error { device: DeviceId, error: String },
71 ReconnectStarted { device: DeviceId, attempt: u32 },
73 ReconnectSucceeded { device: DeviceId, attempts: u32 },
75 BatteryLow { device: DeviceId, level: u8 },
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[non_exhaustive]
85pub enum DisconnectReason {
86 UserRequested,
88 OutOfRange,
90 Timeout,
92 DevicePoweredOff,
94 BleError(String),
96 Unknown,
98}
99
100pub type EventSender = broadcast::Sender<DeviceEvent>;
102
103pub type EventReceiver = broadcast::Receiver<DeviceEvent>;
105
106pub fn event_channel(capacity: usize) -> (EventSender, EventReceiver) {
108 broadcast::channel(capacity)
109}
110
111pub fn default_event_channel() -> (EventSender, EventReceiver) {
113 event_channel(100)
114}
115
116#[derive(Debug, Clone)]
118pub struct EventDispatcher {
119 sender: EventSender,
120}
121
122impl EventDispatcher {
123 pub fn new(capacity: usize) -> Self {
125 let (sender, _) = broadcast::channel(capacity);
126 Self { sender }
127 }
128
129 pub fn subscribe(&self) -> EventReceiver {
131 self.sender.subscribe()
132 }
133
134 pub fn send(&self, event: DeviceEvent) {
136 let _ = self.sender.send(event);
138 }
139
140 pub fn receiver_count(&self) -> usize {
142 self.sender.receiver_count()
143 }
144
145 pub fn sender(&self) -> EventSender {
147 self.sender.clone()
148 }
149}
150
151impl Default for EventDispatcher {
152 fn default() -> Self {
153 Self::new(100)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use aranet_types::{CurrentReading, DeviceType, Status};
161
162 #[test]
165 fn test_device_id_new() {
166 let id = DeviceId::new("AA:BB:CC:DD:EE:FF");
167 assert_eq!(id.id, "AA:BB:CC:DD:EE:FF");
168 assert!(id.name.is_none());
169 assert!(id.device_type.is_none());
170 }
171
172 #[test]
173 fn test_device_id_with_name() {
174 let id = DeviceId::with_name("AA:BB:CC:DD:EE:FF", "Kitchen Sensor");
175 assert_eq!(id.id, "AA:BB:CC:DD:EE:FF");
176 assert_eq!(id.name, Some("Kitchen Sensor".to_string()));
177 assert!(id.device_type.is_none());
178 }
179
180 #[test]
181 fn test_device_id_with_device_type() {
182 let mut id = DeviceId::new("test-id");
183 id.device_type = Some(DeviceType::Aranet4);
184 assert_eq!(id.device_type, Some(DeviceType::Aranet4));
185 }
186
187 #[test]
188 fn test_device_id_equality() {
189 let id1 = DeviceId::new("test");
190 let id2 = DeviceId::new("test");
191 let id3 = DeviceId::new("different");
192
193 assert_eq!(id1, id2);
194 assert_ne!(id1, id3);
195 }
196
197 #[test]
198 fn test_device_id_clone() {
199 let id1 = DeviceId::with_name("test", "name");
200 let id2 = id1.clone();
201 assert_eq!(id1, id2);
202 }
203
204 #[test]
205 fn test_device_id_serialization() {
206 let id = DeviceId::with_name("device-123", "My Device");
207 let json = serde_json::to_string(&id).unwrap();
208 assert!(json.contains("device-123"));
209 assert!(json.contains("My Device"));
210
211 let deserialized: DeviceId = serde_json::from_str(&json).unwrap();
212 assert_eq!(deserialized, id);
213 }
214
215 #[test]
218 fn test_disconnect_reason_equality() {
219 assert_eq!(
220 DisconnectReason::UserRequested,
221 DisconnectReason::UserRequested
222 );
223 assert_eq!(DisconnectReason::OutOfRange, DisconnectReason::OutOfRange);
224 assert_eq!(DisconnectReason::Timeout, DisconnectReason::Timeout);
225 assert_eq!(
226 DisconnectReason::DevicePoweredOff,
227 DisconnectReason::DevicePoweredOff
228 );
229 assert_eq!(DisconnectReason::Unknown, DisconnectReason::Unknown);
230
231 assert_ne!(DisconnectReason::UserRequested, DisconnectReason::Timeout);
232 }
233
234 #[test]
235 fn test_disconnect_reason_ble_error() {
236 let reason1 = DisconnectReason::BleError("error 1".to_string());
237 let reason2 = DisconnectReason::BleError("error 1".to_string());
238 let reason3 = DisconnectReason::BleError("error 2".to_string());
239
240 assert_eq!(reason1, reason2);
241 assert_ne!(reason1, reason3);
242 }
243
244 #[test]
245 fn test_disconnect_reason_serialization() {
246 for reason in [
247 DisconnectReason::UserRequested,
248 DisconnectReason::OutOfRange,
249 DisconnectReason::Timeout,
250 DisconnectReason::DevicePoweredOff,
251 DisconnectReason::BleError("test error".to_string()),
252 DisconnectReason::Unknown,
253 ] {
254 let json = serde_json::to_string(&reason).unwrap();
255 let deserialized: DisconnectReason = serde_json::from_str(&json).unwrap();
256 assert_eq!(deserialized, reason);
257 }
258 }
259
260 #[test]
261 fn test_disconnect_reason_clone() {
262 let reason = DisconnectReason::BleError("connection lost".to_string());
263 let cloned = reason.clone();
264 assert_eq!(reason, cloned);
265 }
266
267 fn create_test_reading() -> CurrentReading {
270 CurrentReading {
271 co2: 800,
272 temperature: 22.5,
273 pressure: 1013.0,
274 humidity: 45,
275 battery: 85,
276 status: Status::Green,
277 interval: 60,
278 age: 30,
279 captured_at: None,
280 radon: None,
281 radiation_rate: None,
282 radiation_total: None,
283 radon_avg_24h: None,
284 radon_avg_7d: None,
285 radon_avg_30d: None,
286 }
287 }
288
289 #[test]
290 fn test_device_event_discovered() {
291 let event = DeviceEvent::Discovered {
292 device: DeviceId::new("test"),
293 rssi: Some(-65),
294 };
295
296 let json = serde_json::to_string(&event).unwrap();
297 assert!(json.contains("discovered"));
298 assert!(json.contains("-65"));
299 }
300
301 #[test]
302 fn test_device_event_connected() {
303 let event = DeviceEvent::Connected {
304 device: DeviceId::new("test"),
305 info: None,
306 };
307
308 let json = serde_json::to_string(&event).unwrap();
309 assert!(json.contains("connected"));
310 }
311
312 #[test]
313 fn test_device_event_disconnected() {
314 let event = DeviceEvent::Disconnected {
315 device: DeviceId::new("test"),
316 reason: DisconnectReason::UserRequested,
317 };
318
319 let json = serde_json::to_string(&event).unwrap();
320 assert!(json.contains("disconnected"));
321 }
322
323 #[test]
324 fn test_device_event_reading() {
325 let event = DeviceEvent::Reading {
326 device: DeviceId::new("test"),
327 reading: create_test_reading(),
328 };
329
330 let json = serde_json::to_string(&event).unwrap();
331 assert!(json.contains("reading"));
332 assert!(json.contains("800")); }
334
335 #[test]
336 fn test_device_event_error() {
337 let event = DeviceEvent::Error {
338 device: DeviceId::new("test"),
339 error: "Connection timeout".to_string(),
340 };
341
342 let json = serde_json::to_string(&event).unwrap();
343 assert!(json.contains("error"));
344 assert!(json.contains("Connection timeout"));
345 }
346
347 #[test]
348 fn test_device_event_reconnect_started() {
349 let event = DeviceEvent::ReconnectStarted {
350 device: DeviceId::new("test"),
351 attempt: 3,
352 };
353
354 let json = serde_json::to_string(&event).unwrap();
355 assert!(json.contains("reconnect_started"));
356 assert!(json.contains("3"));
357 }
358
359 #[test]
360 fn test_device_event_reconnect_succeeded() {
361 let event = DeviceEvent::ReconnectSucceeded {
362 device: DeviceId::new("test"),
363 attempts: 2,
364 };
365
366 let json = serde_json::to_string(&event).unwrap();
367 assert!(json.contains("reconnect_succeeded"));
368 }
369
370 #[test]
371 fn test_device_event_battery_low() {
372 let event = DeviceEvent::BatteryLow {
373 device: DeviceId::new("test"),
374 level: 10,
375 };
376
377 let json = serde_json::to_string(&event).unwrap();
378 assert!(json.contains("battery_low"));
379 assert!(json.contains("10"));
380 }
381
382 #[test]
383 fn test_device_event_clone() {
384 let event = DeviceEvent::Reading {
385 device: DeviceId::new("test"),
386 reading: create_test_reading(),
387 };
388
389 let cloned = event.clone();
390 match cloned {
391 DeviceEvent::Reading { device, reading } => {
392 assert_eq!(device.id, "test");
393 assert_eq!(reading.co2, 800);
394 }
395 _ => unreachable!("Clone should preserve event type as Reading"),
396 }
397 }
398
399 #[test]
402 fn test_event_channel() {
403 let (tx, rx) = event_channel(50);
404 assert_eq!(tx.receiver_count(), 1);
406 drop(rx);
407 assert_eq!(tx.receiver_count(), 0);
408 }
409
410 #[test]
411 fn test_default_event_channel() {
412 let (tx, rx) = default_event_channel();
413 assert_eq!(tx.receiver_count(), 1);
415 drop(rx);
416 assert_eq!(tx.receiver_count(), 0);
417 }
418
419 #[tokio::test]
420 async fn test_event_channel_send_receive() {
421 let (tx, mut rx) = event_channel(10);
422
423 let event = DeviceEvent::Discovered {
424 device: DeviceId::new("test"),
425 rssi: Some(-70),
426 };
427
428 tx.send(event.clone()).unwrap();
429
430 let received = rx.recv().await.unwrap();
431 match received {
432 DeviceEvent::Discovered { device, rssi } => {
433 assert_eq!(device.id, "test");
434 assert_eq!(rssi, Some(-70));
435 }
436 _ => unreachable!("Expected Discovered event"),
437 }
438 }
439
440 #[test]
443 fn test_event_dispatcher_new() {
444 let dispatcher = EventDispatcher::new(50);
445 assert_eq!(dispatcher.receiver_count(), 0);
446 }
447
448 #[test]
449 fn test_event_dispatcher_default() {
450 let dispatcher = EventDispatcher::default();
451 assert_eq!(dispatcher.receiver_count(), 0);
452 }
453
454 #[test]
455 fn test_event_dispatcher_subscribe() {
456 let dispatcher = EventDispatcher::new(10);
457 assert_eq!(dispatcher.receiver_count(), 0);
458
459 let _rx1 = dispatcher.subscribe();
460 assert_eq!(dispatcher.receiver_count(), 1);
461
462 let _rx2 = dispatcher.subscribe();
463 assert_eq!(dispatcher.receiver_count(), 2);
464 }
465
466 #[tokio::test]
467 async fn test_event_dispatcher_send_receive() {
468 let dispatcher = EventDispatcher::new(10);
469 let mut rx = dispatcher.subscribe();
470
471 let event = DeviceEvent::Connected {
472 device: DeviceId::with_name("test", "Test Device"),
473 info: None,
474 };
475
476 dispatcher.send(event);
477
478 let received = rx.recv().await.unwrap();
479 match received {
480 DeviceEvent::Connected { device, .. } => {
481 assert_eq!(device.id, "test");
482 assert_eq!(device.name, Some("Test Device".to_string()));
483 }
484 _ => unreachable!("Expected Connected event"),
485 }
486 }
487
488 #[tokio::test]
489 async fn test_event_dispatcher_multiple_receivers() {
490 let dispatcher = EventDispatcher::new(10);
491 let mut rx1 = dispatcher.subscribe();
492 let mut rx2 = dispatcher.subscribe();
493
494 let event = DeviceEvent::Discovered {
495 device: DeviceId::new("multi-test"),
496 rssi: Some(-50),
497 };
498
499 dispatcher.send(event);
500
501 let received1 = rx1.recv().await.unwrap();
503 let received2 = rx2.recv().await.unwrap();
504
505 match (received1, received2) {
506 (
507 DeviceEvent::Discovered { device: d1, .. },
508 DeviceEvent::Discovered { device: d2, .. },
509 ) => {
510 assert_eq!(d1.id, "multi-test");
511 assert_eq!(d2.id, "multi-test");
512 }
513 _ => unreachable!("Expected Discovered events from both receivers"),
514 }
515 }
516
517 #[test]
518 fn test_event_dispatcher_send_no_receivers() {
519 let dispatcher = EventDispatcher::new(10);
520
521 let event = DeviceEvent::Error {
523 device: DeviceId::new("test"),
524 error: "no one listening".to_string(),
525 };
526
527 dispatcher.send(event);
528 }
529
530 #[test]
531 fn test_event_dispatcher_sender() {
532 let dispatcher = EventDispatcher::new(10);
533 let sender = dispatcher.sender();
534
535 assert_eq!(sender.receiver_count(), 0);
537 }
538
539 #[test]
540 fn test_event_dispatcher_clone() {
541 let dispatcher1 = EventDispatcher::new(10);
542 let _rx1 = dispatcher1.subscribe();
543
544 let dispatcher2 = dispatcher1.clone();
545
546 assert_eq!(dispatcher1.receiver_count(), 1);
548 assert_eq!(dispatcher2.receiver_count(), 1);
549
550 let _rx2 = dispatcher2.subscribe();
551 assert_eq!(dispatcher1.receiver_count(), 2);
552 assert_eq!(dispatcher2.receiver_count(), 2);
553 }
554
555 #[test]
556 fn test_event_dispatcher_debug() {
557 let dispatcher = EventDispatcher::new(10);
558 let debug = format!("{:?}", dispatcher);
559 assert!(debug.contains("EventDispatcher"));
560 }
561}