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}