Skip to main content

dahua_camera_server/
state.rs

1//! Shared state for every handler.
2//!
3//! Wraps the camera service and, when the `alarms` feature is on, the alarm
4//! registry. [`Deref`](std::ops::Deref) to the service keeps every existing
5//! handler that says `state.get(id)` working unchanged.
6
7use dahua_camera_rtsp::CameraService;
8use std::sync::Arc;
9
10/// What handlers are given.
11pub type AppState = Arc<AppStateInner>;
12
13/// The state behind [`AppState`].
14pub struct AppStateInner {
15    /// The cameras.
16    pub service: Arc<CameraService>,
17
18    /// Latched alarms, fed by a single aggregator task.
19    ///
20    /// A [`std::sync::Mutex`], not tokio's: every critical section is a few
21    /// map operations with no `.await` inside, so an async mutex would add a
22    /// scheduling hop for nothing.
23    #[cfg(feature = "alarms")]
24    pub alarms: Arc<std::sync::Mutex<dahua_camera_alarms::AlarmRegistry>>,
25}
26
27impl AppStateInner {
28    /// State for a service, with alarm tracking started if the feature is on.
29    ///
30    /// # Task budget
31    ///
32    /// The alarm aggregator is **one task for the whole service**, not one per
33    /// camera: it reads the already-merged event bus. The workspace budget
34    /// allows no feature more than one task per camera, and `events` already
35    /// spends that one.
36    pub fn new(service: Arc<CameraService>) -> AppState {
37        #[cfg(feature = "alarms")]
38        let alarms = {
39            let registry = Arc::new(std::sync::Mutex::new(
40                dahua_camera_alarms::AlarmRegistry::new(Default::default()),
41            ));
42            spawn_alarm_aggregator(service.clone(), registry.clone());
43            registry
44        };
45
46        Arc::new(Self {
47            service,
48            #[cfg(feature = "alarms")]
49            alarms,
50        })
51    }
52}
53
54/// Fold the merged event stream into the alarm registry.
55///
56/// One task total. Reads only; never touches the video path.
57#[cfg(feature = "alarms")]
58fn spawn_alarm_aggregator(
59    service: Arc<CameraService>,
60    registry: Arc<std::sync::Mutex<dahua_camera_alarms::AlarmRegistry>>,
61) {
62    let mut events = service.subscribe_events();
63
64    tokio::spawn(async move {
65        loop {
66            match events.recv().await {
67                Ok(event) => {
68                    let now = now_unix_ms();
69                    if let Ok(mut registry) = registry.lock() {
70                        registry.observe(&event, now);
71                    }
72                }
73                // Alarms are edge-triggered, so a dropped burst can leave a
74                // condition latched with no STOP coming. The auto-clear
75                // timeout is what recovers from exactly this.
76                Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
77                    tracing::warn!(
78                        skipped,
79                        "alarm aggregator lagged; any alarm whose STOP was dropped \
80                         will auto-clear on its timeout"
81                    );
82                }
83                Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
84            }
85        }
86    });
87}
88
89/// Host wall-clock time in Unix milliseconds.
90#[cfg(feature = "alarms")]
91pub(crate) fn now_unix_ms() -> u64 {
92    std::time::SystemTime::now()
93        .duration_since(std::time::UNIX_EPOCH)
94        .unwrap_or_default()
95        .as_millis() as u64
96}
97
98/// So a handler written against the service keeps working unchanged.
99impl std::ops::Deref for AppStateInner {
100    type Target = CameraService;
101    fn deref(&self) -> &CameraService {
102        &self.service
103    }
104}