/ src / hardware / alarm.rs
alarm.rs
  1  /// Alarm system for critical failure detection
  2  use anyhow::Result;
  3  use esp_idf_hal::gpio::{AnyOutputPin, Output, PinDriver};
  4  use log::{error, info, warn};
  5  use std::sync::{Arc, Mutex};
  6  
  7  /// Alarm conditions that can trigger the external alarm
  8  #[derive(Debug, Clone, Copy, PartialEq)]
  9  pub enum AlarmCondition {
 10      WifiDisconnectedTooLong,
 11      MqttFailureTooLong,
 12      SensorFailureTooLong,
 13      AutomationFailure,
 14      CriticalTemperatureLow,
 15      CriticalTemperatureHigh,
 16  }
 17  
 18  impl AlarmCondition {
 19      pub fn description(&self) -> &'static str {
 20          match self {
 21              AlarmCondition::WifiDisconnectedTooLong => "WiFi disconnected for too long",
 22              AlarmCondition::MqttFailureTooLong => "MQTT publishing failed for too long",
 23              AlarmCondition::SensorFailureTooLong => "Sensor reading failed for too long",
 24              AlarmCondition::AutomationFailure => "Automation failed to activate device",
 25              AlarmCondition::CriticalTemperatureLow => "Critical low temperature",
 26              AlarmCondition::CriticalTemperatureHigh => "Critical high temperature",
 27          }
 28      }
 29  }
 30  
 31  /// External alarm controller
 32  pub struct AlarmController {
 33      relay: Arc<Mutex<PinDriver<'static, AnyOutputPin, Output>>>,
 34      active: Arc<Mutex<bool>>,
 35      active_conditions: Arc<Mutex<Vec<AlarmCondition>>>,
 36  }
 37  
 38  impl AlarmController {
 39      /// Create new alarm controller
 40      pub fn new(pin: AnyOutputPin, active_high: bool) -> Result<Self> {
 41          let mut driver = PinDriver::output(pin)?;
 42  
 43          // Initialize to OFF state
 44          if active_high {
 45              driver.set_low()?;
 46          } else {
 47              driver.set_high()?;
 48          }
 49  
 50          info!(
 51              "Alarm controller initialized (active_high: {})",
 52              active_high
 53          );
 54  
 55          Ok(Self {
 56              relay: Arc::new(Mutex::new(driver)),
 57              active: Arc::new(Mutex::new(false)),
 58              active_conditions: Arc::new(Mutex::new(Vec::new())),
 59          })
 60      }
 61  
 62      /// Activate alarm with a specific condition
 63      pub fn activate(&self, condition: AlarmCondition) -> Result<()> {
 64          let mut conditions = self.active_conditions.lock().unwrap();
 65  
 66          // Add condition if not already present
 67          if !conditions.contains(&condition) {
 68              conditions.push(condition);
 69              error!("ALARM ACTIVATED: {}", condition.description());
 70          }
 71  
 72          // Turn on alarm if not already active
 73          let mut active = self.active.lock().unwrap();
 74          if !*active {
 75              let mut relay = self.relay.lock().unwrap();
 76              relay.set_high()?; // Assuming active high for external relay
 77              *active = true;
 78              warn!("External alarm lamp turned ON");
 79          }
 80  
 81          Ok(())
 82      }
 83  
 84      /// Clear a specific alarm condition
 85      pub fn clear_condition(&self, condition: AlarmCondition) -> Result<()> {
 86          let mut conditions = self.active_conditions.lock().unwrap();
 87  
 88          if let Some(pos) = conditions.iter().position(|c| *c == condition) {
 89              conditions.remove(pos);
 90              info!("Alarm condition cleared: {}", condition.description());
 91          }
 92  
 93          // Turn off alarm if no more active conditions
 94          if conditions.is_empty() {
 95              let mut active = self.active.lock().unwrap();
 96              if *active {
 97                  let mut relay = self.relay.lock().unwrap();
 98                  relay.set_low()?;
 99                  *active = false;
100                  info!("External alarm lamp turned OFF - all conditions cleared");
101              }
102          }
103  
104          Ok(())
105      }
106  
107      /// Clear all alarm conditions and turn off alarm
108      pub fn clear_all(&self) -> Result<()> {
109          let mut conditions = self.active_conditions.lock().unwrap();
110          conditions.clear();
111  
112          let mut active = self.active.lock().unwrap();
113          if *active {
114              let mut relay = self.relay.lock().unwrap();
115              relay.set_low()?;
116              *active = false;
117              info!("External alarm lamp turned OFF - all conditions manually cleared");
118          }
119  
120          Ok(())
121      }
122  
123      /// Check if alarm is currently active
124      pub fn is_active(&self) -> bool {
125          *self.active.lock().unwrap()
126      }
127  
128      /// Get list of active alarm conditions
129      pub fn get_active_conditions(&self) -> Vec<AlarmCondition> {
130          self.active_conditions.lock().unwrap().clone()
131      }
132  
133      /// Test alarm (turn on briefly for testing)
134      pub fn test(&self) -> Result<()> {
135          info!("Testing alarm lamp...");
136          let mut relay = self.relay.lock().unwrap();
137          relay.set_high()?;
138          std::thread::sleep(std::time::Duration::from_millis(500));
139          relay.set_low()?;
140          info!("Alarm test complete");
141          Ok(())
142      }
143  }
144  
145  impl Drop for AlarmController {
146      fn drop(&mut self) {
147          // Ensure alarm is off when controller is dropped
148          if let Ok(mut relay) = self.relay.lock() {
149              let _ = relay.set_low();
150          }
151      }
152  }
153  
154  /// Alarm monitoring state
155  pub struct AlarmMonitor {
156      pub wifi_disconnected_since: Option<i64>,
157      pub mqtt_failed_since: Option<i64>,
158      pub sensor_failed_since: Option<i64>,
159      pub last_temp_check: i64,
160  }
161  
162  impl AlarmMonitor {
163      pub fn new() -> Self {
164          Self {
165              wifi_disconnected_since: None,
166              mqtt_failed_since: None,
167              sensor_failed_since: None,
168              last_temp_check: 0,
169          }
170      }
171  }