track regulator + config changes refresh interval
This commit is contained in:
parent
162d32756c
commit
dceefffd45
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2262,7 +2262,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tesla-charge-controller"
|
name = "tesla-charge-controller"
|
||||||
version = "0.1.14"
|
version = "0.1.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tesla-charge-controller"
|
name = "tesla-charge-controller"
|
||||||
version = "0.1.14"
|
version = "0.1.15"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MITNFA"
|
license = "MITNFA"
|
||||||
description = "Controls Tesla charge rate based on solar charge data"
|
description = "Controls Tesla charge rate based on solar charge data"
|
||||||
|
|
|
@ -142,7 +142,10 @@ impl TeslaInterface {
|
||||||
Ok(new_state) => {
|
Ok(new_state) => {
|
||||||
self.metrics.tesla_online.set(1.);
|
self.metrics.tesla_online.set(1.);
|
||||||
self.last_refresh = Instant::now();
|
self.last_refresh = Instant::now();
|
||||||
let mut state = self.state.write().expect("State handler panicked!!");
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.write()
|
||||||
|
.expect("Tesla API state handler panicked!!");
|
||||||
|
|
||||||
if let Some(new_charge_state) = new_state.charge_state {
|
if let Some(new_charge_state) = new_state.charge_state {
|
||||||
self.metrics
|
self.metrics
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::types::Coords;
|
use crate::types::Coords;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub watch_interval: Duration,
|
pub tesla_watch_interval: u64,
|
||||||
|
pub pl_watch_interval: u64,
|
||||||
pub coords: Coords,
|
pub coords: Coords,
|
||||||
pub serial_port: String,
|
pub serial_port: String,
|
||||||
pub baud_rate: u32,
|
pub baud_rate: u32,
|
||||||
|
@ -15,7 +14,8 @@ pub struct Config {
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
watch_interval: Duration::from_secs(60),
|
tesla_watch_interval: 120,
|
||||||
|
pl_watch_interval: 30,
|
||||||
coords: Coords {
|
coords: Coords {
|
||||||
latitude: 0.,
|
latitude: 0.,
|
||||||
longitude: 0.,
|
longitude: 0.,
|
||||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -61,16 +61,45 @@ async fn main() {
|
||||||
// and to the pli thread
|
// and to the pli thread
|
||||||
let (pli_requests, pli_receiver) = async_channel::unbounded();
|
let (pli_requests, pli_receiver) = async_channel::unbounded();
|
||||||
|
|
||||||
|
// try to spawn the pli loop
|
||||||
|
let pl_state = match Pli::new(config.serial_port.clone(), config.baud_rate) {
|
||||||
|
Ok(mut pli) => {
|
||||||
|
let pl_state = pli.state.clone();
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
let mut interval = tokio::time::interval(
|
||||||
|
std::time::Duration::from_secs(config.pl_watch_interval),
|
||||||
|
);
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = interval.tick() => pli.refresh(),
|
||||||
|
message = pli_receiver.recv() => match message {
|
||||||
|
Ok(message) => pli.process_request(message),
|
||||||
|
Err(e) => error!("Error on receive channel: {e:#?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Some(pl_state)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error connecting to serial device for PLI: {e:?}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let server_handle = server::launch_server(server::ServerState {
|
let server_handle = server::launch_server(server::ServerState {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
state: interface.state.clone(),
|
car_state: interface.state.clone(),
|
||||||
|
pl_state,
|
||||||
api_requests,
|
api_requests,
|
||||||
pli_requests,
|
pli_requests,
|
||||||
});
|
});
|
||||||
|
|
||||||
// spawn the api loop
|
// spawn the api loop
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(120));
|
let mut interval = tokio::time::interval(std::time::Duration::from_secs(
|
||||||
|
config.tesla_watch_interval,
|
||||||
|
));
|
||||||
loop {
|
loop {
|
||||||
// await either the next interval OR a message from the other thread
|
// await either the next interval OR a message from the other thread
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
@ -83,26 +112,6 @@ async fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// try to spawn the pli loop
|
|
||||||
match Pli::new(config.serial_port, config.baud_rate) {
|
|
||||||
Ok(mut pli) => {
|
|
||||||
tokio::task::spawn(async move {
|
|
||||||
let mut interval =
|
|
||||||
tokio::time::interval(std::time::Duration::from_secs(30));
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = interval.tick() => pli.refresh(),
|
|
||||||
message = pli_receiver.recv() => match message {
|
|
||||||
Ok(message) => pli.process_request(message),
|
|
||||||
Err(e) => error!("Error on receive channel: {e:#?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => log::error!("Error connecting to serial device for PLI: {e:?}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
server_handle.await;
|
server_handle.await;
|
||||||
}
|
}
|
||||||
Err(e) => error!("{}", e.error_string()),
|
Err(e) => error!("{}", e.error_string()),
|
||||||
|
|
|
@ -1,16 +1,116 @@
|
||||||
use std::{io::Write, time::Duration};
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use metrics::{describe_gauge, gauge, Gauge};
|
use metrics::{describe_gauge, gauge, Gauge};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serialport::SerialPort;
|
use serialport::SerialPort;
|
||||||
use termcolor::WriteColor;
|
use termcolor::WriteColor;
|
||||||
|
|
||||||
pub struct Pli {
|
pub struct Pli {
|
||||||
|
pub state: Arc<RwLock<PlState>>,
|
||||||
port: Box<dyn SerialPort>,
|
port: Box<dyn SerialPort>,
|
||||||
voltage_gauge: Gauge,
|
voltage_gauge: Gauge,
|
||||||
duty_cycle: Gauge,
|
duty_cycle: Gauge,
|
||||||
internal_charge_current: Gauge,
|
internal_charge_current: Gauge,
|
||||||
internal_load_current: Gauge,
|
internal_load_current: Gauge,
|
||||||
|
regulator_gauges: RegulatorGauges,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct PlState {
|
||||||
|
pub battery_voltage: f64,
|
||||||
|
pub duty_cycle: f64,
|
||||||
|
pub internal_charge_current: f64,
|
||||||
|
pub internal_load_current: f64,
|
||||||
|
pub regulator_state: RegulatorState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PlState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
battery_voltage: Default::default(),
|
||||||
|
duty_cycle: Default::default(),
|
||||||
|
internal_charge_current: Default::default(),
|
||||||
|
internal_load_current: Default::default(),
|
||||||
|
regulator_state: RegulatorState::Absorption,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum RegulatorState {
|
||||||
|
Boost,
|
||||||
|
Equalise,
|
||||||
|
Absorption,
|
||||||
|
Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for RegulatorState {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value & 0b11 {
|
||||||
|
0b00 => Self::Boost,
|
||||||
|
0b01 => Self::Equalise,
|
||||||
|
0b10 => Self::Absorption,
|
||||||
|
0b11 => Self::Float,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RegulatorGauges {
|
||||||
|
boost: Gauge,
|
||||||
|
equalise: Gauge,
|
||||||
|
absorption: Gauge,
|
||||||
|
float: Gauge,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegulatorGauges {
|
||||||
|
fn new() -> Self {
|
||||||
|
describe_gauge!("pl_regulator", "Regulator state");
|
||||||
|
let boost = gauge!("pl_regulator", "state" => "boost");
|
||||||
|
let equalise = gauge!("pl_regulator", "state" => "equalise");
|
||||||
|
let absorption = gauge!("pl_regulator", "state" => "absorption");
|
||||||
|
let float = gauge!("pl_regulator", "state" => "float");
|
||||||
|
Self {
|
||||||
|
boost,
|
||||||
|
equalise,
|
||||||
|
absorption,
|
||||||
|
float,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, state: &RegulatorState) {
|
||||||
|
match state {
|
||||||
|
RegulatorState::Boost => {
|
||||||
|
self.boost.set(1.);
|
||||||
|
self.equalise.set(0.);
|
||||||
|
self.absorption.set(0.);
|
||||||
|
self.float.set(0.);
|
||||||
|
}
|
||||||
|
RegulatorState::Equalise => {
|
||||||
|
self.boost.set(0.);
|
||||||
|
self.equalise.set(1.);
|
||||||
|
self.absorption.set(0.);
|
||||||
|
self.float.set(0.);
|
||||||
|
}
|
||||||
|
RegulatorState::Absorption => {
|
||||||
|
self.boost.set(0.);
|
||||||
|
self.equalise.set(0.);
|
||||||
|
self.absorption.set(1.);
|
||||||
|
self.float.set(0.);
|
||||||
|
}
|
||||||
|
RegulatorState::Float => {
|
||||||
|
self.boost.set(0.);
|
||||||
|
self.equalise.set(0.);
|
||||||
|
self.absorption.set(0.);
|
||||||
|
self.float.set(1.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -34,28 +134,27 @@ impl Pli {
|
||||||
let internal_load_current = gauge!("pl_internal_load_current");
|
let internal_load_current = gauge!("pl_internal_load_current");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
state: Arc::new(RwLock::new(Default::default())),
|
||||||
port,
|
port,
|
||||||
voltage_gauge,
|
voltage_gauge,
|
||||||
duty_cycle,
|
duty_cycle,
|
||||||
internal_charge_current,
|
internal_charge_current,
|
||||||
internal_load_current,
|
internal_load_current,
|
||||||
|
regulator_gauges: RegulatorGauges::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh(&mut self) {
|
pub fn refresh(&mut self) {
|
||||||
if let Ok(batv) = self.read_ram(PlRamAddress::Batv) {
|
if let Ok(new_state) = self.read_state() {
|
||||||
self.voltage_gauge.set((batv as f64) * (4. / 10.));
|
self.voltage_gauge.set(new_state.battery_voltage);
|
||||||
}
|
self.duty_cycle.set(new_state.duty_cycle);
|
||||||
if let Ok(duty_cycle) = self.read_ram(PlRamAddress::Dutycyc) {
|
|
||||||
self.duty_cycle.set((duty_cycle as f64) / 255.);
|
|
||||||
}
|
|
||||||
if let Ok(internal_charge_current) = self.read_ram(PlRamAddress::Cint) {
|
|
||||||
self.internal_charge_current
|
self.internal_charge_current
|
||||||
.set((internal_charge_current as f64) * (4. / 10.));
|
.set(new_state.internal_charge_current);
|
||||||
}
|
|
||||||
if let Ok(internal_load_current) = self.read_ram(PlRamAddress::Lint) {
|
|
||||||
self.internal_load_current
|
self.internal_load_current
|
||||||
.set((internal_load_current as f64) * (4. / 10.));
|
.set(new_state.internal_load_current);
|
||||||
|
self.regulator_gauges.set(&new_state.regulator_state);
|
||||||
|
|
||||||
|
*self.state.write().expect("PLI state handler panicked!!") = new_state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +178,16 @@ impl Pli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_state(&mut self) -> anyhow::Result<PlState> {
|
||||||
|
Ok(PlState {
|
||||||
|
battery_voltage: (self.read_ram(PlRamAddress::Batv)? as f64) * (4. / 10.),
|
||||||
|
duty_cycle: (self.read_ram(PlRamAddress::Dutycyc)? as f64) / 255.,
|
||||||
|
internal_charge_current: (self.read_ram(PlRamAddress::Cint)? as f64) * (4. / 10.),
|
||||||
|
internal_load_current: (self.read_ram(PlRamAddress::Lint)? as f64) * (4. / 10.),
|
||||||
|
regulator_state: self.read_ram(PlRamAddress::Rstate)?.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn send_command(&mut self, req: [u8; 4]) {
|
fn send_command(&mut self, req: [u8; 4]) {
|
||||||
self.port
|
self.port
|
||||||
.write_all(&req)
|
.write_all(&req)
|
||||||
|
@ -108,6 +217,7 @@ impl Pli {
|
||||||
enum PlRamAddress {
|
enum PlRamAddress {
|
||||||
Dutycyc,
|
Dutycyc,
|
||||||
Batv,
|
Batv,
|
||||||
|
Rstate,
|
||||||
Cint,
|
Cint,
|
||||||
Lint,
|
Lint,
|
||||||
}
|
}
|
||||||
|
@ -117,6 +227,7 @@ impl From<PlRamAddress> for u8 {
|
||||||
match value {
|
match value {
|
||||||
PlRamAddress::Dutycyc => 39,
|
PlRamAddress::Dutycyc => 39,
|
||||||
PlRamAddress::Batv => 50,
|
PlRamAddress::Batv => 50,
|
||||||
|
PlRamAddress::Rstate => 101,
|
||||||
PlRamAddress::Cint => 213,
|
PlRamAddress::Cint => 213,
|
||||||
PlRamAddress::Lint => 217,
|
PlRamAddress::Lint => 217,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use rocket::{
|
||||||
use crate::{
|
use crate::{
|
||||||
api_interface::InterfaceRequest,
|
api_interface::InterfaceRequest,
|
||||||
config::Config,
|
config::Config,
|
||||||
pl_interface::PliRequest,
|
pl_interface::{PlState, PliRequest},
|
||||||
types::{CarState, ChargeState, ClimateState},
|
types::{CarState, ChargeState, ClimateState},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ mod static_handler;
|
||||||
|
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub state: Arc<RwLock<CarState>>,
|
pub car_state: Arc<RwLock<CarState>>,
|
||||||
|
pub pl_state: Option<Arc<RwLock<PlState>>>,
|
||||||
pub api_requests: Sender<InterfaceRequest>,
|
pub api_requests: Sender<InterfaceRequest>,
|
||||||
pub pli_requests: Sender<PliRequest>,
|
pub pli_requests: Sender<PliRequest>,
|
||||||
}
|
}
|
||||||
|
@ -49,24 +50,32 @@ fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
||||||
.mount("/", fileserver)
|
.mount("/", fileserver)
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![home, charge_state, flash, climate_state, metrics, read_ram],
|
routes![
|
||||||
|
home,
|
||||||
|
charge_state,
|
||||||
|
flash,
|
||||||
|
climate_state,
|
||||||
|
metrics,
|
||||||
|
read_ram,
|
||||||
|
regulator_state
|
||||||
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/home")]
|
#[get("/home")]
|
||||||
async fn home(state: &State<ServerState>) -> Option<Json<bool>> {
|
async fn home(state: &State<ServerState>) -> Option<Json<bool>> {
|
||||||
let location_data = &state.state.read().ok()?.location_data?;
|
let location_data = &state.car_state.read().ok()?.location_data?;
|
||||||
Some(Json(location_data.coords.overlaps(&state.config.coords)))
|
Some(Json(location_data.coords.overlaps(&state.config.coords)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/charge-state")]
|
#[get("/charge-state")]
|
||||||
async fn charge_state(state: &State<ServerState>) -> Option<Json<ChargeState>> {
|
async fn charge_state(state: &State<ServerState>) -> Option<Json<ChargeState>> {
|
||||||
Some(Json(state.state.read().ok()?.charge_state?))
|
Some(Json(state.car_state.read().ok()?.charge_state?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/climate-state")]
|
#[get("/climate-state")]
|
||||||
async fn climate_state(state: &State<ServerState>) -> Option<Json<ClimateState>> {
|
async fn climate_state(state: &State<ServerState>) -> Option<Json<ClimateState>> {
|
||||||
Some(Json(state.state.read().ok()?.climate_state?))
|
Some(Json(state.car_state.read().ok()?.climate_state?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/flash")]
|
#[post("/flash")]
|
||||||
|
@ -87,6 +96,14 @@ async fn read_ram(address: u8, state: &State<ServerState>) -> String {
|
||||||
format!("reading at ram address {address}")
|
format!("reading at ram address {address}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/regulator-state")]
|
||||||
|
async fn regulator_state(state: &State<ServerState>) -> Option<Json<PlState>> {
|
||||||
|
state
|
||||||
|
.pl_state
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|v| Some(Json(*(v.read().ok()?))))
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Cors;
|
pub struct Cors;
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
|
|
Loading…
Reference in a new issue