wip: Refactor vehicle api. cli.
This commit is contained in:
parent
9cca1ca5bd
commit
6214bf1b5f
|
@ -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]
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
125
src/lib.rs
125
src/lib.rs
|
@ -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#"{
|
||||
|
|
|
@ -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();
|
||||
}
|
Loading…
Reference in a new issue