diff --git a/src/main.rs b/src/main.rs index 9258d73..e81a0dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ use clap::ValueEnum; use clap::{Parser, Subcommand}; -use anyhow::Result; -use easee::api; +use anyhow::{Context, Result}; +use easee::api::ChargingSession; use easee::stream; use tracing::info; -#[derive(Debug,Clone,ValueEnum)] +#[derive(Debug,Clone,Copy,ValueEnum)] enum Command { Start, Stop, @@ -14,93 +14,133 @@ enum Command { Resume, } +#[derive(Debug,Clone,Copy,ValueEnum)] +enum Session { + Ongoing, + Latest +} + #[derive(Debug,Clone,Subcommand)] enum Mode { - Status { id: Option }, - Ongoing, - Latest, + Login, + Status, + Session { + #[arg(default_value = "ongoing")] + session: Session + }, + Charge { command: Command }, Stream, - Command { id: String, command: Command } } #[derive(Debug,Parser)] struct CLI { - #[arg(short,long,env)] - username: String, - #[arg(short,long,env)] - password: String, + #[arg(short,long)] + debug: bool, + + #[arg(short,long)] + charger_id: Vec, + #[command(subcommand)] mode: Mode, } +const SAVED_TOKEN_PATH: &str = ".easee_token"; + fn main() -> Result<()> { - tracing::subscriber::set_global_default(tracing_subscriber::FmtSubscriber::new()) - .expect("Tracing subscriber failed"); - let args = CLI::parse(); - let mut ctx = api::Context::from_login(&args.username, &args.password)?; - - info!("Logged in"); - - let sites = ctx.sites()?; - let chargers = ctx.chargers()?; - info!("{} sites and {} chargers available", sites.len(), chargers.len()); - - match args.mode { - Mode::Status { id } => { - for c in &chargers { - if id.as_deref().map(|id| id == &c.id).unwrap_or(true) { - println!("{}: {:?}", c.id, c.state(&mut ctx)); - } - } - Ok(()) - }, - Mode::Ongoing => { - for c in &chargers { - println!("{}: {:?}", c.id, c.ongoing_session(&mut ctx)); - } - Ok(()) - }, - Mode::Latest => { - for c in &chargers { - println!("{:?}", c.latest_session(&mut ctx)); - } - Ok(()) - }, - Mode::Stream => { - let mut stream = stream::Stream::open(&mut ctx)?; - for c in &chargers { - stream.subscribe(&c.id)?; - } - - let mut stream = easee::signalr::Stream::from_ws(stream); - loop { - println!("{:?}", stream.recv()?); - } - }, - Mode::Command { id, command } => { - - for c in &chargers { - if c.id == id { - match command { - Command::Start => c.start(&mut ctx)?, - Command::Stop => c.stop(&mut ctx)?, - Command::Pause => c.pause(&mut ctx)?, - Command::Resume => c.resume(&mut ctx)?, - } - return Ok(()) - } - } - - eprintln!("Charger not found."); - Ok(()) - - }, - - + if args.debug { + tracing::subscriber::set_global_default(tracing_subscriber::FmtSubscriber::new()) + .expect("Tracing subscriber failed"); } + if let Mode::Login = args.mode { + use std::io::Write; + let stdin = std::io::stdin(); + let mut stderr = std::io::stderr(); + let mut username = String::new(); + let mut password = String::new(); + + write!(stderr, "Username: ")?; + stdin.read_line(&mut username)?; + write!(stderr, "Password: ")?; + stdin.read_line(&mut password)?; + + let username = username.trim(); + let password = password.trim(); + + let ctx = easee::api::Context::from_login(&username, &password)?; + eprintln!("Login successful."); + + std::fs::write(SAVED_TOKEN_PATH, ctx.save().as_bytes())?; + return Ok(()) + } + + let saved = std::fs::read_to_string(SAVED_TOKEN_PATH) + .context("Cannot read saved token (did you log in ?)")?; + let mut ctx = easee::api::Context::from_saved(&saved)?; + + let chargers; + if args.charger_id.is_empty() { + chargers = ctx.chargers()?; + info!("{} chargers available.", chargers.len()); + } else { + chargers = args.charger_id.iter() + .map(|id| ctx.charger(&id)) + .collect::>()?; + } + + if let Mode::Stream = args.mode { + let mut stream = stream::Stream::open(&mut ctx)?; + for c in &chargers { + stream.subscribe(&c.id)?; + } + + let mut stream = easee::signalr::Stream::from_ws(stream); + loop { + println!("{:?}", stream.recv()?); + } + } + + for c in &chargers { + match args.mode { + Mode::Status => { + println!("{}: {:?}", c.id, c.state(&mut ctx)); + }, + Mode::Session { session: Session::Ongoing }=> { + show_session(&c.ongoing_session(&mut ctx)?); + }, + Mode::Session { session: Session::Latest } => { + show_session(&c.latest_session(&mut ctx)?); + }, + Mode::Charge { command } => { + match command { + Command::Start => c.start(&mut ctx)?, + Command::Stop => c.stop(&mut ctx)?, + Command::Pause => c.pause(&mut ctx)?, + Command::Resume => c.resume(&mut ctx)?, + } + }, + _other => { + unreachable!("Stream was already ruled out above") + }, + } + } + Ok(()) } + +fn show_session(s: &Option) { + let Some(s) = s.as_ref() else { return }; + + let duration = std::time::Duration::from_secs( + s.charge_duration_in_seconds.unwrap_or(0) as u64 + ); + + println!("{}\t{}\t{}kWh", + s.charger_id.as_deref().unwrap_or(""), + humantime::format_duration(duration), + s.session_energy, + ) +} \ No newline at end of file