use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::{io::BufRead, path::PathBuf}; use teslatte::{ auth::{AccessToken, RefreshToken}, OwnerApi, VehicleApi, }; use crate::{config::Config, errors::*}; mod config; mod control; mod errors; #[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 { #[clap(long)] flash: bool, }, /// Authenticate with Tesla login Auth, /// Print the default config file GenerateConfig, } fn press_y_to_continue() -> bool { println!("Continue? [y/N]"); let mut line = String::new(); let stdin = std::io::stdin(); stdin.lock().read_line(&mut line).unwrap(); if line.to_uppercase() == "Y\n" { true } else { println!("Exiting now!"); false } } #[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::Auth => { if auth_path.exists() { println!("Auth file already exists"); if !press_y_to_continue() { return; } } if let Err(e) = log_in(auth_path).await { println!("{}", e.error_string()); } } Commands::Watch { flash } => match get_auth(auth_path).await { Ok(api) => { let products = api.products().await; println!("got products: {:#?}", products); if flash { if let Ok(res) = products { if let Some(teslatte::products::Product::Vehicle(vehicle)) = res.first() { let _ = api.honk_horn(&vehicle.id).await; match api.flash_lights(&vehicle.id).await { Ok(_r) => println!("flashed"), Err(e) => println!("error: {e:#?}"), } } } } control::control_loop(api).await; } Err(e) => println!("{}", e.error_string()), }, } } async fn get_auth(auth_path: PathBuf) -> Result { let key: AuthInfo = ron::from_str(&std::fs::read_to_string(&auth_path)?)?; let mut api = OwnerApi::new(key.access_token, key.refresh_token); api.refresh().await?; println!("Refreshed auth key"); save_key(auth_path, &api)?; Ok(api) } #[derive(Serialize, Deserialize)] struct AuthInfo { access_token: AccessToken, refresh_token: Option, } async fn log_in(auth_path: PathBuf) -> Result<(), LoginError> { let v = OwnerApi::from_interactive_url().await?; let products = v.products().await; println!("got products: {:#?}", products); save_key(auth_path, &v)?; Ok(()) } fn save_key(auth_path: PathBuf, api: &OwnerApi) -> 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(()) }