wip: Add cli example. Change api macros.
This commit is contained in:
parent
3e62f354e8
commit
9cca1ca5bd
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
|
cli.json
|
||||||
|
|
|
@ -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"]}
|
||||||
|
|
|
@ -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),
|
||||||
|
Err(_) => {
|
||||||
|
let auth = Authentication::new().unwrap();
|
||||||
|
let (access_token, refresh_token) = auth.interactive_get_access_token().await.unwrap();
|
||||||
println!("Access token: {}", access_token.0);
|
println!("Access token: {}", access_token.0);
|
||||||
println!("Refresh token: {}", refresh_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);
|
||||||
|
|
||||||
|
if vehicles.len() > 0 {
|
||||||
|
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();
|
let charge_state = api.charge_state(&vehicles[0].id).await.unwrap();
|
||||||
dbg!(&charge_state);
|
dbg!(&charge_state);
|
||||||
|
} else {
|
||||||
|
println!("No vehicles found!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
154
examples/cli.rs
Normal file
154
examples/cli.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
15
src/auth.rs
15
src/auth.rs
|
@ -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()
|
||||||
|
|
205
src/lib.rs
205
src/lib.rs
|
@ -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
306
src/vehicle_state.rs
Normal 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,
|
||||||
|
}
|
Loading…
Reference in a new issue