use std::collections::HashMap; use anyhow::{anyhow, bail, Result}; use easee::api::Triphase; use ureq::serde::Deserialize; use tracing::warn; const PROM_QUERY: &str = "/api/v1/query?query=ac_power_watts%7Bmeter%3D%22mains%22%2Cpower%3D%22active%22%2Cphase%21%3D%22total%22%7D"; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(tag = "status")] enum PromReply { Success { data: PromData, }, Error { data: PromData, error_type: String, error: String, }, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(tag = "resultType", content = "result")] enum PromData { Vector(Vec), Matrix(Vec), } #[derive(Debug, Deserialize)] struct VecEntry { metric: HashMap, value: (f64, String), } #[derive(Debug, Deserialize)] struct MatrixEntry { metric: HashMap, values: Vec<(f64, String)>, } pub struct PromClient { base: String, power_query_url: String, } impl PromClient { pub fn new(base: String) -> Self { let power_query_url = format!("{}{}", &base, PROM_QUERY); PromClient { base, power_query_url, } } pub fn current_power(&self) -> Result { let reply: PromReply = ureq::get(&self.power_query_url).call()?.into_json()?; let PromReply::Success { data: PromData::Vector(v), } = reply else { bail!("Could not understand Prometheus reply: {:?}", reply) }; let mut r = (None, None, None); for entry in &v { let val: f64 = entry.value.1.parse()?; match entry.metric.get("phase").map(|s| &**s) { Some("a") => r.0 = Some(val), Some("b") => r.1 = Some(val), Some("c") => r.2 = Some(val), _ => warn!("Metric with unexpected phase"), } } Ok(Triphase { phase1: r.0.ok_or_else(|| anyhow!("Missing phase a"))?, phase2: r.1.ok_or_else(|| anyhow!("Missing phase b"))?, phase3: r.2.ok_or_else(|| anyhow!("Missing phase c"))?, }) } }