use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use crate::maxmin::*; use crate::models::*; /// Status of a device. #[derive(Serialize)] pub struct DeviceResponse { device_id: String, name: String, battery_level: Option, current_value: Option, water_level: Option, last_watered: Option>, last_updated: Option>, } impl DeviceResponse { pub fn new(device_id: String) -> Self { DeviceResponse { name: device_id.to_string(), device_id: device_id, battery_level: None, water_level: None, current_value: None, last_watered: None, last_updated: None, } } pub fn device_id(&self) -> &str { &self.device_id } pub fn name(&self) -> &str { &self.name } pub fn battery_level(&self) -> Option { self.battery_level } pub fn current_value(&self) -> Option { self.current_value } pub fn water_level(&self) -> Option { self.water_level } pub fn last_watered(&self) -> Option> { self.last_watered } pub fn last_updated(&self) -> Option> { self.last_updated } pub fn calculate_status(&mut self, data: &[Datapoint]) { self.last_updated = data .last() .map(|v| DateTime::::from_utc(v.timestamp, Utc)); self.battery_level = data.last().map(|v| v.battery_status); // Find the local extrema let mut values: Vec = data.iter().map(|v| v.value).collect(); clamp(&mut values, 1000.0, 300000.0); normalize(&mut values); smooth(&mut values, 2.0); derive(&mut values); let extrema = find_extrema(&values); // Look for a sharp drop in the value to indicate watering. let last_watered_index = extrema .iter() .filter_map(|x| match x { Extremum::Minimum(i) => { if 1 == 1 || values[*i] < -0.1 { Some(i) } else { None } } _ => None, }) .last(); self.last_watered = last_watered_index.map(|i| DateTime::::from_utc(data[*i].timestamp, Utc)); // How much water is left? let low_watermark = f64::min(1000.0, last_watered_index.map(|i| data[*i].value) .unwrap_or(10000.0)); let high_watermark = f64::min(300000.0, last_watered_index .and_then(|i| { extrema .iter() .rev() .filter_map(|x| match x { Extremum::Maximum(j) => { if j < i { Some(j) } else { None } } _ => None, }) .next() }) .map(|i| data[*i].value) .unwrap_or(300000.0)); self.current_value = data.last().map(|v| v.value); self.water_level = if let Some(current) = self.current_value { Some( 1.0 - (current - f64::min(low_watermark, current)) / (f64::max(high_watermark, current) - f64::max(low_watermark, current)), ) } else { None }; } } impl From for DeviceResponse { fn from(device: Device) -> DeviceResponse { DeviceResponse { device_id: device.device_id, name: device.name, battery_level: None, water_level: None, current_value: None, last_watered: None, last_updated: None, } } }