use chrono::DateTime; use serde::{Deserialize, Serialize}; use teslatte::vehicles::{ChargingState, ShiftState}; use crate::{config::access_config, errors::TeslaStateParseError}; #[derive(Default, Clone, Copy, Serialize, Deserialize, Debug)] pub struct CarState { pub charge_state: Option, pub location_data: Option, pub climate_state: Option, } 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, pub outside_temp: f64, pub battery_heater: bool, pub climate_on: bool, pub preconditioning: bool, pub remote_heater_control_enabled: bool, pub is_auto_conditioning_on: bool, pub driver_temp_setting: f64, pub passenger_temp_setting: f64, } impl TryFrom for ClimateState { type Error = TeslaStateParseError; fn try_from(value: teslatte::vehicles::ClimateState) -> Result { Ok(Self { inside_temp: value.inside_temp.ok_or(TeslaStateParseError::NoValue)?, outside_temp: value.outside_temp.ok_or(TeslaStateParseError::NoValue)?, battery_heater: value.battery_heater, climate_on: value.is_climate_on, preconditioning: value.is_preconditioning, remote_heater_control_enabled: value.remote_heater_control_enabled, is_auto_conditioning_on: value.is_auto_conditioning_on.unwrap_or(false), driver_temp_setting: value.driver_temp_setting, passenger_temp_setting: value.passenger_temp_setting, }) } } #[derive(Clone, Copy, Serialize, Deserialize, Debug)] pub struct ChargeState { pub battery_level: i64, pub battery_range: f64, pub charge_amps: i64, pub charge_rate: f64, pub charge_current_request: i64, pub charge_current_request_max: i64, pub charger_connected: bool, pub charge_enable_request: bool, pub charging_state: ChargingState, pub charge_limit_soc: i64, } impl ChargeState { #[allow(unused)] pub fn range_km(&self) -> f64 { self.battery_range * 1.60934 } } impl From for ChargeState { fn from(value: teslatte::vehicles::ChargeState) -> Self { ChargeState { battery_level: value.battery_level, battery_range: value.battery_range, charge_amps: value.charge_amps, charge_rate: value.charge_rate, charge_current_request: value.charge_current_request, charge_current_request_max: value.charge_current_request_max, charger_connected: value.conn_charge_cable != "", charge_enable_request: value.charge_enable_request, charging_state: value.charging_state, charge_limit_soc: value.charge_limit_soc, } } } pub struct FromDriveState(pub LocationData, pub DriveState); #[derive(Clone, Copy, Serialize, Deserialize, Debug)] pub struct LocationData { pub gps_as_of: DateTime, pub home: bool, } #[derive(Clone, Copy, Serialize, Deserialize, Debug)] pub struct DriveState { pub power: i64, pub speed: i64, pub shift_state: ShiftState, } impl TryFrom for FromDriveState { type Error = TeslaStateParseError; fn try_from(value: teslatte::vehicles::DriveState) -> Result { let gps_as_of = chrono::DateTime::from_timestamp( value.gps_as_of.ok_or(TeslaStateParseError::NoValue)?, 0, ) .ok_or(TeslaStateParseError::InvalidTimestamp)?; let coords = Coords { latitude: value.latitude.ok_or(TeslaStateParseError::NoValue)?, longitude: value.longitude.ok_or(TeslaStateParseError::NoValue)?, }; let home = coords.overlaps(&access_config().coords); let power = value.power; let speed = value.speed.unwrap_or(0); let shift_state = value.shift_state; Ok(Self( LocationData { gps_as_of, home }, DriveState { power, speed, shift_state, }, )) } } #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct Coords { pub latitude: f64, pub longitude: f64, } const COORD_PRECISION: f64 = 0.001; impl Coords { pub fn overlaps(&self, other: &Coords) -> bool { (self.latitude - other.latitude).abs() < COORD_PRECISION && (self.longitude - other.longitude).abs() < COORD_PRECISION } }