use std::{collections::HashMap, sync::{Arc, atomic::AtomicBool}, time::Instant}; use chrono::Utc; use serde_json::Value; use tokio::sync::{broadcast, Mutex, Notify, OwnedMutexGuard, RwLock}; use crate::{config::Config, db::Db, models::{ApiEvent, DeviceCommand, RuntimeSettings}, protocol::GreeClient}; #[derive(Debug, Clone)] pub(crate) struct PendingControllerCommand { /// Recent controller-requested values for climate fields. Keep a short history because /// some GREE modules expose intermediate/out-of-order status snapshots while settling. pub commands: Vec, /// Matching pre-command values for the same fields. A brief rollback to one of these /// values is firmware settling, not necessarily a physical-remote takeover. pub baselines: Vec, pub expires_at: Instant, } #[derive(Clone)] pub struct AppState { pub db: Db, pub settings: Arc>, pub config: Arc, pub gree: GreeClient, pub events: broadcast::Sender, pub http: reqwest::Client, pub outdoor_temperature: Arc>>, pub debug_gree_frames: Arc, /// Thermostat/automation control stays passive until every enabled device has had /// one startup poll, preventing stale persisted device state from causing restart commands. pub initial_device_sync_complete: Arc, /// Explicit thermostat changes wake the regulator instead of waiting for the next fixed interval. pub zone_control_wakeup: Arc, pub(crate) device_operation_locks: Arc>>>>, /// Short-lived expected climate state from controller-originated commands. It prevents /// a delayed GREE status update from being mistaken for remote/manual takeover. pub(crate) pending_controller_commands: Arc>>, pub started: Instant, } impl AppState { pub async fn lock_device_operation(&self, device_id: &str) -> OwnedMutexGuard<()> { let lock = { let mut locks = self.device_operation_locks.lock().await; locks.entry(device_id.to_string()).or_insert_with(|| Arc::new(Mutex::new(()))).clone() }; lock.lock_owned().await } pub fn wake_zone_control(&self) { self.zone_control_wakeup.notify_one(); } pub fn broadcast(&self, event: impl Into, data: Value) { let _ = self.events.send(ApiEvent { event: event.into(), timestamp: Utc::now(), data, }); } pub fn log(&self, level: &str, kind: &str, message: &str, metadata: Value) { if let Err(err) = self.db.log_event(level, kind, message, &metadata) { tracing::warn!(error=?err, "cannot persist event log"); } self.broadcast("log.created", serde_json::json!({ "level": level, "kind": kind, "message": message, "metadata": metadata, })); if let Ok(handle) = tokio::runtime::Handle::try_current() { let state = self.clone(); let level = level.to_string(); let kind = kind.to_string(); let message = message.to_string(); handle.spawn(async move { crate::notifications::dispatch(state, level, kind, message, metadata).await; }); } } }