use chrono::DateTime; use serde::{Deserialize, Serialize}; use teslatte::vehicles::{CabinOverheatProtection, ChargingState, HvacAutoRequest, 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, pub vehicle_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 VehicleState { pub sentry_mode: Option, pub sentry_mode_available: Option, } impl From for VehicleState { fn from(value: teslatte::vehicles::VehicleState) -> Self { Self { sentry_mode: value.sentry_mode, sentry_mode_available: value.sentry_mode_available, } } } #[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, pub cabin_overheat_protection: CabinOverheatProtection, pub hvac_auto_request: HvacAutoRequest, } 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, cabin_overheat_protection: value.cabin_overheat_protection, hvac_auto_request: value.hvac_auto_request, }) } } #[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, pub charger_actual_current: Option, pub charger_phases: Option, pub charger_pilot_current: Option, pub charger_power: Option, pub charger_voltage: Option, } 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, charger_actual_current: value.charger_actual_current, charger_phases: value.charger_phases, charger_pilot_current: value.charger_pilot_current, charger_power: value.charger_power, charger_voltage: value.charger_voltage, } } } 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, PartialEq)] pub struct Coords { pub latitude: f64, pub longitude: f64, } // ~111 metres 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 } }