diff --git a/Cargo.lock b/Cargo.lock index 90dca37..d9383ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1072,6 +1072,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "include_dir" version = "0.7.3" @@ -2573,6 +2579,7 @@ dependencies = [ "chrono", "clap 4.4.11", "env_logger 0.10.1", + "if_chain", "include_dir", "libmodbus-rs", "log 0.4.20", diff --git a/Cargo.toml b/Cargo.toml index c5cb765..5655ca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,4 @@ env_logger = "0.10" log = "0.4" serialport = "4.3" libmodbus-rs = "0.8.3" +if_chain = "1.0.2" diff --git a/control-loop.txt b/control-loop.txt new file mode 100644 index 0000000..b456349 --- /dev/null +++ b/control-loop.txt @@ -0,0 +1,2 @@ +targeting pl's target voltage +if load current > 0 or duty cycle < ~0.8 then there is power to spare \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 04ab8af..bb7f695 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,7 +39,6 @@ enum Commands { #[tokio::main] async fn main() { - let args = Args::parse(); env_logger::builder() .format_module_path(false) .format_timestamp( @@ -51,10 +50,9 @@ async fn main() { ) .init(); + let args = Args::parse(); let auth_path = args.config_dir.join("auth"); - let _ = CONFIG_PATH.set(args.config_dir.join("config.json")); - let _recorder = metrics_prometheus::install(); match args.command { @@ -110,6 +108,8 @@ async fn main() { } }; + // spawn a loop for each additional charge controller to log + // failed connections will print an error but the program will continue let _additional_controllers: Vec<_> = access_config() .additional_charge_controllers .clone() @@ -165,7 +165,7 @@ async fn main() { tcrc_requests, }); - // spawn the api loop + // spawn the api / charge rate control loop tokio::task::spawn(async move { let mut normal_data_update_interval = tokio::time::interval(std::time::Duration::from_secs( @@ -208,6 +208,7 @@ async fn main() { .charge_state .is_some_and(|v| v.charging_state == ChargingState::Disconnected) { + // reenable control when charger is disconnected was_connected = false; tesla_charge_rate_controller.process_request( tesla_charge_rate::TcrcRequest::EnableAutomaticControl, diff --git a/src/server/static_handler.rs b/src/server/static_handler.rs index 3315fde..f2ea290 100644 --- a/src/server/static_handler.rs +++ b/src/server/static_handler.rs @@ -1,3 +1,4 @@ +use if_chain::if_chain; use include_dir::{include_dir, Dir}; use rocket::{ http::{ContentType, Status}, @@ -64,8 +65,10 @@ struct RawHtml { impl<'r> Responder<'r, 'static> for RawHtml { fn respond_to(self, request: &'r Request<'_>) -> rocket::response::Result<'static> { let mut response = self.data.respond_to(request)?; - if let Some(ext) = self.name.extension() { - if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) { + if_chain! { + if let Some(ext) = self.name.extension(); + if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()); + then { response.set_header(ct); } } diff --git a/src/tesla_charge_rate.rs b/src/tesla_charge_rate.rs index 382f4ab..62d1da9 100644 --- a/src/tesla_charge_rate.rs +++ b/src/tesla_charge_rate.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, RwLock}; +use if_chain::if_chain; use metrics::{describe_gauge, gauge, Gauge}; use serde::{Deserialize, Serialize}; @@ -49,25 +50,21 @@ impl TeslaChargeRateController { } 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 { - // 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); - } + if_chain! { + if let Some(pl_state) = self.pl_state.as_ref().and_then(|v| v.read().ok()); + if let Some(charge_state) = self.car_state.read().ok().and_then(|v| v.charge_state); + then { + 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); } } } @@ -105,6 +102,8 @@ fn get_control(pl_state: &PlState, charge_state: &ChargeState) -> Option f64 { - self.battery_range * 1.60934 - } -} - impl From for ChargeState { fn from(value: teslatte::vehicles::ChargeState) -> Self { ChargeState { @@ -157,6 +150,7 @@ pub struct Coords { pub longitude: f64, } +// ~111 metres const COORD_PRECISION: f64 = 0.001; impl Coords {