use std::{ sync::{Arc, RwLock}, time::{Duration, Instant}, }; 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>, pub pl_state: Option>>, pub tcrc_state: Arc>, voltage_low: Option, control_enable_gauge: Gauge, } pub enum TcrcRequest { DisableAutomaticControl, EnableAutomaticControl, } #[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, } } } impl TeslaChargeRateController { pub fn new(car_state: Arc>, pl_state: Option>>) -> Self { describe_gauge!("tcrc_control_enable", "Enable Tesla charge rate control"); Self { car_state, pl_state, tcrc_state: Default::default(), voltage_low: None, control_enable_gauge: gauge!("tcrc_control_enable"), } } pub fn control_charge_rate(&mut self) -> Option { 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 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 { 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 .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 { let config = access_config(); if pl_state.duty_cycle > config.duty_cycle_too_high { 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 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 { let config = access_config(); let new = rate.clamp(config.min_rate, config.max_rate); if new == other { None } else { Some(new) } }