383 lines
14 KiB
Rust
383 lines
14 KiB
Rust
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<Mutex<f32>>,
|
|
}
|
|
|
|
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<f32> {
|
|
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<DateTime<Utc>>,
|
|
/// 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<Box<dyn TemperatureHumiditySensor>>,
|
|
/// Light sensor for sky brightness
|
|
light_sensor: Option<Box<dyn LightSensor>>,
|
|
/// Current environmental data
|
|
current_data: Arc<Mutex<EnvironmentData>>,
|
|
/// Broadcast channel for data updates
|
|
data_tx: broadcast::Sender<EnvironmentData>,
|
|
/// Whether the controller is running
|
|
is_running: Arc<Mutex<bool>>,
|
|
/// 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<Self> {
|
|
// 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<Box<dyn TemperatureHumiditySensor>> =
|
|
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<Box<dyn LightSensor>> =
|
|
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);
|
|
|
|
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<EnvironmentData> {
|
|
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::<CameraLightSensor>() {
|
|
camera_sensor.update_brightness(brightness);
|
|
|
|
// Update the current data
|
|
let mut data = self.current_data.lock().unwrap();
|
|
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<T: LightSensor + std::any::Any> LightSensorExt for T {
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl LightSensor for Box<dyn LightSensor> {
|
|
fn read_light_level(&self) -> Result<f32> {
|
|
(**self).read_light_level()
|
|
}
|
|
}
|
|
|
|
impl LightSensorExt for Box<dyn LightSensor> {
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
}
|