use chrono::DateTime; use anyhow::{anyhow, Context, Result}; use chrono::Utc; use log::{debug, error, info, warn}; use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::sync::broadcast; use tokio::time; use crate::sensors::dht22::Dht22Sensor; use crate::sensors::{EnvironmentData, LightSensor, SensorConfig, TemperatureHumiditySensor}; /// A simple light sensor implementation that uses camera brightness as a proxy pub struct CameraLightSensor { /// The brightness value (0-1) brightness: Arc>, } impl CameraLightSensor { pub fn new() -> Self { Self { brightness: Arc::new(Mutex::new(0.0)), } } /// Update the brightness level from camera data pub fn update_brightness(&self, value: f32) { let mut brightness = self.brightness.lock().unwrap(); *brightness = value; } } impl LightSensor for CameraLightSensor { fn read_light_level(&self) -> Result { let brightness = self.brightness.lock().unwrap(); Ok(*brightness) } } /// Sensor state information #[derive(Debug, Clone)] struct SensorState { /// Whether the sensor is initialized initialized: bool, /// Whether the sensor is in degraded mode (using fallback values) degraded: bool, /// Last successful reading time last_reading: Option>, /// Initialization failure count init_failures: u32, } impl Default for SensorState { fn default() -> Self { Self { initialized: false, degraded: false, last_reading: None, init_failures: 0, } } } /// Controller for environmental sensors pub struct SensorController { /// Sensor configuration config: SensorConfig, /// Temperature and humidity sensor (DHT22) temp_humidity_sensor: Option>, /// Light sensor for sky brightness light_sensor: Option>, /// Current environmental data current_data: Arc>, /// Broadcast channel for data updates data_tx: broadcast::Sender, /// Whether the controller is running is_running: Arc>, /// Sensor state tracking temp_sensor_state: SensorState, /// Light sensor state tracking light_sensor_state: SensorState, } impl SensorController { /// Create a new sensor controller with the given configuration pub async fn new(config: &crate::Config) -> Result { // Extract sensor settings from config let sensor_config = config.sensors.clone(); // Create broadcast channel for data updates let (data_tx, _) = broadcast::channel(10); // Initialize environmental data with fallback values let initial_data = EnvironmentData { temperature: sensor_config.fallback_temperature, humidity: sensor_config.fallback_humidity, sky_brightness: sensor_config.fallback_sky_brightness, timestamp: Utc::now(), }; let current_data = Arc::new(Mutex::new(initial_data)); Ok(Self { config: sensor_config, temp_humidity_sensor: None, light_sensor: None, current_data, data_tx, is_running: Arc::new(Mutex::new(false)), temp_sensor_state: SensorState::default(), light_sensor_state: SensorState::default(), }) } /// Initialize the sensor hardware pub async fn initialize(&mut self) -> Result<()> { let mut init_failed = false; // Initialize temperature/humidity sensor if configured if self.config.use_dht22 { info!("Initializing DHT22 temperature/humidity sensor"); match Dht22Sensor::new(self.config.dht22_pin) { Ok(dht22) => { self.temp_humidity_sensor = Some(Box::new(dht22)); self.temp_sensor_state.initialized = true; info!("DHT22 temperature/humidity sensor initialized successfully"); }, Err(e) => { self.temp_sensor_state.init_failures += 1; self.temp_sensor_state.degraded = true; if self.config.allow_degraded_mode { warn!("Failed to initialize DHT22 sensor: {}. Using fallback values.", e); init_failed = true; } else { return Err(anyhow!("Failed to initialize temperature sensor and degraded mode is not allowed: {}", e)); } } } } else { // Sensor is not enabled, mark as degraded and use fallback info!("Temperature sensor disabled in config. Using fallback values."); self.temp_sensor_state.degraded = true; } // Initialize light sensor if configured if self.config.use_light_sensor { info!("Initializing light sensor"); // For now, we'll use a camera-based light sensor since we don't have a direct // interface for analog light sensors. In a real implementation, this would // use an ADC to read from a photodiode or similar sensor. let light_sensor = CameraLightSensor::new(); self.light_sensor = Some(Box::new(light_sensor)); self.light_sensor_state.initialized = true; info!("Light sensor initialized successfully"); } else { // Sensor is not enabled, mark as degraded and use fallback info!("Light sensor disabled in config. Using fallback values."); self.light_sensor_state.degraded = true; } if init_failed && !self.config.allow_degraded_mode { return Err(anyhow!("Sensor initialization failed and degraded mode is not allowed")); } info!("Sensor initialization complete"); Ok(()) } /// Start the sensor sampling loop pub async fn start(&self) -> Result<()> { { let mut is_running = self.is_running.lock().unwrap(); if *is_running { warn!("Sensor controller is already running"); return Ok(()); } *is_running = true; } // Clone Arc references for the background task let interval = self.config.sampling_interval; let current_data = self.current_data.clone(); let data_tx = self.data_tx.clone(); let is_running = self.is_running.clone(); // Clone config for fallback values let config = self.config.clone(); // Clone sensor state let mut temp_sensor_state = self.temp_sensor_state.clone(); let mut light_sensor_state = self.light_sensor_state.clone(); // Get sensor instances that implement the trait let temp_humidity_sensor: Option> = if self.temp_sensor_state.initialized && !self.temp_sensor_state.degraded { if let Some(sensor) = &self.temp_humidity_sensor { // Clone the sensor for the background task match Dht22Sensor::new(self.config.dht22_pin) { Ok(dht22) => Some(Box::new(dht22)), Err(e) => { warn!("Failed to clone temperature sensor for sampling task: {}. Using fallback values.", e); temp_sensor_state.degraded = true; None } } } else { None } } else { None }; // Get camera light sensor if available let light_sensor: Option> = if self.light_sensor_state.initialized && !self.light_sensor_state.degraded { // For simplicity, we'll just create a new camera light sensor // rather than trying to clone the existing one Some(Box::new(CameraLightSensor::new())) } else { None }; // Start sampling task tokio::spawn(async move { let mut interval = time::interval(Duration::from_secs(interval)); info!("Starting sensor sampling loop"); loop { interval.tick().await; // Check if we should exit { let is_running = is_running.lock().unwrap(); if !*is_running { break; } } // Create a new environment data object with fallback values let mut data = EnvironmentData { temperature: config.fallback_temperature, humidity: config.fallback_humidity, sky_brightness: config.fallback_sky_brightness, timestamp: Utc::now(), }; // Read temperature if sensor is available if let Some(sensor) = &temp_humidity_sensor { match sensor.read_temperature() { Ok(temp) => { data.temperature = temp; temp_sensor_state.last_reading = Some(Utc::now()); }, Err(e) => { error!("Failed to read temperature: {}. Using fallback value.", e); // Keep using fallback value from data initialization } } match sensor.read_humidity() { Ok(humidity) => { data.humidity = humidity; }, Err(e) => { error!("Failed to read humidity: {}. Using fallback value.", e); // Keep using fallback value from data initialization } } } else if temp_sensor_state.degraded { debug!("Using fallback temperature value: {:.1}°C", data.temperature); } // Read light level if sensor is available if let Some(sensor) = &light_sensor { match sensor.read_light_level() { Ok(level) => { data.sky_brightness = level; light_sensor_state.last_reading = Some(Utc::now()); }, Err(e) => { error!("Failed to read light level: {}. Using fallback value.", e); // Keep using fallback value from data initialization } } } else if light_sensor_state.degraded { debug!("Using fallback sky brightness value: {:.3}", data.sky_brightness); } // Update current data { let mut current = current_data.lock().unwrap(); *current = data.clone(); } // Broadcast update let _ = data_tx.send(data.clone()); debug!("Sensor data updated: temp={:.1}°C, humidity={:.1}%, light={:.3}", data.temperature, data.humidity, data.sky_brightness); } info!("Sensor sampling loop stopped"); }); info!("Sensor controller started"); Ok(()) } /// Stop the sensor sampling loop pub async fn stop(&self) -> Result<()> { { let mut is_running = self.is_running.lock().unwrap(); if !*is_running { warn!("Sensor controller is not running"); return Ok(()); } *is_running = false; } info!("Sensor controller stopping"); Ok(()) } /// Get the current environment data pub fn get_current_data(&self) -> EnvironmentData { self.current_data.lock().unwrap().clone() } /// Subscribe to environment data updates pub fn subscribe(&self) -> broadcast::Receiver { self.data_tx.subscribe() } /// Update the sky brightness from camera data pub fn update_sky_brightness(&self, brightness: f32) -> Result<()> { // If we have a camera light sensor, update it if let Some(sensor) = &self.light_sensor { if let Some(camera_sensor) = sensor.as_any().downcast_ref::() { camera_sensor.update_brightness(brightness); // Update the current data let mut data = self.current_data.lock()?; data.sky_brightness = brightness; data.timestamp = Utc::now(); // Broadcast update let _ = self.data_tx.send(data.clone()); return Ok(()); } } Err(anyhow!("No camera light sensor available")) } } /// Extension trait to allow downcasting of trait objects trait LightSensorExt: LightSensor { fn as_any(&self) -> &dyn std::any::Any; } impl LightSensorExt for T { fn as_any(&self) -> &dyn std::any::Any { self } } impl LightSensor for Box { fn read_light_level(&self) -> Result { (**self).read_light_level() } }