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::error::TeslatteError::{CouldNotFindCallbackCode, CouldNotFindState};
|
||||||
use crate::{OwnerApi, TeslatteError};
|
use crate::{FleetApi, OwnerApi, TeslatteError};
|
||||||
use derive_more::{Display, FromStr};
|
use derive_more::{Display, FromStr};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
@ -10,6 +10,7 @@ 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";
|
||||||
const TOKEN_URL: &str = "https://auth.tesla.com/oauth2/v3/token";
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, FromStr, Display)]
|
||||||
pub struct AccessToken(pub String);
|
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)]
|
#[derive(Debug, Serialize)]
|
||||||
struct RefreshTokenRequest {
|
struct RefreshTokenRequest {
|
||||||
grant_type: String,
|
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::error::TeslatteError;
|
||||||
use crate::vehicles::{
|
use crate::vehicles::{
|
||||||
GetVehicleData, SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture,
|
GetVehicleData, SetChargeLimit, SetChargingAmps, SetScheduledCharging, SetScheduledDeparture,
|
||||||
SetTemperatures, VehicleData,
|
SetTemperatures, Vehicle, VehicleData,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, SecondsFormat, TimeZone};
|
use chrono::{DateTime, SecondsFormat, TimeZone};
|
||||||
use derive_more::{Deref, Display, From, FromStr};
|
use derive_more::{Deref, Display, From, FromStr};
|
||||||
|
@ -25,7 +25,11 @@ pub mod vehicles;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod 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 {
|
pub trait VehicleApi {
|
||||||
async fn vehicle_data(
|
async fn vehicle_data(
|
||||||
|
@ -97,6 +101,61 @@ pub trait VehicleApi {
|
||||||
) -> Result<PostResponse, TeslatteError>;
|
) -> 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 {
|
trait ApiValues {
|
||||||
fn format(&self, url: &str) -> String;
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ResponseDeserializer<T> {
|
struct ResponseDeserializer<T> {
|
||||||
error: Option<ResponseError>,
|
error: Option<ResponseError>,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::energy_sites::WallConnector;
|
||||||
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::{pub_get, OwnerApi};
|
use crate::{pub_get, FleetApi, OwnerApi};
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
@ -13,6 +13,11 @@ impl OwnerApi {
|
||||||
pub_get!(products, Vec<Product>, "/products");
|
pub_get!(products, Vec<Product>, "/products");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl FleetApi {
|
||||||
|
pub_get!(products, Vec<Product>, "/products");
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Display)]
|
#[derive(Debug, Clone, Deserialize, Display)]
|
||||||
pub struct EnergySiteId(pub u64);
|
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
|
// 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_args, post_arg, post_arg_empty, ApiValues, Empty, ExternalVehicleId, OwnerApi, VehicleApi,
|
get, get_args, post_arg, post_arg_empty, ApiValues, Empty, ExternalVehicleId, FleetApi,
|
||||||
VehicleId,
|
FleetVehicleApi, OwnerApi, VehicleApi, VehicleId,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
|
@ -42,6 +42,39 @@ impl VehicleApi for OwnerApi {
|
||||||
post_arg_empty!(remote_start_drive, "/vehicles/{}/command/remote_start_drive", VehicleId);
|
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)]
|
#[derive(Debug, Clone, Display, EnumString)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Endpoint {
|
pub enum Endpoint {
|
||||||
|
@ -207,8 +240,8 @@ pub struct ChargeState {
|
||||||
pub scheduled_charging_start_time: Option<i64>,
|
pub scheduled_charging_start_time: Option<i64>,
|
||||||
pub scheduled_charging_start_time_app: Option<i64>,
|
pub scheduled_charging_start_time_app: Option<i64>,
|
||||||
pub scheduled_charging_start_time_minutes: Option<i64>,
|
pub scheduled_charging_start_time_minutes: Option<i64>,
|
||||||
pub scheduled_departure_time: i64,
|
pub scheduled_departure_time: Option<i64>,
|
||||||
pub scheduled_departure_time_minutes: i64,
|
pub scheduled_departure_time_minutes: Option<i64>,
|
||||||
pub supercharger_session_trip_planner: bool,
|
pub supercharger_session_trip_planner: bool,
|
||||||
pub time_to_full_charge: f64,
|
pub time_to_full_charge: f64,
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
|
|
Loading…
Reference in a new issue