summaryrefslogtreecommitdiff
path: root/src/device.rs
diff options
context:
space:
mode:
authorJesse Morgan <jesse@jesterpm.net>2022-04-06 07:38:23 -0700
committerJesse Morgan <jesse@jesterpm.net>2022-04-06 07:38:23 -0700
commita34d968a08d6723968a5f1081d6bad0779875785 (patch)
treeec0a327ff99317021d7995e45ad9ef120121fc34 /src/device.rs
parent462e9cca1d021652972067de39ff0118ce5faa2b (diff)
Snapshot of progress post-christmasHEADmaster
Diffstat (limited to 'src/device.rs')
-rw-r--r--src/device.rs141
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,
+ }
+ }
+}
+