wip: Refactor vehicle api. cli.

This commit is contained in:
gak 2022-07-21 18:00:41 +10:00
parent 9cca1ca5bd
commit 6214bf1b5f
4 changed files with 162 additions and 129 deletions

View file

@ -1,7 +1,7 @@
use clap::Parser; use clap::Parser;
use std::env; use std::env;
use teslatte::auth::{AccessToken, Authentication}; use teslatte::auth::{AccessToken, Authentication};
use teslatte::vehicle_state::SetChargeLimit; use teslatte::vehicles::SetChargeLimit;
use teslatte::Api; use teslatte::Api;
#[tokio::main] #[tokio::main]

View file

@ -1,7 +1,7 @@
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use teslatte::auth::{AccessToken, Authentication, RefreshToken}; use teslatte::auth::{AccessToken, Authentication, RefreshToken};
use teslatte::vehicle_state::SetChargeLimit; use teslatte::vehicles::{SetChargeLimit, SetChargingAmps};
use teslatte::{Api, Id}; use teslatte::{Api, Id};
const TESLA_ACCESS_TOKEN: &str = "TESLA_ACCESS_TOKEN"; const TESLA_ACCESS_TOKEN: &str = "TESLA_ACCESS_TOKEN";
@ -55,14 +55,37 @@ enum ApiCommand {
/// Get a list of vehicles. /// Get a list of vehicles.
Vehicles, Vehicles,
/// Specific Vehicle.
Vehicle(Vehicle),
}
#[derive(Debug, Args)]
struct Vehicle {
pub id: Id,
#[clap(subcommand)]
pub command: VehicleCommand,
}
#[derive(Debug, Subcommand)]
enum VehicleCommand {
/// Get vehicle data. /// Get vehicle data.
VehicleData { id: Id }, Data,
/// Get charge state. /// Get charge state.
ChargeState { id: Id }, ChargeState,
/// Set charge limit. /// Set charge limit.
SetChargeLimit { id: Id, percent: u8 }, SetChargeLimit { percent: u8 },
/// Set charge amps.
SetChargingAmps { charging_amps: i64 },
/// Start charging.
ChargeStart,
/// Stop charging.
ChargeStop,
} }
#[tokio::main] #[tokio::main]
@ -89,33 +112,51 @@ async fn main() {
updated_tokens(save, response.access_token, refresh_token); updated_tokens(save, response.access_token, refresh_token);
} }
Command::Api(api_args) => { Command::Api(api_args) => {
let access_token = match api_args.access_token { let access_token = match &api_args.access_token {
Some(a) => a, Some(a) => a.clone(),
None => { None => {
let config = Config::load(); let config = Config::load();
config.access_token config.access_token.clone()
} }
}; };
let api = Api::new(&access_token); let api = Api::new(&access_token);
#[allow(unused_results)]
match api_args.command { match api_args.command {
ApiCommand::Vehicles => { ApiCommand::Vehicles => {
dbg!(api.vehicles().await.unwrap()); let vehicles = api.vehicles().await.unwrap();
dbg!(&vehicles);
} }
ApiCommand::VehicleData { id } => { ApiCommand::Vehicle(v) => vehicles(&api, v).await,
dbg!(api.vehicle_data(&id).await.unwrap());
} }
ApiCommand::ChargeState { id } => {
dbg!(api.charge_state(&id).await.unwrap());
} }
ApiCommand::SetChargeLimit { id, percent } => { }
}
async fn vehicles(api: &Api, vehicle: Vehicle) {
match vehicle.command {
VehicleCommand::Data => {
dbg!(api.vehicle_data(&vehicle.id).await.unwrap());
}
VehicleCommand::ChargeState => {
dbg!(api.charge_state(&vehicle.id).await.unwrap());
}
VehicleCommand::SetChargeLimit { percent } => {
dbg!(api dbg!(api
.set_charge_limit(&id, &SetChargeLimit { percent }) .set_charge_limit(&vehicle.id, &SetChargeLimit { percent })
.await .await
.unwrap()); .unwrap());
} }
VehicleCommand::SetChargingAmps { charging_amps } => {
dbg!(api
.set_charging_amps(&vehicle.id, &SetChargingAmps { charging_amps })
.await
.unwrap());
} }
VehicleCommand::ChargeStart => {
dbg!(api.charge_start(&vehicle.id).await.unwrap());
}
VehicleCommand::ChargeStop => {
dbg!(api.charge_stop(&vehicle.id).await.unwrap());
} }
} }
} }

View file

@ -9,7 +9,7 @@ use tracing::{debug, instrument, trace};
pub mod auth; pub mod auth;
pub mod error; pub mod error;
pub mod vehicle_state; pub mod vehicles;
const API_URL: &str = "https://owner-api.teslamotors.com"; const API_URL: &str = "https://owner-api.teslamotors.com";
@ -209,6 +209,7 @@ macro_rules! get {
} }
}; };
} }
pub(crate) use get;
/// GET /api/1/[url] with an argument. /// GET /api/1/[url] with an argument.
/// ///
@ -216,11 +217,13 @@ macro_rules! get {
macro_rules! get_arg { macro_rules! get_arg {
($name:ident, $return_type:ty, $url:expr, $arg_type:ty) => { ($name:ident, $return_type:ty, $url:expr, $arg_type:ty) => {
pub async fn $name(&self, arg: &$arg_type) -> miette::Result<$return_type, TeslatteError> { pub async fn $name(&self, arg: &$arg_type) -> miette::Result<$return_type, TeslatteError> {
let url = format!(concat!("/api/1", $url), arg); let url = format!($url, arg);
let url = format!("/api/1{}", url);
self.get(&url).await self.get(&url).await
} }
}; };
} }
pub(crate) use get_arg;
/// POST /api/1/[url] with an argument and data /// POST /api/1/[url] with an argument and data
macro_rules! post_arg { macro_rules! post_arg {
@ -230,122 +233,32 @@ macro_rules! post_arg {
arg: &$arg_type, arg: &$arg_type,
data: &$request_type, data: &$request_type,
) -> miette::Result<(), TeslatteError> { ) -> miette::Result<(), TeslatteError> {
let url_fmt = format!($url, arg); let url = format!($url, arg);
let url = format!(concat!("/api/1", $url), arg); let url = format!("/api/1{}", url);
self.post(&url, data).await self.post(&url, data).await
} }
}; };
} }
pub(crate) use post_arg;
// /// POST /api/1/vehicles/[id]/... without data /// Post like above but with an empty body using the Empty struct.
// macro_rules! post_v { macro_rules! post_arg_empty {
// ($name:ident, $url:expr) => { ($name:ident, $url:expr, $arg_type:ty) => {
// pub async fn $name(&self, id: &Id) -> miette::Result<(), TeslatteError> { pub async fn $name(&self, arg: &$arg_type) -> miette::Result<(), TeslatteError> {
// let url = format!("/vehicles/{}{}", id.0, $url); let url = format!($url, arg);
// self.post(&url, &Empty {}).await let url = format!("/api/1{}", url);
// } self.post(&url, &Empty {}).await
// };
// }
//
// /// POST /api/1/vehicles/[id]/... with data
// macro_rules! post_vd {
// ($name:ident, $struct:ty, $url:expr) => {
// pub async fn $name(&self, id: &Id, data: &$struct) -> miette::Result<(), TeslatteError> {
// let url = format!("/api/1/vehicles/{}{}", id.0, $url);
// self.post(&url, &data).await
// }
// };
// }
use crate::vehicle_state::ChargeState;
use crate::vehicle_state::SetChargeLimit;
use crate::vehicle_state::Vehicle;
use crate::vehicle_state::VehicleData;
#[rustfmt::skip]
impl Api {
get!(vehicles, Vec<Vehicle>, "/vehicles");
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", Id);
get_arg!(charge_state, ChargeState, "/vehicles/{}/data_request/charge_state", Id);
post_arg!(set_charge_limit, SetChargeLimit, "/vehicles/{}/command/set_charge_limit", Id);
// post_vd!(set_charging_amps, SetChargingAmps, "/command/set_charging_amps");
// post_v!(charge_start, "/command/charge_start");
// post_v!(charge_stop, "/command/charge_stop");
} }
};
}
pub(crate) use post_arg_empty;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::vehicle_state::ChargeState; use crate::vehicles::ChargeState;
use test_log::test; use test_log::test;
#[test]
fn json() {
let s = r#"
{
"response": {
"battery_heater_on": false,
"battery_level": 50,
"battery_range": 176.08,
"charge_amps": 5,
"charge_current_request": 5,
"charge_current_request_max": 16,
"charge_enable_request": true,
"charge_energy_added": 1.05,
"charge_limit_soc": 75,
"charge_limit_soc_max": 100,
"charge_limit_soc_min": 50,
"charge_limit_soc_std": 90,
"charge_miles_added_ideal": 5,
"charge_miles_added_rated": 5,
"charge_port_cold_weather_mode": false,
"charge_port_color": "<invalid>",
"charge_port_door_open": true,
"charge_port_latch": "Engaged",
"charge_rate": 14.8,
"charge_to_max_range": false,
"charger_actual_current": 5,
"charger_phases": 2,
"charger_pilot_current": 16,
"charger_power": 4,
"charger_voltage": 241,
"charging_state": "Charging",
"conn_charge_cable": "IEC",
"est_battery_range": 163.81,
"fast_charger_brand": "<invalid>",
"fast_charger_present": false,
"fast_charger_type": "ACSingleWireCAN",
"ideal_battery_range": 176.08,
"managed_charging_active": false,
"managed_charging_start_time": null,
"managed_charging_user_canceled": false,
"max_range_charge_counter": 0,
"minutes_to_full_charge": 350,
"not_enough_power_to_heat": null,
"off_peak_charging_enabled": false,
"off_peak_charging_times": "all_week",
"off_peak_hours_end_time": 1140,
"preconditioning_enabled": false,
"preconditioning_times": "all_week",
"scheduled_charging_mode": "StartAt",
"scheduled_charging_pending": false,
"scheduled_charging_start_time": 1647045000,
"scheduled_charging_start_time_app": 690,
"scheduled_charging_start_time_minutes": 690,
"scheduled_departure_time": 1641337200,
"scheduled_departure_time_minutes": 600,
"supercharger_session_trip_planner": false,
"time_to_full_charge": 5.83,
"timestamp": 1646978638155,
"trip_charging": false,
"usable_battery_level": 50,
"user_charge_enable_request": null
}
}
"#;
Api::parse_json::<ChargeState, _>(s, || "req".to_string()).unwrap();
}
#[test] #[test]
fn error() { fn error() {
let s = r#"{ let s = r#"{

View file

@ -1,10 +1,22 @@
use crate::error::TeslatteError;
/// Please note that these structs are generated from my own responses. /// Please note that these structs are generated from my own responses.
/// ///
/// Sometimes the API will return a null for a field where I've put in a non Option type, which /// Sometimes the API will return a null for a field where I've put in a non Option type, which
/// will cause the deserializer to fail. Please log an issue to fix these if you come across it. /// will cause the deserializer to fail. Please log an issue to fix these if you come across it.
use crate::{Id, VehicleId}; use crate::{get, get_arg, post_arg, post_arg_empty, Api, Empty, Id, VehicleId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[rustfmt::skip]
impl Api {
get!(vehicles, Vec<Vehicle>, "/vehicles");
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", Id);
get_arg!(charge_state, ChargeState, "/vehicles/{}/data_request/charge_state", Id);
post_arg!(set_charge_limit, SetChargeLimit, "/vehicles/{}/command/set_charge_limit", Id);
post_arg!(set_charging_amps, SetChargingAmps, "/vehicles/{}/command/set_charging_amps", Id);
post_arg_empty!(charge_start, "/vehicles/{}/command/charge_start", Id);
post_arg_empty!(charge_stop, "/vehicles/{}/command/charge_stop", Id);
}
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct VehicleData { pub struct VehicleData {
pub id: Id, pub id: Id,
@ -304,3 +316,70 @@ pub struct SetChargeLimit {
// pub percent: Percentage, // pub percent: Percentage,
pub percent: u8, pub percent: u8,
} }
#[test]
fn json() {
let s = r#"
{
"response": {
"battery_heater_on": false,
"battery_level": 50,
"battery_range": 176.08,
"charge_amps": 5,
"charge_current_request": 5,
"charge_current_request_max": 16,
"charge_enable_request": true,
"charge_energy_added": 1.05,
"charge_limit_soc": 75,
"charge_limit_soc_max": 100,
"charge_limit_soc_min": 50,
"charge_limit_soc_std": 90,
"charge_miles_added_ideal": 5,
"charge_miles_added_rated": 5,
"charge_port_cold_weather_mode": false,
"charge_port_color": "<invalid>",
"charge_port_door_open": true,
"charge_port_latch": "Engaged",
"charge_rate": 14.8,
"charge_to_max_range": false,
"charger_actual_current": 5,
"charger_phases": 2,
"charger_pilot_current": 16,
"charger_power": 4,
"charger_voltage": 241,
"charging_state": "Charging",
"conn_charge_cable": "IEC",
"est_battery_range": 163.81,
"fast_charger_brand": "<invalid>",
"fast_charger_present": false,
"fast_charger_type": "ACSingleWireCAN",
"ideal_battery_range": 176.08,
"managed_charging_active": false,
"managed_charging_start_time": null,
"managed_charging_user_canceled": false,
"max_range_charge_counter": 0,
"minutes_to_full_charge": 350,
"not_enough_power_to_heat": null,
"off_peak_charging_enabled": false,
"off_peak_charging_times": "all_week",
"off_peak_hours_end_time": 1140,
"preconditioning_enabled": false,
"preconditioning_times": "all_week",
"scheduled_charging_mode": "StartAt",
"scheduled_charging_pending": false,
"scheduled_charging_start_time": 1647045000,
"scheduled_charging_start_time_app": 690,
"scheduled_charging_start_time_minutes": 690,
"scheduled_departure_time": 1641337200,
"scheduled_departure_time_minutes": 600,
"supercharger_session_trip_planner": false,
"time_to_full_charge": 5.83,
"timestamp": 1646978638155,
"trip_charging": false,
"usable_battery_level": 50,
"user_charge_enable_request": null
}
}
"#;
Api::parse_json::<ChargeState, _>(s, || "req".to_string()).unwrap();
}