use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, sync::{Arc, RwLock}, time::{Duration, Instant}, }; use teslatte::{ auth::{AccessToken, RefreshToken}, vehicles::{Endpoint, GetVehicleData}, FleetApi, FleetVehicleApi, VehicleId, }; use crate::{errors::*, types::CarState}; pub struct TeslaInterface { pub state: Arc>, api: FleetApi, vehicle: Box, last_refresh: Instant, auth_path: PathBuf, } #[derive(Serialize, Deserialize, Clone)] struct AuthInfo { access_token: AccessToken, refresh_token: Option, } #[derive(Clone, Copy, Debug)] pub enum InterfaceRequest { FlashLights, } const REFRESH_INTERVAL: Duration = Duration::from_secs(12 * 60 * 60); impl TeslaInterface { pub async fn load(auth_path: PathBuf) -> Result { let key: AuthInfo = ron::from_str(&std::fs::read_to_string(&auth_path)?)?; let mut api = FleetApi::new(key.access_token, key.refresh_token); api.refresh().await?; let last_refresh = Instant::now(); println!("Refreshed auth key"); let vehicle = api .products() .await? .into_iter() .filter_map(|v| match v { teslatte::products::Product::Vehicle(vehicle) => Some(vehicle), _ => None, }) .next() .context("No vehicles attached to account!")?; let interface = Self { state: Arc::new(RwLock::new(Default::default())), api, last_refresh, auth_path, vehicle, }; interface.save_key()?; Ok(interface) } fn save_key(&self) -> Result<(), SaveError> { std::fs::write( self.auth_path.clone(), ron::ser::to_string(&AuthInfo { access_token: self.api.access_token.clone(), refresh_token: self.api.refresh_token.clone(), })?, )?; println!("Auth successfully saved"); Ok(()) } pub async fn refresh(&mut self) { println!("refreshing..."); self.refresh_keys().await; self.refresh_state().await; } pub async fn process_request(&mut self, request: InterfaceRequest) { match request { InterfaceRequest::FlashLights => { let _ = self.api.flash_lights(&self.vehicle.vin).await; } } } async fn refresh_state(&mut self) { match get_state(&self.api, self.vehicle.id.clone()).await { Ok(new_state) => { self.last_refresh = Instant::now(); let mut state = self.state.write().expect("State handler panicked!!"); if let Some(new_charge_state) = new_state.charge_state { state.charge_state = Some(new_charge_state); } if let Some(new_location_data) = new_state.location_data { state.location_data = Some(new_location_data); } } Err(e) => eprintln!("Error getting charge state: {e:#?}"), } } #[allow(unused)] async fn refresh_keys(&mut self) { if Instant::now().duration_since(self.last_refresh) >= REFRESH_INTERVAL { match self.api.refresh().await { Ok(_) => { let now = Instant::now(); match self.save_key() { Ok(_) => self.last_refresh = now, Err(e) => eprintln!("error saving auth token: {e:?}"), } } Err(e) => eprintln!("error refreshing auth token: {e:?}"), } } } } async fn get_state(api: &FleetApi, vehicle_id: VehicleId) -> Result { let vehicle_data = api .vehicle_data(&GetVehicleData { vehicle_id: vehicle_id.clone(), endpoints: vec![Endpoint::ChargeState].into(), // endpoints: vec![Endpoint::VehicleDataCombo].into(), }) .await?; let charge_state = vehicle_data.charge_state.map(|v| v.into()); let vehicle_data = api .vehicle_data(&GetVehicleData { vehicle_id, endpoints: vec![Endpoint::LocationData].into(), }) .await?; let location_data = vehicle_data.drive_state.and_then(|v| v.try_into().ok()); Ok(CarState { charge_state, location_data, }) }