Explicit login, token save, ergonomics

This commit is contained in:
Maxime Augier 2024-08-08 14:19:21 +02:00
parent 14164037d3
commit d490970ab0

View File

@ -1,12 +1,12 @@
use clap::ValueEnum; use clap::ValueEnum;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use anyhow::Result; use anyhow::{Context, Result};
use easee::api; use easee::api::ChargingSession;
use easee::stream; use easee::stream;
use tracing::info; use tracing::info;
#[derive(Debug,Clone,ValueEnum)] #[derive(Debug,Clone,Copy,ValueEnum)]
enum Command { enum Command {
Start, Start,
Stop, Stop,
@ -14,93 +14,133 @@ enum Command {
Resume, Resume,
} }
#[derive(Debug,Clone,Copy,ValueEnum)]
enum Session {
Ongoing,
Latest
}
#[derive(Debug,Clone,Subcommand)] #[derive(Debug,Clone,Subcommand)]
enum Mode { enum Mode {
Status { id: Option<String> }, Login,
Ongoing, Status,
Latest, Session {
#[arg(default_value = "ongoing")]
session: Session
},
Charge { command: Command },
Stream, Stream,
Command { id: String, command: Command }
} }
#[derive(Debug,Parser)] #[derive(Debug,Parser)]
struct CLI { struct CLI {
#[arg(short,long,env)] #[arg(short,long)]
username: String, debug: bool,
#[arg(short,long,env)]
password: String, #[arg(short,long)]
charger_id: Vec<String>,
#[command(subcommand)] #[command(subcommand)]
mode: Mode, mode: Mode,
} }
const SAVED_TOKEN_PATH: &str = ".easee_token";
fn main() -> Result<()> { fn main() -> Result<()> {
tracing::subscriber::set_global_default(tracing_subscriber::FmtSubscriber::new())
.expect("Tracing subscriber failed");
let args = CLI::parse(); let args = CLI::parse();
let mut ctx = api::Context::from_login(&args.username, &args.password)?; if args.debug {
tracing::subscriber::set_global_default(tracing_subscriber::FmtSubscriber::new())
info!("Logged in"); .expect("Tracing subscriber failed");
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 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::<Result<_,_>>()?;
}
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<ChargingSession>) {
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("<none>"),
humantime::format_duration(duration),
s.session_energy,
)
}