change!: vehicle_data now accepts a struct instead of VehicleId
To support "endpoints", e.g. requesting GPS. From https://developer.tesla.com/docs/fleet-api#vehicle_data String of URL-encoded, semicolon-separated values. Can be many of 'charge_state', 'climate_state', 'closures_state', 'drive_state', 'gui_settings', 'location_data', 'vehicle_config', 'vehicle_state', 'vehicle_data_combo'. Before: let vehicle_data = api.vehicle_data(&vehicle_id).await.unwrap(); After: let get_vehicle_data = GetVehicleData::new(vehicles_id); let vehicle_data = api.vehicle_data(&get_vehicle_data).await.unwrap(); Or with a endpoints: let get_vehicle_data = GetVehicleData::new_with_endpoints(123u64, vec![Endpoint::ChargeState, Endpoint::ClimateState]); let vehicle_data = api.vehicle_data(&get_vehicle_data).await.unwrap(); CLI: You can still use vehicle-data without endpoints, but you won't get location data. To fetch location_data: teslatte api vehicle 123 vehicle-data location_data
This commit is contained in:
parent
f0f4a61fa2
commit
6facc27d8b
|
@ -1,6 +1,7 @@
|
|||
use std::env;
|
||||
use teslatte::auth::AccessToken;
|
||||
use teslatte::products::Product;
|
||||
use teslatte::vehicles::GetVehicleData;
|
||||
use teslatte::{OwnerApi, VehicleApi};
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -20,7 +21,8 @@ async fn main() {
|
|||
dbg!(&*vehicles);
|
||||
|
||||
if !vehicles.is_empty() {
|
||||
let vehicle_data = api.vehicle_data(&vehicles[0].id).await.unwrap();
|
||||
let get_vehicle_data = GetVehicleData::new(vehicles[0].id.clone());
|
||||
let vehicle_data = api.vehicle_data(&get_vehicle_data).await.unwrap();
|
||||
dbg!(&vehicle_data);
|
||||
} else {
|
||||
println!("No vehicles found!");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::vehicles::{
|
||||
SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture, SetTemperatures,
|
||||
Endpoints, GetVehicleData, SetChargeLimit, SetChargingAmps, SetScheduledCharging,
|
||||
SetScheduledDeparture, SetTemperatures,
|
||||
};
|
||||
use crate::{OwnerApi, VehicleApi, VehicleId};
|
||||
use clap::{Args, Subcommand};
|
||||
|
@ -7,7 +8,7 @@ use clap::{Args, Subcommand};
|
|||
#[derive(Debug, Subcommand)]
|
||||
pub enum VehicleCommand {
|
||||
/// Get vehicle data.
|
||||
VehicleData,
|
||||
VehicleData(Endpoints),
|
||||
|
||||
/// Open the charge port door or unlocks the cable.
|
||||
ChargePortDoorOpen,
|
||||
|
@ -75,8 +76,9 @@ pub struct VehicleArgs {
|
|||
impl VehicleArgs {
|
||||
pub async fn run(self, api: &OwnerApi) -> miette::Result<()> {
|
||||
match self.command {
|
||||
VehicleCommand::VehicleData => {
|
||||
api.vehicle_data(&self.id).await?;
|
||||
VehicleCommand::VehicleData(endpoints) => {
|
||||
let get_vehicle_data = GetVehicleData::new_with_endpoints(self.id, endpoints);
|
||||
api.vehicle_data(&get_vehicle_data).await?;
|
||||
}
|
||||
VehicleCommand::SetChargeLimit(limit) => {
|
||||
api.set_charge_limit(&self.id, &limit).await?;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::products::EnergySiteId;
|
||||
use crate::{join_query_pairs, pub_get_arg, pub_get_args, rfc3339, OwnerApi, Values};
|
||||
use crate::{join_query_pairs, pub_get_arg, pub_get_args, rfc3339, ApiValues, OwnerApi};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use serde::Deserialize;
|
||||
use strum::{Display, EnumString, IntoStaticStr};
|
||||
|
@ -194,7 +194,7 @@ pub struct CalendarHistoryValues {
|
|||
pub end_date: Option<DateTime<FixedOffset>>,
|
||||
}
|
||||
|
||||
impl Values for CalendarHistoryValues {
|
||||
impl ApiValues for CalendarHistoryValues {
|
||||
fn format(&self, url: &str) -> String {
|
||||
let url = url.replace("{}", &format!("{}", self.site_id.0));
|
||||
let mut pairs: Vec<(&str, String)> = vec![
|
||||
|
|
27
src/lib.rs
27
src/lib.rs
|
@ -5,11 +5,11 @@
|
|||
use crate::auth::{AccessToken, RefreshToken};
|
||||
use crate::error::TeslatteError;
|
||||
use crate::vehicles::{
|
||||
SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture, SetTemperatures,
|
||||
Vehicle, VehicleData,
|
||||
GetVehicleData, SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture,
|
||||
SetTemperatures, Vehicle, VehicleData,
|
||||
};
|
||||
use chrono::{DateTime, SecondsFormat, TimeZone};
|
||||
use derive_more::{Display, FromStr};
|
||||
use derive_more::{Deref, Display, From, FromStr};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
|
@ -29,7 +29,10 @@ 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 vehicle_data(
|
||||
&self,
|
||||
get_vehicle_data: &GetVehicleData,
|
||||
) -> Result<VehicleData, TeslatteError>;
|
||||
async fn wake_up(&self, vehicle_id: &VehicleId) -> Result<PostResponse, TeslatteError>;
|
||||
|
||||
// Alerts
|
||||
|
@ -97,16 +100,22 @@ pub trait VehicleApi {
|
|||
|
||||
trait EnergySitesApi {}
|
||||
|
||||
trait Values {
|
||||
trait ApiValues {
|
||||
fn format(&self, url: &str) -> String;
|
||||
}
|
||||
|
||||
/// Vehicle ID used by the owner-api endpoint.
|
||||
///
|
||||
/// This data comes from [`OwnerApi::vehicles()`] `id` field.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Display, FromStr)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Display, FromStr, From, Deref)]
|
||||
pub struct VehicleId(u64);
|
||||
|
||||
impl VehicleId {
|
||||
pub fn new(id: u64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Vehicle ID used by other endpoints.
|
||||
///
|
||||
/// This data comes from [`OwnerApi::vehicles()`] `vehicle_id` field.
|
||||
|
@ -333,6 +342,7 @@ pub(crate) use pub_get;
|
|||
/// GET /api/1/[url] with an argument.
|
||||
///
|
||||
/// Pass in the URL as a format string with one arg, which has to impl Display.
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! get_arg {
|
||||
($name:ident, $return_type:ty, $url:expr, $arg_type:ty) => {
|
||||
async fn $name(
|
||||
|
@ -345,6 +355,7 @@ macro_rules! get_arg {
|
|||
}
|
||||
};
|
||||
}
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use get_arg;
|
||||
|
||||
/// Public variant of get_arg.
|
||||
|
@ -362,8 +373,7 @@ macro_rules! pub_get_arg {
|
|||
}
|
||||
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.
|
||||
/// GET /api/1/[url] with a struct to format the URL.
|
||||
macro_rules! get_args {
|
||||
($name:ident, $return_type:ty, $url:expr, $args:ty) => {
|
||||
async fn $name(
|
||||
|
@ -376,7 +386,6 @@ macro_rules! get_args {
|
|||
}
|
||||
};
|
||||
}
|
||||
#[allow(unused)] // Leaving this here for now. I'm sure it'll be used during this refactor.
|
||||
pub(crate) use get_args;
|
||||
|
||||
/// Public variant of get_args.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::energy_sites::{HistoryKind, HistoryPeriod};
|
||||
use crate::products::GatewayId;
|
||||
use crate::{join_query_pairs, pub_get_arg, pub_get_args, rfc3339, OwnerApi, Values};
|
||||
use crate::{join_query_pairs, pub_get_arg, pub_get_args, rfc3339, ApiValues, OwnerApi};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use derive_more::{Display, FromStr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -33,7 +33,7 @@ pub struct PowerwallEnergyHistoryValues {
|
|||
pub end_date: Option<DateTime<FixedOffset>>,
|
||||
}
|
||||
|
||||
impl Values for PowerwallEnergyHistoryValues {
|
||||
impl ApiValues for PowerwallEnergyHistoryValues {
|
||||
fn format(&self, url: &str) -> String {
|
||||
let url = url.replace("{}", &self.powerwall_id.0.to_string());
|
||||
let mut pairs: Vec<(&str, String)> = vec![
|
||||
|
|
|
@ -88,7 +88,7 @@ pub struct Components {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod};
|
||||
use crate::Values;
|
||||
use crate::ApiValues;
|
||||
use chrono::DateTime;
|
||||
|
||||
#[test]
|
||||
|
|
117
src/vehicles.rs
117
src/vehicles.rs
|
@ -1,17 +1,19 @@
|
|||
/// Please note that many of these structs are generated from my own API call 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 derive_more::{Deref, DerefMut, From};
|
||||
// Please note that many of these structs are generated from my own API call 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::{
|
||||
get, get_arg, post_arg, post_arg_empty, Empty, ExternalVehicleId, OwnerApi, VehicleApi,
|
||||
VehicleId,
|
||||
get, get_args, post_arg, post_arg_empty, ApiValues, Empty, ExternalVehicleId, OwnerApi,
|
||||
VehicleApi, VehicleId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl VehicleApi for OwnerApi {
|
||||
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
||||
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", VehicleId);
|
||||
get_args!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", GetVehicleData);
|
||||
post_arg_empty!(wake_up, "/vehicles/{}/command/wake_up", VehicleId);
|
||||
|
||||
// Alerts
|
||||
|
@ -41,6 +43,84 @@ impl VehicleApi for OwnerApi {
|
|||
post_arg_empty!(remote_start_drive, "/vehicles/{}/command/remote_start_drive", VehicleId);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Endpoint {
|
||||
ChargeState,
|
||||
ClimateState,
|
||||
ClosuresState,
|
||||
DriveState,
|
||||
GuiSettings,
|
||||
LocationData,
|
||||
VehicleConfig,
|
||||
VehicleState,
|
||||
VehicleDataCombo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, From, Deref, DerefMut)]
|
||||
#[cfg_attr(feature = "cli", derive(clap::Args))]
|
||||
pub struct Endpoints {
|
||||
endpoints: Vec<Endpoint>,
|
||||
}
|
||||
|
||||
pub struct GetVehicleData {
|
||||
pub vehicle_id: VehicleId,
|
||||
|
||||
/// From https://developer.tesla.com/docs/fleet-api#vehicle_data
|
||||
/// String of URL-encoded, semicolon-separated values. Can be many of 'charge_state',
|
||||
/// 'climate_state', 'closures_state', 'drive_state', 'gui_settings', 'location_data',
|
||||
/// 'vehicle_config', 'vehicle_state', 'vehicle_data_combo'.
|
||||
pub endpoints: Endpoints,
|
||||
}
|
||||
|
||||
impl GetVehicleData {
|
||||
/// Create a new GetVehicleData request with no endpoints.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use teslatte::VehicleId;
|
||||
/// # use teslatte::vehicles::GetVehicleData;
|
||||
/// let get_vehicle_data = GetVehicleData::new(123u64);
|
||||
/// let get_vehicle_data = GetVehicleData::new(VehicleId::new(123u64));
|
||||
/// ```
|
||||
pub fn new(vehicle_id: impl Into<VehicleId>) -> Self {
|
||||
Self::new_with_endpoints(vehicle_id, vec![])
|
||||
}
|
||||
|
||||
/// Create a new GetVehicleData request with endpoints.
|
||||
/// ```rust
|
||||
/// # use teslatte::vehicles::{Endpoint, GetVehicleData};
|
||||
/// let get_vehicle_data = GetVehicleData::new_with_endpoints(123u64, vec![Endpoint::ChargeState, Endpoint::ClimateState]);
|
||||
/// ```
|
||||
pub fn new_with_endpoints(
|
||||
vehicle_id: impl Into<VehicleId>,
|
||||
endpoints: impl Into<Endpoints>,
|
||||
) -> Self {
|
||||
Self {
|
||||
vehicle_id: vehicle_id.into(),
|
||||
endpoints: endpoints.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiValues for GetVehicleData {
|
||||
fn format(&self, url: &str) -> String {
|
||||
let url = url.replace("{}", &format!("{}", *self.vehicle_id));
|
||||
|
||||
if self.endpoints.is_empty() {
|
||||
return url;
|
||||
}
|
||||
|
||||
let endpoints = self
|
||||
.endpoints
|
||||
.iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(";");
|
||||
|
||||
format!("{}?endpoints={}", url, endpoints)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct VehicleData {
|
||||
pub id: VehicleId,
|
||||
|
@ -183,9 +263,9 @@ pub struct ClimateState {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DriveState {
|
||||
/// gak: The following fields (up to native_type) suddenly vanished from the API response so
|
||||
/// I've made them all Option. Maybe API now only returns them when driving?
|
||||
/// TODO: Check if they come back
|
||||
/// From https://developer.tesla.com/docs/fleet-api#vehicle_data
|
||||
/// For vehicles running firmware versions 2023.38+, location_data is required to fetch vehicle
|
||||
/// location. This will result in a location sharing icon to show on the vehicle UI.
|
||||
pub gps_as_of: Option<i64>,
|
||||
pub heading: Option<i64>,
|
||||
pub latitude: Option<f64>,
|
||||
|
@ -461,6 +541,23 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{PrintResponses, RequestData};
|
||||
|
||||
#[test]
|
||||
fn vehicle_query() {
|
||||
let url = "A/{}/B";
|
||||
|
||||
let get_vehicle_data = GetVehicleData::new(123);
|
||||
assert_eq!(get_vehicle_data.format(url), "A/123/B");
|
||||
|
||||
let get_vehicle_data = GetVehicleData::new_with_endpoints(
|
||||
123,
|
||||
vec![Endpoint::ChargeState, Endpoint::ClimateState],
|
||||
);
|
||||
assert_eq!(
|
||||
get_vehicle_data.format(url),
|
||||
"A/123/B?endpoints=charge_state;climate_state"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_charge_state() {
|
||||
let s = r#"
|
||||
|
|
Loading…
Reference in a new issue