diff options
Diffstat (limited to 'src/device.rs')
-rw-r--r-- | src/device.rs | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..b62b633 --- /dev/null +++ b/src/device.rs @@ -0,0 +1,141 @@ +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<f64>, + current_value: Option<f64>, + water_level: Option<f64>, + last_watered: Option<DateTime<Utc>>, + last_updated: Option<DateTime<Utc>>, +} + +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<f64> { + self.battery_level + } + + pub fn current_value(&self) -> Option<f64> { + self.current_value + } + + pub fn water_level(&self) -> Option<f64> { + self.water_level + } + + pub fn last_watered(&self) -> Option<DateTime<Utc>> { + self.last_watered + } + + pub fn last_updated(&self) -> Option<DateTime<Utc>> { + self.last_updated + } + + pub fn calculate_status(&mut self, data: &[Datapoint]) { + self.last_updated = data + .last() + .map(|v| DateTime::<Utc>::from_utc(v.timestamp, Utc)); + self.battery_level = data.last().map(|v| v.battery_status); + + // Find the local extrema + let mut values: Vec<f64> = 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::<Utc>::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<Device> 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, + } + } +} + |