refactor!: Rename Api to OwnerApi. Remove ResponseData<T>. Add VehicleApi trait.
Significant refactor to progress towards different API access that Tesla has introduced. See issues #6 and #7. Removed `ResponseData` because it wasn't very ergonomic, forcing the user to deref or call data(). Also it had specific fields for JSON output which was only used for the CLI, so I introduced a field `print_responses` in OwnerApi that the CLI can use.
This commit is contained in:
parent
a8b58e1157
commit
8c059769ee
|
@ -1,7 +1,7 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use teslatte::auth::AccessToken;
|
use teslatte::auth::AccessToken;
|
||||||
use teslatte::products::Product;
|
use teslatte::products::Product;
|
||||||
use teslatte::OwnerApi;
|
use teslatte::{OwnerApi, VehicleApi};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -21,7 +21,7 @@ async fn main() {
|
||||||
|
|
||||||
if !vehicles.is_empty() {
|
if !vehicles.is_empty() {
|
||||||
let vehicle_data = api.vehicle_data(&vehicles[0].id).await.unwrap();
|
let vehicle_data = api.vehicle_data(&vehicles[0].id).await.unwrap();
|
||||||
dbg!(&*vehicle_data);
|
dbg!(&vehicle_data);
|
||||||
} else {
|
} else {
|
||||||
println!("No vehicles found!");
|
println!("No vehicles found!");
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,13 @@ async fn main() {
|
||||||
|
|
||||||
Product::Solar(e) => {
|
Product::Solar(e) => {
|
||||||
let site_info = api.energy_sites_site_info(&e.energy_site_id).await.unwrap();
|
let site_info = api.energy_sites_site_info(&e.energy_site_id).await.unwrap();
|
||||||
dbg!(&*site_info);
|
dbg!(&site_info);
|
||||||
|
|
||||||
let live_info = api
|
let live_info = api
|
||||||
.energy_sites_live_status(&e.energy_site_id)
|
.energy_sites_live_status(&e.energy_site_id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
dbg!(&*live_info);
|
dbg!(&live_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Product::Powerwall(p) => {
|
Product::Powerwall(p) => {
|
||||||
|
@ -52,7 +52,7 @@ async fn main() {
|
||||||
.energy_sites_live_status(&p.energy_site_id)
|
.energy_sites_live_status(&p.energy_site_id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
dbg!(&*live_info);
|
dbg!(&live_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
src/cli.rs
34
src/cli.rs
|
@ -1,37 +1,3 @@
|
||||||
pub mod energy;
|
pub mod energy;
|
||||||
pub mod powerwall;
|
pub mod powerwall;
|
||||||
pub mod vehicle;
|
pub mod vehicle;
|
||||||
|
|
||||||
use crate::error::TeslatteError;
|
|
||||||
use crate::ResponseData;
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
pub fn print_json<T>(result: Result<ResponseData<T>, TeslatteError>) {
|
|
||||||
match result {
|
|
||||||
Ok(data) => print_json_data(data),
|
|
||||||
Err(TeslatteError::ServerError { ref body, .. }) if body.is_some() => {
|
|
||||||
print_json_str(&body.clone().unwrap())
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{}", e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn print_json_data<T>(data: ResponseData<T>) {
|
|
||||||
// TODO: pretty print cli option
|
|
||||||
print_json_str(data.body());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_json_str(body: &str) {
|
|
||||||
#[cfg(feature = "cli-pretty-json")]
|
|
||||||
{
|
|
||||||
use colored_json::prelude::*;
|
|
||||||
println!("{}", body.to_colored_json_auto().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "cli-pretty-json"))]
|
|
||||||
{
|
|
||||||
println!("{}", body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::cli::print_json;
|
|
||||||
use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod};
|
use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod};
|
||||||
use crate::products::EnergySiteId;
|
use crate::products::EnergySiteId;
|
||||||
use crate::OwnerApi;
|
use crate::OwnerApi;
|
||||||
|
@ -26,13 +25,13 @@ impl EnergySiteArgs {
|
||||||
pub async fn run(&self, api: &OwnerApi) -> miette::Result<()> {
|
pub async fn run(&self, api: &OwnerApi) -> miette::Result<()> {
|
||||||
match &self.command {
|
match &self.command {
|
||||||
EnergySiteCommand::SiteStatus => {
|
EnergySiteCommand::SiteStatus => {
|
||||||
print_json(api.energy_sites_site_status(&self.id).await);
|
api.energy_sites_site_status(&self.id).await?;
|
||||||
}
|
}
|
||||||
EnergySiteCommand::LiveStatus => {
|
EnergySiteCommand::LiveStatus => {
|
||||||
print_json(api.energy_sites_live_status(&self.id).await);
|
api.energy_sites_live_status(&self.id).await?;
|
||||||
}
|
}
|
||||||
EnergySiteCommand::SiteInfo => {
|
EnergySiteCommand::SiteInfo => {
|
||||||
print_json(api.energy_sites_site_info(&self.id).await);
|
api.energy_sites_site_info(&self.id).await?;
|
||||||
}
|
}
|
||||||
EnergySiteCommand::CalendarHistory(args) => {
|
EnergySiteCommand::CalendarHistory(args) => {
|
||||||
let start_date = args
|
let start_date = args
|
||||||
|
@ -54,7 +53,7 @@ impl EnergySiteArgs {
|
||||||
start_date,
|
start_date,
|
||||||
end_date,
|
end_date,
|
||||||
};
|
};
|
||||||
print_json(api.energy_sites_calendar_history(&values).await);
|
api.energy_sites_calendar_history(&values).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::cli::print_json_data;
|
|
||||||
use crate::energy_sites::{HistoryKind, HistoryPeriod};
|
use crate::energy_sites::{HistoryKind, HistoryPeriod};
|
||||||
use crate::powerwall::{PowerwallEnergyHistoryValues, PowerwallId};
|
use crate::powerwall::{PowerwallEnergyHistoryValues, PowerwallId};
|
||||||
use crate::OwnerApi;
|
use crate::OwnerApi;
|
||||||
|
@ -24,19 +23,17 @@ impl PowerwallArgs {
|
||||||
pub async fn run(&self, api: &OwnerApi) -> miette::Result<()> {
|
pub async fn run(&self, api: &OwnerApi) -> miette::Result<()> {
|
||||||
match self.command {
|
match self.command {
|
||||||
PowerwallCommand::Status => {
|
PowerwallCommand::Status => {
|
||||||
print_json_data(api.powerwall_status(&self.id).await?);
|
api.powerwall_status(&self.id).await?;
|
||||||
}
|
}
|
||||||
PowerwallCommand::History => {
|
PowerwallCommand::History => {
|
||||||
print_json_data(
|
api.powerwall_energy_history(&PowerwallEnergyHistoryValues {
|
||||||
api.powerwall_energy_history(&PowerwallEnergyHistoryValues {
|
powerwall_id: self.id.clone(),
|
||||||
powerwall_id: self.id.clone(),
|
period: HistoryPeriod::Day,
|
||||||
period: HistoryPeriod::Day,
|
kind: HistoryKind::Power,
|
||||||
kind: HistoryKind::Power,
|
start_date: None,
|
||||||
start_date: None,
|
end_date: None,
|
||||||
end_date: None,
|
})
|
||||||
})
|
.await?;
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::cli::print_json;
|
|
||||||
use crate::vehicles::{
|
use crate::vehicles::{
|
||||||
SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture, SetTemperatures,
|
SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture, SetTemperatures,
|
||||||
};
|
};
|
||||||
use crate::{OwnerApi, VehicleId};
|
use crate::{OwnerApi, VehicleApi, VehicleId};
|
||||||
use clap::{Args, Subcommand};
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
|
@ -77,61 +76,61 @@ impl VehicleArgs {
|
||||||
pub async fn run(self, api: &OwnerApi) -> miette::Result<()> {
|
pub async fn run(self, api: &OwnerApi) -> miette::Result<()> {
|
||||||
match self.command {
|
match self.command {
|
||||||
VehicleCommand::VehicleData => {
|
VehicleCommand::VehicleData => {
|
||||||
print_json(api.vehicle_data(&self.id).await);
|
api.vehicle_data(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::SetChargeLimit(limit) => {
|
VehicleCommand::SetChargeLimit(limit) => {
|
||||||
print_json(api.set_charge_limit(&self.id, &limit).await);
|
api.set_charge_limit(&self.id, &limit).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::SetChargingAmps(charging_amps) => {
|
VehicleCommand::SetChargingAmps(charging_amps) => {
|
||||||
print_json(api.set_charging_amps(&self.id, &charging_amps).await);
|
api.set_charging_amps(&self.id, &charging_amps).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::ChargeStart => {
|
VehicleCommand::ChargeStart => {
|
||||||
print_json(api.charge_start(&self.id).await);
|
api.charge_start(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::ChargeStop => {
|
VehicleCommand::ChargeStop => {
|
||||||
print_json(api.charge_stop(&self.id).await);
|
api.charge_stop(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::ChargePortDoorOpen => {
|
VehicleCommand::ChargePortDoorOpen => {
|
||||||
print_json(api.charge_port_door_open(&self.id).await);
|
api.charge_port_door_open(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::ChargePortDoorClose => {
|
VehicleCommand::ChargePortDoorClose => {
|
||||||
print_json(api.charge_port_door_close(&self.id).await);
|
api.charge_port_door_close(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::ChargeStandard => {
|
VehicleCommand::ChargeStandard => {
|
||||||
print_json(api.charge_standard(&self.id).await);
|
api.charge_standard(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::ChargeMaxRange => {
|
VehicleCommand::ChargeMaxRange => {
|
||||||
print_json(api.charge_max_range(&self.id).await);
|
api.charge_max_range(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::SetScheduledCharging(charging) => {
|
VehicleCommand::SetScheduledCharging(charging) => {
|
||||||
print_json(api.set_scheduled_charging(&self.id, &charging).await);
|
api.set_scheduled_charging(&self.id, &charging).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::SetScheduledDeparture(departure) => {
|
VehicleCommand::SetScheduledDeparture(departure) => {
|
||||||
print_json(api.set_scheduled_departure(&self.id, &departure).await);
|
api.set_scheduled_departure(&self.id, &departure).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::HonkHorn => {
|
VehicleCommand::HonkHorn => {
|
||||||
print_json(api.honk_horn(&self.id).await);
|
api.honk_horn(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::FlashLights => {
|
VehicleCommand::FlashLights => {
|
||||||
print_json(api.flash_lights(&self.id).await);
|
api.flash_lights(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::EnableHvac => {
|
VehicleCommand::EnableHvac => {
|
||||||
print_json(api.auto_conditioning_start(&self.id).await);
|
api.auto_conditioning_start(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::DisableHvac => {
|
VehicleCommand::DisableHvac => {
|
||||||
print_json(api.auto_conditioning_stop(&self.id).await);
|
api.auto_conditioning_stop(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::HvacTemperature(temps) => {
|
VehicleCommand::HvacTemperature(temps) => {
|
||||||
print_json(api.set_temps(&self.id, &temps).await);
|
api.set_temps(&self.id, &temps).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::DoorUnlock => {
|
VehicleCommand::DoorUnlock => {
|
||||||
print_json(api.door_unlock(&self.id).await);
|
api.door_unlock(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::DoorLock => {
|
VehicleCommand::DoorLock => {
|
||||||
print_json(api.door_lock(&self.id).await);
|
api.door_lock(&self.id).await?;
|
||||||
}
|
}
|
||||||
VehicleCommand::RemoteStartDrive => {
|
VehicleCommand::RemoteStartDrive => {
|
||||||
print_json(api.remote_start_drive(&self.id).await);
|
api.remote_start_drive(&self.id).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::products::EnergySiteId;
|
use crate::products::EnergySiteId;
|
||||||
use crate::{get_arg, get_args, join_query_pairs, rfc3339, OwnerApi, Values};
|
use crate::{join_query_pairs, pub_get_arg, pub_get_args, rfc3339, OwnerApi, Values};
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::{Display, EnumString, IntoStaticStr};
|
use strum::{Display, EnumString, IntoStaticStr};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl OwnerApi {
|
impl OwnerApi {
|
||||||
get_arg!(energy_sites_site_status, SiteStatus, "/energy_sites/{}/site_status", EnergySiteId);
|
pub_get_arg!(energy_sites_site_status, SiteStatus, "/energy_sites/{}/site_status", EnergySiteId);
|
||||||
get_arg!(energy_sites_live_status, LiveStatus, "/energy_sites/{}/live_status", EnergySiteId);
|
pub_get_arg!(energy_sites_live_status, LiveStatus, "/energy_sites/{}/live_status", EnergySiteId);
|
||||||
get_arg!(energy_sites_site_info, SiteInfo, "/energy_sites/{}/site_info", EnergySiteId);
|
pub_get_arg!(energy_sites_site_info, SiteInfo, "/energy_sites/{}/site_info", EnergySiteId);
|
||||||
get_args!(energy_sites_calendar_history, CalendarHistory, "/energy_sites/{}/calendar_history", CalendarHistoryValues);
|
pub_get_args!(energy_sites_calendar_history, CalendarHistory, "/energy_sites/{}/calendar_history", CalendarHistoryValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -22,8 +22,10 @@ pub struct SiteStatus {
|
||||||
pub gateway_id: String,
|
pub gateway_id: String,
|
||||||
pub percentage_charged: f64,
|
pub percentage_charged: f64,
|
||||||
pub powerwall_onboarding_settings_set: bool,
|
pub powerwall_onboarding_settings_set: bool,
|
||||||
pub powerwall_tesla_electric_interested_in: Option<()>, // TODO: Unknown type. Was null.
|
// TODO: Unknown type. Was null.
|
||||||
pub resource_type: String, // battery
|
pub powerwall_tesla_electric_interested_in: Option<()>,
|
||||||
|
// battery
|
||||||
|
pub resource_type: String,
|
||||||
pub site_name: String,
|
pub site_name: String,
|
||||||
pub storm_mode_enabled: bool,
|
pub storm_mode_enabled: bool,
|
||||||
pub sync_grid_alert_enabled: bool,
|
pub sync_grid_alert_enabled: bool,
|
||||||
|
|
241
src/lib.rs
241
src/lib.rs
|
@ -1,5 +1,11 @@
|
||||||
|
#![feature(async_fn_in_trait)]
|
||||||
|
|
||||||
use crate::auth::{AccessToken, RefreshToken};
|
use crate::auth::{AccessToken, RefreshToken};
|
||||||
use crate::error::TeslatteError;
|
use crate::error::TeslatteError;
|
||||||
|
use crate::vehicles::{
|
||||||
|
SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture, SetTemperatures,
|
||||||
|
Vehicle, VehicleData,
|
||||||
|
};
|
||||||
use chrono::{DateTime, SecondsFormat, TimeZone};
|
use chrono::{DateTime, SecondsFormat, TimeZone};
|
||||||
use derive_more::{Display, FromStr};
|
use derive_more::{Display, FromStr};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
@ -19,6 +25,76 @@ pub mod cli;
|
||||||
|
|
||||||
const API_URL: &str = "https://owner-api.teslamotors.com/api/1";
|
const API_URL: &str = "https://owner-api.teslamotors.com/api/1";
|
||||||
|
|
||||||
|
pub trait VehicleApi {
|
||||||
|
async fn vehicles(&self) -> Result<Vec<Vehicle>, TeslatteError>;
|
||||||
|
async fn vehicle_data(&self, vehicle_id: &VehicleId) -> Result<VehicleData, TeslatteError>;
|
||||||
|
async fn wake_up(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
|
||||||
|
// Alerts
|
||||||
|
async fn honk_horn(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn flash_lights(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
|
||||||
|
// Charging
|
||||||
|
async fn charge_port_door_open(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn charge_port_door_close(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn set_charge_limit(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
data: &SetChargeLimit,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn set_charging_amps(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
data: &SetChargingAmps,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn charge_standard(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn charge_max_range(&self, vehicle_id: &VehicleId)
|
||||||
|
-> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn charge_start(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn charge_stop(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn set_scheduled_charging(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
data: &SetScheduledCharging,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn set_scheduled_departure(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
data: &SetScheduledDeparture,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
|
||||||
|
// HVAC
|
||||||
|
async fn auto_conditioning_start(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn auto_conditioning_stop(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn set_temps(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
data: &SetTemperatures,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
|
||||||
|
// Doors
|
||||||
|
async fn door_unlock(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn door_lock(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||||
|
async fn remote_start_drive(
|
||||||
|
&self,
|
||||||
|
vehicle_id: &VehicleId,
|
||||||
|
) -> Result<PostResponse, TeslatteError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait EnergySitesApi {}
|
||||||
|
|
||||||
trait Values {
|
trait Values {
|
||||||
fn format(&self, url: &str) -> String;
|
fn format(&self, url: &str) -> String;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +125,13 @@ impl Display for RequestData<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum PrintResponses {
|
||||||
|
No,
|
||||||
|
Plain,
|
||||||
|
Pretty,
|
||||||
|
}
|
||||||
|
|
||||||
/// API client for the Tesla API.
|
/// API client for the Tesla API.
|
||||||
///
|
///
|
||||||
/// Main entry point for the API. It contains the access token and refresh token, and can be used
|
/// Main entry point for the API. It contains the access token and refresh token, and can be used
|
||||||
|
@ -56,6 +139,7 @@ impl Display for RequestData<'_> {
|
||||||
pub struct OwnerApi {
|
pub struct OwnerApi {
|
||||||
pub access_token: AccessToken,
|
pub access_token: AccessToken,
|
||||||
pub refresh_token: Option<RefreshToken>,
|
pub refresh_token: Option<RefreshToken>,
|
||||||
|
pub print_responses: PrintResponses,
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +148,7 @@ impl OwnerApi {
|
||||||
OwnerApi {
|
OwnerApi {
|
||||||
access_token,
|
access_token,
|
||||||
refresh_token,
|
refresh_token,
|
||||||
|
print_responses: PrintResponses::No,
|
||||||
client: Client::builder()
|
client: Client::builder()
|
||||||
.timeout(std::time::Duration::from_secs(10))
|
.timeout(std::time::Duration::from_secs(10))
|
||||||
.build()
|
.build()
|
||||||
|
@ -71,14 +156,14 @@ impl OwnerApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get<D>(&self, url: &str) -> Result<ResponseData<D>, TeslatteError>
|
async fn get<D>(&self, url: &str) -> Result<D, TeslatteError>
|
||||||
where
|
where
|
||||||
D: for<'de> Deserialize<'de> + Debug,
|
D: for<'de> Deserialize<'de> + Debug,
|
||||||
{
|
{
|
||||||
self.request(&RequestData::Get { url }).await
|
self.request(&RequestData::Get { url }).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post<S>(&self, url: &str, body: S) -> Result<ResponseData<PostResponse>, TeslatteError>
|
async fn post<S>(&self, url: &str, body: S) -> Result<PostResponse, TeslatteError>
|
||||||
where
|
where
|
||||||
S: Serialize + Debug,
|
S: Serialize + Debug,
|
||||||
{
|
{
|
||||||
|
@ -87,22 +172,19 @@ impl OwnerApi {
|
||||||
let request_data = RequestData::Post { url, payload };
|
let request_data = RequestData::Post { url, payload };
|
||||||
let data = self.request::<PostResponse>(&request_data).await?;
|
let data = self.request::<PostResponse>(&request_data).await?;
|
||||||
|
|
||||||
if !data.data.result {
|
if !data.result {
|
||||||
return Err(TeslatteError::ServerError {
|
return Err(TeslatteError::ServerError {
|
||||||
request: format!("{request_data}"),
|
request: format!("{request_data}"),
|
||||||
msg: data.data.reason,
|
|
||||||
description: None,
|
description: None,
|
||||||
body: Some(data.body),
|
msg: data.reason,
|
||||||
|
body: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request<T>(
|
async fn request<T>(&self, request_data: &RequestData<'_>) -> Result<T, TeslatteError>
|
||||||
&self,
|
|
||||||
request_data: &RequestData<'_>,
|
|
||||||
) -> Result<ResponseData<T>, TeslatteError>
|
|
||||||
where
|
where
|
||||||
T: for<'de> Deserialize<'de> + Debug,
|
T: for<'de> Deserialize<'de> + Debug,
|
||||||
{
|
{
|
||||||
|
@ -138,16 +220,27 @@ impl OwnerApi {
|
||||||
|
|
||||||
debug!("Response: {response_body}");
|
debug!("Response: {response_body}");
|
||||||
|
|
||||||
Self::parse_json(request_data, response_body)
|
Self::parse_json(request_data, response_body, self.print_responses)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_json<T>(
|
fn parse_json<T>(
|
||||||
request_data: &RequestData,
|
request_data: &RequestData,
|
||||||
response_body: String,
|
response_body: String,
|
||||||
) -> Result<ResponseData<T>, TeslatteError>
|
print_response: PrintResponses,
|
||||||
|
) -> Result<T, TeslatteError>
|
||||||
where
|
where
|
||||||
T: for<'de> Deserialize<'de> + Debug,
|
T: for<'de> Deserialize<'de> + Debug,
|
||||||
{
|
{
|
||||||
|
match print_response {
|
||||||
|
PrintResponses::No => {}
|
||||||
|
PrintResponses::Plain => {
|
||||||
|
println!("{}", response_body);
|
||||||
|
}
|
||||||
|
PrintResponses::Pretty => {
|
||||||
|
print_json_str(&response_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let response: Response<T> = serde_json::from_str::<ResponseDeserializer<T>>(&response_body)
|
let response: Response<T> = serde_json::from_str::<ResponseDeserializer<T>>(&response_body)
|
||||||
.map_err(|source| TeslatteError::DecodeJsonError {
|
.map_err(|source| TeslatteError::DecodeJsonError {
|
||||||
source,
|
source,
|
||||||
|
@ -157,10 +250,7 @@ impl OwnerApi {
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Response::Response(data) => Ok(ResponseData {
|
Response::Response(data) => Ok(data),
|
||||||
data,
|
|
||||||
body: response_body,
|
|
||||||
}),
|
|
||||||
Response::Error(e) => Err(TeslatteError::ServerError {
|
Response::Error(e) => Err(TeslatteError::ServerError {
|
||||||
request: format!("{request_data}"),
|
request: format!("{request_data}"),
|
||||||
msg: e.error,
|
msg: e.error,
|
||||||
|
@ -212,57 +302,41 @@ struct ResponseError {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct Empty {}
|
struct Empty {}
|
||||||
|
|
||||||
/// Data and body from a request. The body can be used for debugging.
|
|
||||||
///
|
|
||||||
/// The CLI can optionally print the raw JSON so the user can manipulate it.
|
|
||||||
///
|
|
||||||
/// This struct will automatically deref to the `data` type for better ergonomics.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ResponseData<T> {
|
|
||||||
data: T,
|
|
||||||
body: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ResponseData<T> {
|
|
||||||
pub fn data(&self) -> &T {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn body(&self) -> &str {
|
|
||||||
&self.body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::ops::Deref for ResponseData<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// GET /api/1/[url]
|
/// GET /api/1/[url]
|
||||||
macro_rules! get {
|
macro_rules! get {
|
||||||
($name:ident, $return_type:ty, $url:expr) => {
|
($name:ident, $return_type:ty, $url:expr) => {
|
||||||
pub async fn $name(
|
async fn $name(&self) -> Result<$return_type, crate::error::TeslatteError> {
|
||||||
&self,
|
|
||||||
) -> Result<crate::ResponseData<$return_type>, crate::error::TeslatteError> {
|
|
||||||
let url = format!("{}{}", crate::API_URL, $url);
|
let url = format!("{}{}", crate::API_URL, $url);
|
||||||
self.get(&url).await
|
self.get(&url)
|
||||||
|
.await
|
||||||
|
.map_err(|e| crate::error::TeslatteError::from(e))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(crate) use get;
|
pub(crate) use get;
|
||||||
|
|
||||||
|
/// Same as get, but public.
|
||||||
|
macro_rules! pub_get {
|
||||||
|
($name:ident, $return_type:ty, $url:expr) => {
|
||||||
|
pub async fn $name(&self) -> Result<$return_type, crate::error::TeslatteError> {
|
||||||
|
let url = format!("{}{}", crate::API_URL, $url);
|
||||||
|
self.get(&url)
|
||||||
|
.await
|
||||||
|
.map_err(|e| crate::error::TeslatteError::from(e))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use pub_get;
|
||||||
|
|
||||||
/// GET /api/1/[url] with an argument.
|
/// GET /api/1/[url] with an argument.
|
||||||
///
|
///
|
||||||
/// Pass in the URL as a format string with one arg, which has to impl Display.
|
/// Pass in the URL as a format string with one arg, which has to impl Display.
|
||||||
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(
|
async fn $name(
|
||||||
&self,
|
&self,
|
||||||
arg: &$arg_type,
|
arg: &$arg_type,
|
||||||
) -> miette::Result<crate::ResponseData<$return_type>, crate::error::TeslatteError> {
|
) -> miette::Result<$return_type, crate::error::TeslatteError> {
|
||||||
let url = format!($url, arg);
|
let url = format!($url, arg);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.get(&url).await
|
self.get(&url).await
|
||||||
|
@ -271,29 +345,61 @@ macro_rules! get_arg {
|
||||||
}
|
}
|
||||||
pub(crate) use get_arg;
|
pub(crate) use get_arg;
|
||||||
|
|
||||||
/// GET /api/1/[url] with a struct.
|
/// Public variant of get_arg.
|
||||||
macro_rules! get_args {
|
macro_rules! pub_get_arg {
|
||||||
($name:ident, $return_type:ty, $url:expr, $args:ty) => {
|
($name:ident, $return_type:ty, $url:expr, $arg_type:ty) => {
|
||||||
pub async fn $name(
|
pub async fn $name(
|
||||||
|
&self,
|
||||||
|
arg: &$arg_type,
|
||||||
|
) -> miette::Result<$return_type, crate::error::TeslatteError> {
|
||||||
|
let url = format!($url, arg);
|
||||||
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
|
self.get(&url).await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use pub_get_arg;
|
||||||
|
|
||||||
|
/// GET /api/1/[url] with a struct.
|
||||||
|
#[allow(unused)] // Leaving this here for now. I'm sure it'll be used during this refactor.
|
||||||
|
macro_rules! get_args {
|
||||||
|
($name:ident, $return_type:ty, $url:expr, $args:ty) => {
|
||||||
|
async fn $name(
|
||||||
&self,
|
&self,
|
||||||
values: &$args,
|
values: &$args,
|
||||||
) -> miette::Result<crate::ResponseData<$return_type>, crate::error::TeslatteError> {
|
) -> miette::Result<$return_type, crate::error::TeslatteError> {
|
||||||
let url = values.format($url);
|
let url = values.format($url);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.get(&url).await
|
self.get(&url).await
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
#[allow(unused)] // Leaving this here for now. I'm sure it'll be used during this refactor.
|
||||||
pub(crate) use get_args;
|
pub(crate) use get_args;
|
||||||
|
|
||||||
|
/// Public variant of get_args.
|
||||||
|
macro_rules! pub_get_args {
|
||||||
|
($name:ident, $return_type:ty, $url:expr, $args:ty) => {
|
||||||
|
pub async fn $name(
|
||||||
|
&self,
|
||||||
|
values: &$args,
|
||||||
|
) -> miette::Result<$return_type, crate::error::TeslatteError> {
|
||||||
|
let url = values.format($url);
|
||||||
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
|
self.get(&url).await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use pub_get_args;
|
||||||
|
|
||||||
/// 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 {
|
||||||
($name:ident, $request_type:ty, $url:expr, $arg_type:ty) => {
|
($name:ident, $request_type:ty, $url:expr, $arg_type:ty) => {
|
||||||
pub async fn $name(
|
async fn $name(
|
||||||
&self,
|
&self,
|
||||||
arg: &$arg_type,
|
arg: &$arg_type,
|
||||||
data: &$request_type,
|
data: &$request_type,
|
||||||
) -> miette::Result<crate::ResponseData<crate::PostResponse>, crate::error::TeslatteError> {
|
) -> miette::Result<crate::PostResponse, crate::error::TeslatteError> {
|
||||||
let url = format!($url, arg);
|
let url = format!($url, arg);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.post(&url, data).await
|
self.post(&url, data).await
|
||||||
|
@ -305,10 +411,10 @@ pub(crate) use post_arg;
|
||||||
/// Post like above but with an empty body using the Empty struct.
|
/// Post like above but with an empty body using the Empty struct.
|
||||||
macro_rules! post_arg_empty {
|
macro_rules! post_arg_empty {
|
||||||
($name:ident, $url:expr, $arg_type:ty) => {
|
($name:ident, $url:expr, $arg_type:ty) => {
|
||||||
pub async fn $name(
|
async fn $name(
|
||||||
&self,
|
&self,
|
||||||
arg: &$arg_type,
|
arg: &$arg_type,
|
||||||
) -> miette::Result<crate::ResponseData<crate::PostResponse>, crate::error::TeslatteError> {
|
) -> miette::Result<crate::PostResponse, crate::error::TeslatteError> {
|
||||||
let url = format!($url, arg);
|
let url = format!($url, arg);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.post(&url, &Empty {}).await
|
self.post(&url, &Empty {}).await
|
||||||
|
@ -333,6 +439,19 @@ pub(crate) fn join_query_pairs(pairs: &[(&str, String)]) -> String {
|
||||||
.join("&")
|
.join("&")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_json_str(body: &str) {
|
||||||
|
#[cfg(feature = "cli-pretty-json")]
|
||||||
|
{
|
||||||
|
use colored_json::prelude::*;
|
||||||
|
println!("{}", body.to_colored_json_auto().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cli-pretty-json"))]
|
||||||
|
{
|
||||||
|
println!("{}", body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -350,7 +469,11 @@ mod tests {
|
||||||
payload: "doesn't matter",
|
payload: "doesn't matter",
|
||||||
};
|
};
|
||||||
|
|
||||||
let e = OwnerApi::parse_json::<ChargeState>(&request_data, s.to_string());
|
let e = OwnerApi::parse_json::<ChargeState>(
|
||||||
|
&request_data,
|
||||||
|
s.to_string(),
|
||||||
|
PrintResponses::Pretty,
|
||||||
|
);
|
||||||
if let Err(e) = e {
|
if let Err(e) = e {
|
||||||
if let TeslatteError::ServerError {
|
if let TeslatteError::ServerError {
|
||||||
msg, description, ..
|
msg, description, ..
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -3,9 +3,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use teslatte::auth::{AccessToken, RefreshToken};
|
use teslatte::auth::{AccessToken, RefreshToken};
|
||||||
use teslatte::cli::energy::EnergySiteArgs;
|
use teslatte::cli::energy::EnergySiteArgs;
|
||||||
use teslatte::cli::powerwall::PowerwallArgs;
|
use teslatte::cli::powerwall::PowerwallArgs;
|
||||||
use teslatte::cli::print_json;
|
|
||||||
use teslatte::cli::vehicle::VehicleArgs;
|
use teslatte::cli::vehicle::VehicleArgs;
|
||||||
use teslatte::OwnerApi;
|
use teslatte::{OwnerApi, PrintResponses, VehicleApi};
|
||||||
|
|
||||||
/// Teslatte
|
/// Teslatte
|
||||||
///
|
///
|
||||||
|
@ -103,16 +102,17 @@ async fn main() -> miette::Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let api = OwnerApi::new(access_token, refresh_token);
|
let mut api = OwnerApi::new(access_token, refresh_token);
|
||||||
|
api.print_responses = PrintResponses::Pretty;
|
||||||
match api_args.command {
|
match api_args.command {
|
||||||
ApiCommand::Vehicles => {
|
ApiCommand::Vehicles => {
|
||||||
print_json(api.vehicles().await);
|
api.vehicles().await?;
|
||||||
}
|
}
|
||||||
ApiCommand::Vehicle(v) => {
|
ApiCommand::Vehicle(v) => {
|
||||||
v.run(&api).await?;
|
v.run(&api).await?;
|
||||||
}
|
}
|
||||||
ApiCommand::Products => {
|
ApiCommand::Products => {
|
||||||
print_json(api.products().await);
|
api.products().await?;
|
||||||
}
|
}
|
||||||
ApiCommand::EnergySite(e) => {
|
ApiCommand::EnergySite(e) => {
|
||||||
e.run(&api).await?;
|
e.run(&api).await?;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::energy_sites::{HistoryKind, HistoryPeriod};
|
use crate::energy_sites::{HistoryKind, HistoryPeriod};
|
||||||
use crate::products::GatewayId;
|
use crate::products::GatewayId;
|
||||||
use crate::{get_arg, get_args, join_query_pairs, rfc3339, OwnerApi, Values};
|
use crate::{join_query_pairs, pub_get_arg, pub_get_args, rfc3339, OwnerApi, Values};
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use derive_more::{Display, FromStr};
|
use derive_more::{Display, FromStr};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl OwnerApi {
|
impl OwnerApi {
|
||||||
get_arg!(powerwall_status, PowerwallStatus, "/powerwalls/{}/status", PowerwallId);
|
pub_get_arg!(powerwall_status, PowerwallStatus, "/powerwalls/{}/status", PowerwallId);
|
||||||
get_args!(powerwall_energy_history, PowerwallEnergyHistory, "/powerwalls/{}/energyhistory", PowerwallEnergyHistoryValues);
|
pub_get_args!(powerwall_energy_history, PowerwallEnergyHistory, "/powerwalls/{}/energyhistory", PowerwallEnergyHistoryValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Display, FromStr)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Display, FromStr)]
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::error::TeslatteError;
|
use crate::error::TeslatteError;
|
||||||
use crate::powerwall::PowerwallId;
|
use crate::powerwall::PowerwallId;
|
||||||
use crate::vehicles::VehicleData;
|
use crate::vehicles::VehicleData;
|
||||||
use crate::{get, OwnerApi};
|
use crate::{pub_get, OwnerApi};
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl OwnerApi {
|
impl OwnerApi {
|
||||||
get!(products, Vec<Product>, "/products");
|
pub_get!(products, Vec<Product>, "/products");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Display)]
|
#[derive(Debug, Clone, Deserialize, Display)]
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
/// 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::{
|
use crate::{
|
||||||
get, get_arg, post_arg, post_arg_empty, Empty, ExternalVehicleId, OwnerApi, VehicleId,
|
get, get_arg, post_arg, post_arg_empty, Empty, ExternalVehicleId, OwnerApi, VehicleApi,
|
||||||
|
VehicleId,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl OwnerApi {
|
impl VehicleApi for OwnerApi {
|
||||||
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
||||||
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", VehicleId);
|
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", VehicleId);
|
||||||
post_arg_empty!(wake_up, "/vehicles/{}/command/wake_up", VehicleId);
|
post_arg_empty!(wake_up, "/vehicles/{}/command/wake_up", VehicleId);
|
||||||
|
@ -414,7 +415,7 @@ pub struct SetScheduledDeparture {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::RequestData;
|
use crate::{PrintResponses, RequestData};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn json_charge_state() {
|
fn json_charge_state() {
|
||||||
|
@ -484,7 +485,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/charge_state",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/charge_state",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<ChargeState>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<ChargeState>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -531,7 +533,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/climate_state",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/climate_state",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<ClimateState>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<ClimateState>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -558,7 +561,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/drive_state",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/drive_state",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<DriveState>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<DriveState>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -580,7 +584,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/gui_settings",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/gui_settings",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<GuiSettings>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<GuiSettings>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -630,7 +635,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/vehicle_config",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/vehicle_config",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleConfig>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleConfig>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -705,7 +711,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/vehicle_state",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/vehicle_state",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleState>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleState>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -715,7 +722,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -725,7 +733,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -735,7 +744,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -745,7 +755,8 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -755,6 +766,7 @@ mod tests {
|
||||||
let request_data = RequestData::Get {
|
let request_data = RequestData::Get {
|
||||||
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/vehicle_data",
|
||||||
};
|
};
|
||||||
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string()).unwrap();
|
OwnerApi::parse_json::<VehicleData>(&request_data, s.to_string(), PrintResponses::Pretty)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue