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]]
|
||||
name = "tesla-charge-controller"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "tesla-charge-controller"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
edition = "2021"
|
||||
license = "MITNFA"
|
||||
description = "Controls Tesla charge rate based on solar charge data"
|
||||
|
|
|
@ -142,7 +142,10 @@ impl TeslaInterface {
|
|||
Ok(new_state) => {
|
||||
self.metrics.tesla_online.set(1.);
|
||||
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 {
|
||||
self.metrics
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::Coords;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Config {
|
||||
pub watch_interval: Duration,
|
||||
pub tesla_watch_interval: u64,
|
||||
pub pl_watch_interval: u64,
|
||||
pub coords: Coords,
|
||||
pub serial_port: String,
|
||||
pub baud_rate: u32,
|
||||
|
@ -15,7 +14,8 @@ pub struct Config {
|
|||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
watch_interval: Duration::from_secs(60),
|
||||
tesla_watch_interval: 120,
|
||||
pl_watch_interval: 30,
|
||||
coords: Coords {
|
||||
latitude: 0.,
|
||||
longitude: 0.,
|
||||
|
|
53
src/main.rs
53
src/main.rs
|
@ -61,16 +61,45 @@ async fn main() {
|
|||
// and to the pli thread
|
||||
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 {
|
||||
config: config.clone(),
|
||||
state: interface.state.clone(),
|
||||
car_state: interface.state.clone(),
|
||||
pl_state,
|
||||
api_requests,
|
||||
pli_requests,
|
||||
});
|
||||
|
||||
// spawn the api loop
|
||||
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 {
|
||||
// await either the next interval OR a message from the other thread
|
||||
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;
|
||||
}
|
||||
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 metrics::{describe_gauge, gauge, Gauge};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serialport::SerialPort;
|
||||
use termcolor::WriteColor;
|
||||
|
||||
pub struct Pli {
|
||||
pub state: Arc<RwLock<PlState>>,
|
||||
port: Box<dyn SerialPort>,
|
||||
voltage_gauge: Gauge,
|
||||
duty_cycle: Gauge,
|
||||
internal_charge_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)]
|
||||
|
@ -34,28 +134,27 @@ impl Pli {
|
|||
let internal_load_current = gauge!("pl_internal_load_current");
|
||||
|
||||
Ok(Self {
|
||||
state: Arc::new(RwLock::new(Default::default())),
|
||||
port,
|
||||
voltage_gauge,
|
||||
duty_cycle,
|
||||
internal_charge_current,
|
||||
internal_load_current,
|
||||
regulator_gauges: RegulatorGauges::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
if let Ok(batv) = self.read_ram(PlRamAddress::Batv) {
|
||||
self.voltage_gauge.set((batv as f64) * (4. / 10.));
|
||||
}
|
||||
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) {
|
||||
if let Ok(new_state) = self.read_state() {
|
||||
self.voltage_gauge.set(new_state.battery_voltage);
|
||||
self.duty_cycle.set(new_state.duty_cycle);
|
||||
self.internal_charge_current
|
||||
.set((internal_charge_current as f64) * (4. / 10.));
|
||||
}
|
||||
if let Ok(internal_load_current) = self.read_ram(PlRamAddress::Lint) {
|
||||
.set(new_state.internal_charge_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]) {
|
||||
self.port
|
||||
.write_all(&req)
|
||||
|
@ -108,6 +217,7 @@ impl Pli {
|
|||
enum PlRamAddress {
|
||||
Dutycyc,
|
||||
Batv,
|
||||
Rstate,
|
||||
Cint,
|
||||
Lint,
|
||||
}
|
||||
|
@ -117,6 +227,7 @@ impl From<PlRamAddress> for u8 {
|
|||
match value {
|
||||
PlRamAddress::Dutycyc => 39,
|
||||
PlRamAddress::Batv => 50,
|
||||
PlRamAddress::Rstate => 101,
|
||||
PlRamAddress::Cint => 213,
|
||||
PlRamAddress::Lint => 217,
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use rocket::{
|
|||
use crate::{
|
||||
api_interface::InterfaceRequest,
|
||||
config::Config,
|
||||
pl_interface::PliRequest,
|
||||
pl_interface::{PlState, PliRequest},
|
||||
types::{CarState, ChargeState, ClimateState},
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,8 @@ mod static_handler;
|
|||
|
||||
pub struct ServerState {
|
||||
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 pli_requests: Sender<PliRequest>,
|
||||
}
|
||||
|
@ -49,24 +50,32 @@ fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
|
|||
.mount("/", fileserver)
|
||||
.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")]
|
||||
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)))
|
||||
}
|
||||
|
||||
#[get("/charge-state")]
|
||||
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")]
|
||||
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")]
|
||||
|
@ -87,6 +96,14 @@ async fn read_ram(address: u8, state: &State<ServerState>) -> String {
|
|||
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;
|
||||
|
||||
#[rocket::async_trait]
|
||||
|
|
Loading…
Reference in a new issue