Compare commits
10 commits
ef26ce4697
...
cf30a7d1bc
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | cf30a7d1bc | ||
Alex Janka | a350280662 | ||
Alex Janka | db77d944cb | ||
Alex Janka | cd1ba77cf4 | ||
Alex Janka | 636c5fc482 | ||
Alex Janka | 4888ce9d39 | ||
Alex Janka | b2e7fd7004 | ||
Alex Janka | 7fb468b072 | ||
Alex Janka | ca778af269 | ||
Alex Janka | 2735f59b3f |
46
CHANGELOG.md
46
CHANGELOG.md
|
@ -5,46 +5,10 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [0.1.15] - 2023-04-12
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Forgot a `total_pack_energy`. (#22)
|
|
||||||
|
|
||||||
## [0.1.14] - 2023-04-12
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- `energy_left` and `total_pack_energy` are gone from the API. (#22)
|
|
||||||
- Don't rely on serde untagged to determine the product type, as any missing
|
|
||||||
fields will give an unspecified error. Instead directly check known fields
|
|
||||||
that will probably not be removed. https://github.com/serde-rs/serde/pull/2376
|
|
||||||
|
|
||||||
## [0.1.13] - 2023-01-24
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Vehicles list is gone from the API. Use `products` instead. (#13)
|
|
||||||
|
|
||||||
## [0.1.12] - 2023-01-20
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- PowerwallData new fields:
|
|
||||||
- powerwall_onboarding_settings_set
|
|
||||||
- storm_mode_enabled
|
|
||||||
- features
|
|
||||||
- warp_site_number
|
|
||||||
- go_off_grid_test_banner_enabled
|
|
||||||
- powerwall_tesla_electric_interested_in
|
|
||||||
- vpp_tour_enabled
|
|
||||||
- Components market_type is now Option<String>
|
|
||||||
- LiveWallConnector wall_connector_power is now f32
|
|
||||||
|
|
||||||
## [0.1.11] - 2023-11-11
|
## [0.1.11] - 2023-11-11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `vehicle_data` now accepts `GetVehicleData` instead of `VehicleId`. (See [6facc27](https://github.com/gak/teslatte/commit/6facc27d8b408d35b98b4c6c0ad3e5df82328d2c))
|
- `vehicle_data` now accepts `GetVehicleData` instead of `VehicleId`. (See [6facc27](https://github.com/gak/teslatte/commit/6facc27d8b408d35b98b4c6c0ad3e5df82328d2c))
|
||||||
|
|
||||||
## [0.1.10] - 2023-11-11
|
## [0.1.10] - 2023-11-11
|
||||||
|
@ -52,21 +16,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- API changes for "api_version 67"
|
- API changes for "api_version 67"
|
||||||
|
|
||||||
- VehicleData new fields:
|
- VehicleData new fields:
|
||||||
|
|
||||||
- cached_data
|
- cached_data
|
||||||
- command_signing
|
- command_signing
|
||||||
- release_notes_supported
|
- release_notes_supported
|
||||||
|
|
||||||
- ClimateState new fields:
|
- ClimateState new fields:
|
||||||
|
|
||||||
- auto_steering_wheel_heat
|
- auto_steering_wheel_heat
|
||||||
- cop_activation_temperature,
|
- cop_activation_temperature,
|
||||||
- steering_wheel_heat_level
|
- steering_wheel_heat_level
|
||||||
|
|
||||||
- DriveState now Optional:
|
- DriveState now Optional:
|
||||||
|
|
||||||
- gps_as_of
|
- gps_as_of
|
||||||
- heading
|
- heading
|
||||||
- latitude
|
- latitude
|
||||||
|
@ -77,20 +37,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- native_type
|
- native_type
|
||||||
|
|
||||||
- DriveState new fields:
|
- DriveState new fields:
|
||||||
|
|
||||||
- active_route_traffic_minutes_delay
|
- active_route_traffic_minutes_delay
|
||||||
|
|
||||||
- GuiSettings new field:
|
- GuiSettings new field:
|
||||||
|
|
||||||
- gui_tirepressure_units
|
- gui_tirepressure_units
|
||||||
|
|
||||||
- VehicleConfig new fields:
|
- VehicleConfig new fields:
|
||||||
|
|
||||||
- cop_user_set_temp_supported
|
- cop_user_set_temp_supported
|
||||||
- webcam_selfie_supported
|
- webcam_selfie_supported
|
||||||
|
|
||||||
- VehicleState new fields:
|
- VehicleState new fields:
|
||||||
|
|
||||||
- media_info: MediaInfo
|
- media_info: MediaInfo
|
||||||
- tpms_hard_warning_fl
|
- tpms_hard_warning_fl
|
||||||
- tpms_hard_warning_fr
|
- tpms_hard_warning_fr
|
||||||
|
|
28
Cargo.toml
28
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "teslatte"
|
name = "teslatte"
|
||||||
version = "0.1.15"
|
version = "0.1.11"
|
||||||
description = "A command line tool and Rust crate for querying the Tesla API."
|
description = "A command line tool and Rust crate for querying the Tesla API."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.75.0"
|
rust-version = "1.75.0"
|
||||||
|
@ -20,25 +20,25 @@ path = "src/main.rs"
|
||||||
required-features = ["cli"]
|
required-features = ["cli"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
miette = { version = "7.2.0", features = ["fancy"] }
|
miette = { version = "5.10.0", features = ["fancy"] }
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.50"
|
||||||
tokio = { version = "1.35.1", features = ["full"] }
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
reqwest = { version = "0.12.3", features = ["rustls-tls", "cookies", "json"] }
|
reqwest = { version = "0.11.22", features = ["rustls-tls", "cookies", "json"] }
|
||||||
url = "2.5.0"
|
url = "2.4.1"
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.189", features = ["derive"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.107"
|
||||||
rustls = "0.23.0"
|
rustls = "0.22.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
strum = { version = "0.26.1", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
pkce = "0.2.0"
|
pkce = "0.2.0"
|
||||||
|
|
||||||
clap = { version = "4.4.18", features = ["derive", "env"], optional = true }
|
clap = { version = "4.4.6", features = ["derive", "env"], optional = true }
|
||||||
tracing-subscriber = { version = "0.3.18", optional = true }
|
tracing-subscriber = { version = "0.3.17", optional = true }
|
||||||
colored_json = { version = "5.0.0", optional = true }
|
colored_json = { version = "4.0.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-log = { version = "0.2.14", default-features = false, features = ["trace"] }
|
test-log = { version = "0.2.13", default-features = false, features = ["trace"] }
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
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::vehicles::GetVehicleData;
|
||||||
|
use teslatte::{OwnerApi, VehicleApi};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -16,8 +17,19 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let vehicles = api.vehicles().await.unwrap();
|
||||||
|
dbg!(&*vehicles);
|
||||||
|
|
||||||
|
if !vehicles.is_empty() {
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
|
||||||
let products = api.products().await.unwrap();
|
let products = api.products().await.unwrap();
|
||||||
dbg!(&products);
|
dbg!(&*products);
|
||||||
|
|
||||||
if !products.is_empty() {
|
if !products.is_empty() {
|
||||||
for product in &*products {
|
for product in &*products {
|
||||||
|
|
7
justfile
7
justfile
|
@ -7,8 +7,9 @@ no_token_test:
|
||||||
|
|
||||||
# Require an access token from "cli.json". Use `just auth` to generate.
|
# Require an access token from "cli.json". Use `just auth` to generate.
|
||||||
token_tests:
|
token_tests:
|
||||||
|
cargo run -- api vehicles
|
||||||
|
cargo run --no-default-features --features cli -- api vehicles
|
||||||
cargo run -- api products
|
cargo run -- api products
|
||||||
cargo run --no-default-features --features cli -- api products
|
|
||||||
|
|
||||||
publish version:
|
publish version:
|
||||||
git diff-index --quiet HEAD
|
git diff-index --quiet HEAD
|
||||||
|
@ -29,7 +30,3 @@ auth:
|
||||||
|
|
||||||
audit:
|
audit:
|
||||||
cargo audit
|
cargo audit
|
||||||
|
|
||||||
update:
|
|
||||||
cargo update && cargo upgrade
|
|
||||||
cd tesla_api_coverage && cargo update && cargo upgrade
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub struct SiteStatus {
|
||||||
pub battery_power: i64,
|
pub battery_power: i64,
|
||||||
pub battery_type: String,
|
pub battery_type: String,
|
||||||
pub breaker_alert_enabled: bool,
|
pub breaker_alert_enabled: bool,
|
||||||
|
pub energy_left: f64,
|
||||||
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,
|
||||||
|
@ -28,12 +29,14 @@ pub struct SiteStatus {
|
||||||
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,
|
||||||
|
pub total_pack_energy: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||||
pub struct LiveStatus {
|
pub struct LiveStatus {
|
||||||
pub backup_capable: bool,
|
pub backup_capable: bool,
|
||||||
pub battery_power: i64,
|
pub battery_power: i64,
|
||||||
|
pub energy_left: f64,
|
||||||
pub generator_power: i64,
|
pub generator_power: i64,
|
||||||
pub grid_power: i64,
|
pub grid_power: i64,
|
||||||
pub grid_services_active: bool,
|
pub grid_services_active: bool,
|
||||||
|
@ -45,6 +48,7 @@ pub struct LiveStatus {
|
||||||
pub solar_power: i64,
|
pub solar_power: i64,
|
||||||
pub storm_mode_active: bool,
|
pub storm_mode_active: bool,
|
||||||
pub timestamp: String,
|
pub timestamp: String,
|
||||||
|
pub total_pack_energy: i64,
|
||||||
pub wall_connectors: Vec<LiveWallConnector>,
|
pub wall_connectors: Vec<LiveWallConnector>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +69,7 @@ pub struct LiveWallConnector {
|
||||||
/// 2: not plugged in
|
/// 2: not plugged in
|
||||||
/// 4: plugged in (not charging)
|
/// 4: plugged in (not charging)
|
||||||
pub wall_connector_state: i64,
|
pub wall_connector_state: i64,
|
||||||
pub wall_connector_power: f32,
|
pub wall_connector_power: i64,
|
||||||
pub wall_connector_fault_state: i64,
|
pub wall_connector_fault_state: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +311,7 @@ mod tests {
|
||||||
vin: Some("1234".to_string()),
|
vin: Some("1234".to_string()),
|
||||||
din: "5432".to_string(),
|
din: "5432".to_string(),
|
||||||
wall_connector_state: 4,
|
wall_connector_state: 4,
|
||||||
wall_connector_power: 0.,
|
wall_connector_power: 0,
|
||||||
wall_connector_fault_state: 2,
|
wall_connector_fault_state: 2,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -332,7 +336,7 @@ mod tests {
|
||||||
vin: None,
|
vin: None,
|
||||||
din: "1234".to_string(),
|
din: "1234".to_string(),
|
||||||
wall_connector_state: 2,
|
wall_connector_state: 2,
|
||||||
wall_connector_power: 0.,
|
wall_connector_power: 0,
|
||||||
wall_connector_fault_state: 2,
|
wall_connector_fault_state: 2,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,6 +32,7 @@ const API_URL: &str = if cfg!(debug_assertions) {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait VehicleApi {
|
pub trait VehicleApi {
|
||||||
|
async fn vehicles(&self) -> Result<Vec<Vehicle>, TeslatteError>;
|
||||||
async fn vehicle_data(
|
async fn vehicle_data(
|
||||||
&self,
|
&self,
|
||||||
get_vehicle_data: &GetVehicleData,
|
get_vehicle_data: &GetVehicleData,
|
||||||
|
@ -156,6 +157,8 @@ pub trait FleetVehicleApi {
|
||||||
async fn remote_start_drive(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
async fn remote_start_drive(&self, vin: &str) -> Result<PostResponse, TeslatteError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait EnergySitesApi {}
|
||||||
|
|
||||||
trait ApiValues {
|
trait ApiValues {
|
||||||
fn format(&self, url: &str) -> String;
|
fn format(&self, url: &str) -> String;
|
||||||
}
|
}
|
||||||
|
@ -499,7 +502,6 @@ struct ResponseError {
|
||||||
struct Empty {}
|
struct Empty {}
|
||||||
|
|
||||||
/// GET /api/1/[url]
|
/// GET /api/1/[url]
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! get {
|
macro_rules! get {
|
||||||
($name:ident, $return_type:ty, $url:expr) => {
|
($name:ident, $return_type:ty, $url:expr) => {
|
||||||
async fn $name(&self) -> Result<$return_type, crate::error::TeslatteError> {
|
async fn $name(&self) -> Result<$return_type, crate::error::TeslatteError> {
|
||||||
|
@ -510,7 +512,6 @@ macro_rules! get {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use get;
|
pub(crate) use get;
|
||||||
|
|
||||||
/// Same as get, but public.
|
/// Same as get, but public.
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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::vehicle::VehicleArgs;
|
use teslatte::cli::vehicle::VehicleArgs;
|
||||||
use teslatte::{OwnerApi, PrintResponses};
|
use teslatte::{OwnerApi, PrintResponses, VehicleApi};
|
||||||
|
|
||||||
/// Teslatte
|
/// Teslatte
|
||||||
///
|
///
|
||||||
|
@ -106,7 +106,7 @@ async fn main() -> miette::Result<()> {
|
||||||
api.print_responses = PrintResponses::Pretty;
|
api.print_responses = PrintResponses::Pretty;
|
||||||
match api_args.command {
|
match api_args.command {
|
||||||
ApiCommand::Vehicles => {
|
ApiCommand::Vehicles => {
|
||||||
panic!("Tesla API has changed. This command is no longer works with the Owners API.");
|
api.vehicles().await?;
|
||||||
}
|
}
|
||||||
ApiCommand::Vehicle(v) => {
|
ApiCommand::Vehicle(v) => {
|
||||||
v.run(&api).await?;
|
v.run(&api).await?;
|
||||||
|
|
123
src/products.rs
123
src/products.rs
|
@ -4,8 +4,7 @@ use crate::powerwall::PowerwallId;
|
||||||
use crate::vehicles::VehicleData;
|
use crate::vehicles::VehicleData;
|
||||||
use crate::{pub_get, FleetApi, OwnerApi};
|
use crate::{pub_get, FleetApi, OwnerApi};
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -34,44 +33,14 @@ impl FromStr for EnergySiteId {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct GatewayId(String);
|
pub struct GatewayId(String);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum Product {
|
pub enum Product {
|
||||||
Vehicle(Box<VehicleData>),
|
Vehicle(Box<VehicleData>),
|
||||||
Solar(Box<SolarData>),
|
Solar(Box<SolarData>),
|
||||||
Powerwall(Box<PowerwallData>),
|
Powerwall(Box<PowerwallData>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_product<'de, D>(deserializer: D) -> Result<Product, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let v = Value::deserialize(deserializer)?;
|
|
||||||
|
|
||||||
if v.get("vehicle_id").is_some() {
|
|
||||||
let vehicle_data = VehicleData::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
||||||
Ok(Product::Vehicle(Box::new(vehicle_data)))
|
|
||||||
} else if v.get("solar_type").is_some() {
|
|
||||||
let solar_data = SolarData::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
||||||
Ok(Product::Solar(Box::new(solar_data)))
|
|
||||||
} else if v.get("battery_type").is_some() {
|
|
||||||
let powerwall_data = PowerwallData::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
||||||
Ok(Product::Powerwall(Box::new(powerwall_data)))
|
|
||||||
} else {
|
|
||||||
Err(serde::de::Error::custom(
|
|
||||||
"No valid key found to determine the product type",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Product {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Product, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserialize_product(deserializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is assumed from https://tesla-api.timdorr.com/api-basics/products
|
/// This is assumed from https://tesla-api.timdorr.com/api-basics/products
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct SolarData {
|
pub struct SolarData {
|
||||||
|
@ -97,25 +66,14 @@ pub struct PowerwallData {
|
||||||
pub id: PowerwallId,
|
pub id: PowerwallId,
|
||||||
pub gateway_id: GatewayId,
|
pub gateway_id: GatewayId,
|
||||||
pub asset_site_id: String,
|
pub asset_site_id: String,
|
||||||
|
pub energy_left: f64,
|
||||||
|
pub total_pack_energy: i64,
|
||||||
pub percentage_charged: f64,
|
pub percentage_charged: f64,
|
||||||
pub backup_capable: bool,
|
pub backup_capable: bool,
|
||||||
pub battery_power: i64,
|
pub battery_power: i64,
|
||||||
pub sync_grid_alert_enabled: bool,
|
pub sync_grid_alert_enabled: bool,
|
||||||
pub breaker_alert_enabled: bool,
|
pub breaker_alert_enabled: bool,
|
||||||
pub components: Components,
|
pub components: Components,
|
||||||
// New fields as of 2024-01-20
|
|
||||||
pub powerwall_onboarding_settings_set: bool,
|
|
||||||
pub storm_mode_enabled: bool,
|
|
||||||
pub features: PowerwallFeatures,
|
|
||||||
pub warp_site_number: String,
|
|
||||||
pub go_off_grid_test_banner_enabled: Option<bool>,
|
|
||||||
pub powerwall_tesla_electric_interested_in: Option<bool>,
|
|
||||||
pub vpp_tour_enabled: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
pub struct PowerwallFeatures {
|
|
||||||
pub rate_plan_manager_no_pricing_constraint: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -126,7 +84,7 @@ pub struct Components {
|
||||||
pub solar_type: Option<String>,
|
pub solar_type: Option<String>,
|
||||||
pub grid: bool,
|
pub grid: bool,
|
||||||
pub load_meter: bool,
|
pub load_meter: bool,
|
||||||
pub market_type: Option<String>,
|
pub market_type: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub wall_connectors: Vec<WallConnector>,
|
pub wall_connectors: Vec<WallConnector>,
|
||||||
}
|
}
|
||||||
|
@ -135,9 +93,60 @@ pub struct Components {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod};
|
use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod};
|
||||||
use crate::{ApiValues, PrintResponses, RequestData};
|
use crate::ApiValues;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn energy_match_powerwall() {
|
||||||
|
let json = r#"
|
||||||
|
{
|
||||||
|
"energy_site_id": 1032748243,
|
||||||
|
"resource_type": "battery",
|
||||||
|
"site_name": "1 Railway Pde",
|
||||||
|
"id": "ABC2010-1234",
|
||||||
|
"gateway_id": "3287423824-QWE",
|
||||||
|
"asset_site_id": "123ecd-123ecd-12345-12345",
|
||||||
|
"energy_left": 4394.000000000001,
|
||||||
|
"total_pack_energy": 13494,
|
||||||
|
"percentage_charged": 32.562620423892106,
|
||||||
|
"battery_type": "ac_powerwall",
|
||||||
|
"backup_capable": true,
|
||||||
|
"battery_power": -280,
|
||||||
|
"sync_grid_alert_enabled": true,
|
||||||
|
"breaker_alert_enabled": false,
|
||||||
|
"components": {
|
||||||
|
"battery": true,
|
||||||
|
"battery_type": "ac_powerwall",
|
||||||
|
"solar": true,
|
||||||
|
"solar_type": "pv_panel",
|
||||||
|
"grid": true,
|
||||||
|
"load_meter": true,
|
||||||
|
"market_type": "residential"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
if let Product::Powerwall(data) = serde_json::from_str(json).unwrap() {
|
||||||
|
assert_eq!(data.battery_type, "ac_powerwall");
|
||||||
|
assert!(data.backup_capable);
|
||||||
|
assert_eq!(data.battery_power, -280);
|
||||||
|
assert!(data.sync_grid_alert_enabled);
|
||||||
|
assert!(!data.breaker_alert_enabled);
|
||||||
|
assert!(data.components.battery);
|
||||||
|
assert_eq!(
|
||||||
|
data.components.battery_type,
|
||||||
|
Some("ac_powerwall".to_string())
|
||||||
|
);
|
||||||
|
assert!(data.components.solar);
|
||||||
|
assert_eq!(data.components.solar_type, Some("pv_panel".to_string()));
|
||||||
|
assert!(data.components.grid);
|
||||||
|
assert!(data.components.load_meter);
|
||||||
|
assert_eq!(data.components.market_type, "residential");
|
||||||
|
} else {
|
||||||
|
panic!("Expected PowerwallData");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn energy_match_vehicle() {
|
fn energy_match_vehicle() {
|
||||||
let json = r#"
|
let json = r#"
|
||||||
|
@ -265,20 +274,4 @@ mod tests {
|
||||||
"https://base.com/e/123/history?period=month&kind=energy&start_date=2020-01-01T00:00:00Z&end_date=2020-01-31T23:59:59Z"
|
"https://base.com/e/123/history?period=month&kind=energy&start_date=2020-01-01T00:00:00Z&end_date=2020-01-31T23:59:59Z"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn json_products_gak_2024_01_20() {
|
|
||||||
let s = include_str!("../testdata/products_gak_2024_01_20.json");
|
|
||||||
let request_data = RequestData::Get { url: "" };
|
|
||||||
OwnerApi::parse_json::<Vec<Product>>(&request_data, s.to_string(), PrintResponses::Pretty)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn json_products_gak_2024_04_12() {
|
|
||||||
let s = include_str!("../testdata/products_gak_2024_04_12.json");
|
|
||||||
let request_data = RequestData::Get { url: "" };
|
|
||||||
OwnerApi::parse_json::<Vec<Product>>(&request_data, s.to_string(), PrintResponses::Pretty)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use strum::{Display, EnumString};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl VehicleApi for OwnerApi {
|
impl VehicleApi for OwnerApi {
|
||||||
|
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
||||||
get_args!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", GetVehicleData);
|
get_args!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", GetVehicleData);
|
||||||
post_arg_empty!(wake_up, "/vehicles/{}/command/wake_up", VehicleId);
|
post_arg_empty!(wake_up, "/vehicles/{}/command/wake_up", VehicleId);
|
||||||
|
|
||||||
|
@ -546,6 +547,9 @@ pub struct GranularAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Vehicles(Vec<Vehicle>);
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Vehicle {
|
pub struct Vehicle {
|
||||||
pub id: VehicleId,
|
pub id: VehicleId,
|
||||||
|
|
|
@ -4,18 +4,18 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = "0.11.23"
|
reqwest = "0.11.22"
|
||||||
tokio = { version = "1.35.1", features = ["full"] }
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
clap = { version = "4.4.6", features = ["derive"] }
|
||||||
scraper = "0.18.1"
|
scraper = "0.17.1"
|
||||||
strum = { version = "0.25.0", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.17"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.75"
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.189", features = ["derive"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.107"
|
||||||
heck = "0.5.0-rc.1"
|
heck = "0.5.0-rc.1"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
syn = { version = "2.0.48" , features = ["full", "extra-traits"] }
|
syn = { version = "2.0.38" , features = ["full", "extra-traits"] }
|
118
testdata/products_gak_2024_01_20.json
vendored
118
testdata/products_gak_2024_01_20.json
vendored
|
@ -1,118 +0,0 @@
|
||||||
{
|
|
||||||
"count": 2,
|
|
||||||
"response": [
|
|
||||||
{
|
|
||||||
"access_type": "OWNER",
|
|
||||||
"api_version": 71,
|
|
||||||
"backseat_token": null,
|
|
||||||
"backseat_token_updated_at": null,
|
|
||||||
"ble_autopair_enrolled": false,
|
|
||||||
"cached_data": "",
|
|
||||||
"calendar_enabled": true,
|
|
||||||
"color": null,
|
|
||||||
"command_signing": "required",
|
|
||||||
"display_name": "Kool Beans",
|
|
||||||
"granular_access": {
|
|
||||||
"hide_private": false
|
|
||||||
},
|
|
||||||
"id": 1,
|
|
||||||
"id_s": "1",
|
|
||||||
"in_service": false,
|
|
||||||
"option_codes": null,
|
|
||||||
"release_notes_supported": true,
|
|
||||||
"state": "online",
|
|
||||||
"tokens": [
|
|
||||||
],
|
|
||||||
"user_id": 2,
|
|
||||||
"vehicle_config": {
|
|
||||||
"aux_park_lamps": "Eu",
|
|
||||||
"badge_version": 0,
|
|
||||||
"can_accept_navigation_requests": true,
|
|
||||||
"can_actuate_trunks": true,
|
|
||||||
"car_special_type": "base",
|
|
||||||
"car_type": "model3",
|
|
||||||
"charge_port_type": "CCS",
|
|
||||||
"cop_user_set_temp_supported": false,
|
|
||||||
"dashcam_clip_save_supported": true,
|
|
||||||
"default_charge_to_max": false,
|
|
||||||
"driver_assist": "TeslaAP3",
|
|
||||||
"ece_restrictions": false,
|
|
||||||
"efficiency_package": "M32021",
|
|
||||||
"eu_vehicle": true,
|
|
||||||
"exterior_color": "MidnightSilver",
|
|
||||||
"exterior_trim": "Black",
|
|
||||||
"exterior_trim_override": "",
|
|
||||||
"has_air_suspension": false,
|
|
||||||
"has_ludicrous_mode": false,
|
|
||||||
"has_seat_cooling": false,
|
|
||||||
"headlamp_type": "Global",
|
|
||||||
"interior_trim_type": "Black2",
|
|
||||||
"key_version": 2,
|
|
||||||
"motorized_charge_port": true,
|
|
||||||
"paint_color_override": "24,24,27,0.9,0.3",
|
|
||||||
"performance_package": "Base",
|
|
||||||
"plg": true,
|
|
||||||
"pws": false,
|
|
||||||
"rear_drive_unit": "PM216MOSFET",
|
|
||||||
"rear_seat_heaters": 1,
|
|
||||||
"rear_seat_type": 0,
|
|
||||||
"rhd": true,
|
|
||||||
"roof_color": "RoofColorGlass",
|
|
||||||
"seat_type": null,
|
|
||||||
"spoiler_type": "None",
|
|
||||||
"sun_roof_installed": null,
|
|
||||||
"supports_qr_pairing": false,
|
|
||||||
"third_row_seats": "None",
|
|
||||||
"timestamp": 1705717676876,
|
|
||||||
"trim_badging": "74d",
|
|
||||||
"use_range_badging": true,
|
|
||||||
"utc_offset": 36000,
|
|
||||||
"webcam_selfie_supported": true,
|
|
||||||
"webcam_supported": true,
|
|
||||||
"wheel_type": "StilettoRefresh19"
|
|
||||||
},
|
|
||||||
"vehicle_id": 1,
|
|
||||||
"vin": "LR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asset_site_id": "",
|
|
||||||
"backup_capable": true,
|
|
||||||
"battery_power": 3520,
|
|
||||||
"battery_type": "ac_powerwall",
|
|
||||||
"breaker_alert_enabled": true,
|
|
||||||
"components": {
|
|
||||||
"battery": true,
|
|
||||||
"battery_type": "ac_powerwall",
|
|
||||||
"grid": true,
|
|
||||||
"load_meter": true,
|
|
||||||
"solar": true,
|
|
||||||
"solar_type": "pv_panel",
|
|
||||||
"wall_connectors": [
|
|
||||||
{
|
|
||||||
"device_id": "",
|
|
||||||
"din": "",
|
|
||||||
"is_active": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"energy_left": 13136.26315789474,
|
|
||||||
"energy_site_id": 247079748146,
|
|
||||||
"features": {
|
|
||||||
"rate_plan_manager_no_pricing_constraint": true
|
|
||||||
},
|
|
||||||
"gateway_id": "a",
|
|
||||||
"go_off_grid_test_banner_enabled": null,
|
|
||||||
"id": "1234",
|
|
||||||
"percentage_charged": 31.52375310862408,
|
|
||||||
"powerwall_onboarding_settings_set": true,
|
|
||||||
"powerwall_tesla_electric_interested_in": null,
|
|
||||||
"resource_type": "battery",
|
|
||||||
"site_name": "",
|
|
||||||
"storm_mode_enabled": true,
|
|
||||||
"sync_grid_alert_enabled": true,
|
|
||||||
"total_pack_energy": 41671,
|
|
||||||
"vpp_tour_enabled": null,
|
|
||||||
"warp_site_number": "STE00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
70
testdata/products_gak_2024_04_12.json
vendored
70
testdata/products_gak_2024_04_12.json
vendored
|
@ -1,70 +0,0 @@
|
||||||
{
|
|
||||||
"count": 2,
|
|
||||||
"response": [
|
|
||||||
{
|
|
||||||
"access_type": "OWNER",
|
|
||||||
"api_version": 73,
|
|
||||||
"backseat_token": null,
|
|
||||||
"backseat_token_updated_at": null,
|
|
||||||
"ble_autopair_enrolled": false,
|
|
||||||
"cached_data": "",
|
|
||||||
"calendar_enabled": true,
|
|
||||||
"color": null,
|
|
||||||
"command_signing": "required",
|
|
||||||
"display_name": "Kool Beans",
|
|
||||||
"granular_access": {
|
|
||||||
"hide_private": false
|
|
||||||
},
|
|
||||||
"id": 123,
|
|
||||||
"id_s": "123",
|
|
||||||
"in_service": false,
|
|
||||||
"option_codes": null,
|
|
||||||
"release_notes_supported": true,
|
|
||||||
"state": "online",
|
|
||||||
"tokens": [],
|
|
||||||
"user_id": 123,
|
|
||||||
"vehicle_id": 123,
|
|
||||||
"vin": "LRW"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"asset_site_id": "qwfp",
|
|
||||||
"backup_capable": true,
|
|
||||||
"battery_power": -1590,
|
|
||||||
"battery_type": "ac_powerwall",
|
|
||||||
"breaker_alert_enabled": true,
|
|
||||||
"components": {
|
|
||||||
"battery": true,
|
|
||||||
"battery_type": "ac_powerwall",
|
|
||||||
"grid": true,
|
|
||||||
"load_meter": true,
|
|
||||||
"market_type": "residential",
|
|
||||||
"solar": true,
|
|
||||||
"solar_type": "pv_panel",
|
|
||||||
"wall_connectors": [
|
|
||||||
{
|
|
||||||
"device_id": "qwfp",
|
|
||||||
"din": "qwfp",
|
|
||||||
"is_active": true,
|
|
||||||
"part_number": "1529455-02-D"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"energy_site_id": 1234,
|
|
||||||
"features": {
|
|
||||||
"rate_plan_manager_no_pricing_constraint": true
|
|
||||||
},
|
|
||||||
"gateway_id": "qwfp",
|
|
||||||
"go_off_grid_test_banner_enabled": null,
|
|
||||||
"id": "qwfp",
|
|
||||||
"percentage_charged": 48.872760297620715,
|
|
||||||
"powerwall_onboarding_settings_set": true,
|
|
||||||
"powerwall_tesla_electric_interested_in": null,
|
|
||||||
"resource_type": "battery",
|
|
||||||
"site_name": "",
|
|
||||||
"storm_mode_enabled": true,
|
|
||||||
"sync_grid_alert_enabled": true,
|
|
||||||
"vpp_tour_enabled": null,
|
|
||||||
"warp_site_number": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Loading…
Reference in a new issue