wip: Add cli example. Change api macros.

This commit is contained in:
gak 2022-07-21 17:08:49 +10:00
parent 3e62f354e8
commit 9cca1ca5bd
7 changed files with 578 additions and 135 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target /target
/Cargo.lock /Cargo.lock
cli.json

View file

@ -26,3 +26,5 @@ regex = "1.5"
[dev-dependencies] [dev-dependencies]
test-log = { version = "0.2", default-features = false, features = ["trace"] } test-log = { version = "0.2", default-features = false, features = ["trace"] }
tracing-subscriber = "0.3"
clap = { version = "3.2", features = ["derive", "env"]}

View file

@ -1,20 +1,36 @@
use teslatte::auth::Authentication; use clap::Parser;
use std::env;
use teslatte::auth::{AccessToken, Authentication};
use teslatte::vehicle_state::SetChargeLimit;
use teslatte::Api; use teslatte::Api;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let api = Authentication::new().unwrap(); let access_token = match env::var("TESLA_ACCESS_TOKEN") {
let (access_token, refresh_token) = api.interactive_get_access_token().await.unwrap(); Ok(t) => AccessToken(t),
println!("Access token: {}", access_token.0); Err(_) => {
println!("Refresh token: {}", refresh_token.0); let auth = Authentication::new().unwrap();
let (access_token, refresh_token) = auth.interactive_get_access_token().await.unwrap();
println!("Access token: {}", access_token.0);
println!("Refresh token: {}", refresh_token.0);
access_token
}
};
let api = Api::new(&access_token); let api = Api::new(&access_token);
let vehicles = api.vehicles().await.unwrap(); let vehicles = api.vehicles().await.unwrap();
dbg!(&vehicles); dbg!(&vehicles);
let charge_state = api.charge_state(&vehicles[0].id).await.unwrap(); if vehicles.len() > 0 {
dbg!(&charge_state); let vehicle_data = api.vehicle_data(&vehicles[0].id).await.unwrap();
dbg!(vehicle_data);
let charge_state = api.charge_state(&vehicles[0].id).await.unwrap();
dbg!(&charge_state);
} else {
println!("No vehicles found!");
}
} }

154
examples/cli.rs Normal file
View file

@ -0,0 +1,154 @@
use clap::{Args, Parser, Subcommand};
use serde::{Deserialize, Serialize};
use teslatte::auth::{AccessToken, Authentication, RefreshToken};
use teslatte::vehicle_state::SetChargeLimit;
use teslatte::{Api, Id};
const TESLA_ACCESS_TOKEN: &str = "TESLA_ACCESS_TOKEN";
const TESLA_REFRESH_TOKEN: &str = "TESLA_REFRESH_TOKEN";
/// Teslatte
///
/// A command line interface for the Tesla API.
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Cli {
#[clap(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Authenticate with Tesla via URL, and receive an access token and refresh token.
Auth {
/// Save tokens to a cli.json file.
///
/// Be careful with your access tokens!
#[clap(short, long)]
save: bool,
},
/// Refresh your tokens.
Refresh {
/// If not provided, will try to read the token from a cli.json file and automatically
/// update the file.
#[clap(short, long, env = "TESLA_REFRESH_TOKEN")]
refresh_token: Option<RefreshToken>,
},
/// Run API commands.
Api(ApiArgs),
}
#[derive(Debug, Args)]
struct ApiArgs {
/// Access token. If not provided, will try to load from the cli.json file.
#[clap(short, long, env = "TESLA_ACCESS_TOKEN")]
access_token: Option<AccessToken>,
#[clap(subcommand)]
command: ApiCommand,
}
#[derive(Debug, Subcommand)]
enum ApiCommand {
/// Get a list of vehicles.
Vehicles,
/// Get vehicle data.
VehicleData { id: Id },
/// Get charge state.
ChargeState { id: Id },
/// Set charge limit.
SetChargeLimit { id: Id, percent: u8 },
}
#[tokio::main]
async fn main() {
let args = Cli::parse();
match args.command {
Command::Auth { save } => {
let auth = Authentication::new().unwrap();
let (access_token, refresh_token) = auth.interactive_get_access_token().await.unwrap();
updated_tokens(save, access_token, refresh_token);
}
Command::Refresh { refresh_token } => {
let (save, refresh_token) = match refresh_token {
Some(refresh_token) => (false, refresh_token),
None => {
let config = Config::load();
(true, config.refresh_token)
}
};
let auth = Authentication::new().unwrap();
let response = auth.refresh_access_token(&refresh_token).await.unwrap();
updated_tokens(save, response.access_token, refresh_token);
}
Command::Api(api_args) => {
let access_token = match api_args.access_token {
Some(a) => a,
None => {
let config = Config::load();
config.access_token
}
};
let api = Api::new(&access_token);
#[allow(unused_results)]
match api_args.command {
ApiCommand::Vehicles => {
dbg!(api.vehicles().await.unwrap());
}
ApiCommand::VehicleData { id } => {
dbg!(api.vehicle_data(&id).await.unwrap());
}
ApiCommand::ChargeState { id } => {
dbg!(api.charge_state(&id).await.unwrap());
}
ApiCommand::SetChargeLimit { id, percent } => {
dbg!(api
.set_charge_limit(&id, &SetChargeLimit { percent })
.await
.unwrap());
}
}
}
}
}
fn updated_tokens(save: bool, access_token: AccessToken, refresh_token: RefreshToken) {
println!("Access token: {}", access_token.0);
println!("Refresh token: {}", refresh_token.0);
if save {
Config {
access_token,
refresh_token,
}
.save();
}
}
#[derive(Serialize, Deserialize)]
struct Config {
access_token: AccessToken,
refresh_token: RefreshToken,
}
impl Config {
fn save(&self) {
let json = serde_json::to_string(&self).unwrap();
std::fs::write("cli.json", json).unwrap();
}
fn load() -> Self {
let file = std::fs::File::open("cli.json").unwrap();
let reader = std::io::BufReader::new(file);
let json: serde_json::Value = serde_json::from_reader(reader).unwrap();
let config: Config = serde_json::from_str(&json.to_string()).unwrap();
config
}
}

View file

@ -5,6 +5,7 @@ use reqwest::Client;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::{stdin, stdout, Write}; use std::io::{stdin, stdout, Write};
use std::str::FromStr;
use url::Url; use url::Url;
const AUTHORIZE_URL: &str = "https://auth.tesla.com/oauth2/v3/authorize"; const AUTHORIZE_URL: &str = "https://auth.tesla.com/oauth2/v3/authorize";
@ -17,9 +18,23 @@ pub struct Authentication {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessToken(pub String); pub struct AccessToken(pub String);
impl FromStr for AccessToken {
type Err = TeslatteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(AccessToken(s.to_string()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefreshToken(pub String); pub struct RefreshToken(pub String);
impl FromStr for RefreshToken {
type Err = TeslatteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(RefreshToken(s.to_string()))
}
}
impl Authentication { impl Authentication {
pub fn new() -> Result<Self, TeslatteError> { pub fn new() -> Result<Self, TeslatteError> {
let client = Client::builder() let client = Client::builder()

View file

@ -1,12 +1,15 @@
use crate::auth::AccessToken; use crate::auth::AccessToken;
use crate::error::TeslatteError; use crate::error::TeslatteError;
use miette::IntoDiagnostic;
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use tracing::{debug, instrument, trace}; use tracing::{debug, instrument, trace};
pub mod auth; pub mod auth;
pub mod error; pub mod error;
pub mod vehicle_state;
const API_URL: &str = "https://owner-api.teslamotors.com"; const API_URL: &str = "https://owner-api.teslamotors.com";
@ -16,6 +19,19 @@ const API_URL: &str = "https://owner-api.teslamotors.com";
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Id(u64); pub struct Id(u64);
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Id {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Id(s.parse().into_diagnostic()?))
}
}
/// Vehicle ID used by other endpoints. /// Vehicle ID used by other endpoints.
/// ///
/// This data comes from [`Api::vehicles()`] `vehicle_id` field. /// This data comes from [`Api::vehicles()`] `vehicle_id` field.
@ -44,10 +60,9 @@ impl Api {
// I don't understand but it works: https://stackoverflow.com/a/60131725/11125 // I don't understand but it works: https://stackoverflow.com/a/60131725/11125
D: for<'de> Deserialize<'de> + Debug, D: for<'de> Deserialize<'de> + Debug,
{ {
trace!("Fetching");
let url = format!("{}{}", API_URL, path); let url = format!("{}{}", API_URL, path);
let request = || format!("GET {url}"); let request = || format!("GET {url}");
debug!("Fetching"); trace!(?url, "Fetching");
let response = self let response = self
.client .client
.get(&url) .get(&url)
@ -69,7 +84,7 @@ impl Api {
})?; })?;
trace!(?body); trace!(?body);
let json = Self::json::<D, _>(&body, request)?; let json = Self::parse_json::<D, _>(&body, request)?;
trace!(?json); trace!(?json);
Ok(json) Ok(json)
@ -106,7 +121,7 @@ impl Api {
source, source,
request: request(), request: request(),
})?; })?;
let json = Self::json::<PostResponse, _>(&body, request)?; let json = Self::parse_json::<PostResponse, _>(&body, request)?;
trace!(?json); trace!(?json);
if json.result { if json.result {
@ -121,7 +136,7 @@ impl Api {
} }
// The `request` argument is for additional context in the error. // The `request` argument is for additional context in the error.
fn json<T, F>(body: &str, request: F) -> Result<T, TeslatteError> fn parse_json<T, F>(body: &str, request: F) -> Result<T, TeslatteError>
where where
T: for<'de> Deserialize<'de> + Debug, T: for<'de> Deserialize<'de> + Debug,
F: FnOnce() -> String + Copy, F: FnOnce() -> String + Copy,
@ -182,152 +197,86 @@ struct ResponseError {
error_description: Option<String>, error_description: Option<String>,
} }
#[derive(Debug, Deserialize)]
pub struct ChargeState {
pub battery_heater_on: bool,
pub battery_level: i64,
pub battery_range: f64,
pub charge_amps: i64,
pub charge_current_request: i64,
pub charge_current_request_max: i64,
pub charge_enable_request: bool,
pub charge_energy_added: f64,
pub charge_limit_soc: i64,
pub charge_limit_soc_max: i64,
pub charge_limit_soc_min: i64,
pub charge_limit_soc_std: i64,
pub charge_miles_added_ideal: f64,
pub charge_miles_added_rated: f64,
pub charge_port_cold_weather_mode: bool,
pub charge_port_color: String,
pub charge_port_door_open: bool,
pub charge_port_latch: String,
pub charge_rate: f64,
pub charge_to_max_range: bool,
pub charger_actual_current: i64,
pub charger_phases: Option<i64>,
pub charger_pilot_current: i64,
pub charger_power: i64,
pub charger_voltage: i64,
pub charging_state: String,
pub conn_charge_cable: String,
pub est_battery_range: f64,
pub fast_charger_brand: String,
pub fast_charger_present: bool,
pub fast_charger_type: String,
pub ideal_battery_range: f64,
pub managed_charging_active: bool,
pub managed_charging_start_time: Option<u64>,
pub managed_charging_user_canceled: bool,
pub max_range_charge_counter: i64,
pub minutes_to_full_charge: i64,
pub not_enough_power_to_heat: Option<bool>,
pub off_peak_charging_enabled: bool,
pub off_peak_charging_times: String,
pub off_peak_hours_end_time: i64,
pub preconditioning_enabled: bool,
pub preconditioning_times: String,
pub scheduled_charging_mode: String,
pub scheduled_charging_pending: bool,
pub scheduled_charging_start_time: Option<i64>,
pub scheduled_charging_start_time_app: Option<i64>,
pub scheduled_charging_start_time_minutes: Option<i64>,
pub scheduled_departure_time: i64,
pub scheduled_departure_time_minutes: i64,
pub supercharger_session_trip_planner: bool,
pub time_to_full_charge: f64,
pub timestamp: u64,
pub trip_charging: bool,
pub usable_battery_level: i64,
pub user_charge_enable_request: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct Vehicles(Vec<Vehicle>);
#[derive(Debug, Deserialize)]
pub struct Vehicle {
pub id: Id,
pub vehicle_id: VehicleId,
pub vin: String,
pub display_name: String,
}
#[derive(Debug, Deserialize)]
pub struct VehicleData {
id: Id,
user_id: u64,
display_name: String,
}
#[derive(Debug, Serialize)]
pub struct SetChargingAmps {
pub charging_amps: i64,
}
#[derive(Debug, Serialize)]
pub struct SetChargeLimit {
// pub percent: Percentage,
pub percent: u8,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct Empty {} struct Empty {}
/// GET /api/1/vehicles/[id]/... /// GET /api/1/[url]
macro_rules! get { macro_rules! get {
($name:ident, $struct:ty, $url:expr) => { ($name:ident, $return_type:ty, $url:expr) => {
pub async fn $name(&self) -> Result<$struct, TeslatteError> { pub async fn $name(&self) -> Result<$return_type, TeslatteError> {
let url = format!("/api/1/vehicles{}", $url); let url = format!("/api/1{}", $url);
self.get(&url).await self.get(&url).await
} }
}; };
} }
/// GET /api/1/vehicles/[id]/... /// GET /api/1/[url] with an argument.
macro_rules! get_v { ///
($name:ident, $struct:ty, $url:expr) => { /// Pass in the URL as a format string with one arg, which has to impl Display.
pub async fn $name(&self, id: &Id) -> Result<$struct, TeslatteError> { macro_rules! get_arg {
let url = format!("/api/1/vehicles/{}{}", id.0, $url); ($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);
self.get(&url).await self.get(&url).await
} }
}; };
} }
/// POST /api/1/vehicles/[id]/... without data /// POST /api/1/[url] with an argument and data
macro_rules! post_v { macro_rules! post_arg {
($name:ident, $url:expr) => { ($name:ident, $request_type:ty, $url:expr, $arg_type:ty) => {
pub async fn $name(&self, id: &Id) -> miette::Result<(), TeslatteError> { pub async fn $name(
let url = format!("/api/1/vehicles/{}{}", id.0, $url); &self,
self.post(&url, &Empty {}).await arg: &$arg_type,
data: &$request_type,
) -> miette::Result<(), TeslatteError> {
let url_fmt = format!($url, arg);
let url = format!(concat!("/api/1", $url), arg);
self.post(&url, data).await
} }
}; };
} }
/// POST /api/1/vehicles/[id]/... with data // /// POST /api/1/vehicles/[id]/... without data
macro_rules! post_vd { // macro_rules! post_v {
($name:ident, $struct:ty, $url:expr) => { // ($name:ident, $url:expr) => {
pub async fn $name(&self, id: &Id, data: &$struct) -> miette::Result<(), TeslatteError> { // pub async fn $name(&self, id: &Id) -> miette::Result<(), TeslatteError> {
let url = format!("/api/1/vehicles/{}{}", id.0, $url); // let url = format!("/vehicles/{}{}", id.0, $url);
self.post(&url, &data).await // 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] #[rustfmt::skip]
impl Api { impl Api {
get!(vehicles, Vec<Vehicle>, ""); get!(vehicles, Vec<Vehicle>, "/vehicles");
get_v!(vehicle_data, VehicleData, "/vehicle_data"); get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", Id);
get_v!(charge_state, ChargeState, "/data_request/charge_state"); get_arg!(charge_state, ChargeState, "/vehicles/{}/data_request/charge_state", Id);
post_vd!(set_charge_limit, SetChargeLimit, "/command/set_charge_limit"); post_arg!(set_charge_limit, SetChargeLimit, "/vehicles/{}/command/set_charge_limit", Id);
post_vd!(set_charging_amps, SetChargingAmps, "/command/set_charging_amps"); // post_vd!(set_charging_amps, SetChargingAmps, "/command/set_charging_amps");
post_v!(charge_start, "/command/charge_start"); // post_v!(charge_start, "/command/charge_start");
post_v!(charge_stop, "/command/charge_stop"); // post_v!(charge_stop, "/command/charge_stop");
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::vehicle_state::ChargeState;
use test_log::test; use test_log::test;
#[test] #[test]
@ -394,7 +343,7 @@ mod tests {
} }
} }
"#; "#;
Api::json::<ChargeState, _>(s, || "req".to_string()).unwrap(); Api::parse_json::<ChargeState, _>(s, || "req".to_string()).unwrap();
} }
#[test] #[test]
@ -403,7 +352,7 @@ mod tests {
"response": null, "response": null,
"error":{"error": "timeout","error_description": "s"} "error":{"error": "timeout","error_description": "s"}
}"#; }"#;
let e = Api::json::<ChargeState, _>(s, || "req".to_string()); let e = Api::parse_json::<ChargeState, _>(s, || "req".to_string());
if let Err(e) = e { if let Err(e) = e {
if let TeslatteError::ServerError { if let TeslatteError::ServerError {
msg, description, .. msg, description, ..

306
src/vehicle_state.rs Normal file
View file

@ -0,0 +1,306 @@
/// 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 serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
pub struct VehicleData {
pub id: Id,
pub user_id: i64,
pub vehicle_id: VehicleId,
pub vin: String,
pub display_name: String,
pub option_codes: String,
/// gak: This was null for me, assuming String.
pub color: Option<String>,
pub access_type: String,
pub tokens: Vec<String>,
pub state: String,
pub in_service: bool,
pub id_s: String,
pub calendar_enabled: bool,
pub api_version: i64,
/// gak: This was null for me, assuming String.
pub backseat_token: Option<String>,
/// gak: This was null for me, assuming String.
pub backseat_token_updated_at: Option<String>,
pub charge_state: ChargeState,
pub climate_state: ClimateState,
pub drive_state: DriveState,
pub gui_settings: GuiSettings,
pub vehicle_config: VehicleConfig,
pub vehicle_state: VehicleState,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ChargeState {
pub battery_heater_on: bool,
pub battery_level: i64,
pub battery_range: f64,
pub charge_amps: i64,
pub charge_current_request: i64,
pub charge_current_request_max: i64,
pub charge_enable_request: bool,
pub charge_energy_added: f64,
pub charge_limit_soc: i64,
pub charge_limit_soc_max: i64,
pub charge_limit_soc_min: i64,
pub charge_limit_soc_std: i64,
pub charge_miles_added_ideal: f64,
pub charge_miles_added_rated: f64,
pub charge_port_cold_weather_mode: bool,
pub charge_port_color: String,
pub charge_port_door_open: bool,
pub charge_port_latch: String,
pub charge_rate: f64,
pub charge_to_max_range: bool,
pub charger_actual_current: i64,
pub charger_phases: Option<i64>,
pub charger_pilot_current: i64,
pub charger_power: i64,
pub charger_voltage: i64,
pub charging_state: String,
pub conn_charge_cable: String,
pub est_battery_range: f64,
pub fast_charger_brand: String,
pub fast_charger_present: bool,
pub fast_charger_type: String,
pub ideal_battery_range: f64,
pub managed_charging_active: bool,
pub managed_charging_start_time: Option<u64>,
pub managed_charging_user_canceled: bool,
pub max_range_charge_counter: i64,
pub minutes_to_full_charge: i64,
pub not_enough_power_to_heat: Option<bool>,
pub off_peak_charging_enabled: bool,
pub off_peak_charging_times: String,
pub off_peak_hours_end_time: i64,
pub preconditioning_enabled: bool,
pub preconditioning_times: String,
pub scheduled_charging_mode: String,
pub scheduled_charging_pending: bool,
pub scheduled_charging_start_time: Option<i64>,
pub scheduled_charging_start_time_app: Option<i64>,
pub scheduled_charging_start_time_minutes: Option<i64>,
pub scheduled_departure_time: i64,
pub scheduled_departure_time_minutes: i64,
pub supercharger_session_trip_planner: bool,
pub time_to_full_charge: f64,
pub timestamp: u64,
pub trip_charging: bool,
pub usable_battery_level: i64,
pub user_charge_enable_request: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClimateState {
pub allow_cabin_overheat_protection: bool,
pub auto_seat_climate_left: bool,
pub auto_seat_climate_right: bool,
pub battery_heater: bool,
pub battery_heater_no_power: Option<bool>,
pub cabin_overheat_protection: String,
pub cabin_overheat_protection_actively_cooling: bool,
pub climate_keeper_mode: String,
pub defrost_mode: i64,
pub driver_temp_setting: f64,
pub fan_status: i64,
pub hvac_auto_request: String,
pub inside_temp: f64,
pub is_auto_conditioning_on: bool,
pub is_climate_on: bool,
pub is_front_defroster_on: bool,
pub is_preconditioning: bool,
pub is_rear_defroster_on: bool,
pub left_temp_direction: i64,
pub max_avail_temp: f64,
pub min_avail_temp: f64,
pub outside_temp: f64,
pub passenger_temp_setting: f64,
pub remote_heater_control_enabled: bool,
pub right_temp_direction: i64,
pub seat_heater_left: i64,
pub seat_heater_rear_center: i64,
pub seat_heater_rear_left: i64,
pub seat_heater_rear_right: i64,
pub seat_heater_right: i64,
pub side_mirror_heaters: bool,
pub steering_wheel_heater: bool,
pub supports_fan_only_cabin_overheat_protection: bool,
pub timestamp: i64,
pub wiper_blade_heater: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriveState {
pub gps_as_of: i64,
pub heading: i64,
pub latitude: f64,
pub longitude: f64,
pub native_latitude: f64,
pub native_location_supported: i64,
pub native_longitude: f64,
pub native_type: String,
pub power: i64,
pub shift_state: Option<String>,
/// gak: I've assumed this to be String.
pub speed: Option<String>,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuiSettings {
pub gui_24_hour_time: bool,
pub gui_charge_rate_units: String,
pub gui_distance_units: String,
pub gui_range_display: String,
pub gui_temperature_units: String,
pub show_range_units: bool,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VehicleConfig {
pub aux_park_lamps: String,
pub badge_version: i64,
pub can_accept_navigation_requests: bool,
pub can_actuate_trunks: bool,
pub car_special_type: String,
pub car_type: String,
pub charge_port_type: String,
pub dashcam_clip_save_supported: bool,
pub default_charge_to_max: bool,
pub driver_assist: String,
pub ece_restrictions: bool,
pub efficiency_package: String,
pub eu_vehicle: bool,
pub exterior_color: String,
pub exterior_trim: String,
pub exterior_trim_override: String,
pub has_air_suspension: bool,
pub has_ludicrous_mode: bool,
pub has_seat_cooling: bool,
pub headlamp_type: String,
pub interior_trim_type: String,
pub key_version: i64,
pub motorized_charge_port: bool,
pub paint_color_override: String,
pub performance_package: String,
pub plg: bool,
pub pws: bool,
pub rear_drive_unit: String,
pub rear_seat_heaters: i64,
pub rear_seat_type: i64,
pub rhd: bool,
pub roof_color: String,
pub seat_type: Option<u32>,
pub spoiler_type: String,
pub sun_roof_installed: Option<u32>,
pub supports_qr_pairing: bool,
pub third_row_seats: String,
pub timestamp: i64,
pub trim_badging: String,
pub use_range_badging: bool,
pub utc_offset: i64,
pub webcam_supported: bool,
pub wheel_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VehicleState {
pub api_version: i64,
pub autopark_state_v2: String,
pub autopark_style: String,
pub calendar_supported: bool,
pub car_version: String,
pub center_display_state: i64,
pub dashcam_clip_save_available: bool,
pub dashcam_state: String,
pub df: i64,
pub dr: i64,
pub fd_window: i64,
pub feature_bitmask: String,
pub fp_window: i64,
pub ft: i64,
pub is_user_present: bool,
pub last_autopark_error: String,
pub locked: bool,
pub media_state: MediaState,
pub notifications_supported: bool,
pub odometer: f64,
pub parsed_calendar_supported: bool,
pub pf: i64,
pub pr: i64,
pub rd_window: i64,
pub remote_start: bool,
pub remote_start_enabled: bool,
pub remote_start_supported: bool,
pub rp_window: i64,
pub rt: i64,
pub santa_mode: i64,
pub sentry_mode: bool,
pub sentry_mode_available: bool,
pub service_mode: bool,
pub service_mode_plus: bool,
pub smart_summon_available: bool,
pub software_update: SoftwareUpdate,
pub speed_limit_mode: SpeedLimitMode,
pub summon_standby_mode_enabled: bool,
pub timestamp: i64,
pub tpms_pressure_fl: f64,
pub tpms_pressure_fr: f64,
pub tpms_pressure_rl: f64,
pub tpms_pressure_rr: f64,
pub valet_mode: bool,
pub vehicle_name: String,
pub vehicle_self_test_progress: i64,
pub vehicle_self_test_requested: bool,
pub webcam_available: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MediaState {
pub remote_control_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SoftwareUpdate {
pub download_perc: i64,
pub expected_duration_sec: i64,
pub install_perc: i64,
pub status: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpeedLimitMode {
pub active: bool,
pub current_limit_mph: f64,
pub max_limit_mph: i64,
pub min_limit_mph: f64,
pub pin_code_set: bool,
}
#[derive(Debug, Deserialize)]
pub struct Vehicles(Vec<Vehicle>);
#[derive(Debug, Deserialize)]
pub struct Vehicle {
pub id: Id,
pub vehicle_id: VehicleId,
pub vin: String,
pub display_name: String,
}
#[derive(Debug, Serialize)]
pub struct SetChargingAmps {
pub charging_amps: i64,
}
#[derive(Debug, Serialize)]
pub struct SetChargeLimit {
// pub percent: Percentage,
pub percent: u8,
}