my changes
This commit is contained in:
parent
13f4be3478
commit
3123cad1a5
65
src/auth.rs
65
src/auth.rs
|
@ -1,5 +1,5 @@
|
|||
use crate::error::TeslatteError::{CouldNotFindCallbackCode, CouldNotFindState};
|
||||
use crate::{OwnerApi, TeslatteError};
|
||||
use crate::{FleetApi, OwnerApi, TeslatteError};
|
||||
use derive_more::{Display, FromStr};
|
||||
use rand::Rng;
|
||||
use reqwest::Client;
|
||||
|
@ -10,6 +10,7 @@ use url::Url;
|
|||
|
||||
const AUTHORIZE_URL: &str = "https://auth.tesla.com/oauth2/v3/authorize";
|
||||
const TOKEN_URL: &str = "https://auth.tesla.com/oauth2/v3/token";
|
||||
const CLIENT_ID: &str = "48ad82d96e76-4cf0-a301-08794a139ad9";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromStr, Display)]
|
||||
pub struct AccessToken(pub String);
|
||||
|
@ -193,6 +194,68 @@ page, where the URL will start with https://auth.tesla.com/void/callback?code=..
|
|||
}
|
||||
}
|
||||
|
||||
impl FleetApi {
|
||||
/// Refresh the internally stored access token using the known refresh token.
|
||||
pub async fn refresh(&mut self) -> Result<(), TeslatteError> {
|
||||
match &self.refresh_token {
|
||||
None => Err(TeslatteError::NoRefreshToken),
|
||||
Some(refresh_token) => {
|
||||
let response = Self::refresh_token(refresh_token).await?;
|
||||
self.access_token = response.access_token;
|
||||
self.refresh_token = Some(response.refresh_token);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn refresh_token(
|
||||
refresh_token: &RefreshToken,
|
||||
) -> Result<RefreshTokenResponse, TeslatteError> {
|
||||
let url = "https://auth.tesla.com/oauth2/v3/token";
|
||||
let payload = RefreshTokenRequest {
|
||||
grant_type: "refresh_token".into(),
|
||||
client_id: CLIENT_ID.into(),
|
||||
refresh_token: refresh_token.0.clone(),
|
||||
scope: "openid email offline_access".into(),
|
||||
};
|
||||
Self::auth_post(url, &payload).await
|
||||
}
|
||||
|
||||
async fn auth_post<'a, S, D>(url: &str, payload: &S) -> Result<D, TeslatteError>
|
||||
where
|
||||
S: Serialize,
|
||||
D: DeserializeOwned,
|
||||
{
|
||||
let response = Client::new()
|
||||
.post(url)
|
||||
.header("Accept", "application/json")
|
||||
.json(payload)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|source| TeslatteError::FetchError {
|
||||
source,
|
||||
request: url.to_string(),
|
||||
})?;
|
||||
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|source| TeslatteError::FetchError {
|
||||
source,
|
||||
request: url.to_string(),
|
||||
})?;
|
||||
|
||||
let json =
|
||||
serde_json::from_str::<D>(&body).map_err(|source| TeslatteError::DecodeJsonError {
|
||||
source,
|
||||
body: body.to_string(),
|
||||
request: url.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(json)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RefreshTokenRequest {
|
||||
grant_type: String,
|
||||
|
|
192
src/lib.rs
192
src/lib.rs
|
@ -6,7 +6,7 @@ use crate::auth::{AccessToken, RefreshToken};
|
|||
use crate::error::TeslatteError;
|
||||
use crate::vehicles::{
|
||||
GetVehicleData, SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture,
|
||||
SetTemperatures, VehicleData,
|
||||
SetTemperatures, Vehicle, VehicleData,
|
||||
};
|
||||
use chrono::{DateTime, SecondsFormat, TimeZone};
|
||||
use derive_more::{Deref, Display, From, FromStr};
|
||||
|
@ -25,7 +25,11 @@ pub mod vehicles;
|
|||
#[cfg(feature = "cli")]
|
||||
pub mod cli;
|
||||
|
||||
const API_URL: &str = "https://owner-api.teslamotors.com/api/1";
|
||||
const API_URL: &str = if cfg!(debug_assertions) {
|
||||
"https://cnut:4443/api/1"
|
||||
} else {
|
||||
"https://localhost:4443/api/1"
|
||||
};
|
||||
|
||||
pub trait VehicleApi {
|
||||
async fn vehicle_data(
|
||||
|
@ -97,6 +101,61 @@ pub trait VehicleApi {
|
|||
) -> Result<PostResponse, TeslatteError>;
|
||||
}
|
||||
|
||||
pub trait FleetVehicleApi {
|
||||
async fn vehicles(&self) -> Result<Vec<Vehicle>, TeslatteError>;
|
||||
async fn vehicle_data(
|
||||
&self,
|
||||
get_vehicle_data: &GetVehicleData,
|
||||
) -> Result<VehicleData, TeslatteError>;
|
||||
async fn wake_up(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
|
||||
// Alerts
|
||||
async fn honk_horn(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn flash_lights(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
|
||||
// Charging
|
||||
async fn charge_port_door_open(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn charge_port_door_close(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn set_charge_limit(
|
||||
&self,
|
||||
vin: &str,
|
||||
data: &SetChargeLimit,
|
||||
) -> Result<PostResponse, TeslatteError>;
|
||||
async fn set_charging_amps(
|
||||
&self,
|
||||
vin: &str,
|
||||
data: &SetChargingAmps,
|
||||
) -> Result<PostResponse, TeslatteError>;
|
||||
async fn charge_standard(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn charge_max_range(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn charge_start(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn charge_stop(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn set_scheduled_charging(
|
||||
&self,
|
||||
vin: &str,
|
||||
data: &SetScheduledCharging,
|
||||
) -> Result<PostResponse, TeslatteError>;
|
||||
async fn set_scheduled_departure(
|
||||
&self,
|
||||
vin: &str,
|
||||
data: &SetScheduledDeparture,
|
||||
) -> Result<PostResponse, TeslatteError>;
|
||||
|
||||
// HVAC
|
||||
async fn auto_conditioning_start(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn auto_conditioning_stop(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn set_temps(
|
||||
&self,
|
||||
vin: &str,
|
||||
data: &SetTemperatures,
|
||||
) -> Result<PostResponse, TeslatteError>;
|
||||
|
||||
// Doors
|
||||
async fn door_unlock(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn door_lock(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
async fn remote_start_drive(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||
}
|
||||
|
||||
trait ApiValues {
|
||||
fn format(&self, url: &str) -> String;
|
||||
}
|
||||
|
@ -269,6 +328,135 @@ impl OwnerApi {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FleetApi {
|
||||
pub access_token: AccessToken,
|
||||
pub refresh_token: Option<RefreshToken>,
|
||||
pub print_responses: PrintResponses,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl FleetApi {
|
||||
pub fn new(access_token: AccessToken, refresh_token: Option<RefreshToken>) -> Self {
|
||||
Self {
|
||||
access_token,
|
||||
refresh_token,
|
||||
print_responses: PrintResponses::No,
|
||||
client: Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.add_root_certificate(
|
||||
reqwest::Certificate::from_pem(include_bytes!("./selfsigned.pem")).unwrap(),
|
||||
)
|
||||
.danger_accept_invalid_certs(cfg!(debug_assertions))
|
||||
.build()
|
||||
.unwrap(), // TODO: unwrap
|
||||
}
|
||||
}
|
||||
|
||||
async fn get<D>(&self, url: &str) -> Result<D, TeslatteError>
|
||||
where
|
||||
D: for<'de> Deserialize<'de> + Debug,
|
||||
{
|
||||
self.request(&RequestData::Get { url }).await
|
||||
}
|
||||
|
||||
async fn post<S>(&self, url: &str, body: S) -> Result<PostResponse, TeslatteError>
|
||||
where
|
||||
S: Serialize + Debug,
|
||||
{
|
||||
let payload =
|
||||
&serde_json::to_string(&body).expect("Should not fail creating the request struct.");
|
||||
let request_data = RequestData::Post { url, payload };
|
||||
let data = self.request::<PostResponse>(&request_data).await?;
|
||||
|
||||
if !data.result {
|
||||
return Err(TeslatteError::ServerError {
|
||||
request: format!("{request_data}"),
|
||||
description: None,
|
||||
msg: data.reason,
|
||||
body: None,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
async fn request<T>(&self, request_data: &RequestData<'_>) -> Result<T, TeslatteError>
|
||||
where
|
||||
T: for<'de> Deserialize<'de> + Debug,
|
||||
{
|
||||
debug!("{request_data}");
|
||||
|
||||
let request_builder = match request_data {
|
||||
RequestData::Get { url } => self.client.get(*url),
|
||||
RequestData::Post { url, payload } => self
|
||||
.client
|
||||
.post(*url)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(payload.to_string()),
|
||||
};
|
||||
|
||||
let response_body = request_builder
|
||||
.header("Accept", "application/json")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Bearer {}", self.access_token.0.trim()),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|source| TeslatteError::FetchError {
|
||||
source,
|
||||
request: format!("{request_data}"),
|
||||
})?
|
||||
.text()
|
||||
.await
|
||||
.map_err(|source| TeslatteError::FetchError {
|
||||
source,
|
||||
request: format!("{request_data}"),
|
||||
})?;
|
||||
|
||||
debug!("Response: {response_body}");
|
||||
|
||||
Self::parse_json(request_data, response_body, self.print_responses)
|
||||
}
|
||||
|
||||
fn parse_json<T>(
|
||||
request_data: &RequestData,
|
||||
response_body: String,
|
||||
print_response: PrintResponses,
|
||||
) -> Result<T, TeslatteError>
|
||||
where
|
||||
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)
|
||||
.map_err(|source| TeslatteError::DecodeJsonError {
|
||||
source,
|
||||
request: format!("{request_data}"),
|
||||
body: response_body.to_string(),
|
||||
})?
|
||||
.into();
|
||||
|
||||
match response {
|
||||
Response::Response(data) => Ok(data),
|
||||
Response::Error(e) => Err(TeslatteError::ServerError {
|
||||
request: format!("{request_data}"),
|
||||
msg: e.error,
|
||||
description: e.error_description,
|
||||
body: Some(response_body.to_owned()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ResponseDeserializer<T> {
|
||||
error: Option<ResponseError>,
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::energy_sites::WallConnector;
|
|||
use crate::error::TeslatteError;
|
||||
use crate::powerwall::PowerwallId;
|
||||
use crate::vehicles::VehicleData;
|
||||
use crate::{pub_get, OwnerApi};
|
||||
use crate::{pub_get, FleetApi, OwnerApi};
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
|
@ -13,6 +13,11 @@ impl OwnerApi {
|
|||
pub_get!(products, Vec<Product>, "/products");
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl FleetApi {
|
||||
pub_get!(products, Vec<Product>, "/products");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Display)]
|
||||
pub struct EnergySiteId(pub u64);
|
||||
|
||||
|
|
14
src/selfsigned.pem
Normal file
14
src/selfsigned.pem
Normal file
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICJjCCAYigAwIBAgIUT9V89Ca28OFWbQM4tx7rBOaqw4UwCgYIKoZIzj0EAwIw
|
||||
FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMTIyNzA3MDcxNVoXDTMzMTIyNDA3
|
||||
MDcxNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIGbMBAGByqGSM49AgEGBSuBBAAj
|
||||
A4GGAAQAQ0BavfhlRoxnE4CmRx22LKafnx6Oqs0fO/vSmPqgqWzMmMUCsak9bNoJ
|
||||
fNSFS2beUY8+myR2kpAPadp5AQAz2wsBCHEriqlp88sQKJWGaAzzmuZP0UmxaIJK
|
||||
Ftcv0RLyuWST5NN61xp0yzrbF9tSjXq34qrIPcxU7t2t3IylP3rApYujdTBzMB0G
|
||||
A1UdDgQWBBThM5D2TLjTuZ6we4CgyRl+iWScezAfBgNVHSMEGDAWgBThM5D2TLjT
|
||||
uZ6we4CgyRl+iWScezAPBgNVHRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUF
|
||||
BwMBMAsGA1UdDwQEAwICjDAKBggqhkjOPQQDAgOBiwAwgYcCQUx062mqiK+K8AFf
|
||||
/TkjzxXYacUbvy0+ubIpytOMOT36noMkShe8m0Y/1Y3l2HGlSvWeTQzkCF0fIu/d
|
||||
NdBiRFkwAkIBtqfzcXGHklZgKNg9iKfUhoX93mDUFv/b1Z3AGHYHnVT3kJvOy2zO
|
||||
uQLK0NgYXCVMADGzjWuY14XYvTyTBGxfw2w=
|
||||
-----END CERTIFICATE-----
|
|
@ -4,8 +4,8 @@ use derive_more::{Deref, DerefMut, From};
|
|||
// 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_args, post_arg, post_arg_empty, ApiValues, Empty, ExternalVehicleId, OwnerApi, VehicleApi,
|
||||
VehicleId,
|
||||
get, get_args, post_arg, post_arg_empty, ApiValues, Empty, ExternalVehicleId, FleetApi,
|
||||
FleetVehicleApi, OwnerApi, VehicleApi, VehicleId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
|
@ -42,6 +42,39 @@ impl VehicleApi for OwnerApi {
|
|||
post_arg_empty!(remote_start_drive, "/vehicles/{}/command/remote_start_drive", VehicleId);
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl FleetVehicleApi for FleetApi {
|
||||
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
||||
get_args!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", GetVehicleData);
|
||||
post_arg_empty!(wake_up, "/vehicles/{}/command/wake_up", str);
|
||||
|
||||
// Alerts
|
||||
post_arg_empty!(honk_horn, "/vehicles/{}/command/honk_horn", str);
|
||||
post_arg_empty!(flash_lights, "/vehicles/{}/command/flash_lights", str);
|
||||
|
||||
// Charging
|
||||
post_arg_empty!(charge_port_door_open, "/vehicles/{}/command/charge_port_door_open", str);
|
||||
post_arg_empty!(charge_port_door_close, "/vehicles/{}/command/charge_port_door_close", str);
|
||||
post_arg!(set_charge_limit, SetChargeLimit, "/vehicles/{}/command/set_charge_limit", str);
|
||||
post_arg!(set_charging_amps, SetChargingAmps, "/vehicles/{}/command/set_charging_amps", str);
|
||||
post_arg_empty!(charge_standard, "/vehicles/{}/command/charge_standard", str);
|
||||
post_arg_empty!(charge_max_range, "/vehicles/{}/command/charge_max_range", str);
|
||||
post_arg_empty!(charge_start, "/vehicles/{}/command/charge_start", str);
|
||||
post_arg_empty!(charge_stop, "/vehicles/{}/command/charge_stop", str);
|
||||
post_arg!(set_scheduled_charging, SetScheduledCharging, "/vehicles/{}/command/set_scheduled_charging", str);
|
||||
post_arg!(set_scheduled_departure, SetScheduledDeparture, "/vehicles/{}/command/set_scheduled_departure", str);
|
||||
|
||||
// HVAC
|
||||
post_arg_empty!(auto_conditioning_start, "/vehicles/{}/command/auto_conditioning_start", str);
|
||||
post_arg_empty!(auto_conditioning_stop, "/vehicles/{}/command/auto_conditioning_stop", str);
|
||||
post_arg!(set_temps, SetTemperatures, "/vehicles/{}/command/set_temps", str);
|
||||
|
||||
// Doors
|
||||
post_arg_empty!(door_unlock, "/vehicles/{}/command/door_unlock", str);
|
||||
post_arg_empty!(door_lock, "/vehicles/{}/command/door_lock", str);
|
||||
post_arg_empty!(remote_start_drive, "/vehicles/{}/command/remote_start_drive", str);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Display, EnumString)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Endpoint {
|
||||
|
@ -207,8 +240,8 @@ pub struct ChargeState {
|
|||
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 scheduled_departure_time: Option<i64>,
|
||||
pub scheduled_departure_time_minutes: Option<i64>,
|
||||
pub supercharger_session_trip_planner: bool,
|
||||
pub time_to_full_charge: f64,
|
||||
pub timestamp: u64,
|
||||
|
|
Loading…
Reference in a new issue