use hidapi::{HidApi, HidDevice};
use chrono::{DateTime, Utc};
use std::{thread, time, sync::{Arc, Mutex}};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
const VENDOR_ID: u16 = 0x04d9;
const PRODUCT_ID: u16 = 0xa052;
const CO2_ADDRESS: u8 = 0x50;
const TEMPERATURE_ADDRESS: u8 = 0x42;
const HUMIDITY_ADDRESS: u8 = 0x41;
pub struct DeviceData {
time: DateTime<Utc>,
co2: u16,
temperature: f32,
humidity: f32,
}
type Callback = Box<dyn Fn(DateTime<Utc>, u16, f32, f32) + Send>;
pub struct AirControl {
device: Arc<Mutex<HidDevice>>,
callbacks: Arc<Mutex<Vec<Callback>>>,
running: Arc<AtomicBool>,
monitoring_thread: Option<JoinHandle<()>>,
}
impl AirControl {
pub fn new() -> Result<Self, &'static str> {
let api = HidApi::new().map_err(|_| "Failed to create HID API instance")?;
let device = api.open(VENDOR_ID, PRODUCT_ID).map_err(|_| "Failed to open device")?;
device.send_feature_report(&[0x00, 0x00]).expect("Failed to send feature report");
let device = Arc::new(Mutex::new(device));
let callbacks = Arc::new(Mutex::new(Vec::new()));
let running = Arc::new(AtomicBool::new(true));
let monitoring_thread = None;
Ok( AirControl {
device,
callbacks,
running,
monitoring_thread,
})
}
pub fn start_monitoring(&mut self) {
let device = self.device.clone();
let running = self.running.clone();
let callbacks = self.callbacks.clone();
let monitoring_thread = thread::spawn(move || {
while running.load(Ordering::SeqCst) {
let device = device.lock().unwrap();
match AirControl::read_data(&*device) {
Ok(data) => {
let cbs = callbacks.lock().unwrap();
for cb in cbs.iter() {
cb(data.time, data.co2, data.temperature, data.humidity);
}
}
Err(error) => {
eprintln!("Error reading data: {}", error);
break;
}
}
thread::sleep(time::Duration::from_millis(100));
}
});
self.monitoring_thread = Some(monitoring_thread);
}
pub fn stop_monitoring(&mut self){
self.running.store(false, Ordering::SeqCst);
if let Some(monitoring_thread) = self.monitoring_thread.take() {
let _ = monitoring_thread.join();
}
}
pub fn register_callback(&self, callback: Callback) {
let mut cbs = self.callbacks.lock().unwrap();
cbs.push(callback);
}
fn read_data(device: &HidDevice) -> Result<DeviceData, String> {
let mut buf = [0u8; 8];
let mut co2: Option<u16> = None;
let mut temperature: Option<f32> = None;
let mut humidity: Option<f32> = None;
while co2.is_none() || temperature.is_none() || humidity.is_none() {
match device.read_timeout(&mut buf, 10000) {
Ok(_) => {
let key = buf[0];
let value = ((buf[1] as u16) << 8) | buf[2] as u16;
match key {
CO2_ADDRESS => co2 = Some(value),
TEMPERATURE_ADDRESS => temperature = Some(format!("{:.2}", value as f32 / 16.0 - 273.15).parse::<f32>().unwrap()),
HUMIDITY_ADDRESS => humidity = Some(format!("{:.2}", value as f32 / 100.0).parse::<f32>().unwrap()),
_ => {}
}
},
Err(error) => return Err(format!("Could not read the device: {:?}", error)),
}
}
Ok(DeviceData {
time: Utc::now(),
co2: co2.unwrap(),
temperature: temperature.unwrap(),
humidity: humidity.unwrap(),
})
}
}