#![feature(async_closure)] #[macro_use] extern crate rocket; use anyhow::Result; use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, time::{Duration, Instant}, }; use teslatte::{ auth::{AccessToken, RefreshToken}, FleetApi, }; use crate::{config::Config, errors::*}; mod config; mod errors; mod server; #[derive(Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] struct Args { #[command(subcommand)] command: Commands, #[clap(long, default_value = "/etc/tesla-charge-controller")] config_dir: PathBuf, } #[derive(Subcommand, Debug, Clone)] enum Commands { /// Run charge controller server Watch, /// Print the default config file GenerateConfig, } #[tokio::main] async fn main() { let args = Args::parse(); let auth_path = args.config_dir.join("auth"); let config_path = args.config_dir.join("config"); match args.command { Commands::GenerateConfig => { println!( "{}", ron::ser::to_string_pretty(&Config::default(), Default::default()).unwrap() ); } Commands::Watch => match get_auth(auth_path).await { Ok(auth) => { let config: Config = ron::from_str(&std::fs::read_to_string(&config_path).unwrap()).unwrap(); let products = auth.api().products().await; match products { Ok(res) => match res.first() { Some(teslatte::products::Product::Vehicle(vehicle)) => { server::launch_server(server::ServerState { config, auth, vehicle: vehicle.clone(), }) .await; } _ => println!("No first item"), }, Err(e) => println!("Error getting products: {e:#?}"), } } Err(e) => println!("{}", e.error_string()), }, } } #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct Coords { pub latitude: f64, pub longitude: f64, } const COORD_PRECISION: f64 = 0.001; impl Coords { fn overlaps(&self, other: &Coords) -> bool { (self.latitude - other.latitude).abs() < COORD_PRECISION && (self.longitude - other.longitude).abs() < COORD_PRECISION } } async fn get_auth(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?; println!("Refreshed auth key"); save_key(&auth_path, &api)?; // api.print_responses = teslatte::PrintResponses::Pretty; Ok(FleetApiAuth::new(api, Instant::now(), auth_path)) } struct FleetApiAuth { api: FleetApi, last_refresh: Instant, auth_path: PathBuf, } const REFRESH_INTERVAL: Duration = Duration::from_secs(12 * 60 * 60); impl FleetApiAuth { fn new(api: FleetApi, last_refresh: Instant, auth_path: PathBuf) -> Self { Self { api, last_refresh, auth_path, } } fn api(&self) -> &FleetApi { &self.api } #[allow(unused)] async fn refresh(&mut self) { if Instant::now().duration_since(self.last_refresh) >= REFRESH_INTERVAL { match self.api.refresh().await { Ok(_) => { let now = Instant::now(); match save_key(&self.auth_path, &self.api) { Ok(_) => self.last_refresh = now, Err(e) => eprintln!("error saving auth token: {e:?}"), } } Err(e) => eprintln!("error refreshing auth token: {e:?}"), } } } } #[derive(Serialize, Deserialize, Clone)] struct AuthInfo { access_token: AccessToken, refresh_token: Option, } fn save_key(auth_path: &PathBuf, api: &FleetApi) -> Result<(), SaveError> { std::fs::write( auth_path, ron::ser::to_string(&AuthInfo { access_token: api.access_token.clone(), refresh_token: api.refresh_token.clone(), })?, )?; println!("Auth successfully saved"); Ok(()) }