Skip to main content

csi_webserver/
state.rs

1//! Shared application state used by Axum route handlers.
2//!
3//! This state object wraps channels and runtime flags that coordinate route
4//! requests with the long-running serial background task.
5
6use std::sync::Arc;
7use std::sync::atomic::AtomicBool;
8use tokio::sync::{Mutex, broadcast, mpsc, oneshot, watch};
9
10use crate::models::{DeviceConfig, DeviceInfo, LogMode, OutputMode};
11
12/// One-shot reply channel for an in-flight `info` exchange.
13pub type InfoResponder = oneshot::Sender<Result<DeviceInfo, String>>;
14
15/// Shared application state, cheaply cloned into every route handler via Axum's `State` extractor.
16#[derive(Clone)]
17pub struct AppState {
18    /// USB serial port path used to reach the ESP32 (e.g. `/dev/ttyUSB0`).
19    /// Stored so route handlers can open a short-lived second fd for control
20    /// operations such as RTS-triggered reset.
21    pub port_path: Arc<Mutex<String>>,
22    /// Baud rate negotiated at startup. The serial task and the RTS-reset
23    /// handler both read this so a single source of truth governs the link.
24    pub baud_rate: u32,
25    /// Whether the serial task currently has an open and healthy ESP32 link.
26    pub serial_connected: Arc<AtomicBool>,
27    /// Best-effort flag: true after successful `start`, false after reset/disconnect.
28    pub collection_running: Arc<AtomicBool>,
29    /// Send CLI command strings to the serial background task.
30    pub cmd_tx: mpsc::Sender<String>,
31    /// Broadcast raw CSI frame bytes to all connected WebSocket clients.
32    pub csi_tx: broadcast::Sender<Vec<u8>>,
33    /// Notify the serial task of log-mode changes (affects the frame delimiter).
34    pub log_mode_tx: Arc<watch::Sender<LogMode>>,
35    /// Notify the serial task of output-mode changes (stream / dump / both).
36    pub output_mode_tx: Arc<watch::Sender<OutputMode>>,
37    /// Signal the serial task of the current session's dump file path.
38    /// `Some(path)` → open/reuse that file; `None` → session ended, close file.
39    pub session_file_tx: Arc<watch::Sender<Option<String>>>,
40    /// Cached view of the current device configuration.
41    pub config: Arc<Mutex<DeviceConfig>>,
42    /// Issue an `info` command on the device and capture the magic block.
43    /// The serial task synchronously consumes the responder.
44    pub info_request_tx: mpsc::Sender<InfoResponder>,
45    /// `true` once the serial task (or an explicit `/api/info` call) has
46    /// observed a valid `ESP-CSI-CLI/<version>` magic block from the device.
47    /// Cleared on disconnect, on `POST /api/control/reset` (until the
48    /// post-reset re-verification completes), and on a failed verification.
49    /// Command endpoints refuse to send while this is `false`.
50    pub firmware_verified: Arc<AtomicBool>,
51    /// Last successfully parsed firmware identification block. `None` until
52    /// the first verification succeeds; cleared alongside `firmware_verified`.
53    pub device_info: Arc<Mutex<Option<DeviceInfo>>>,
54}
55
56impl AppState {
57    /// Returns an early-return tuple suitable for handlers when the firmware
58    /// has not yet been verified as `esp-csi-cli-rs`. Use this to short-circuit
59    /// any endpoint that issues a CLI command — sending commands to an
60    /// unverified device may interact with whatever bootloader/firmware is
61    /// listening in unintended ways.
62    pub fn require_firmware(
63        &self,
64    ) -> Option<(axum::http::StatusCode, axum::Json<crate::models::ApiResponse>)> {
65        if self
66            .firmware_verified
67            .load(std::sync::atomic::Ordering::SeqCst)
68        {
69            None
70        } else {
71            Some((
72                axum::http::StatusCode::PRECONDITION_FAILED,
73                axum::Json(crate::models::ApiResponse {
74                    success: false,
75                    message:
76                        "Firmware not verified as esp-csi-cli-rs. Call GET /api/info to verify, \
77                         or POST /api/control/reset to power-cycle and re-check."
78                            .to_string(),
79                }),
80            ))
81        }
82    }
83}