Compare commits

..

No commits in common. "36c0e28f020a8709b037ac99f41bc3df1c9c1178" and "67a27284b36df17752abede637d7c83128e5080d" have entirely different histories.

5 changed files with 74 additions and 97 deletions

View File

@ -1,5 +1,5 @@
use anyhow::Result;
use serde::Deserialize; use serde::Deserialize;
use anyhow::Result;
use serde_json; use serde_json;
#[derive(Debug,Deserialize)] #[derive(Debug,Deserialize)]
@ -17,7 +17,7 @@ pub struct Mattermost {
#[derive(Debug,Deserialize)] #[derive(Debug,Deserialize)]
pub struct Prometheus { pub struct Prometheus {
pub base: String, pub base: String
} }
#[derive(Debug,Deserialize)] #[derive(Debug,Deserialize)]

View File

@ -1,4 +1,3 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::Infallible; use std::convert::Infallible;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -10,47 +9,39 @@ use easee::api::{self, ChargerOpMode, Circuit, Context, SetCurrent, Triphase};
use easee::observation::{self, Event, ObservationError}; use easee::observation::{self, Event, ObservationError};
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::config::{self, Config};
use crate::mattermost::{self, Channel}; use crate::mattermost::{self, Channel};
use crate::prom::PromClient; use crate::prom::PromClient;
use crate::config::{self, Config};
use observation::{Observation,PilotMode}; use observation::{Observation,PilotMode};
struct Charger { struct Charger {
inner: api::Charger, inner: api::Charger,
owners: Vec<String>, owners: Vec<String>,
current: Option<(f64, f64, f64)>, current: Option<(f64, f64, f64)>
} }
impl Charger { impl Charger {
pub fn from_api(inner: api::Charger, configs: &[config::Charger]) -> Self { pub fn from_api(inner: api::Charger, configs: &[config::Charger]) -> Self {
let owners = configs let owners = configs.iter()
.iter()
.find(|c| c.id == inner.id) .find(|c| c.id == inner.id)
.map(|c| &c.owners) .map(|c| &c.owners)
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
Charger { Charger { inner, owners, current: None }
inner,
owners,
current: None,
}
}
} }
pub fn start( }
mut ctx: Context,
config: Config, pub fn start(mut ctx: Context, config: Config, mut chargers: Vec<api::Charger>) -> Result<Infallible> {
mut chargers: Vec<api::Charger>,
) -> Result<Infallible> {
let mattermost = mattermost::Context::new(config.mattermost.base, &config.mattermost.token)?; let mattermost = mattermost::Context::new(config.mattermost.base, &config.mattermost.token)?;
let mut stream = observation::Stream::from_context(&mut ctx)?; let mut stream = observation::Stream::from_context(&mut ctx)?;
let chargers: HashMap<String, Charger> = chargers let chargers: HashMap<String, Charger> = chargers.into_iter()
.into_iter()
.filter_map(|c| { .filter_map(|c| {
stream stream.subscribe(&c.id)
.subscribe(&c.id)
.map_err(|e| error!("Cannot subscribe {}: {e}", &c.id)) .map_err(|e| error!("Cannot subscribe {}: {e}", &c.id))
.ok()?; .ok()?;
let name = c.name.clone(); let name = c.name.clone();
@ -70,13 +61,11 @@ pub fn start(
mattermost.send_to_channel(&channel, "Easee Controller started")?; mattermost.send_to_channel(&channel, "Easee Controller started")?;
if let Some(reg) = config.regulator { if let Some(reg) = config.regulator {
let circuit = ctx
.sites_details()? let circuit = ctx.sites_details()?
.into_iter() .into_iter().find(|s| s.site.id == reg.site_id)
.find(|s| s.site.id == reg.site_id)
.ok_or(anyhow!("Invalid site id {}", reg.site_id))? .ok_or(anyhow!("Invalid site id {}", reg.site_id))?
.circuits .circuits.into_iter()
.into_iter()
.find(|c| c.id == reg.circuit_id) .find(|c| c.id == reg.circuit_id)
.ok_or(anyhow!("Invalid circuit id {}", reg.circuit_id))?; .ok_or(anyhow!("Invalid circuit id {}", reg.circuit_id))?;
@ -92,49 +81,45 @@ pub fn start(
}; };
let _ctrl = thread::spawn(move || controller.adjust_power()); let _ctrl = thread::spawn(move || controller.adjust_power());
} }
loop { loop {
let evt = match stream.recv() { let evt = match stream.recv() {
Ok(e) => e, Ok(e) => e,
Err(ObservationError::Stream(stream_error)) => Err(stream_error)?, Err(ObservationError::Stream(stream_error)) => Err(stream_error)?,
Err(other) => { Err(other) => { error!("Cannot process message: {}", other); continue },
error!("Cannot process message: {}", other);
continue;
}
}; };
let mut chargers = chargers.lock().unwrap(); let mut chargers = chargers.lock().unwrap();
let Some(charger) = chargers.get_mut(&evt.charger) else { let Some(charger) = chargers.get_mut(&evt.charger)
warn!("Received message for unknown charger {}", &evt.charger); else { warn!("Received message for unknown charger {}", &evt.charger); continue };
continue;
};
let result = handle_event(evt, charger, &mattermost, &channel); let result = handle_event(evt, charger, &mattermost, &channel);
if let Err(err) = result { if let Err(err) = result {
error!("Error handling observation: {:?}", err); error!("Error handling observation: {:?}", err);
} }
} }
} }
fn handle_event( fn handle_event(evt: Event, charger: &mut Charger, ctx: &mattermost::Context, channel: &Channel) -> Result<()> {
evt: Event,
charger: &mut Charger,
ctx: &mattermost::Context,
channel: &Channel,
) -> Result<()> {
let send = |msg: &str| ctx.send_to_channel(channel, msg); let send = |msg: &str| ctx.send_to_channel(channel, msg);
match evt.observation { match evt.observation {
Observation::PilotMode(mode) => match mode { Observation::PilotMode(mode) => {
match mode {
PilotMode::Disconnected => send("Car Disconnected"), PilotMode::Disconnected => send("Car Disconnected"),
PilotMode::Connected => send("Car Connected"), PilotMode::Connected => send("Car Connected"),
PilotMode::Charging => send("Car Charging"), PilotMode::Charging => send("Car Charging"),
PilotMode::NeedsVentilation => send("Car needs ventilation"), PilotMode::NeedsVentilation => send("Car needs ventilation"),
PilotMode::FaultDetected => send("Fault detected"), PilotMode::FaultDetected => send("Fault detected"),
PilotMode::Unknown => send("Unknown"), PilotMode::Unknown => send("Unknown"),
}
}, },
Observation::ChargerOpMode(mode) => { Observation::ChargerOpMode(mode) => {
match mode { match mode {
@ -145,11 +130,9 @@ fn handle_event(
ChargerOpMode::Finished => send("Charging finished"), ChargerOpMode::Finished => send("Charging finished"),
ChargerOpMode::Error => send("Charger error"), ChargerOpMode::Error => send("Charger error"),
ChargerOpMode::Ready => send("Charger ready"), ChargerOpMode::Ready => send("Charger ready"),
ChargerOpMode::AwaitingAuthentication => send("Charger awaiting authentication"),
ChargerOpMode::Deauthenticating => send("Charger deauthenticating"),
}?; }?;
ctx.set_status(mode) ctx.set_status(mode)
} },
other => Ok(info!("{}: {:?}", evt.charger, other)), other => Ok(info!("{}: {:?}", evt.charger, other)),
} }
} }
@ -167,7 +150,9 @@ struct Controller {
} }
impl Controller { impl Controller {
pub fn adjust_power(mut self) -> Result<()> { pub fn adjust_power(mut self) -> Result<()> {
let voltage = 240f64; let voltage = 240f64;
let time_to_live = Some(self.delay.as_secs() as i32); let time_to_live = Some(self.delay.as_secs() as i32);
@ -184,18 +169,15 @@ impl Controller {
let differentiated = available_current - prev_available_current; let differentiated = available_current - prev_available_current;
prev_available_current = available_current; prev_available_current = available_current;
let delta = available_current * self.p + differentiated * self.d + integrated * self.i; let delta = available_current * self.p
+ differentiated * self.d
+ integrated * self.i;
current = current + delta; current = current + delta;
self.circuit.set_dynamic_current( self.circuit.set_dynamic_current(&mut self.ctx, SetCurrent { time_to_live, current })?;
&mut self.ctx,
SetCurrent {
time_to_live,
current,
},
)?;
thread::sleep(self.delay); thread::sleep(self.delay);
} }
} }
} }

View File

@ -8,10 +8,10 @@ use easee::observation;
use tracing::info; use tracing::info;
mod prom;
mod mattermost;
mod config; mod config;
mod control; mod control;
mod mattermost;
mod prom;
#[derive(Debug, Clone, Copy, ValueEnum)] #[derive(Debug, Clone, Copy, ValueEnum)]
enum Command { enum Command {
@ -87,7 +87,8 @@ fn login() -> Result<()> {
fn load_context() -> Result<easee::api::Context> { fn load_context() -> Result<easee::api::Context> {
let saved = std::fs::read_to_string(SAVED_TOKEN_PATH) let saved = std::fs::read_to_string(SAVED_TOKEN_PATH)
.context("Cannot read saved token (did you log in ?)")?; .context("Cannot read saved token (did you log in ?)")?;
let ctx = easee::api::Context::from_saved(&saved)?.on_refresh(save_context); let ctx = easee::api::Context::from_saved(&saved)?
.on_refresh(save_context);
Ok(ctx) Ok(ctx)
} }
@ -153,8 +154,7 @@ fn main() -> Result<()> {
// We need to do this before loading the context // We need to do this before loading the context
if let Mode::Login = args.mode { if let Mode::Login = args.mode {
login()?; login()?; return Ok(())
return Ok(());
} }
let mut ctx = load_context()?; let mut ctx = load_context()?;
@ -164,10 +164,7 @@ fn main() -> Result<()> {
Mode::Login => login()?, Mode::Login => login()?,
Mode::List => { Mode::List => {
for site in ctx.sites_details()? { for site in ctx.sites_details()? {
println!( println!("Site {} (level {})", site.site.id, site.site.level_of_access);
"Site {} (level {})",
site.site.id, site.site.level_of_access
);
for circuit in site.circuits { for circuit in site.circuits {
println!(" Circuit {} ({}A)", circuit.id, circuit.rated_current); println!(" Circuit {} ({}A)", circuit.id, circuit.rated_current);
for charger in circuit.chargers { for charger in circuit.chargers {
@ -175,7 +172,7 @@ fn main() -> Result<()> {
} }
} }
} }
} },
Mode::Status => loop_chargers(&mut ctx, &args.charger_id, |c, ctx| { Mode::Status => loop_chargers(&mut ctx, &args.charger_id, |c, ctx| {
c.state(ctx).map(|s| status(&c.id, s)) c.state(ctx).map(|s| status(&c.id, s))
})?, })?,
@ -199,7 +196,7 @@ fn main() -> Result<()> {
Mode::Power => { Mode::Power => {
let pow = prom::PromClient::new(config.prometheus.base).current_power()?; let pow = prom::PromClient::new(config.prometheus.base).current_power()?;
println!("P1:{}W P2:{}W P3:{}W", pow.phase1, pow.phase2, pow.phase3); println!("P1:{}W P2:{}W P3:{}W", pow.phase1, pow.phase2, pow.phase3);
} },
Mode::Control => { Mode::Control => {
let chargers = load_chargers(&mut ctx, &args.charger_id)?; let chargers = load_chargers(&mut ctx, &args.charger_id)?;
control::start(ctx, config, chargers)?; control::start(ctx, config, chargers)?;

View File

@ -1,6 +1,6 @@
use anyhow::Result;
use easee::api::ChargerOpMode; use easee::api::ChargerOpMode;
use ureq::json; use ureq::json;
use anyhow::Result;
pub struct Context { pub struct Context {
pub base: String, pub base: String,
@ -11,7 +11,9 @@ pub struct Channel {
channel_id: String, channel_id: String,
} }
impl Context { impl Context {
pub fn new(base: String, token: &str) -> Result<Self> { pub fn new(base: String, token: &str) -> Result<Self> {
Ok(Self { Ok(Self {
base, base,
@ -25,7 +27,8 @@ impl Context {
pub fn set_custom_status(&self, text: &str, emoji: &str) -> Result<()> { pub fn set_custom_status(&self, text: &str, emoji: &str) -> Result<()> {
let path = &self.path("users/me/status/custom"); let path = &self.path("users/me/status/custom");
ureq::put(path).send_json(json!( { "emoji": emoji, "text": text } ))?; ureq::put(path)
.send_json(json!( { "emoji": emoji, "text": text } ))?;
Ok(()) Ok(())
} }
@ -39,16 +42,12 @@ impl Context {
Finished => ("Finished", "white_check_mark"), Finished => ("Finished", "white_check_mark"),
Error => ("Error", "no_entry_sign"), Error => ("Error", "no_entry_sign"),
Ready => ("Ready", "electric_plug"), Ready => ("Ready", "electric_plug"),
AwaitingAuthentication => ("Awaiting authentication", "key"),
Deauthenticating => ("Deauthenticating", "closed_lock_with_key"),
}; };
self.set_custom_status(text, emoji) self.set_custom_status(text, emoji)
} }
pub fn channel(&self, id: &str) -> Channel { pub fn channel(&self, id: &str) -> Channel {
Channel { Channel { channel_id: id.to_owned() }
channel_id: id.to_owned(),
}
} }
pub fn send_to_channel(&self, channel: &Channel, msg: &str) -> Result<()> { pub fn send_to_channel(&self, channel: &Channel, msg: &str) -> Result<()> {
@ -60,4 +59,5 @@ impl Context {
))?; ))?;
Ok(()) Ok(())
} }
} }

View File

@ -44,16 +44,13 @@ struct MatrixEntry {
pub struct PromClient { pub struct PromClient {
base: String, base: String,
power_query_url: String, power_query_url: String
} }
impl PromClient { impl PromClient {
pub fn new(base: String) -> Self { pub fn new(base: String) -> Self {
let power_query_url = format!("{}{}", &base, PROM_QUERY); let power_query_url = format!("{}{}", &base, PROM_QUERY);
PromClient { PromClient { base, power_query_url }
base,
power_query_url,
}
} }
pub fn current_power(&self) -> Result<Triphase> { pub fn current_power(&self) -> Result<Triphase> {
@ -84,4 +81,5 @@ impl PromClient {
phase3: r.2.ok_or_else(|| anyhow!("Missing phase c"))?, phase3: r.2.ok_or_else(|| anyhow!("Missing phase c"))?,
}) })
} }
} }