diff --git a/examples/basic.rs b/examples/basic.rs index 0a15f7c..bfe4344 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,5 +1,6 @@ use std::env; use teslatte::auth::AccessToken; +use teslatte::products::Product; use teslatte::Api; #[tokio::main] @@ -16,12 +17,46 @@ async fn main() { }; let vehicles = api.vehicles().await.unwrap(); - dbg!(&vehicles); + dbg!(&*vehicles); if !vehicles.is_empty() { let vehicle_data = api.vehicle_data(&vehicles[0].id).await.unwrap(); - dbg!(vehicle_data); + dbg!(&*vehicle_data); } else { println!("No vehicles found!"); } + + let products = api.products().await.unwrap(); + dbg!(&*products); + + if !products.is_empty() { + for product in &*products { + match product { + Product::Vehicle(v) => { + dbg!(v); + } + + Product::Solar(e) => { + let site_info = api.energy_sites_site_info(&e.energy_site_id).await.unwrap(); + dbg!(&*site_info); + + let live_info = api + .energy_sites_live_status(&e.energy_site_id) + .await + .unwrap(); + dbg!(&*live_info); + } + + Product::Powerwall(p) => { + let live_info = api + .energy_sites_live_status(&p.energy_site_id) + .await + .unwrap(); + dbg!(&*live_info); + } + } + } + } else { + println!("No products found!"); + } } diff --git a/justfile b/justfile index d1bad3b..dfa98d2 100644 --- a/justfile +++ b/justfile @@ -9,7 +9,7 @@ no_token_test: token_tests: cargo run -- api vehicles cargo run --no-default-features --features cli -- api vehicles - cargo run -- api energy-sites + cargo run -- api products publish version: git diff-index --quiet HEAD diff --git a/src/calendar_history.rs b/src/calendar_history.rs deleted file mode 100644 index b2385b4..0000000 --- a/src/calendar_history.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::energy::EnergySiteId; -use crate::Values; -use crate::{get_args, join_query_pairs, rfc3339, Api}; -use chrono::{DateTime, FixedOffset}; -use serde::Deserialize; -use strum::{Display, EnumString, IntoStaticStr}; - -#[rustfmt::skip] -impl Api { - get_args!(energy_sites_calendar_history, CalendarHistory, "/energy_sites/{}/calendar_history", CalendarHistoryValues); -} - -#[derive(Debug, Clone, Display, EnumString, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] -pub enum HistoryKind { - Power, - Energy, -} - -#[derive(Debug, Clone, Display, EnumString, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] -pub enum HistoryPeriod { - Day, - Month, - Year, - Lifetime, -} - -pub struct CalendarHistoryValues { - // Modify URL: - pub site_id: EnergySiteId, - - // Query params: - pub period: HistoryPeriod, - pub kind: HistoryKind, - pub start_date: Option>, - pub end_date: Option>, -} - -impl Values for CalendarHistoryValues { - fn format(&self, url: &str) -> String { - let url = url.replace("{}", &format!("{}", self.site_id.0)); - let mut pairs: Vec<(&str, String)> = vec![ - ("period", self.period.to_string()), - ("kind", self.kind.to_string()), - ]; - if let Some(start_date) = self.start_date { - let start_date = rfc3339(&start_date); - pairs.push(("start_date", start_date)); - } - if let Some(end_date) = self.end_date { - let end_date = rfc3339(&end_date); - pairs.push(("end_date", end_date)); - } - format!("{}?{}", url, join_query_pairs(&pairs)) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct CalendarHistory { - pub serial_number: String, - /// Only appears in energy kind. - pub period: Option, - pub installation_time_zone: String, - /// Optional because if there are no `Series` fields, this field is omitted. - pub time_series: Option>, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum Series { - Power(PowerSeries), - Energy(EnergySeries), -} - -#[derive(Debug, Clone, Deserialize)] -pub struct PowerSeries { - pub timestamp: DateTime, - pub solar_power: f64, - pub battery_power: f64, - pub grid_power: f64, - pub grid_services_power: f64, - pub generator_power: f64, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct EnergySeries { - pub timestamp: DateTime, - pub solar_energy_exported: f64, - pub generator_energy_exported: f64, - pub grid_energy_imported: f64, - pub grid_services_energy_imported: f64, - pub grid_services_energy_exported: f64, - pub grid_energy_exported_from_solar: f64, - pub grid_energy_exported_from_generator: f64, - pub grid_energy_exported_from_battery: f64, - pub battery_energy_exported: f64, - pub battery_energy_imported_from_grid: f64, - pub battery_energy_imported_from_solar: f64, - pub battery_energy_imported_from_generator: f64, - pub consumer_energy_imported_from_grid: f64, - pub consumer_energy_imported_from_solar: f64, - pub consumer_energy_imported_from_battery: f64, - pub consumer_energy_imported_from_generator: f64, -} diff --git a/src/cli/energy.rs b/src/cli/energy.rs index ae3f19b..d383efa 100644 --- a/src/cli/energy.rs +++ b/src/cli/energy.rs @@ -1,6 +1,6 @@ -use crate::calendar_history::{CalendarHistoryValues, HistoryKind, HistoryPeriod}; use crate::cli::print_json; -use crate::energy::EnergySiteId; +use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod}; +use crate::products::EnergySiteId; use crate::Api; use chrono::DateTime; use clap::{Args, Subcommand}; @@ -8,6 +8,9 @@ use miette::{IntoDiagnostic, WrapErr}; #[derive(Debug, Subcommand)] pub enum EnergySiteCommand { + SiteStatus, + LiveStatus, + SiteInfo, CalendarHistory(CalendarHistoryArgs), } @@ -22,6 +25,15 @@ pub struct EnergySiteArgs { impl EnergySiteArgs { pub async fn run(&self, api: &Api) -> miette::Result<()> { match &self.command { + EnergySiteCommand::SiteStatus => { + print_json(api.energy_sites_site_status(&self.id).await); + } + EnergySiteCommand::LiveStatus => { + print_json(api.energy_sites_live_status(&self.id).await); + } + EnergySiteCommand::SiteInfo => { + print_json(api.energy_sites_site_info(&self.id).await); + } EnergySiteCommand::CalendarHistory(args) => { let start_date = args .start diff --git a/src/cli/powerwall.rs b/src/cli/powerwall.rs index 86d51e7..8b5e3e0 100644 --- a/src/cli/powerwall.rs +++ b/src/cli/powerwall.rs @@ -1,5 +1,5 @@ -use crate::calendar_history::{HistoryKind, HistoryPeriod}; use crate::cli::print_json_data; +use crate::energy_sites::{HistoryKind, HistoryPeriod}; use crate::powerwall::{PowerwallEnergyHistoryValues, PowerwallId}; use crate::Api; use clap::{Args, Subcommand}; diff --git a/src/energy_sites.rs b/src/energy_sites.rs new file mode 100644 index 0000000..359866e --- /dev/null +++ b/src/energy_sites.rs @@ -0,0 +1,239 @@ +use crate::products::EnergySiteId; +use crate::{get_arg, get_args, join_query_pairs, rfc3339, Api, Values}; +use chrono::{DateTime, FixedOffset}; +use serde::Deserialize; +use strum::{Display, EnumString, IntoStaticStr}; + +#[rustfmt::skip] +impl Api { + get_arg!(energy_sites_site_status, SiteStatus, "/energy_sites/{}/site_status", EnergySiteId); + get_arg!(energy_sites_live_status, LiveStatus, "/energy_sites/{}/live_status", EnergySiteId); + get_arg!(energy_sites_site_info, SiteInfo, "/energy_sites/{}/site_info", EnergySiteId); + get_args!(energy_sites_calendar_history, CalendarHistory, "/energy_sites/{}/calendar_history", CalendarHistoryValues); +} + +#[derive(Debug, Clone, Deserialize)] +pub struct SiteStatus { + pub backup_capable: bool, + pub battery_power: i64, + pub battery_type: String, + pub breaker_alert_enabled: bool, + pub energy_left: f64, + pub gateway_id: String, + pub percentage_charged: f64, + pub powerwall_onboarding_settings_set: bool, + pub powerwall_tesla_electric_interested_in: Option<()>, // TODO: Unknown type. Was null. + pub resource_type: String, // battery + pub site_name: String, + pub storm_mode_enabled: bool, + pub sync_grid_alert_enabled: bool, + pub total_pack_energy: i64, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct LiveStatus { + pub backup_capable: bool, + pub battery_power: i64, + pub energy_left: f64, + pub generator_power: i64, + pub grid_power: i64, + pub grid_services_active: bool, + pub grid_services_power: i64, + pub grid_status: String, + pub island_status: String, + pub load_power: i64, + pub percentage_charged: f64, + pub solar_power: i64, + pub storm_mode_active: bool, + pub timestamp: String, + pub total_pack_energy: i64, + pub wall_connectors: Vec<()>, // TODO: gak: This is empty so I don't know what it looks like. +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UserSettings { + pub breaker_alert_enabled: bool, + pub powerwall_onboarding_settings_set: bool, + pub powerwall_tesla_electric_interested_in: bool, + pub storm_mode_enabled: bool, + pub sync_grid_alert_enabled: bool, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Schedule { + pub end_seconds: i64, + pub start_seconds: i64, + pub target: String, + pub week_days: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct TouSettings { + pub optimization_strategy: String, + pub schedule: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Geolocation { + pub latitude: f64, + pub longitude: f64, + pub source: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Components { + pub backup: bool, + pub backup_time_remaining_enabled: bool, + pub battery: bool, + pub battery_solar_offset_view_enabled: bool, + pub battery_type: String, + pub car_charging_data_supported: bool, + pub configurable: bool, + pub edit_setting_energy_exports: bool, + pub edit_setting_grid_charging: bool, + pub edit_setting_permission_to_export: bool, + pub energy_service_self_scheduling_enabled: bool, + pub energy_value_header: String, + pub energy_value_subheader: String, + pub flex_energy_request_capable: bool, + pub gateway: String, + pub grid: bool, + pub grid_services_enabled: bool, + pub load_meter: bool, + pub off_grid_vehicle_charging_reserve_supported: bool, + pub set_islanding_mode_enabled: bool, + pub show_grid_import_battery_source_cards: bool, + pub solar: bool, + pub solar_type: String, + pub solar_value_enabled: bool, + pub storm_mode_capable: bool, + pub tou_capable: bool, + pub vehicle_charging_performance_view_enabled: bool, + pub vehicle_charging_solar_offset_view_enabled: bool, + pub wifi_commissioning_enabled: bool, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Address { + pub address_line1: String, + pub city: String, + pub country: String, + pub state: String, + pub zip: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct SiteInfo { + pub address: Address, + pub backup_reserve_percent: i64, + pub battery_count: i64, + pub components: Components, + pub default_real_mode: String, + pub geolocation: Geolocation, + pub id: String, + pub installation_date: String, + pub installation_time_zone: String, + pub max_site_meter_power_ac: i64, + pub min_site_meter_power_ac: i64, + pub nameplate_energy: i64, + pub nameplate_power: i64, + pub site_name: String, + pub tou_settings: TouSettings, + pub user_settings: UserSettings, + pub version: String, + pub vpp_backup_reserve_percent: i64, +} + +#[derive(Debug, Clone, Display, EnumString, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum HistoryKind { + Power, + Energy, +} + +#[derive(Debug, Clone, Display, EnumString, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum HistoryPeriod { + Day, + Month, + Year, + Lifetime, +} + +pub struct CalendarHistoryValues { + // Modify URL: + pub site_id: EnergySiteId, + + // Query params: + pub period: HistoryPeriod, + pub kind: HistoryKind, + pub start_date: Option>, + pub end_date: Option>, +} + +impl Values for CalendarHistoryValues { + fn format(&self, url: &str) -> String { + let url = url.replace("{}", &format!("{}", self.site_id.0)); + let mut pairs: Vec<(&str, String)> = vec![ + ("period", self.period.to_string()), + ("kind", self.kind.to_string()), + ]; + if let Some(start_date) = self.start_date { + let start_date = rfc3339(&start_date); + pairs.push(("start_date", start_date)); + } + if let Some(end_date) = self.end_date { + let end_date = rfc3339(&end_date); + pairs.push(("end_date", end_date)); + } + format!("{}?{}", url, join_query_pairs(&pairs)) + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct CalendarHistory { + pub serial_number: String, + /// Only appears in energy kind. + pub period: Option, + pub installation_time_zone: String, + /// Optional because if there are no `Series` fields, this field is omitted. + pub time_series: Option>, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum Series { + Power(PowerSeries), + Energy(EnergySeries), +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PowerSeries { + pub timestamp: DateTime, + pub solar_power: f64, + pub battery_power: f64, + pub grid_power: f64, + pub grid_services_power: f64, + pub generator_power: f64, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct EnergySeries { + pub timestamp: DateTime, + pub solar_energy_exported: f64, + pub generator_energy_exported: f64, + pub grid_energy_imported: f64, + pub grid_services_energy_imported: f64, + pub grid_services_energy_exported: f64, + pub grid_energy_exported_from_solar: f64, + pub grid_energy_exported_from_generator: f64, + pub grid_energy_exported_from_battery: f64, + pub battery_energy_exported: f64, + pub battery_energy_imported_from_grid: f64, + pub battery_energy_imported_from_solar: f64, + pub battery_energy_imported_from_generator: f64, + pub consumer_energy_imported_from_grid: f64, + pub consumer_energy_imported_from_solar: f64, + pub consumer_energy_imported_from_battery: f64, + pub consumer_energy_imported_from_generator: f64, +} diff --git a/src/lib.rs b/src/lib.rs index 9591a54..bc71400 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,10 @@ use std::fmt::{Debug, Display}; use tracing::debug; pub mod auth; -pub mod calendar_history; -pub mod energy; +pub mod energy_sites; pub mod error; pub mod powerwall; +pub mod products; pub mod vehicles; #[cfg(feature = "cli")] diff --git a/src/main.rs b/src/main.rs index c7736c7..9ba48f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ enum ApiCommand { Vehicle(VehicleArgs), /// List of energy sites. - EnergySites, + Products, /// Specific energy site. EnergySite(EnergySiteArgs), @@ -111,8 +111,8 @@ async fn main() -> miette::Result<()> { ApiCommand::Vehicle(v) => { v.run(&api).await?; } - ApiCommand::EnergySites => { - print_json(api.energy_sites().await); + ApiCommand::Products => { + print_json(api.products().await); } ApiCommand::EnergySite(e) => { e.run(&api).await?; diff --git a/src/powerwall.rs b/src/powerwall.rs index 883f3eb..dd3f233 100644 --- a/src/powerwall.rs +++ b/src/powerwall.rs @@ -1,5 +1,5 @@ -use crate::calendar_history::{HistoryKind, HistoryPeriod}; -use crate::energy::GatewayId; +use crate::energy_sites::{HistoryKind, HistoryPeriod}; +use crate::products::GatewayId; use crate::{get_arg, get_args, join_query_pairs, rfc3339, Api, Values}; use chrono::{DateTime, FixedOffset}; use derive_more::{Display, FromStr}; diff --git a/src/energy.rs b/src/products.rs similarity index 95% rename from src/energy.rs rename to src/products.rs index 5c112f2..e6887ce 100644 --- a/src/energy.rs +++ b/src/products.rs @@ -2,15 +2,16 @@ use crate::error::TeslatteError; use crate::powerwall::PowerwallId; use crate::vehicles::VehicleData; use crate::{get, Api}; +use derive_more::Display; use serde::{Deserialize, Serialize}; use std::str::FromStr; #[rustfmt::skip] impl Api { - get!(energy_sites, Vec, "/products"); + get!(products, Vec, "/products"); } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Display)] pub struct EnergySiteId(pub u64); impl FromStr for EnergySiteId { @@ -28,7 +29,7 @@ pub struct GatewayId(String); #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] -pub enum EnergySite { +pub enum Product { Vehicle(Box), Solar(Box), Powerwall(Box), @@ -83,7 +84,7 @@ pub struct Components { #[cfg(test)] mod tests { use super::*; - use crate::calendar_history::{CalendarHistoryValues, HistoryKind, HistoryPeriod}; + use crate::energy_sites::{CalendarHistoryValues, HistoryKind, HistoryPeriod}; use crate::Values; use chrono::DateTime; @@ -117,7 +118,7 @@ mod tests { } "#; - if let EnergySite::Powerwall(data) = serde_json::from_str(json).unwrap() { + 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); @@ -208,8 +209,8 @@ mod tests { "command_signing": "allowed" } "#; - let energy_site: EnergySite = serde_json::from_str(json).unwrap(); - if let EnergySite::Vehicle(v) = energy_site { + let energy_site: Product = serde_json::from_str(json).unwrap(); + if let Product::Vehicle(v) = energy_site { assert_eq!(v.id.0, 1111193485934); assert_eq!(v.user_id, 2222291283912); assert_eq!(v.vehicle_id.0, 333331238921);