use async_trait::async_trait;
use rppal::gpio::{Gpio, Level};
use std::sync::{Arc, Mutex};
use tokio::task;
use tokio::time::{Duration, sleep};
use crate::error::SensorError;
use crate::sensors::traits::FireDetector;
#[derive(Debug, Clone, Copy)]
pub struct FireSensorData {
pub flame_detected: bool,
pub last_detection_timestamp: Option<u64>,
}
pub struct FireSensor {
flame_pin: u8,
buzzer_pin: u8,
is_active: Arc<Mutex<bool>>,
high_active: bool,
}
impl FireSensor {
pub fn new(flame_pin: u8, buzzer_pin: u8, high_active: bool) -> Self {
FireSensor {
flame_pin,
buzzer_pin,
is_active: Arc::new(Mutex::new(true)),
high_active,
}
}
fn read_internal(&self) -> Result<FireSensorData, SensorError> {
let gpio = Gpio::new()?;
let flame_sensor = gpio.get(self.flame_pin)?.into_input();
let flame_detected = if self.high_active {
flame_sensor.read() == Level::High
} else {
flame_sensor.read() == Level::Low
};
let timestamp = if flame_detected {
Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| SensorError::SensorError(format!("Time error: {}", e)))?
.as_secs(),
)
} else {
None
};
Ok(FireSensorData {
flame_detected,
last_detection_timestamp: timestamp,
})
}
}
#[async_trait]
impl FireDetector for FireSensor {
fn read(&self) -> Result<FireSensorData, SensorError> {
self.read_internal()
}
async fn read_async(&self) -> Result<FireSensorData, SensorError> {
let flame_pin = self.flame_pin;
let high_active = self.high_active;
task::spawn_blocking(move || {
let gpio = Gpio::new()?;
let flame_sensor = gpio.get(flame_pin)?.into_input();
let flame_detected = if high_active {
flame_sensor.read() == Level::High
} else {
flame_sensor.read() == Level::Low
};
let timestamp = if flame_detected {
Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| SensorError::SensorError(format!("Time error: {}", e)))?
.as_secs(),
)
} else {
None
};
Ok::<FireSensorData, SensorError>(FireSensorData {
flame_detected,
last_detection_timestamp: timestamp,
})
})
.await
.map_err(|e| SensorError::SensorError(format!("Task join error: {}", e)))?
}
async fn start_monitoring(&self, check_interval_ms: u64) -> Result<(), SensorError> {
println!("Starting fire monitoring (async version)");
println!(
"Sensor configuration: {} level active",
if self.high_active { "high" } else { "low" }
);
let gpio = Gpio::new()?;
let flame_pin_clone = self.flame_pin;
let buzzer_pin_clone = self.buzzer_pin;
let is_active_clone = self.is_active.clone();
let high_active = self.high_active;
tokio::spawn(async move {
let flame_sensor = match gpio.get(flame_pin_clone) {
Ok(pin) => pin.into_input(),
Err(e) => {
eprintln!("Failed to initialize flame sensor: {}", e);
return;
}
};
let mut buzzer = match gpio.get(buzzer_pin_clone) {
Ok(pin) => pin.into_output(),
Err(e) => {
eprintln!("Failed to initialize buzzer: {}", e);
return;
}
};
buzzer.set_high();
loop {
{
let is_active = is_active_clone.lock().unwrap();
if !*is_active {
buzzer.set_high(); break;
}
}
let flame_detected = if high_active {
flame_sensor.read() == Level::High
} else {
flame_sensor.read() == Level::Low
};
if flame_detected {
println!("WARNING: Flame detected!");
const ALARM_FREQ: u32 = 1000; const ALARM_DURATION: u64 = 200;
let half_period = 1_000_000 / ALARM_FREQ / 2;
let cycles = ALARM_DURATION * 1000 / (half_period as u64 * 2);
for _ in 0..cycles {
buzzer.set_low();
std::thread::sleep(std::time::Duration::from_micros(half_period as u64));
buzzer.set_high();
std::thread::sleep(std::time::Duration::from_micros(half_period as u64));
}
} else {
buzzer.set_high();
}
sleep(Duration::from_millis(check_interval_ms)).await;
}
});
Ok(())
}
fn stop_monitoring(&self) {
let mut is_active = self.is_active.lock().unwrap();
*is_active = false;
}
}