From a34d968a08d6723968a5f1081d6bad0779875785 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Wed, 6 Apr 2022 07:38:23 -0700 Subject: Snapshot of progress post-christmas --- src/device.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/device.rs (limited to 'src/device.rs') 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, + 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, + } + } +} + -- cgit v1.2.3