Compare commits

..

No commits in common. "67a27284b36df17752abede637d7c83128e5080d" and "769544b608408d5fc77bcd353ca37ce460dfd4dc" have entirely different histories.

5 changed files with 22 additions and 131 deletions

View File

@ -1,27 +0,0 @@
{
"regulator": {
"power_bias_watts": 3000,
"monophase_volts": 235
},
"mattermost": {
"base": "https://mattermost.example.com",
"token": ""
},
"prometheus": {
"base": "http://prometheus.example.com:9090/"
},
"easee_token_path": "./.easee_token",
"chargers": [
],
"regulator": {
"site_id": 0,
"circuit_id": 0,
"power_bias_watts": 3000.0,
"monophase_volts": 240,
"polling_interval": 15,
"p": 0.2,
"i": 0.1,
"d": 0
}
}

View File

@ -26,19 +26,12 @@ pub struct Config {
pub prometheus: Prometheus,
pub mattermost: Mattermost,
pub chargers: Vec<Charger>,
pub regulator: Option<Regulator>,
}
#[derive(Debug,Deserialize)]
pub struct Regulator {
pub site_id: u32,
pub circuit_id: i64,
pub power_bias_watts: f64,
pub monophase_volts: f64,
pub polling_interval: u64,
pub p: f64,
pub i: f64,
pub d: f64,
power_bias_watts: f64,
monophase_volts: f64,
}
pub fn load_config(path: &str) -> Result<Config> {

View File

@ -1,11 +1,9 @@
use std::collections::HashMap;
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use anyhow::{anyhow, Result};
use easee::api::{self, ChargerOpMode, Circuit, Context, SetCurrent, Triphase};
use anyhow::Result;
use easee::api::{self, ChargerOpMode, Context};
use easee::observation::{self, Event, ObservationError};
use tracing::{error, info, warn};
@ -21,7 +19,6 @@ struct Charger {
current: Option<(f64, f64, f64)>
}
impl Charger {
pub fn from_api(inner: api::Charger, configs: &[config::Charger]) -> Self {
let owners = configs.iter()
@ -39,15 +36,11 @@ pub fn start(mut ctx: Context, config: Config, mut chargers: Vec<api::Charger>)
let mattermost = mattermost::Context::new(config.mattermost.base, &config.mattermost.token)?;
let mut stream = observation::Stream::from_context(&mut ctx)?;
// TODO
let chargers: HashMap<String, Charger> = chargers.into_iter()
.filter_map(|c| {
stream.subscribe(&c.id)
.map_err(|e| error!("Cannot subscribe {}: {e}", &c.id))
.ok()?;
stream.subscribe(&c.id).ok()?;
let name = c.name.clone();
c.enable_smart_charging(&mut ctx)
.map_err(|e| error!("Cannot enable smart charging on {}: {e}", &c.id))
.ok()?;
Some((name, Charger::from_api(c, &config.chargers)))
})
.collect();
@ -57,33 +50,11 @@ pub fn start(mut ctx: Context, config: Config, mut chargers: Vec<api::Charger>)
// TODO get channel on a per-charger basis
let channel = mattermost.channel("9d9o1a5qf7fofk3wqfa493gkfe");
info!("Controller started");
mattermost.send_to_channel(&channel, "Easee Controller started")?;
if let Some(reg) = config.regulator {
let circuit = ctx.sites_details()?
.into_iter().find(|s| s.site.id == reg.site_id)
.ok_or(anyhow!("Invalid site id {}", reg.site_id))?
.circuits.into_iter()
.find(|c| c.id == reg.circuit_id)
.ok_or(anyhow!("Invalid circuit id {}", reg.circuit_id))?;
let controller = Controller {
bias: Triphase::from(reg.power_bias_watts),
ctx,
prom: PromClient::new(config.prometheus.base),
circuit,
delay: Duration::from_secs(reg.polling_interval),
p: reg.p,
i: reg.i,
d: reg.d,
};
let _ctrl = thread::spawn(move || controller.adjust_power());
}
loop {
let evt = match stream.recv() {
@ -137,47 +108,14 @@ fn handle_event(evt: Event, charger: &mut Charger, ctx: &mattermost::Context, ch
}
}
struct Controller {
/// Power bias in W
bias: Triphase,
ctx: Context,
prom: PromClient,
circuit: Circuit,
delay: Duration,
p: f64,
i: f64,
d: f64,
}
impl Controller {
pub fn adjust_power(mut self) -> Result<()> {
let voltage = 240f64;
let time_to_live = Some(self.delay.as_secs() as i32);
let mut current = self.circuit.dynamic_current(&mut self.ctx)?;
let mut integrated = Triphase::default();
let mut prev_available_current = Triphase::default();
pub fn adjust_power(prom: PromClient) -> Result<()> {
loop {
let export_power = self.prom.current_power()?;
let available_power = export_power - self.bias;
let available_current = available_power * (1.0/voltage);
let export_power = prom.current_power()?;
integrated = available_current * 0.3 + integrated * (1.0 - 0.3);
let differentiated = available_current - prev_available_current;
prev_available_current = available_current;
let delta = available_current * self.p
+ differentiated * self.d
+ integrated * self.i;
current = current + delta;
self.circuit.set_dynamic_current(&mut self.ctx, SetCurrent { time_to_live, current })?;
thread::sleep(self.delay);
}
}
}

View File

@ -30,7 +30,6 @@ enum Session {
#[derive(Debug, Clone, Subcommand)]
enum Mode {
Login,
List,
Status,
Session {
#[arg(default_value = "ongoing")]
@ -162,17 +161,6 @@ fn main() -> Result<()> {
match args.mode {
Mode::Login => login()?,
Mode::List => {
for site in ctx.sites_details()? {
println!("Site {} (level {})", site.site.id, site.site.level_of_access);
for circuit in site.circuits {
println!(" Circuit {} ({}A)", circuit.id, circuit.rated_current);
for charger in circuit.chargers {
println!("Charger {} (level {}", charger.id, charger.level_of_access);
}
}
}
},
Mode::Status => loop_chargers(&mut ctx, &args.charger_id, |c, ctx| {
c.state(ctx).map(|s| status(&c.id, s))
})?,
@ -195,7 +183,7 @@ fn main() -> Result<()> {
Mode::Stream => stream(&args.charger_id)?,
Mode::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.0, pow.1, pow.2);
},
Mode::Control => {
let chargers = load_chargers(&mut ctx, &args.charger_id)?;

View File

@ -1,7 +1,6 @@
use std::collections::HashMap;
use anyhow::{anyhow, bail, Result};
use easee::api::Triphase;
use ureq::serde::Deserialize;
use tracing::warn;
@ -53,7 +52,7 @@ impl PromClient {
PromClient { base, power_query_url }
}
pub fn current_power(&self) -> Result<Triphase> {
pub fn current_power(&self) -> Result<(f64, f64, f64)> {
let reply: PromReply = ureq::get(&self.power_query_url).call()?.into_json()?;
let PromReply::Success {
@ -75,11 +74,11 @@ impl PromClient {
}
}
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"))?,
})
Ok((
r.0.ok_or_else(|| anyhow!("Missing phase a"))?,
r.1.ok_or_else(|| anyhow!("Missing phase b"))?,
r.2.ok_or_else(|| anyhow!("Missing phase c"))?,
))
}
}