feat: rename and add api calls (energy_sites)

- rename api/cli energy_sites to products (to match the api)
- add energy_sites site_status
- add energy_sites live_status
- add energy_sites site_info
This commit is contained in:
gak 2023-09-05 13:48:16 +10:00
parent cc01e30c63
commit a64a04e3d7
No known key found for this signature in database
10 changed files with 307 additions and 125 deletions

View file

@ -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!");
}
}

View file

@ -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

View file

@ -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<DateTime<FixedOffset>>,
pub end_date: Option<DateTime<FixedOffset>>,
}
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<String>,
pub installation_time_zone: String,
/// Optional because if there are no `Series` fields, this field is omitted.
pub time_series: Option<Vec<Series>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum Series {
Power(PowerSeries),
Energy(EnergySeries),
}
#[derive(Debug, Clone, Deserialize)]
pub struct PowerSeries {
pub timestamp: DateTime<FixedOffset>,
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<FixedOffset>,
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,
}

View file

@ -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

View file

@ -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};

239
src/energy_sites.rs Normal file
View file

@ -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<i64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TouSettings {
pub optimization_strategy: String,
pub schedule: Vec<Schedule>,
}
#[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<DateTime<FixedOffset>>,
pub end_date: Option<DateTime<FixedOffset>>,
}
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<String>,
pub installation_time_zone: String,
/// Optional because if there are no `Series` fields, this field is omitted.
pub time_series: Option<Vec<Series>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum Series {
Power(PowerSeries),
Energy(EnergySeries),
}
#[derive(Debug, Clone, Deserialize)]
pub struct PowerSeries {
pub timestamp: DateTime<FixedOffset>,
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<FixedOffset>,
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,
}

View file

@ -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")]

View file

@ -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?;

View file

@ -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};

View file

@ -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<EnergySite>, "/products");
get!(products, Vec<Product>, "/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<VehicleData>),
Solar(Box<SolarData>),
Powerwall(Box<PowerwallData>),
@ -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);