2024-01-08 12:00:09 +11:00
|
|
|
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<RwLock<CarState>>,
|
|
|
|
api: FleetApi,
|
|
|
|
vehicle: Box<teslatte::vehicles::VehicleData>,
|
|
|
|
last_refresh: Instant,
|
|
|
|
auth_path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
|
|
|
struct AuthInfo {
|
|
|
|
access_token: AccessToken,
|
|
|
|
refresh_token: Option<RefreshToken>,
|
|
|
|
}
|
|
|
|
|
2024-01-08 12:16:25 +11:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub enum InterfaceRequest {
|
|
|
|
FlashLights,
|
|
|
|
}
|
|
|
|
|
2024-01-08 12:00:09 +11:00
|
|
|
const REFRESH_INTERVAL: Duration = Duration::from_secs(12 * 60 * 60);
|
|
|
|
|
|
|
|
impl TeslaInterface {
|
|
|
|
pub async fn load(auth_path: PathBuf) -> Result<Self, AuthLoadError> {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-01-08 12:16:25 +11:00
|
|
|
pub async fn process_request(&mut self, request: InterfaceRequest) {
|
|
|
|
match request {
|
|
|
|
InterfaceRequest::FlashLights => {
|
|
|
|
let _ = self.api.flash_lights(&self.vehicle.vin).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-08 12:00:09 +11:00
|
|
|
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<CarState> {
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|