variable tesla refresh period + low voltage timeout + better valid rate checking
This commit is contained in:
parent
79454ff95a
commit
e7a07b30ae
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2256,7 +2256,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tesla-charge-controller"
|
||||
version = "1.0.0-beta-2"
|
||||
version = "1.0.0-beta-3"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"chrono",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "tesla-charge-controller"
|
||||
version = "1.0.0-beta-2"
|
||||
version = "1.0.0-beta-3"
|
||||
edition = "2021"
|
||||
license = "MITNFA"
|
||||
description = "Controls Tesla charge rate based on solar charge data"
|
||||
|
|
|
@ -16,12 +16,14 @@ pub fn access_config<'a>() -> &'a Config {
|
|||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Config {
|
||||
pub tesla_update_interval_seconds: u64,
|
||||
pub tesla_update_interval_while_charging_seconds: u64,
|
||||
pub pl_watch_interval_seconds: u64,
|
||||
pub pl_timeout_milliseconds: u64,
|
||||
pub coords: Coords,
|
||||
pub serial_port: String,
|
||||
pub baud_rate: u32,
|
||||
pub shutoff_voltage: f64,
|
||||
pub shutoff_voltage_time_seconds: u64,
|
||||
pub min_rate: i64,
|
||||
pub max_rate: i64,
|
||||
pub duty_cycle_too_high: f64,
|
||||
|
@ -32,6 +34,7 @@ impl Default for Config {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
tesla_update_interval_seconds: 120,
|
||||
tesla_update_interval_while_charging_seconds: 5,
|
||||
pl_watch_interval_seconds: 30,
|
||||
pl_timeout_milliseconds: 250,
|
||||
coords: Coords {
|
||||
|
@ -41,6 +44,7 @@ impl Default for Config {
|
|||
serial_port: String::from("/dev/ttyUSB0"),
|
||||
baud_rate: 9600,
|
||||
shutoff_voltage: 52.,
|
||||
shutoff_voltage_time_seconds: 15,
|
||||
min_rate: 5,
|
||||
max_rate: 10,
|
||||
duty_cycle_too_high: 0.9,
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -127,15 +127,26 @@ async fn main() {
|
|||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(
|
||||
config.tesla_update_interval_seconds,
|
||||
));
|
||||
let mut charge_interval =
|
||||
tokio::time::interval(std::time::Duration::from_secs(
|
||||
config.tesla_update_interval_while_charging_seconds,
|
||||
));
|
||||
loop {
|
||||
// await either the next interval OR a message from the other thread
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
if !interface.state.read().unwrap().is_charging_at_home() {
|
||||
interface.refresh().await
|
||||
}
|
||||
},
|
||||
_ = charge_interval.tick() => {
|
||||
if interface.state.read().unwrap().is_charging_at_home() {
|
||||
if let Some(request) = tesla_charge_rate_controller.control_charge_rate() {
|
||||
interface.process_request(request).await;
|
||||
}
|
||||
interface.refresh().await
|
||||
},
|
||||
}
|
||||
}
|
||||
api_message = api_receiver.recv() => match api_message {
|
||||
Ok(message) => interface.process_request(message).await,
|
||||
Err(e) => panic!("Error on Tesla receive channel: {e:?}")
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
use std::{
|
||||
sync::{Arc, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use metrics::{describe_gauge, gauge, Gauge};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -15,6 +18,7 @@ pub struct TeslaChargeRateController {
|
|||
pub car_state: Arc<RwLock<CarState>>,
|
||||
pub pl_state: Option<Arc<RwLock<PlState>>>,
|
||||
pub tcrc_state: Arc<RwLock<TcrcState>>,
|
||||
voltage_low: Option<Instant>,
|
||||
control_enable_gauge: Gauge,
|
||||
}
|
||||
|
||||
|
@ -26,12 +30,14 @@ pub enum TcrcRequest {
|
|||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub struct TcrcState {
|
||||
pub control_enable: bool,
|
||||
pub currently_controlling: bool,
|
||||
}
|
||||
|
||||
impl Default for TcrcState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
control_enable: true,
|
||||
currently_controlling: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +49,7 @@ impl TeslaChargeRateController {
|
|||
car_state,
|
||||
pl_state,
|
||||
tcrc_state: Default::default(),
|
||||
voltage_low: None,
|
||||
control_enable_gauge: gauge!("tcrc_control_enable"),
|
||||
}
|
||||
}
|
||||
|
@ -52,15 +59,27 @@ impl TeslaChargeRateController {
|
|||
if let Ok(car_state) = self.car_state.read() {
|
||||
if let Some(charge_state) = car_state.charge_state {
|
||||
// check if we're charging at home
|
||||
if charge_state.charging_state == ChargingState::Charging
|
||||
&& car_state.location_data.is_some_and(|v| v.home)
|
||||
{
|
||||
if car_state.is_charging_at_home() {
|
||||
// automatic control or not, check if we're below shutoff voltage
|
||||
if pl_state.battery_voltage < access_config().shutoff_voltage {
|
||||
if let Some(low_voltage_time) = self.voltage_low {
|
||||
// if we've been below shutoff for long enough, stop charging
|
||||
if Instant::now().duration_since(low_voltage_time)
|
||||
>= Duration::from_secs(
|
||||
access_config().shutoff_voltage_time_seconds,
|
||||
)
|
||||
{
|
||||
return Some(InterfaceRequest::StopCharge);
|
||||
} else if self.tcrc_state.read().is_ok_and(|v| v.control_enable) {
|
||||
}
|
||||
} else {
|
||||
self.voltage_low = Some(Instant::now());
|
||||
}
|
||||
} else {
|
||||
self.voltage_low = None;
|
||||
if self.tcrc_state.read().is_ok_and(|v| v.control_enable) {
|
||||
return get_control(&pl_state, &charge_state);
|
||||
}
|
||||
}
|
||||
} else if charge_state.charging_state == ChargingState::Disconnected {
|
||||
// only disable automatic control until the next time we're unplugged
|
||||
self.tcrc_state
|
||||
|
@ -98,20 +117,22 @@ impl TeslaChargeRateController {
|
|||
fn get_control(pl_state: &PlState, charge_state: &ChargeState) -> Option<InterfaceRequest> {
|
||||
let config = access_config();
|
||||
if pl_state.duty_cycle > config.duty_cycle_too_high {
|
||||
let new_rate = charge_state.charge_amps - 1;
|
||||
return if new_rate >= config.min_rate {
|
||||
Some(InterfaceRequest::SetChargeRate(new_rate))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let rate = charge_state.charge_amps - 2;
|
||||
return valid_rate(rate, charge_state.charge_amps).map(InterfaceRequest::SetChargeRate);
|
||||
} else if pl_state.duty_cycle < config.duty_cycle_too_low {
|
||||
let new_rate = charge_state.charge_amps + 1;
|
||||
return if new_rate <= config.max_rate {
|
||||
Some(InterfaceRequest::SetChargeRate(new_rate))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let rate = charge_state.charge_amps + 1;
|
||||
return valid_rate(rate, charge_state.charge_amps).map(InterfaceRequest::SetChargeRate);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn valid_rate(rate: i64, other: i64) -> Option<i64> {
|
||||
let config = access_config();
|
||||
let new = rate.min(config.max_rate).max(config.min_rate);
|
||||
if new == other {
|
||||
None
|
||||
} else {
|
||||
Some(new)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,14 @@ pub struct CarState {
|
|||
pub climate_state: Option<ClimateState>,
|
||||
}
|
||||
|
||||
impl CarState {
|
||||
pub fn is_charging_at_home(&self) -> bool {
|
||||
self.charge_state
|
||||
.is_some_and(|v| v.charging_state == ChargingState::Charging)
|
||||
&& self.location_data.is_some_and(|v| v.home)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub struct ClimateState {
|
||||
pub inside_temp: f64,
|
||||
|
|
Loading…
Reference in a new issue