diff --git a/Cargo.lock b/Cargo.lock index 81ab5d2..03e8407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2256,7 +2256,7 @@ dependencies = [ [[package]] name = "tesla-charge-controller" -version = "1.0.0" +version = "1.0.1" dependencies = [ "async-channel", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 5a40760..fa8ca46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tesla-charge-controller" -version = "1.0.0" +version = "1.0.1" edition = "2021" license = "MITNFA" description = "Controls Tesla charge rate based on solar charge data" diff --git a/src/main.rs b/src/main.rs index 7f4021b..6e3aae3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use config::{access_config, CONFIG_PATH}; use errors::PrintErrors; use std::path::PathBuf; use tesla_charge_rate::TeslaChargeRateController; +use teslatte::vehicles::ChargingState; use crate::config::Config; @@ -134,6 +135,8 @@ async fn main() { let mut charge_rate_update_interval = tokio::time::interval( std::time::Duration::from_secs(config.charge_rate_update_interval_seconds), ); + let mut was_connected = false; + loop { // await either the next interval OR a message from the other thread tokio::select! { @@ -149,9 +152,22 @@ async fn main() { }, _ = charge_rate_update_interval.tick() => { if interface.state.read().unwrap().is_charging_at_home() { + was_connected = true; if let Some(request) = tesla_charge_rate_controller.control_charge_rate() { interface.process_request(request).await; } + } else if was_connected + && interface + .state + .read() + .unwrap() + .charge_state + .is_some_and(|v| v.charging_state == ChargingState::Disconnected) + { + was_connected = false; + tesla_charge_rate_controller.process_request( + tesla_charge_rate::TcrcRequest::EnableAutomaticControl, + ); } }, api_message = api_receiver.recv() => match api_message { diff --git a/src/tesla_charge_rate.rs b/src/tesla_charge_rate.rs index 65fb5bb..e6d3aa3 100644 --- a/src/tesla_charge_rate.rs +++ b/src/tesla_charge_rate.rs @@ -1,11 +1,7 @@ -use std::{ - sync::{Arc, RwLock}, - time::{Duration, Instant}, -}; +use std::sync::{Arc, RwLock}; use metrics::{describe_gauge, gauge, Gauge}; use serde::{Deserialize, Serialize}; -use teslatte::vehicles::ChargingState; use crate::{ api_interface::InterfaceRequest, @@ -18,7 +14,7 @@ pub struct TeslaChargeRateController { pub car_state: Arc>, pub pl_state: Option>>, pub tcrc_state: Arc>, - voltage_low: Option, + voltage_low: u64, control_enable_gauge: Gauge, } @@ -49,7 +45,7 @@ impl TeslaChargeRateController { car_state, pl_state, tcrc_state: Default::default(), - voltage_low: None, + voltage_low: 0, control_enable_gauge: gauge!("tcrc_control_enable"), } } @@ -58,35 +54,22 @@ impl TeslaChargeRateController { 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); - } + // we don't need to check if we're at home because we only call this when we are + // TODO: enforce this through type system + + // automatic control or not, check if we're below shutoff voltage + if pl_state.battery_voltage < access_config().shutoff_voltage { + self.voltage_low += 1; + if (self.voltage_low * access_config().charge_rate_update_interval_seconds) + >= access_config().shutoff_voltage_time_seconds + { + return Some(InterfaceRequest::StopCharge); + } + } else { + self.voltage_low = 0; + 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.); } } }