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 std::env;
use teslatte::auth::{AccessToken, Authentication};
use teslatte::vehicle_state::SetChargeLimit;
use teslatte::vehicles::SetChargeLimit;
use teslatte::Api;
#[tokio::main]

View file

@ -1,7 +1,7 @@
use clap::{Args, Parser, Subcommand};
use serde::{Deserialize, Serialize};
use teslatte::auth::{AccessToken, Authentication, RefreshToken};
use teslatte::vehicle_state::SetChargeLimit;
use teslatte::vehicles::{SetChargeLimit, SetChargingAmps};
use teslatte::{Api, Id};
const TESLA_ACCESS_TOKEN: &str = "TESLA_ACCESS_TOKEN";
@ -55,14 +55,37 @@ enum ApiCommand {
/// Get a list of 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.
VehicleData { id: Id },
Data,
/// Get charge state.
ChargeState { id: Id },
ChargeState,
/// 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]
@ -89,33 +112,51 @@ async fn main() {
updated_tokens(save, response.access_token, refresh_token);
}
Command::Api(api_args) => {
let access_token = match api_args.access_token {
Some(a) => a,
let access_token = match &api_args.access_token {
Some(a) => a.clone(),
None => {
let config = Config::load();
config.access_token
config.access_token.clone()
}
};
let api = Api::new(&access_token);
#[allow(unused_results)]
match api_args.command {
ApiCommand::Vehicles => {
dbg!(api.vehicles().await.unwrap());
let vehicles = api.vehicles().await.unwrap();
dbg!(&vehicles);
}
ApiCommand::VehicleData { id } => {
dbg!(api.vehicle_data(&id).await.unwrap());
ApiCommand::Vehicle(v) => vehicles(&api, v).await,
}
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
.set_charge_limit(&id, &SetChargeLimit { percent })
.set_charge_limit(&vehicle.id, &SetChargeLimit { percent })
.await
.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 error;
pub mod vehicle_state;
pub mod vehicles;
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.
///
@ -216,11 +217,13 @@ macro_rules! get {
macro_rules! get_arg {
($name:ident, $return_type:ty, $url:expr, $arg_type:ty) => {
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
}
};
}
pub(crate) use get_arg;
/// POST /api/1/[url] with an argument and data
macro_rules! post_arg {
@ -230,122 +233,32 @@ macro_rules! post_arg {
arg: &$arg_type,
data: &$request_type,
) -> miette::Result<(), TeslatteError> {
let url_fmt = format!($url, arg);
let url = format!(concat!("/api/1", $url), arg);
let url = format!($url, arg);
let url = format!("/api/1{}", url);
self.post(&url, data).await
}
};
}
pub(crate) use post_arg;
// /// POST /api/1/vehicles/[id]/... without data
// macro_rules! post_v {
// ($name:ident, $url:expr) => {
// pub async fn $name(&self, id: &Id) -> miette::Result<(), TeslatteError> {
// let url = format!("/vehicles/{}{}", id.0, $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");
/// Post like above but with an empty body using the Empty struct.
macro_rules! post_arg_empty {
($name:ident, $url:expr, $arg_type:ty) => {
pub async fn $name(&self, arg: &$arg_type) -> miette::Result<(), TeslatteError> {
let url = format!($url, arg);
let url = format!("/api/1{}", url);
self.post(&url, &Empty {}).await
}
};
}
pub(crate) use post_arg_empty;
#[cfg(test)]
mod tests {
use super::*;
use crate::vehicle_state::ChargeState;
use crate::vehicles::ChargeState;
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]
fn error() {
let s = r#"{

View file

@ -1,10 +1,22 @@
use crate::error::TeslatteError;
/// 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
/// 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};
#[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)]
pub struct VehicleData {
pub id: Id,
@ -304,3 +316,70 @@ pub struct SetChargeLimit {
// pub percent: Percentage,
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();
}