meteor_detect/src/sensors/controller.rs
2025-03-16 21:32:43 +08:00

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
}
}