wip: energy/powerwall api
This commit is contained in:
parent
17d8b70331
commit
8b7410729e
100
examples/cli.rs
100
examples/cli.rs
|
@ -1,11 +1,12 @@
|
||||||
|
mod cli_vehicle;
|
||||||
|
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
use cli_vehicle::VehicleArgs;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use teslatte::auth::{AccessToken, Authentication, RefreshToken};
|
use teslatte::auth::{AccessToken, Authentication, RefreshToken};
|
||||||
|
use teslatte::powerwall::PowerwallId;
|
||||||
use teslatte::vehicles::{SetChargeLimit, SetChargingAmps};
|
use teslatte::vehicles::{SetChargeLimit, SetChargingAmps};
|
||||||
use teslatte::{Api, Id};
|
use teslatte::{Api, VehicleId};
|
||||||
|
|
||||||
const TESLA_ACCESS_TOKEN: &str = "TESLA_ACCESS_TOKEN";
|
|
||||||
const TESLA_REFRESH_TOKEN: &str = "TESLA_REFRESH_TOKEN";
|
|
||||||
|
|
||||||
/// Teslatte
|
/// Teslatte
|
||||||
///
|
///
|
||||||
|
@ -52,81 +53,54 @@ struct ApiArgs {
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum ApiCommand {
|
enum ApiCommand {
|
||||||
/// Get a list of vehicles.
|
/// List of vehicles.
|
||||||
Vehicles,
|
Vehicles,
|
||||||
|
|
||||||
/// Specific Vehicle.
|
/// Specific Vehicle.
|
||||||
Vehicle(Vehicle),
|
Vehicle(VehicleArgs),
|
||||||
|
|
||||||
|
/// List of energy sites.
|
||||||
|
EnergySites,
|
||||||
|
|
||||||
|
/// Powerwall queries.
|
||||||
|
Powerwall(PowerwallArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
struct Vehicle {
|
struct PowerwallArgs {
|
||||||
pub id: Id,
|
pub id: PowerwallId,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub command: VehicleCommand,
|
pub command: PowerwallCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vehicle {
|
impl PowerwallArgs {
|
||||||
async fn run(self, api: &Api) {
|
pub async fn run(&self, api: &Api) -> miette::Result<()> {
|
||||||
match self.command {
|
match self.command {
|
||||||
VehicleCommand::Data => {
|
PowerwallCommand::Status => {
|
||||||
dbg!(api.vehicle_data(&self.id).await.unwrap());
|
dbg!(api.powerwall_status(&self.id).await?);
|
||||||
}
|
|
||||||
VehicleCommand::ChargeState => {
|
|
||||||
dbg!(api.charge_state(&self.id).await.unwrap());
|
|
||||||
}
|
|
||||||
VehicleCommand::SetChargeLimit { percent } => {
|
|
||||||
dbg!(api
|
|
||||||
.set_charge_limit(&self.id, &SetChargeLimit { percent })
|
|
||||||
.await
|
|
||||||
.unwrap());
|
|
||||||
}
|
|
||||||
VehicleCommand::SetChargingAmps { charging_amps } => {
|
|
||||||
dbg!(api
|
|
||||||
.set_charging_amps(&self.id, &SetChargingAmps { charging_amps })
|
|
||||||
.await
|
|
||||||
.unwrap());
|
|
||||||
}
|
|
||||||
VehicleCommand::ChargeStart => {
|
|
||||||
dbg!(api.charge_start(&self.id).await.unwrap());
|
|
||||||
}
|
|
||||||
VehicleCommand::ChargeStop => {
|
|
||||||
dbg!(api.charge_stop(&self.id).await.unwrap());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum VehicleCommand {
|
enum PowerwallCommand {
|
||||||
/// Get vehicle data.
|
/// Show the status of the Powerwall.
|
||||||
Data,
|
Status,
|
||||||
|
|
||||||
/// Get charge state.
|
|
||||||
ChargeState,
|
|
||||||
|
|
||||||
/// Set charge limit.
|
|
||||||
SetChargeLimit { percent: u8 },
|
|
||||||
|
|
||||||
/// Set charge amps.
|
|
||||||
SetChargingAmps { charging_amps: i64 },
|
|
||||||
|
|
||||||
/// Start charging.
|
|
||||||
ChargeStart,
|
|
||||||
|
|
||||||
/// Stop charging.
|
|
||||||
ChargeStop,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> miette::Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Command::Auth { save } => {
|
Command::Auth { save } => {
|
||||||
let auth = Authentication::new().unwrap();
|
let auth = Authentication::new()?;
|
||||||
let (access_token, refresh_token) = auth.interactive_get_access_token().await.unwrap();
|
let (access_token, refresh_token) = auth.interactive_get_access_token().await?;
|
||||||
updated_tokens(save, access_token, refresh_token);
|
updated_tokens(save, access_token, refresh_token);
|
||||||
}
|
}
|
||||||
Command::Refresh { refresh_token } => {
|
Command::Refresh { refresh_token } => {
|
||||||
|
@ -138,8 +112,8 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let auth = Authentication::new().unwrap();
|
let auth = Authentication::new()?;
|
||||||
let response = auth.refresh_access_token(&refresh_token).await.unwrap();
|
let response = auth.refresh_access_token(&refresh_token).await?;
|
||||||
updated_tokens(save, response.access_token, refresh_token);
|
updated_tokens(save, response.access_token, refresh_token);
|
||||||
}
|
}
|
||||||
Command::Api(api_args) => {
|
Command::Api(api_args) => {
|
||||||
|
@ -154,15 +128,21 @@ async fn main() {
|
||||||
let api = Api::new(&access_token);
|
let api = Api::new(&access_token);
|
||||||
match api_args.command {
|
match api_args.command {
|
||||||
ApiCommand::Vehicles => {
|
ApiCommand::Vehicles => {
|
||||||
let vehicles = api.vehicles().await.unwrap();
|
dbg!(api.vehicles().await?);
|
||||||
dbg!(&vehicles);
|
|
||||||
}
|
}
|
||||||
ApiCommand::Vehicle(v) => {
|
ApiCommand::Vehicle(v) => {
|
||||||
v.run(&api).await;
|
v.run(&api).await?;
|
||||||
|
}
|
||||||
|
ApiCommand::EnergySites => {
|
||||||
|
dbg!(api.energy_sites().await?);
|
||||||
|
}
|
||||||
|
ApiCommand::Powerwall(p) => {
|
||||||
|
p.run(&api).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn updated_tokens(save: bool, access_token: AccessToken, refresh_token: RefreshToken) {
|
fn updated_tokens(save: bool, access_token: AccessToken, refresh_token: RefreshToken) {
|
||||||
|
|
64
examples/cli_vehicle.rs
Normal file
64
examples/cli_vehicle.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
use teslatte::vehicles::{SetChargeLimit, SetChargingAmps};
|
||||||
|
use teslatte::{Api, VehicleId};
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct VehicleArgs {
|
||||||
|
pub id: VehicleId,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub command: VehicleCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VehicleArgs {
|
||||||
|
pub async fn run(self, api: &Api) -> miette::Result<()> {
|
||||||
|
match self.command {
|
||||||
|
VehicleCommand::Data => {
|
||||||
|
dbg!(api.vehicle_data(&self.id).await?);
|
||||||
|
}
|
||||||
|
VehicleCommand::ChargeState => {
|
||||||
|
dbg!(api.charge_state(&self.id).await?);
|
||||||
|
}
|
||||||
|
VehicleCommand::SetChargeLimit { percent } => {
|
||||||
|
dbg!(
|
||||||
|
api.set_charge_limit(&self.id, &SetChargeLimit { percent })
|
||||||
|
.await?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
VehicleCommand::SetChargingAmps { charging_amps } => {
|
||||||
|
dbg!(
|
||||||
|
api.set_charging_amps(&self.id, &SetChargingAmps { charging_amps })
|
||||||
|
.await?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
VehicleCommand::ChargeStart => {
|
||||||
|
dbg!(api.charge_start(&self.id).await?);
|
||||||
|
}
|
||||||
|
VehicleCommand::ChargeStop => {
|
||||||
|
dbg!(api.charge_stop(&self.id).await?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum VehicleCommand {
|
||||||
|
/// Get vehicle data.
|
||||||
|
Data,
|
||||||
|
|
||||||
|
/// Get charge state.
|
||||||
|
ChargeState,
|
||||||
|
|
||||||
|
/// Set charge limit.
|
||||||
|
SetChargeLimit { percent: u8 },
|
||||||
|
|
||||||
|
/// Set charge amps.
|
||||||
|
SetChargingAmps { charging_amps: i64 },
|
||||||
|
|
||||||
|
/// Start charging.
|
||||||
|
ChargeStart,
|
||||||
|
|
||||||
|
/// Stop charging.
|
||||||
|
ChargeStop,
|
||||||
|
}
|
221
src/energy.rs
Normal file
221
src/energy.rs
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
use crate::error::TeslatteError;
|
||||||
|
use crate::powerwall::PowerwallId;
|
||||||
|
use crate::vehicles::VehicleData;
|
||||||
|
use crate::{get, get_arg, post_arg, post_arg_empty, Api, Empty, ExternalVehicleId, VehicleId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl Api {
|
||||||
|
get!(energy_sites, Vec<EnergySite>, "/products");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct EnergySiteId(u64);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct GatewayId(String);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum EnergySite {
|
||||||
|
Vehicle(VehicleData),
|
||||||
|
Solar(SolarData),
|
||||||
|
Powerwall(PowerwallData),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is assumed from https://tesla-api.timdorr.com/api-basics/products
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct SolarData {
|
||||||
|
// `solar_type` must be first in the struct so serde can properly decode.
|
||||||
|
pub solar_type: String,
|
||||||
|
pub energy_site_id: EnergySiteId,
|
||||||
|
/// Should always be "solar".
|
||||||
|
pub resource_type: String,
|
||||||
|
pub id: String,
|
||||||
|
pub asset_site_id: String,
|
||||||
|
pub solar_power: i64,
|
||||||
|
pub sync_grid_alert_enabled: bool,
|
||||||
|
pub breaker_alert_enabled: bool,
|
||||||
|
pub components: Components,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct PowerwallData {
|
||||||
|
// `battery_type` must be first in the struct so serde can properly decode.
|
||||||
|
pub battery_type: String,
|
||||||
|
pub energy_site_id: i64,
|
||||||
|
/// Should always be "battery".
|
||||||
|
pub resource_type: String,
|
||||||
|
pub site_name: String,
|
||||||
|
pub id: PowerwallId,
|
||||||
|
pub gateway_id: GatewayId,
|
||||||
|
pub asset_site_id: String,
|
||||||
|
pub energy_left: f64,
|
||||||
|
pub total_pack_energy: i64,
|
||||||
|
pub percentage_charged: f64,
|
||||||
|
pub backup_capable: bool,
|
||||||
|
pub battery_power: i64,
|
||||||
|
pub sync_grid_alert_enabled: bool,
|
||||||
|
pub breaker_alert_enabled: bool,
|
||||||
|
pub components: Components,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct Components {
|
||||||
|
pub battery: bool,
|
||||||
|
pub battery_type: Option<String>,
|
||||||
|
pub solar: bool,
|
||||||
|
pub solar_type: Option<String>,
|
||||||
|
pub grid: bool,
|
||||||
|
pub load_meter: bool,
|
||||||
|
pub market_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[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 EnergySite::Powerwall(data) = serde_json::from_str(json).unwrap() {
|
||||||
|
assert_eq!(data.battery_type, "ac_powerwall");
|
||||||
|
assert_eq!(data.backup_capable, true);
|
||||||
|
assert_eq!(data.battery_power, -280);
|
||||||
|
assert_eq!(data.sync_grid_alert_enabled, true);
|
||||||
|
assert_eq!(data.breaker_alert_enabled, false);
|
||||||
|
assert_eq!(data.components.battery, true);
|
||||||
|
assert_eq!(
|
||||||
|
data.components.battery_type,
|
||||||
|
Some("ac_powerwall".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(data.components.solar, true);
|
||||||
|
assert_eq!(data.components.solar_type, Some("pv_panel".to_string()));
|
||||||
|
assert_eq!(data.components.grid, true);
|
||||||
|
assert_eq!(data.components.load_meter, true);
|
||||||
|
assert_eq!(data.components.market_type, "residential");
|
||||||
|
} else {
|
||||||
|
panic!("Expected PowerwallData");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn energy_match_vehicle() {
|
||||||
|
let json = r#"
|
||||||
|
{
|
||||||
|
"id": 1111193485934,
|
||||||
|
"user_id": 2222291283912,
|
||||||
|
"vehicle_id": 333331238921,
|
||||||
|
"vin": "T234567890123456789",
|
||||||
|
"display_name": "My Vehicle",
|
||||||
|
"option_codes": "ASDF,SDFG,DFGH",
|
||||||
|
"color": null,
|
||||||
|
"access_type": "OWNER",
|
||||||
|
"tokens": [
|
||||||
|
"asdf1234"
|
||||||
|
],
|
||||||
|
"state": "online",
|
||||||
|
"in_service": false,
|
||||||
|
"id_s": "932423",
|
||||||
|
"calendar_enabled": true,
|
||||||
|
"api_version": 42,
|
||||||
|
"backseat_token": null,
|
||||||
|
"backseat_token_updated_at": null,
|
||||||
|
"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",
|
||||||
|
"dashcam_clip_save_supported": true,
|
||||||
|
"default_charge_to_max": false,
|
||||||
|
"driver_assist": "TeslaAP3",
|
||||||
|
"ece_restrictions": false,
|
||||||
|
"efficiency_package": "M32026",
|
||||||
|
"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": "255,200,253,0.9,0.3",
|
||||||
|
"performance_package": "Base",
|
||||||
|
"plg": true,
|
||||||
|
"pws": false,
|
||||||
|
"rear_drive_unit": "T15232Z",
|
||||||
|
"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": 1658390117642,
|
||||||
|
"trim_badging": "9",
|
||||||
|
"use_range_badging": true,
|
||||||
|
"utc_offset": 0,
|
||||||
|
"webcam_supported": false,
|
||||||
|
"wheel_type": "StilettoRefresh19"
|
||||||
|
},
|
||||||
|
"command_signing": "allowed"
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let energy_site: EnergySite = serde_json::from_str(json).unwrap();
|
||||||
|
if let EnergySite::Vehicle(v) = energy_site {
|
||||||
|
assert_eq!(v.id.0, 1111193485934);
|
||||||
|
assert_eq!(v.user_id, 2222291283912);
|
||||||
|
assert_eq!(v.vehicle_id.0, 333331238921);
|
||||||
|
assert_eq!(v.vin, "T234567890123456789");
|
||||||
|
assert_eq!(v.display_name, "My Vehicle");
|
||||||
|
assert_eq!(v.option_codes, "ASDF,SDFG,DFGH");
|
||||||
|
assert_eq!(v.color, None);
|
||||||
|
assert_eq!(v.access_type, "OWNER");
|
||||||
|
assert_eq!(v.tokens, vec!["asdf1234"]);
|
||||||
|
assert_eq!(v.state, "online");
|
||||||
|
assert_eq!(v.in_service, false);
|
||||||
|
assert_eq!(v.calendar_enabled, true);
|
||||||
|
assert_eq!(v.api_version, 42);
|
||||||
|
assert_eq!(v.backseat_token, None);
|
||||||
|
assert_eq!(v.backseat_token_updated_at, None);
|
||||||
|
assert_eq!(v.vehicle_config.unwrap().aux_park_lamps, "Eu");
|
||||||
|
} else {
|
||||||
|
panic!("Wrong EnergySite");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/lib.rs
13
src/lib.rs
|
@ -8,7 +8,9 @@ use std::str::FromStr;
|
||||||
use tracing::{debug, instrument, trace};
|
use tracing::{debug, instrument, trace};
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod energy;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod powerwall;
|
||||||
pub mod vehicles;
|
pub mod vehicles;
|
||||||
|
|
||||||
const API_URL: &str = "https://owner-api.teslamotors.com";
|
const API_URL: &str = "https://owner-api.teslamotors.com";
|
||||||
|
@ -17,18 +19,18 @@ const API_URL: &str = "https://owner-api.teslamotors.com";
|
||||||
///
|
///
|
||||||
/// This data comes from [`Api::vehicles()`] `id` field.
|
/// This data comes from [`Api::vehicles()`] `id` field.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Id(u64);
|
pub struct VehicleId(u64);
|
||||||
|
|
||||||
impl Display for Id {
|
impl Display for VehicleId {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Id {
|
impl FromStr for VehicleId {
|
||||||
type Err = miette::Error;
|
type Err = miette::Error;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(Id(s.parse().into_diagnostic()?))
|
Ok(VehicleId(s.parse().into_diagnostic()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +38,7 @@ impl FromStr for Id {
|
||||||
///
|
///
|
||||||
/// This data comes from [`Api::vehicles()`] `vehicle_id` field.
|
/// This data comes from [`Api::vehicles()`] `vehicle_id` field.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct VehicleId(u64);
|
pub struct ExternalVehicleId(u64);
|
||||||
|
|
||||||
pub struct Api {
|
pub struct Api {
|
||||||
access_token: AccessToken,
|
access_token: AccessToken,
|
||||||
|
@ -257,7 +259,6 @@ pub(crate) use post_arg_empty;
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::vehicles::ChargeState;
|
use crate::vehicles::ChargeState;
|
||||||
use test_log::test;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error() {
|
fn error() {
|
||||||
|
|
39
src/powerwall.rs
Normal file
39
src/powerwall.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::energy::GatewayId;
|
||||||
|
use crate::error::TeslatteError;
|
||||||
|
use crate::vehicles::VehicleData;
|
||||||
|
use crate::{get, get_arg, post_arg, post_arg_empty, Api, Empty, ExternalVehicleId, VehicleId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl Api {
|
||||||
|
get_arg!(powerwall_status, PowerwallStatus, "/powerwalls/{}/status", PowerwallId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PowerwallId(pub String);
|
||||||
|
|
||||||
|
impl Display for PowerwallId {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PowerwallId {
|
||||||
|
type Err = TeslatteError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(PowerwallId(s.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct PowerwallStatus {
|
||||||
|
pub site_name: String,
|
||||||
|
pub id: GatewayId,
|
||||||
|
pub energy_left: f64,
|
||||||
|
pub total_pack_energy: i64,
|
||||||
|
pub percentage_charged: f64,
|
||||||
|
pub battery_power: i64,
|
||||||
|
}
|
|
@ -1,27 +1,28 @@
|
||||||
use crate::error::TeslatteError;
|
|
||||||
/// Please note that these structs are generated from my own responses.
|
/// Please note that these structs are generated from my own responses.
|
||||||
///
|
///
|
||||||
/// 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::{get, get_arg, post_arg, post_arg_empty, Api, Empty, Id, VehicleId};
|
use crate::error::TeslatteError;
|
||||||
|
use crate::{get, get_arg, post_arg, post_arg_empty, Api, Empty, ExternalVehicleId, VehicleId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl Api {
|
impl Api {
|
||||||
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
get!(vehicles, Vec<Vehicle>, "/vehicles");
|
||||||
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", Id);
|
get_arg!(vehicle_data, VehicleData, "/vehicles/{}/vehicle_data", VehicleId);
|
||||||
get_arg!(charge_state, ChargeState, "/vehicles/{}/data_request/charge_state", Id);
|
get_arg!(charge_state, ChargeState, "/vehicles/{}/data_request/charge_state", VehicleId);
|
||||||
post_arg!(set_charge_limit, SetChargeLimit, "/vehicles/{}/command/set_charge_limit", Id);
|
post_arg!(set_charge_limit, SetChargeLimit, "/vehicles/{}/command/set_charge_limit", VehicleId);
|
||||||
post_arg!(set_charging_amps, SetChargingAmps, "/vehicles/{}/command/set_charging_amps", Id);
|
post_arg!(set_charging_amps, SetChargingAmps, "/vehicles/{}/command/set_charging_amps", VehicleId);
|
||||||
post_arg_empty!(charge_start, "/vehicles/{}/command/charge_start", Id);
|
post_arg_empty!(charge_start, "/vehicles/{}/command/charge_start", VehicleId);
|
||||||
post_arg_empty!(charge_stop, "/vehicles/{}/command/charge_stop", Id);
|
post_arg_empty!(charge_stop, "/vehicles/{}/command/charge_stop", VehicleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct VehicleData {
|
pub struct VehicleData {
|
||||||
pub id: Id,
|
// Leave as first field for serde untagged.
|
||||||
|
pub vehicle_id: ExternalVehicleId,
|
||||||
|
pub id: VehicleId,
|
||||||
pub user_id: i64,
|
pub user_id: i64,
|
||||||
pub vehicle_id: VehicleId,
|
|
||||||
pub vin: String,
|
pub vin: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub option_codes: String,
|
pub option_codes: String,
|
||||||
|
@ -38,12 +39,12 @@ pub struct VehicleData {
|
||||||
pub backseat_token: Option<String>,
|
pub backseat_token: Option<String>,
|
||||||
/// gak: This was null for me, assuming String.
|
/// gak: This was null for me, assuming String.
|
||||||
pub backseat_token_updated_at: Option<String>,
|
pub backseat_token_updated_at: Option<String>,
|
||||||
pub charge_state: ChargeState,
|
pub charge_state: Option<ChargeState>,
|
||||||
pub climate_state: ClimateState,
|
pub climate_state: Option<ClimateState>,
|
||||||
pub drive_state: DriveState,
|
pub drive_state: Option<DriveState>,
|
||||||
pub gui_settings: GuiSettings,
|
pub gui_settings: Option<GuiSettings>,
|
||||||
pub vehicle_config: VehicleConfig,
|
pub vehicle_config: Option<VehicleConfig>,
|
||||||
pub vehicle_state: VehicleState,
|
pub vehicle_state: Option<VehicleState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -300,8 +301,8 @@ pub struct Vehicles(Vec<Vehicle>);
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Vehicle {
|
pub struct Vehicle {
|
||||||
pub id: Id,
|
pub id: VehicleId,
|
||||||
pub vehicle_id: VehicleId,
|
pub vehicle_id: ExternalVehicleId,
|
||||||
pub vin: String,
|
pub vin: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
}
|
}
|
||||||
|
@ -317,9 +318,13 @@ pub struct SetChargeLimit {
|
||||||
pub percent: u8,
|
pub percent: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn json() {
|
mod tests {
|
||||||
let s = r#"
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn json() {
|
||||||
|
let s = r#"
|
||||||
{
|
{
|
||||||
"response": {
|
"response": {
|
||||||
"battery_heater_on": false,
|
"battery_heater_on": false,
|
||||||
|
@ -381,5 +386,6 @@ fn json() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
Api::parse_json::<ChargeState, _>(s, || "req".to_string()).unwrap();
|
Api::parse_json::<ChargeState, _>(s, || "req".to_string()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue