2024-01-16 12:28:15 +11:00
|
|
|
use std::{
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2024-01-16 11:00:11 +11:00
|
|
|
|
|
|
|
use metrics::{describe_gauge, gauge, Gauge};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use teslatte::vehicles::ChargingState;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
api_interface::InterfaceRequest,
|
|
|
|
charge_controllers::pl::PlState,
|
|
|
|
config::access_config,
|
|
|
|
types::{CarState, ChargeState},
|
|
|
|
};
|
|
|
|
|
|
|
|
pub struct TeslaChargeRateController {
|
|
|
|
pub car_state: Arc<RwLock<CarState>>,
|
|
|
|
pub pl_state: Option<Arc<RwLock<PlState>>>,
|
|
|
|
pub tcrc_state: Arc<RwLock<TcrcState>>,
|
2024-01-16 12:28:15 +11:00
|
|
|
voltage_low: Option<Instant>,
|
2024-01-16 11:00:11 +11:00
|
|
|
control_enable_gauge: Gauge,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub enum TcrcRequest {
|
|
|
|
DisableAutomaticControl,
|
|
|
|
EnableAutomaticControl,
|
|
|
|
}
|
|
|
|
|
2024-01-16 11:10:20 +11:00
|
|
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
2024-01-16 11:00:11 +11:00
|
|
|
pub struct TcrcState {
|
|
|
|
pub control_enable: bool,
|
2024-01-16 12:28:15 +11:00
|
|
|
pub currently_controlling: bool,
|
2024-01-16 11:00:11 +11:00
|
|
|
}
|
|
|
|
|
2024-01-16 11:10:20 +11:00
|
|
|
impl Default for TcrcState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
control_enable: true,
|
2024-01-16 12:28:15 +11:00
|
|
|
currently_controlling: false,
|
2024-01-16 11:10:20 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-16 11:00:11 +11:00
|
|
|
impl TeslaChargeRateController {
|
|
|
|
pub fn new(car_state: Arc<RwLock<CarState>>, pl_state: Option<Arc<RwLock<PlState>>>) -> Self {
|
|
|
|
describe_gauge!("tcrc_control_enable", "Enable Tesla charge rate control");
|
|
|
|
Self {
|
|
|
|
car_state,
|
|
|
|
pl_state,
|
|
|
|
tcrc_state: Default::default(),
|
2024-01-16 12:28:15 +11:00
|
|
|
voltage_low: None,
|
2024-01-16 11:00:11 +11:00
|
|
|
control_enable_gauge: gauge!("tcrc_control_enable"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn control_charge_rate(&mut self) -> Option<InterfaceRequest> {
|
|
|
|
if let Some(pl_state) = self.pl_state.as_ref().and_then(|v| v.read().ok()) {
|
|
|
|
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
|
2024-01-16 12:28:15 +11:00
|
|
|
if car_state.is_charging_at_home() {
|
2024-01-16 11:00:11 +11:00
|
|
|
// automatic control or not, check if we're below shutoff voltage
|
|
|
|
if pl_state.battery_voltage < access_config().shutoff_voltage {
|
2024-01-16 12:28:15 +11:00
|
|
|
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 {
|
|
|
|
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);
|
|
|
|
}
|
2024-01-16 11:00:11 +11:00
|
|
|
}
|
|
|
|
} else if charge_state.charging_state == ChargingState::Disconnected {
|
|
|
|
// only disable automatic control until the next time we're unplugged
|
|
|
|
self.tcrc_state
|
|
|
|
.write()
|
|
|
|
.expect("failed to write to tcrc state")
|
|
|
|
.control_enable = true;
|
|
|
|
self.control_enable_gauge.set(1.);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_request(&mut self, message: TcrcRequest) {
|
|
|
|
match message {
|
|
|
|
TcrcRequest::DisableAutomaticControl => {
|
|
|
|
self.tcrc_state
|
|
|
|
.write()
|
|
|
|
.expect("failed to write to tcrc state")
|
|
|
|
.control_enable = false;
|
|
|
|
self.control_enable_gauge.set(0.);
|
|
|
|
}
|
|
|
|
TcrcRequest::EnableAutomaticControl => {
|
|
|
|
self.tcrc_state
|
|
|
|
.write()
|
|
|
|
.expect("failed to write to tcrc state")
|
|
|
|
.control_enable = true;
|
|
|
|
self.control_enable_gauge.set(1.);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2024-01-16 12:28:15 +11:00
|
|
|
let rate = charge_state.charge_amps - 2;
|
|
|
|
return valid_rate(rate, charge_state.charge_amps).map(InterfaceRequest::SetChargeRate);
|
2024-01-16 11:00:11 +11:00
|
|
|
} else if pl_state.duty_cycle < config.duty_cycle_too_low {
|
2024-01-16 12:28:15 +11:00
|
|
|
let rate = charge_state.charge_amps + 1;
|
|
|
|
return valid_rate(rate, charge_state.charge_amps).map(InterfaceRequest::SetChargeRate);
|
2024-01-16 11:00:11 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2024-01-16 12:28:15 +11:00
|
|
|
|
|
|
|
fn valid_rate(rate: i64, other: i64) -> Option<i64> {
|
|
|
|
let config = access_config();
|
2024-01-17 08:35:00 +11:00
|
|
|
let new = rate.clamp(config.min_rate, config.max_rate);
|
2024-01-16 12:28:15 +11:00
|
|
|
if new == other {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(new)
|
|
|
|
}
|
|
|
|
}
|