From 8e698e71c1be701f4520a7e05da98e79e46c4d8d Mon Sep 17 00:00:00 2001 From: Maxime Augier Date: Sun, 18 Aug 2024 14:35:52 +0200 Subject: [PATCH] Add config and control support --- src/config.rs | 39 ++++++++++++++++ src/control.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 16 +++++-- 3 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/config.rs create mode 100644 src/control.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d3886e4 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,39 @@ +use serde::Deserialize; +use anyhow::Result; +use serde_json; + +#[derive(Debug,Deserialize)] +pub struct Charger { + pub id: String, + pub channel_id: String, + pub owners: Vec, +} + +#[derive(Debug,Deserialize)] +pub struct Mattermost { + pub base: String, + pub token: String, +} + +#[derive(Debug,Deserialize)] +pub struct Prometheus { + pub base: String +} + +#[derive(Debug,Deserialize)] +pub struct Config { + pub easee_token_path: String, + pub prometheus: Prometheus, + pub mattermost: Mattermost, + pub chargers: Vec, +} + +#[derive(Debug,Deserialize)] +pub struct Regulator { + power_bias_watts: f64, + monophase_volts: f64, +} + +pub fn load_config(path: &str) -> Result { + Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?) +} \ No newline at end of file diff --git a/src/control.rs b/src/control.rs new file mode 100644 index 0000000..9f5e9bb --- /dev/null +++ b/src/control.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; +use std::convert::Infallible; +use std::sync::{Arc, Mutex}; + +use anyhow::Result; +use easee::api::{self, ChargerOpMode, Context}; +use easee::observation::{self, Event, ObservationError}; +use tracing::{error, info, warn}; + +use crate::mattermost::{self, Channel}; +use crate::prom::PromClient; +use crate::config::{self, Config}; + +use observation::{Observation,PilotMode}; + +struct Charger { + inner: api::Charger, + owners: Vec, + current: Option<(f64, f64, f64)> +} + +impl Charger { + pub fn from_api(inner: api::Charger, configs: &[config::Charger]) -> Self { + let owners = configs.iter() + .find(|c| c.id == inner.id) + .map(|c| &c.owners) + .cloned() + .unwrap_or_default(); + Charger { inner, owners, current: None } + } + +} + +pub fn start(mut ctx: Context, config: Config, mut chargers: Vec) -> Result { + + let mattermost = mattermost::Context::new(config.mattermost.base, &config.mattermost.token)?; + let mut stream = observation::Stream::from_context(&mut ctx)?; + + // TODO + let chargers: HashMap = chargers.into_iter() + .filter_map(|c| { + stream.subscribe(&c.id).ok()?; + let name = c.name.clone(); + Some((name, Charger::from_api(c, &config.chargers))) + }) + .collect(); + + let chargers = Arc::new(Mutex::new(chargers)); + + // TODO get channel on a per-charger basis + let channel = mattermost.channel("9d9o1a5qf7fofk3wqfa493gkfe"); + + + + info!("Controller started"); + mattermost.send_to_channel(&channel, "Easee Controller started")?; + + loop { + + let evt = match stream.recv() { + Ok(e) => e, + Err(ObservationError::Stream(stream_error)) => Err(stream_error)?, + Err(other) => { error!("Cannot process message: {}", other); continue }, + }; + + let mut chargers = chargers.lock().unwrap(); + + let Some(charger) = chargers.get_mut(&evt.charger) + else { warn!("Received message for unknown charger {}", &evt.charger); continue }; + + let result = handle_event(evt, charger, &mattermost, &channel); + + if let Err(err) = result { + error!("Error handling observation: {:?}", err); + } + + } +} + +fn handle_event(evt: Event, charger: &mut Charger, ctx: &mattermost::Context, channel: &Channel) -> Result<()> { + + let send = |msg: &str| ctx.send_to_channel(channel, msg); + + match evt.observation { + Observation::PilotMode(mode) => { + match mode { + PilotMode::Disconnected => send("Car Disconnected"), + PilotMode::Connected => send("Car Connected"), + PilotMode::Charging => send("Car Charging"), + PilotMode::NeedsVentilation => send("Car needs ventilation"), + PilotMode::FaultDetected => send("Fault detected"), + PilotMode::Unknown => send("Unknown"), + } + }, + Observation::ChargerOpMode(mode) => { + match mode { + ChargerOpMode::Unknown => send("Unknown"), + ChargerOpMode::Disconnected => send("Charger disconnected"), + ChargerOpMode::Paused => send("Charge paused"), + ChargerOpMode::Charging => send("Charging"), + ChargerOpMode::Finished => send("Charging finished"), + ChargerOpMode::Error => send("Charger error"), + ChargerOpMode::Ready => send("Charger ready"), + }?; + ctx.set_status(mode) + }, + other => Ok(info!("{}: {:?}", evt.charger, other)), + } +} + +pub fn adjust_power(prom: PromClient) -> Result<()> { + + loop { + let export_power = prom.current_power()?; + + + + } + + +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6324b1f..0b28595 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,14 @@ use anyhow::{Context as AnyhowContext, Result}; use clap::ValueEnum; use clap::{Parser, Subcommand}; use easee::api::{ApiError, Charger, ChargerState, ChargingSession, Context}; -use easee::{observation, stream}; +use easee::observation; use tracing::info; mod prom; mod mattermost; +mod config; +mod control; #[derive(Debug, Clone, Copy, ValueEnum)] enum Command { @@ -38,6 +40,7 @@ enum Mode { }, Stream, Power, + Control, } #[derive(Debug, Parser)] @@ -48,8 +51,8 @@ struct CLI { #[arg(short, long)] charger_id: Vec, - #[arg(short, long, default_value = "http://localhost:9090")] - prometheus: String, + #[arg(short, long, default_value = "./config.json")] + config: String, #[command(subcommand)] mode: Mode, @@ -154,6 +157,7 @@ fn main() -> Result<()> { } let mut ctx = load_context()?; + let config = config::load_config(&args.config)?; match args.mode { Mode::Login => login()?, @@ -178,8 +182,12 @@ fn main() -> Result<()> { } Mode::Stream => stream(&args.charger_id)?, Mode::Power => { - let pow = prom::current_power(&*args.prometheus)?; + let pow = prom::PromClient::new(config.prometheus.base).current_power()?; println!("P1:{}W P2:{}W P3:{}W", pow.0, pow.1, pow.2); + }, + Mode::Control => { + let chargers = load_chargers(&mut ctx, &args.charger_id)?; + control::start(ctx, config, chargers)?; } };