diff --git a/.gitignore b/.gitignore index ea8c4bf..3cc8315 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/test-config diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d1e15db --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendored/teslatte"] + path = vendored/teslatte + url = https://github.com/gak/teslatte diff --git a/Cargo.lock b/Cargo.lock index 4139524..a4f038f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,7 +571,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls", + "rustls 0.21.10", "tokio", "tokio-rustls", ] @@ -1083,7 +1083,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.10", "rustls-pemfile", "serde", "serde_json", @@ -1163,10 +1163,24 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6b63262c9fcac8659abfaa96cac103d28166d3ff3eaf8f412e19f3ae9e5a48" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.0", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1176,6 +1190,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustls-pki-types" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1186,6 +1206,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2635c8bc2b88d367767c5de8ea1d8db9af3f6219eba28442242d9ab81d1b89" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -1389,6 +1420,12 @@ dependencies = [ "syn 2.0.43", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "supports-color" version = "2.1.0" @@ -1498,8 +1535,6 @@ dependencies = [ [[package]] name = "teslatte" version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf300415380acad835db51dc04d5f331dfab81aa24e425d7da39af5d17b0830" dependencies = [ "chrono", "clap", @@ -1509,7 +1544,7 @@ dependencies = [ "pkce", "rand", "reqwest", - "rustls", + "rustls 0.22.1", "serde", "serde_json", "strum", @@ -1652,7 +1687,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", "tokio", ] @@ -2100,3 +2135,9 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 3668b50..4eeeace 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,5 +28,5 @@ clap = { version = "4.0", features = ["derive"] } ron = "0.8" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.35.1", features = ["full"] } -teslatte = "0.1.11" +teslatte = { path = "vendored/teslatte" } thiserror = "1.0" diff --git a/src/control.rs b/src/control.rs new file mode 100644 index 0000000..1de702b --- /dev/null +++ b/src/control.rs @@ -0,0 +1,3 @@ +use teslatte::OwnerApi; + +pub async fn control_loop(_api: OwnerApi) {} diff --git a/src/errors.rs b/src/errors.rs index 0eb5d4e..223a3ec 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,18 +1,40 @@ use thiserror::Error; +#[derive(Error, Debug)] +pub enum SaveError { + #[error("stdio error")] + StdIo(#[from] std::io::Error), + #[error("ron")] + RonSpanned(#[from] ron::Error), +} + +impl SaveError { + pub fn error_string(&self) -> String { + match self { + SaveError::StdIo(e) => format!("Error reading access token from disk: {e:?}"), + SaveError::RonSpanned(e) => format!("Error deserialising access token: {e:?}"), + } + } +} #[derive(Error, Debug)] pub enum AuthLoadError { #[error("stdio error")] StdIo(#[from] std::io::Error), #[error("ron - spanned error")] RonSpanned(#[from] ron::error::SpannedError), + #[error("teslatte error")] + Teslatte(#[from] teslatte::error::TeslatteError), + #[error("save error")] + Save(#[from] SaveError), } impl AuthLoadError { pub fn error_string(&self) -> String { match self { + AuthLoadError::Teslatte(e) => format!("Error refreshing access token: {e:?}"), AuthLoadError::StdIo(e) => format!("Error reading access token from disk: {e:?}"), AuthLoadError::RonSpanned(e) => format!("Error deserialising access token: {e:?}"), + AuthLoadError::Save(e) => e.error_string(), } } } @@ -21,18 +43,15 @@ impl AuthLoadError { pub enum LoginError { #[error("teslatte error")] Teslatte(#[from] teslatte::error::TeslatteError), - #[error("ron error")] - Ron(#[from] ron::Error), - #[error("stdio error")] - StdIo(#[from] std::io::Error), + #[error("save error")] + Save(#[from] SaveError), } impl LoginError { pub fn error_string(&self) -> String { match self { LoginError::Teslatte(e) => format!("Authentication flow error: {e:?}"), - LoginError::Ron(e) => format!("Error serialising access token: {e:?}"), - LoginError::StdIo(e) => format!("Error saving access token to disk: {e:?}"), + LoginError::Save(e) => e.error_string(), } } } diff --git a/src/main.rs b/src/main.rs index d46ea04..15fbe31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,15 @@ use clap::{Parser, Subcommand}; +use serde::{Deserialize, Serialize}; use std::{io::BufRead, path::PathBuf}; -use teslatte::{auth::AccessToken, OwnerApi}; +use teslatte::{ + auth::{AccessToken, RefreshToken}, + OwnerApi, VehicleApi, +}; use crate::{config::Config, errors::*}; mod config; +mod control; mod errors; #[derive(Parser, Debug, Clone)] @@ -19,7 +24,10 @@ struct Args { #[derive(Subcommand, Debug, Clone)] enum Commands { /// Run charge controller server - Watch, + Watch { + #[clap(long)] + flash: bool, + }, /// Authenticate with Tesla login Auth, /// Print the default config file @@ -43,7 +51,7 @@ fn press_y_to_continue() -> bool { async fn main() { let args = Args::parse(); let auth_path = args.config_dir.join("auth"); - let config_path = args.config_dir.join("config"); + // let config_path = args.config_dir.join("config"); match args.command { Commands::GenerateConfig => { @@ -63,24 +71,61 @@ async fn main() { println!("{}", e.error_string()); } } - Commands::Watch => match get_auth(auth_path) { + Commands::Watch { flash } => match get_auth(auth_path).await { Ok(api) => { - println!("got products: {:#?}", api.products().await) + 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()), }, } } -fn get_auth(auth_path: PathBuf) -> Result { - let key: AccessToken = ron::from_str(&std::fs::read_to_string(auth_path)?)?; - Ok(OwnerApi::new(key, None)) +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?; - std::fs::write(auth_path, ron::ser::to_string(&v.access_token)?)?; + 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(()) } diff --git a/vendored/teslatte b/vendored/teslatte new file mode 160000 index 0000000..491d9a5 --- /dev/null +++ b/vendored/teslatte @@ -0,0 +1 @@ +Subproject commit 491d9a58a8183b91314990274da805c156a6f48c