/ src / hardware / relay.rs
relay.rs
  1  use anyhow::Result;
  2  use esp_idf_hal::gpio::{AnyOutputPin, Output, PinDriver};
  3  use log::{info, warn};
  4  use serde::{Deserialize, Serialize};
  5  use std::sync::atomic::{AtomicBool, Ordering};
  6  use std::sync::{Arc, Mutex};
  7  
  8  /// Types of devices that can be controlled
  9  #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
 10  pub enum DeviceType {
 11      ExhaustFan,     // 220V exhaust fan
 12      CirculationFan, // 5V air circulation fan
 13      Heater,         // Electric heater
 14      AirPurifier,    // Air purifier/filter
 15  }
 16  
 17  impl DeviceType {
 18      pub fn name(&self) -> &'static str {
 19          match self {
 20              DeviceType::ExhaustFan => "Exhaust Fan",
 21              DeviceType::CirculationFan => "Circulation Fan",
 22              DeviceType::Heater => "Heater",
 23              DeviceType::AirPurifier => "Air Purifier",
 24          }
 25      }
 26  }
 27  
 28  /// Single relay channel controller with lock-free state reads
 29  pub struct RelayChannel {
 30      pin: Mutex<PinDriver<'static, AnyOutputPin, Output>>,
 31      active_high: bool,
 32      state: AtomicBool, // Lock-free atomic state for fast reads
 33      device_type: DeviceType,
 34      name: String,
 35  }
 36  
 37  impl RelayChannel {
 38      /// Create a new relay channel
 39      pub fn new(
 40          pin: AnyOutputPin,
 41          active_high: bool,
 42          device_type: DeviceType,
 43          name: String,
 44      ) -> Result<Self> {
 45          let mut driver = PinDriver::output(pin)?;
 46  
 47          // Initialize to OFF state
 48          if active_high {
 49              driver.set_low()?;
 50          } else {
 51              driver.set_high()?;
 52          }
 53  
 54          info!(
 55              "Relay channel '{}' ({}) initialized on GPIO (active_{})",
 56              name,
 57              device_type.name(),
 58              if active_high { "high" } else { "low" }
 59          );
 60  
 61          Ok(Self {
 62              pin: Mutex::new(driver),
 63              active_high,
 64              state: AtomicBool::new(false),
 65              device_type,
 66              name,
 67          })
 68      }
 69  
 70      /// Turn the relay ON
 71      pub fn turn_on(&self) -> Result<()> {
 72          // Fast check without locking
 73          if self.state.load(Ordering::Acquire) {
 74              return Ok(()); // Already on
 75          }
 76  
 77          let mut pin = self.pin.lock().unwrap();
 78  
 79          if self.active_high {
 80              pin.set_high()?;
 81          } else {
 82              pin.set_low()?;
 83          }
 84  
 85          self.state.store(true, Ordering::Release);
 86          info!("{} ({}) turned ON", self.name, self.device_type.name());
 87          Ok(())
 88      }
 89  
 90      /// Turn the relay OFF
 91      pub fn turn_off(&self) -> Result<()> {
 92          // Fast check without locking
 93          if !self.state.load(Ordering::Acquire) {
 94              return Ok(()); // Already off
 95          }
 96  
 97          let mut pin = self.pin.lock().unwrap();
 98  
 99          if self.active_high {
100              pin.set_low()?;
101          } else {
102              pin.set_high()?;
103          }
104  
105          self.state.store(false, Ordering::Release);
106          info!("{} ({}) turned OFF", self.name, self.device_type.name());
107          Ok(())
108      }
109  
110      /// Get current state (lock-free, very fast)
111      pub fn is_on(&self) -> bool {
112          self.state.load(Ordering::Relaxed)
113      }
114  
115      /// Get device type
116      pub fn device_type(&self) -> DeviceType {
117          self.device_type
118      }
119  
120      /// Get device name
121      pub fn name(&self) -> &str {
122          &self.name
123      }
124  }
125  
126  /// Multi-channel relay controller managing multiple devices
127  pub struct MultiRelayController {
128      channels: Vec<Arc<RelayChannel>>,
129  }
130  
131  impl MultiRelayController {
132      pub fn new() -> Self {
133          Self {
134              channels: Vec::new(),
135          }
136      }
137  
138      /// Add a relay channel
139      pub fn add_channel(&mut self, channel: RelayChannel) -> Arc<RelayChannel> {
140          let arc_channel = Arc::new(channel);
141          self.channels.push(arc_channel.clone());
142          arc_channel
143      }
144  
145      /// Get channel by device type
146      pub fn get_channel(&self, device_type: DeviceType) -> Option<&Arc<RelayChannel>> {
147          self.channels
148              .iter()
149              .find(|ch| ch.device_type() == device_type)
150      }
151  
152      /// Turn on a specific device
153      pub fn turn_on(&self, device_type: DeviceType) -> Result<()> {
154          if let Some(channel) = self.get_channel(device_type) {
155              channel.turn_on()
156          } else {
157              Err(anyhow::anyhow!("Device type {:?} not found", device_type))
158          }
159      }
160  
161      /// Turn off a specific device
162      pub fn turn_off(&self, device_type: DeviceType) -> Result<()> {
163          if let Some(channel) = self.get_channel(device_type) {
164              channel.turn_off()
165          } else {
166              Err(anyhow::anyhow!("Device type {:?} not found", device_type))
167          }
168      }
169  
170      /// Get all channels
171      pub fn channels(&self) -> &[Arc<RelayChannel>] {
172          &self.channels
173      }
174  
175      /// Turn off all channels (safety)
176      pub fn all_off(&self) -> Result<()> {
177          for channel in &self.channels {
178              channel.turn_off()?;
179          }
180          info!("All relay channels turned OFF");
181          Ok(())
182      }
183  }
184  
185  impl Drop for MultiRelayController {
186      fn drop(&mut self) {
187          if let Err(e) = self.all_off() {
188              warn!("Failed to turn off all relays on drop: {:?}", e);
189          }
190      }
191  }