From 38ef815261f0d4e528189812b1b6c97c5d65cc6f Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Sat, 28 May 2022 20:07:27 -0400 Subject: [PATCH] Wrk2 (#12) Added SiteEquipmentList. Added site_get_sensor_list. Added site_get_meters_data Added accounts_list. Fixed some doc holes. Added site_inventory. Added site_equipment_change_log. Added site_inverter_technical_data --- src/accounts_list.rs | 239 ++++++++++++++++++++++++++ src/meter_type.rs | 6 + src/se_ms_api.rs | 99 ++++++++--- src/site_details.rs | 6 +- src/site_equipment_change_log.rs | 81 +++++++++ src/site_equipment_list.rs | 94 ++++++++++ src/site_get_meters_data.rs | 147 ++++++++++++++++ src/site_get_sensor_list.rs | 112 ++++++++++++ src/site_inventory.rs | 207 ++++++++++++++++++++++ src/site_inverter_technical_data.rs | 257 ++++++++++++++++++++++++++++ src/site_list.rs | 38 ++-- src/site_storage_data.rs | 5 + src/sort_order.rs | 31 ++++ tests/integration_reqs_test.rs | 221 +++++++++++++++++++++++- 14 files changed, 1484 insertions(+), 59 deletions(-) create mode 100644 src/accounts_list.rs create mode 100644 src/site_equipment_change_log.rs create mode 100644 src/site_equipment_list.rs create mode 100644 src/site_get_meters_data.rs create mode 100644 src/site_get_sensor_list.rs create mode 100644 src/site_inventory.rs create mode 100644 src/site_inverter_technical_data.rs create mode 100644 src/sort_order.rs diff --git a/src/accounts_list.rs b/src/accounts_list.rs new file mode 100644 index 0000000..11c4a0e --- /dev/null +++ b/src/accounts_list.rs @@ -0,0 +1,239 @@ +//! Module for Return the accounts and list of sub-accounts related to the given token. +//! This API accepts parameters for convenient search, sorting and pagination. + +use crate::{SendReq, SortOrder, MONITORING_API_URL}; +use serde::Deserialize; +use std::collections::HashMap; + +/// accounts_list request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + size: String, + start_index: String, + search_text: String, + sort_property: String, + sort_order: String, +} + +/// A sorting option for this account list, based on one of its properties. +#[derive(Clone, Debug, PartialEq)] +pub enum SortProperty { + /// sort by account name + Name, + + /// sort by account country + Country, + + /// sort by account city + City, + + /// sort by account address + Address, + + /// sort by account zip code + Zip, + + /// sort by account FAX number + Fax, + + /// sort by account phone number + Phone, + + /// sort by account notes + Notes, +} + +impl std::fmt::Display for SortProperty { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + SortProperty::Name => write!(f, "name"), + SortProperty::Country => write!(f, "country"), + SortProperty::City => write!(f, "city"), + SortProperty::Address => write!(f, "address"), + SortProperty::Zip => write!(f, "zip"), + SortProperty::Fax => write!(f, "fax"), + SortProperty::Phone => write!(f, "phone"), + SortProperty::Notes => write!(f, "notes"), + } + } +} + +/// accounts_list response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +pub struct Resp { + /// The sites matching the request. + pub accounts: Accounts, +} + +/// The accounts matching the request. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +pub struct Accounts { + /// The count of matching accounts + pub count: u16, + + /// Array of matching accounts + pub list: Entries, +} + +/// Array of matching accounts +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct Entries { + /// Transparent list of accounts + pub e: Vec, +} + +/// Detailed information for a single account. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AccountDetails { + /// account ID + pub id: u32, + + /// account name + pub name: String, + + /// location associated with account + pub location: AccountLocation, + + /// the company web site + pub company_web_site: String, + + /// the account contact person first name and surname + pub contact_person: String, + + /// the contact person email + pub email: String, + + /// account phone number + pub phone_number: String, + + /// account fax number + pub fax_number: String, + + /// account notes + pub notes: String, + + /// account parent identifier + pub parent_id: u32, + + /// Miscellaneous uris associated with the web page for the site. + pub uris: HashMap, +} + +/// Location of the account. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AccountLocation { + /// Country of the SolarEdge inverter. + pub country: String, + + /// State of the SolarEdge inverter. + pub state: String, // seems US specific. should this be Option? + + /// City of the SolarEdge inverter. + pub city: String, + + /// Address line 1 of the SolarEdge inverter. + pub address: String, + + /// Address line 2 of the SolarEdge inverter. + pub address2: String, + + /// Zip code 1 of the SolarEdge inverter. + pub zip: String, // seems US specific. should this be Option? +} + +impl Req { + /// Create a accounts_list request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * `size` - The maximum number of accounts returned by this call. + /// If you have more than 100 accounts, just request another 100 + /// accounts with startIndex=100. This will fetch accounts 100-199. + /// * `start_index` - The first account index to be returned in the results + /// * `search_text` - Search text for this account + /// * `sort_property` - A sorting option for this account list, based on + /// one of its properties + /// * `sort_order` - Sort order for the sort property + #[must_use] + pub fn new( + size: Option, + start_index: Option, + search_text: Option, + sort_property: Option, + sort_order: Option, + ) -> Self { + let size = match size { + Some(s) => { + if s > 0 && s <= 100 { + format!("size={}&", s) + } else { + "".to_string() + } + } + None => "".to_string(), + }; + + let start_index = match start_index { + Some(si) => format!("startIndex={}&", si), + None => "".to_string(), + }; + + let search_text = match search_text { + Some(st) => format!("searchText={}&", st), + None => "".to_string(), + }; + + let sort_property = match sort_property { + Some(sp) => format!("sortProperty={}&", sp), + None => "".to_string(), + }; + + let sort_order = match sort_order { + Some(so) => format!("sortOrder={}&", so), + None => "".to_string(), + }; + + Req { + size, + start_index, + search_text, + sort_property, + sort_order, + } + } +} + +impl SendReq for Req { + fn build_url(&self, _site_id: &str, api_key: &str) -> String { + format!( + "{}accounts/list?{}{}{}{}{}{}", + *MONITORING_API_URL, + self.size, + self.start_index, + self.search_text, + self.sort_property, + self.sort_order, + api_key, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/meter_type.rs b/src/meter_type.rs index 0c6258a..5cefa86 100644 --- a/src/meter_type.rs +++ b/src/meter_type.rs @@ -33,6 +33,12 @@ impl std::fmt::Display for MeterType { } } +impl Default for MeterType { + fn default() -> MeterType { + MeterType::Production + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 2529fe1..1772c8f 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -36,12 +36,19 @@ //! does not try to be performant. For example, it makes blocking HTTP requests. //! //! Supported API requests/responses include: +//! * [AccountsListReq] / [AccountsListResp] //! * [CurrentVersionReq] / [CurrentVersionResp] //! * [SiteDataPeriodReq] / [SiteDataPeriodResp] //! * [SiteDetailsReq] / [SiteDetailsResp] //! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] //! * [SiteEnvironmentalBenefitsReq] / [SiteEnvironmentalBenefitsResp] +//! * [SiteEquipmentChangeLogReq] / [SiteEquipmentChangeLogResp] +//! * [SiteEquipmentListReq] / [SiteEquipmentListResp] +//! * [SiteGetMetersDataReq] / [SiteGetMetersDataResp] +//! * [SiteGetSensorListReq] / [SiteGetSensorListResp] +//! * [SiteInventoryReq] / [SiteInventoryResp] +//! * [SiteInverterTechnicalDataReq] / [SiteInverterTechnicalDataResp] //! * [SiteListReq] / [SiteListResp] //! * [SiteOverviewReq] / [SiteOverviewResp] //! * [SitePowerReq] / [SitePowerResp] @@ -51,16 +58,9 @@ //! * [SiteTimeFrameEnergyReq] / [SiteTimeFrameEnergyResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! -//! TODO: +//! Unsupported API requests/responses include: //! * SiteImage, //! * SiteInstallerImage, -//! * SiteEquipmentList, -//! * SiteInventory, -//! * SiteInverterTechnicalData, -//! * SiteEquipmentChangeLog, -//! * AccountsList, -//! * SiteMetersData, -//! * SiteSensorList, //! * SiteSensorData #![warn(unused_crate_dependencies)] @@ -70,35 +70,77 @@ #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::doc_markdown)] -pub use current_version::{Req as CurrentVersionReq, Resp as CurrentVersionResp}; -pub use site_data_period::{Req as SiteDataPeriodReq, Resp as SiteDataPeriodResp}; -pub use site_details::{Req as SiteDetailsReq, Resp as SiteDetailsResp}; -pub use site_energy::{Req as SiteEnergyReq, Resp as SiteEnergyResp}; -pub use site_energy_detailed::{Req as SiteEnergyDetailedReq, Resp as SiteEnergyDetailedResp}; -pub use site_environmental_benefits::{ - Req as SiteEnvironmentalBenefitsReq, Resp as SiteEnvironmentalBenefitsResp, +pub use accounts_list::{ + AccountDetails, AccountLocation, Accounts, Entries as AccountListEntries, + Req as AccountsListReq, Resp as AccountsListResp, SortProperty, }; -pub use site_list::{Req as SiteListReq, Resp as SiteListResp}; -pub use site_overview::{Req as SiteOverviewReq, Resp as SiteOverviewResp}; -pub use site_power::{Req as SitePowerReq, Resp as SitePowerResp}; -pub use site_power_detailed::{Req as SitePowerDetailedReq, Resp as SitePowerDetailedResp}; -pub use site_power_flow::{Req as SitePowerFlowReq, Resp as SitePowerFlowResp}; -pub use site_storage_data::{Req as SiteStorageDataReq, Resp as SiteStorageDataResp}; -pub use site_time_frame_energy::{Req as SiteTimeFrameEnergyReq, Resp as SiteTimeFrameEnergyResp}; -pub use supported_versions::{Req as SupportedVersionsReq, Resp as SupportedVersionsResp}; - +pub use current_version::{Req as CurrentVersionReq, Resp as CurrentVersionResp, Version}; pub use date_value::DateValue; pub use error::{Error, Kind}; pub use meter_type::MeterType; pub use meter_value::MeterValue; use serde::Deserialize; -pub use site_details::SiteDetails; +pub use site_data_period::{Req as SiteDataPeriodReq, Resp as SiteDataPeriodResp, SiteDataPeriod}; +pub use site_details::{Req as SiteDetailsReq, Resp as SiteDetailsResp, SiteDetails}; +pub use site_energy::{Energy, Req as SiteEnergyReq, Resp as SiteEnergyResp}; +pub use site_energy_detailed::{ + EnergyDetails, Req as SiteEnergyDetailedReq, Resp as SiteEnergyDetailedResp, +}; +pub use site_environmental_benefits::{ + EnvBenefits, GasEmissionSaved, Req as SiteEnvironmentalBenefitsReq, + Resp as SiteEnvironmentalBenefitsResp, +}; +pub use site_equipment_change_log::{ + Req as SiteEquipmentChangeLogReq, Resp as SiteEquipmentChangeLogResp, +}; +pub use site_equipment_list::{ + Equipment, EquipmentList, Reporters, Req as SiteEquipmentListReq, Resp as SiteEquipmentListResp, +}; +pub use site_get_meters_data::{ + Meter as SiteGetMetersDataMeter, MeterEnergyDetails, Req as SiteGetMetersDataReq, + Resp as SiteGetMetersDataResp, +}; +pub use site_get_sensor_list::{ + Gateway as SiteGetSensorListGateway, Gateways, Req as SiteGetSensorListReq, + Resp as SiteGetSensorListResp, Sensor as SiteGetSensorListSensor, Sensors, SiteSensors, +}; +pub use site_inventory::{ + Battery as SiteInventoryBattery, Gateway as SiteInventoryGateway, Inventory, Inverter, + Meter as SiteInventoryMeter, Req as SiteInventoryReq, Resp as SiteInventoryResp, + Sensor as SiteInventorySensor, +}; +pub use site_inverter_technical_data::{ + InverterData, InverterMode, LxData, Req as SiteInverterTechnicalDataReq, + Resp as SiteInverterTechnicalDataResp, Telemetries, Telemetry, +}; +pub use site_list::{Entries as SiteListEntries, Req as SiteListReq, Resp as SiteListResp, Sites}; pub use site_location::SiteLocation; pub use site_module::SiteModule; +pub use site_overview::{ + CurrentPower, EnergyRevenue, Overview, Req as SiteOverviewReq, Resp as SiteOverviewResp, +}; +pub use site_power::{Power, Req as SitePowerReq, Resp as SitePowerResp}; +pub use site_power_detailed::{ + PowerDetails, Req as SitePowerDetailedReq, Resp as SitePowerDetailedResp, +}; +pub use site_power_flow::{ + Connections, Parameters, Req as SitePowerFlowReq, Resp as SitePowerFlowResp, + SiteCurrentPowerFlow, +}; pub use site_public_settings::SitePublicSettings; +pub use site_storage_data::{ + Batteries, Battery as SiteStorageDataBattery, Req as SiteStorageDataReq, + Resp as SiteStorageDataResp, StorageData, +}; +pub use site_time_frame_energy::{ + Req as SiteTimeFrameEnergyReq, Resp as SiteTimeFrameEnergyResp, TimeFrameEnergy, +}; +pub use sort_order::SortOrder; +pub use supported_versions::{Release, Req as SupportedVersionsReq, Resp as SupportedVersionsResp}; pub use system_units::SystemUnits; pub use time_unit::TimeUnit; +mod accounts_list; mod current_version; mod date_value; mod error; @@ -109,6 +151,12 @@ mod site_details; mod site_energy; mod site_energy_detailed; mod site_environmental_benefits; +mod site_equipment_change_log; +mod site_equipment_list; +mod site_get_meters_data; +mod site_get_sensor_list; +mod site_inventory; +mod site_inverter_technical_data; mod site_list; mod site_location; mod site_module; @@ -119,6 +167,7 @@ mod site_power_flow; mod site_public_settings; mod site_storage_data; mod site_time_frame_energy; +mod sort_order; mod supported_versions; mod system_units; mod time_unit; diff --git a/src/site_details.rs b/src/site_details.rs index b884658..ccf0c9a 100644 --- a/src/site_details.rs +++ b/src/site_details.rs @@ -20,13 +20,13 @@ pub struct Resp { #[serde(rename_all = "camelCase")] pub struct SiteDetails { /// ID of the site. Should match the site_id specified in the Solaredge request. - pub id: i32, + pub id: u32, /// Name of the site. pub name: String, /// Account the site belongs to. - pub account_id: i32, + pub account_id: u32, /// Site status, either Active or Pending Communication. pub status: String, @@ -60,7 +60,7 @@ pub struct SiteDetails { pub primary_module: SiteModule, /// Number of open alerts at the site. - pub alert_quantity: Option, + pub alert_quantity: Option, /// Highest alert severity at the site. pub alert_severity: Option, diff --git a/src/site_equipment_change_log.rs b/src/site_equipment_change_log.rs new file mode 100644 index 0000000..687461c --- /dev/null +++ b/src/site_equipment_change_log.rs @@ -0,0 +1,81 @@ +//! Module for getting a list of equipment component replacements ordered by date. +//! This method is applicable to inverters, optimizers, batteries and gateways + +use crate::{SendReq, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_equipment_change_log request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + serial_number: String, +} + +/// site_equipment_change_log response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct Resp { + /// Equipment change history + pub change_log: ChangeLog, +} + +/// Equipment change history +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ChangeLog { + /// Number of entries in the change list + pub count: u32, + + /// List of changes for the equipment + pub list: Vec, +} + +/// Equipment change record +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ChangeEntry { + /// Equipment short serial number + pub serial_number: String, + + /// Inverter/battery/optimizer/gateway model + pub part_number: String, + + /// Date of replacement of that equipment component + pub date: String, +} + +impl Req { + /// Create an power details request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * `serial_number` - Inverter, battery, optimizer or gateway short serial number + #[must_use] + pub fn new(serial_number: &str) -> Self { + Req { + serial_number: serial_number.to_string(), + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}equipment/{}/{}/changeLog?{}", + *MONITORING_API_URL, site_id, self.serial_number, api_key, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_equipment_list.rs b/src/site_equipment_list.rs new file mode 100644 index 0000000..b99543a --- /dev/null +++ b/src/site_equipment_list.rs @@ -0,0 +1,94 @@ +//! Module for getting a list of inverters/SMIs in the specific site. + +use crate::{SendReq, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_equipment_list request +#[derive(Clone, Debug, PartialEq)] +pub struct Req; + +/// site_equipment_list response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// List size and list + pub reporters: Reporters, +} + +/// List size and list +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Reporters { + /// Number of entries in the equipment list + pub count: u16, + + /// List of equipment + pub list: EquipmentList, +} + +/// List of equipment +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct EquipmentList { + /// Transparent list of equipment + pub eq: Vec, +} + +/// Details on a single piece of equipment +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Equipment { + /// Equipment's name + pub name: String, + + /// Equipment's manufacturer + pub manufacturer: String, + + /// Equipment model + pub model: String, + + /// Equipment's serial number + pub serial_number: String, + + /// Kilowatts peak DC + #[serde(rename = "kWpDC")] + pub kw_pdc: Option, +} + +impl Req { + /// Create a site environmental benefits request message that can be sent to SolarEdge. + #[must_use] + pub fn new() -> Self { + Req {} + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}equipment/{}/list?{}", + *MONITORING_API_URL, site_id, api_key, + ) + } +} + +impl Default for Req { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_get_meters_data.rs b/src/site_get_meters_data.rs new file mode 100644 index 0000000..2ae43d2 --- /dev/null +++ b/src/site_get_meters_data.rs @@ -0,0 +1,147 @@ +//! Module for each meter on site its lifetime energy reading, metadata and the device to which it’s connected to. + +use crate::{DateValue, MeterType, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; +use serde::Deserialize; + +/// site_get_meters_data request +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Req { + start_time: String, + end_time: String, + time_unit: String, + meters: String, +} + +/// site_get_meters_data response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Meter energy details + pub meter_energy_details: MeterEnergyDetails, +} + +/// Meter energy details +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MeterEnergyDetails { + /// Granularity of the energy detail values (should match the request) + pub time_unit: TimeUnit, + + /// Measurement unit (e.g. Wh) + pub unit: String, + + /// For the meter types requested, meter info and energy values over the time period + pub meters: Vec, +} + +/// Meter details +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Meter { + /// Serial number of the meter + pub meter_serial_number: String, + + /// Inverter to which the meter is connected to + #[serde(rename = "connectedSolaredgeDeviceSN")] + pub connected_solaredge_device_sn: String, + + /// Meter model + pub model: String, + + /// Production, Consumption, FeedIn or Purchased + pub meter_type: MeterType, + + /// energy values over the time period + pub values: Vec, +} + +impl Req { + /// Create an energy details request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * `start_time` - beginning of the time period for the energy details + /// * `end_time` - end of the time period for the energy details + /// * `time_unit` - aggregation granularity + /// For the time period requested, energy detail values will be + /// chunked into units of this size. + /// * `meters` - meter types to collect energy details for + #[must_use] + pub fn new( + start_time: chrono::NaiveDateTime, + end_time: chrono::NaiveDateTime, + time_unit: Option, + meters: Option>, + ) -> Self { + let start_time = format!("startTime={}&", start_time.format(URL_DATE_TIME_FORMAT)); + + let end_time = format!("endTime={}&", end_time.format(URL_DATE_TIME_FORMAT)); + + let time_unit = match time_unit { + Some(t) => format!("timeUnit={}&", t), + None => "".to_string(), + }; + + let meters = match meters { + Some(m) => format!( + "meters={}&", + m.iter() + .map(MeterType::to_string) + .collect::>() + .join(",") + ), + None => "".to_string(), + }; + + Req { + start_time, + end_time, + time_unit, + meters, + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/meters?{}{}{}{}{}", + *MONITORING_API_URL, + site_id, + self.meters, + self.time_unit, + self.start_time, + self.end_time, + api_key, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + use chrono::NaiveDateTime; + + #[test] + fn site_get_meters_data_req_new_unit_test() { + let dt = "2022-01-01 00:00:00"; + if let Ok(ndt) = NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S") { + let req = Req::new(ndt, ndt, None, None); + assert_eq!(req.start_time, format!("startTime={}&", dt)); + assert_eq!(req.end_time, format!("endTime={}&", dt)); + assert_eq!(req.time_unit, ""); + assert_eq!(req.meters, ""); + } else { + panic!("test failed"); + } + } + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_get_sensor_list.rs b/src/site_get_sensor_list.rs new file mode 100644 index 0000000..d0d4c13 --- /dev/null +++ b/src/site_get_sensor_list.rs @@ -0,0 +1,112 @@ +//! Module for getting a list of all the sensors in the site, and the device to which they are connected. + +use crate::{SendReq, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_get_sensor_list request +#[derive(Clone, Debug, PartialEq)] +pub struct Req; + +/// site_get_sensor_list response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct Resp { + /// The list of sensors installed in the site associated with the gateway they are connected with. + pub site_sensors: SiteSensors, +} + +/// The list of sensors installed in the site associated with the gateway they are connected with. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SiteSensors { + /// Number of gateways in the list. + pub total: u16, + + /// list of gateways + pub list: Gateways, +} + +/// List of gateways +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct Gateways { + /// Transparent list of gateways + pub g: Vec, +} + +/// Sensor information for a gateway +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Gateway { + /// Gateway that the sensors are connected to + pub connected_to: String, + + /// Number of sensors in list + pub count: u16, + + /// List of sensors + pub sensors: Sensors, +} + +/// List of sensors +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct Sensors { + /// Transparent list of sensors + pub s: Vec, +} + +/// Information for a single sensor +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Sensor { + /// Name of the sensor + pub name: String, + + /// What the sensor measures + pub measurement: String, + + /// Sensor type + #[serde(rename = "type")] + pub sensor_type: String, +} + +impl Req { + /// Create a site details request message that can be sent to SolarEdge. + #[must_use] + pub fn new() -> Self { + Req {} + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}equipment/{}/sensors?{}", + *MONITORING_API_URL, site_id, api_key, + ) + } +} + +impl Default for Req { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_inventory.rs b/src/site_inventory.rs new file mode 100644 index 0000000..470b4f7 --- /dev/null +++ b/src/site_inventory.rs @@ -0,0 +1,207 @@ +//! Module for getting the inventory of SolarEdge equipment in the site, +//! including inverters/SMIs, batteries, meters, gateways and sensors. + +use crate::{MeterType, SendReq, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_inventory request +#[derive(Clone, Debug, PartialEq)] +pub struct Req; + +/// site_inventory response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct Resp { + /// List size and list + pub inventory: Inventory, +} + +/// Inventory of site equipment +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Inventory { + /// List of site meters + pub meters: Vec, + + /// List of site sensors + pub sensors: Vec, + + /// List of site gateways + pub gateways: Vec, + + /// List of site batteries + pub batteries: Vec, + + /// List of site inverters + pub inverters: Vec, +} + +/// Meter info +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Meter { + /// Meter's name + pub name: String, + + /// Meter's manufacturer + pub manufacturer: Option, + + /// Meter model + pub model: Option, + + /// Meter's firmware version + pub firmware_version: String, + + /// Which SolarEdge device the meter is connected to + pub connected_to: String, + + /// Which SolarEdge device the meter is connected to + #[serde(rename = "connectedSolaredgeDeviceSN")] + pub connected_solaredge_device_sn: String, + + /// Meter type + #[serde(rename = "type")] + pub meter_type: MeterType, + + /// Physical for a HW meter or virtual if calculated by arithmetic between other meters + pub form: String, + + /// Meter serial number + #[serde(rename = "SN")] + pub sn: Option, +} + +/// Sensor info +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Sensor { + /// Serial number of connected SolarEdge device + #[serde(rename = "connectedSolaredgeDeviceSN")] + pub connected_solaredge_device_sn: String, + + /// Sensor ID + pub id: String, + + /// Which SolarEdge device the sensor is connected to + pub connected_to: String, + + /// Sensor category + pub category: String, + + /// Sensor type + #[serde(rename = "type")] + pub sensor_type: String, +} + +/// Gateway info +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Gateway { + /// Gateway name + pub name: String, + + /// The communication interface used to connect to server. e.g.: Ethernet. + pub communication_method: String, + + /// Gateway serial number + #[serde(rename = "SN")] + pub sn: String, + + /// CPU version + pub cpu_version: String, +} + +/// Battery info +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Battery { + /// Battery's name + pub name: String, + + /// Battery's manufacturer + pub manufacturer: String, + + /// Battery model + pub model: String, + + /// Battery firmware version + pub firmware_version: String, + + /// Serial number of connected inverter + pub connected_inverter_sn: String, + + /// The nameplate capacity of the battery as provided by the manufacturer + pub nameplate_capacity: String, + + /// Battery serial number + #[serde(rename = "SN")] + pub sn: String, +} + +/// Inverter info +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Inverter { + /// Inverter's name + pub name: String, + + /// Inverter's manufacturer + pub manufacturer: String, + + /// Inverter's model + pub model: String, + + /// The communication interface used to connect to server. e.g.: Ethernet. + pub communication_method: String, + + /// CPU version + pub cpu_version: String, + + /// Inverter serial number + #[serde(rename = "SN")] + pub sn: String, + + /// number of optimizers connected to the inverter + pub connected_optimizers: u32, +} + +impl Req { + /// Create a site environmental benefits request message that can be sent to SolarEdge. + #[must_use] + pub fn new() -> Self { + Req {} + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/inventory?{}", + *MONITORING_API_URL, site_id, api_key, + ) + } +} + +impl Default for Req { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_inverter_technical_data.rs b/src/site_inverter_technical_data.rs new file mode 100644 index 0000000..e3c0a86 --- /dev/null +++ b/src/site_inverter_technical_data.rs @@ -0,0 +1,257 @@ +//! Module for specific inverter data for a given time frame. + +use crate::{SendReq, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; +use serde::Deserialize; + +/// site_inverter_technical_data request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + serial_number: String, + start_time: String, + end_time: String, +} + +/// site_inverter_technical_data response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// inverter technical data + pub data: InverterData, +} + +/// Inverter data for each telemetry +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InverterData { + /// Number of telemetries in the list + pub count: u32, + /// List of telemetries + pub telemetries: Telemetries, +} + +/// Array of telemetries +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct Telemetries { + /// Transparent list of accounts + pub t: Vec, +} + +/// Data for a single telemetry +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Telemetry { + /// Date of telemetry collected + pub date: String, + + /// Total active power + pub total_active_power: Option, + + /// DC voltage + pub dc_voltage: Option, + + /// Power limit + pub power_limit: f32, + + /// Total energy + pub total_energy: f32, + + /// Celsius + pub temperature: f32, + + /// Operating mode of inverter + pub inverter_mode: InverterMode, + + /// 0 – On-grid + /// 1 – Operating in off-grid mode using PV or battery + /// 2 - Operating in off-grid mode with generator (e.g. diesel) is present + pub operation_mode: u16, + + /// Data for phase level 1 + #[serde(rename = "L1Data")] + pub l1_data: LxData, +} + +/// Data for a phase level +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct LxData { + /// AC current + pub ac_current: f32, + + /// AC voltage + pub ac_voltage: f32, + + /// AC frequency + pub ac_frequency: f32, + + /// Apparent power + pub apparent_power: f32, + + /// Active power + pub active_power: f32, + + /// Reactive power + pub reactive_power: f32, + + /// cos phi? + pub cos_phi: f32, +} + +/// Inverter operating mode +#[derive(Clone, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InverterMode { + /// Off + Off, + + /// Night mode + Night, + + /// Pre-production + WakeUp, + /// Production + Production, + + /// Forced power reduction + ProductionLimit, + + /// Shutdown procedure + Shutdown, + + /// Error mode + Error, + + /// Maintenance + Setup, + + /// Standby mode lock + LockedStdby, + + /// Fire fighters lock mode + LockedFireFighters, + + /// Forced shutdown from server + LockedForceShutdown, + + /// Communication timeout + LockedCommTimeout, + + /// Inverter self-lock trip + LockedInvTrip, + + /// Inverter self-lock arc detection + LockedInvArcDetected, + + /// Inverter lock due to DG mode enable + #[serde(rename = "LOCKED_DG")] + LockedDG, + + /// MPPT? + #[serde(rename = "MPPT")] + Mppt, + + /// Sleeping + Sleeping, +} + +impl std::fmt::Display for InverterMode { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + InverterMode::Off => write!(f, "Off"), + InverterMode::Night => write!(f, "Night"), + InverterMode::WakeUp => write!(f, "Wake Up"), + InverterMode::Production => write!(f, "Production"), + InverterMode::ProductionLimit => write!(f, "Production Limit"), + InverterMode::Shutdown => write!(f, "Shutdown"), + InverterMode::Error => write!(f, "Error"), + InverterMode::Setup => write!(f, "Setup"), + InverterMode::LockedStdby => write!(f, "Locked Standby"), + InverterMode::LockedFireFighters => write!(f, "Locked Fire Fighters"), + InverterMode::LockedForceShutdown => write!(f, "Locked Force Shutdown"), + InverterMode::LockedCommTimeout => write!(f, "Locked Communication Timeout"), + InverterMode::LockedInvTrip => write!(f, "Locked Inverter Trip"), + InverterMode::LockedInvArcDetected => write!(f, "Locked Inverter Arc Detected"), + InverterMode::LockedDG => write!(f, "Locked DG"), + InverterMode::Mppt => write!(f, "MPPT"), + InverterMode::Sleeping => write!(f, "Sleeping"), + } + } +} + +impl Default for InverterMode { + fn default() -> InverterMode { + InverterMode::Off + } +} + +impl Req { + /// Create an power details request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * 'serial_number` - inverter short serial number + /// * `start_time` - beginning of the time period for the inverter data + /// * `end_time` - end of the time period for the inverter data + #[must_use] + pub fn new( + serial_number: &str, + start_time: chrono::NaiveDateTime, + end_time: chrono::NaiveDateTime, + ) -> Self { + let start_time = format!("startTime={}&", start_time.format(URL_DATE_TIME_FORMAT)); + + let end_time = format!("endTime={}&", end_time.format(URL_DATE_TIME_FORMAT)); + + Req { + serial_number: serial_number.to_string(), + start_time, + end_time, + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}equipment/{}/{}/data?{}{}{}", + *MONITORING_API_URL, + site_id, + self.serial_number, + self.start_time, + self.end_time, + api_key, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + use chrono::NaiveDateTime; + + #[test] + fn site_inverter_technical_data_req_new_unit_test() { + let dt = "2022-01-01 00:00:00"; + if let Ok(ndt) = NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S") { + let req = Req::new("foo", ndt, ndt); + assert_eq!(req.start_time, format!("startTime={}&", dt)); + assert_eq!(req.end_time, format!("endTime={}&", dt)); + assert_eq!(req.serial_number, "foo"); + } else { + panic!("test failed"); + } + } + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_list.rs b/src/site_list.rs index 62dceb4..94c895f 100644 --- a/src/site_list.rs +++ b/src/site_list.rs @@ -1,7 +1,7 @@ //! Module for querying a list of sites related to the given token, which is the account api_key. //! This API accepts parameters for convenient search, sort and pagination. -use crate::{SendReq, SiteDetails, MONITORING_API_URL}; +use crate::{SendReq, SiteDetails, SortOrder, MONITORING_API_URL}; use serde::Deserialize; /// site_list request @@ -73,24 +73,6 @@ impl std::fmt::Display for SortProperty { } } -#[derive(Clone, Debug, PartialEq)] -pub enum SortOrder { - /// Sort ascending - Asc, - - /// Sort descending - Desc, -} - -impl std::fmt::Display for SortOrder { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - SortOrder::Asc => write!(f, "ASC"), - SortOrder::Desc => write!(f, "DESC"), - } - } -} - #[derive(Clone, Debug, PartialEq)] pub enum Status { /// Active sites @@ -138,6 +120,7 @@ pub struct Sites { #[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(transparent)] pub struct Entries { + /// Transparent list of sites pub e: Vec, } @@ -146,12 +129,16 @@ impl Req { /// /// # Arguments /// - /// * `size` - - /// * `start_index` - - /// * `search_text` - - /// * `sort_property` - - /// * `sort_order` - - /// * `status` - + /// * `size` - The maximum number of sites returned by this call. + /// If you have more than 100 sites, just request another 100 + /// sites with startIndex=100. This will fetch sites 100-199. + /// * `start_index` - The first site index to be returned in the results + /// * `search_text` - Search text for this site + /// * `sort_property` - A sorting option for this site list, based on + /// one of its properties + /// * `sort_order` - Sort order for the sort property + /// * `status` - Select the sites to be included in the list by their status. + /// Default list will include Active and Pending sites. #[must_use] pub fn new( size: Option, @@ -239,7 +226,6 @@ mod tests { fn normal_types_unit_test() { is_normal::(); is_normal::(); - is_normal::(); is_normal::(); is_normal::(); is_normal::(); diff --git a/src/site_storage_data.rs b/src/site_storage_data.rs index 2990081..aecaf7c 100644 --- a/src/site_storage_data.rs +++ b/src/site_storage_data.rs @@ -23,7 +23,10 @@ pub struct Resp { #[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct StorageData { + /// Number of batteries in teh battery list pub battery_count: u16, + + /// List of batteries at the site pub batteries: Batteries, } @@ -31,6 +34,7 @@ pub struct StorageData { #[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(transparent)] pub struct Batteries { + /// Transparent list of batteries pub e: Vec, } @@ -38,6 +42,7 @@ pub struct Batteries { #[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Battery { + /// The nameplate capacity of the battery as provided by the manufacturer pub nameplate: u32, } diff --git a/src/sort_order.rs b/src/sort_order.rs new file mode 100644 index 0000000..b62a7ca --- /dev/null +++ b/src/sort_order.rs @@ -0,0 +1,31 @@ +//! Module for specifying sort order of the output from the SolarEdge server monitoring API. + +/// Sort order for the sort property +#[derive(Clone, Debug, PartialEq)] +pub enum SortOrder { + /// Sort ascending + Asc, + + /// Sort descending + Desc, +} + +impl std::fmt::Display for SortOrder { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + SortOrder::Asc => write!(f, "ASC"), + SortOrder::Desc => write!(f, "DESC"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + } +} diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index 701595e..3b4aeb6 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -6,9 +6,11 @@ extern crate lazy_static; mod common; use se_ms_api::{ - CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SiteEnergyReq, SiteEnvironmentalBenefitsReq, SiteListReq, - SiteOverviewReq, SitePowerDetailedReq, SitePowerFlowReq, SitePowerReq, SiteStorageDataReq, + AccountsListReq, CurrentVersionReq, InverterMode, Kind, MeterType, SendReq, SiteDataPeriodReq, + SiteDetailsReq, SiteEnergyDetailedReq, SiteEnergyReq, SiteEnvironmentalBenefitsReq, + SiteEquipmentChangeLogReq, SiteEquipmentListReq, SiteGetMetersDataReq, SiteGetSensorListReq, + SiteInventoryReq, SiteInverterTechnicalDataReq, SiteListReq, SiteOverviewReq, + SitePowerDetailedReq, SitePowerFlowReq, SitePowerReq, SiteStorageDataReq, SiteTimeFrameEnergyReq, SupportedVersionsReq, SystemUnits, TimeUnit, }; @@ -51,7 +53,7 @@ fn site_energy_detailed_integration_test() { self_consumption += value; } } - assert!(self_consumption as i32 == 292473); + assert_eq!(self_consumption as u32, 292473); } Err(e) => { panic!("Unexpected SiteEnergyDetailedReq response: {:?}", e); @@ -367,7 +369,7 @@ fn site_storage_data_integration_test() { }; let end_ndt = - match NaiveDateTime::parse_from_str("2022-01-7 00:00:00", common::DATE_TIME_FORMAT) { + match NaiveDateTime::parse_from_str("2022-01-07 00:00:00", common::DATE_TIME_FORMAT) { Ok(dt) => dt, Err(error) => panic!("Error parsing end date: {}", error), }; @@ -425,3 +427,212 @@ fn site_environmental_benefits_integration_test() { } }; } + +#[test] +fn site_equipment_list_integration_test() { + let req = SiteEquipmentListReq::new(); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.reporters.list.eq.len(), r.reporters.count as usize); + + assert_eq!(r.reporters.list.eq[0].name, "Gateway 1"); + assert_eq!(r.reporters.list.eq[0].manufacturer, ""); + assert_eq!(r.reporters.list.eq[0].model, ""); + assert_eq!(r.reporters.list.eq[0].serial_number.len(), 11); + assert!(r.reporters.list.eq[0].kw_pdc.is_none()); + + assert_eq!(r.reporters.list.eq[1].name, "Inverter 1"); + assert_eq!(r.reporters.list.eq[1].manufacturer, "SolarEdge"); + assert!(r.reporters.list.eq[1].model.starts_with("SE7600H")); + assert_eq!(r.reporters.list.eq[1].serial_number.len(), 11); + assert!(r.reporters.list.eq[1].kw_pdc.is_none()); + } + Err(e) => { + panic!("Unexpected SiteEquipmentList response: {:?}", e); + } + }; +} + +#[test] +fn site_get_sensor_list_integration_test() { + let req = SiteGetSensorListReq::new(); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.site_sensors.list.g.len(), r.site_sensors.total as usize); + assert_eq!(r.site_sensors.total, 0); + } + Err(e) => { + panic!("Unexpected SiteGetSensors response: {:?}", e); + } + }; +} + +#[test] +fn site_get_meters_data_integration_test() { + let start_ndt = + match NaiveDateTime::parse_from_str("2022-01-01 00:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing start date: {}", error), + }; + + let end_ndt = + match NaiveDateTime::parse_from_str("2022-01-31 00:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SiteGetMetersDataReq::new(start_ndt, end_ndt, None, Some(vec![MeterType::FeedIn])); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.meter_energy_details.unit, "Wh"); + assert_eq!(r.meter_energy_details.time_unit, TimeUnit::Day); + assert_eq!(r.meter_energy_details.meters.len(), 1); + assert_eq!( + r.meter_energy_details.meters[0].meter_serial_number.len(), + 7 + ); + assert_eq!( + r.meter_energy_details.meters[0] + .connected_solaredge_device_sn + .len(), + 11 + ); + assert_eq!(r.meter_energy_details.meters[0].model, "WNC-3D-240-MB"); + assert_eq!( + r.meter_energy_details.meters[0].meter_type, + MeterType::FeedIn + ); + assert_eq!(r.meter_energy_details.meters[0].values.len(), 30); + + let mut self_consumption: f32 = 0.0; + for v in &r.meter_energy_details.meters[0].values { + if let Some(value) = v.value { + self_consumption += value; + } + } + assert_eq!(self_consumption as u32, 906998528); + } + Err(e) => { + panic!("Unexpected SiteGetMetersDataReq response: {:?}", e); + } + }; +} + +#[test] +fn accounts_list_integration_test() { + let req = AccountsListReq::new(Some(1), Some(0), None, None, None); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(_r) => { + panic!("Unexpected AccountsList success"); + /* + assert_eq!(r.accounts.count, 1); + assert_eq!(r.accounts.list.e.len(), 1); + assert_eq!(r.accounts.list.e[0].id, 0); + assert_eq!(r.accounts.list.e[0].location.country, "US"); + assert_eq!(r.accounts.list.e[0].company_web_site, ""); + assert_eq!(r.accounts.list.e[0].contact_person, ""); + assert_eq!(r.accounts.list.e[0].email, ""); + assert_eq!(r.accounts.list.e[0].phone_number, ""); + assert_eq!(r.accounts.list.e[0].fax_number, ""); + assert_eq!(r.accounts.list.e[0].notes, ""); + assert_eq!(r.accounts.list.e[0].parent_id, 0); + assert!(r.accounts.list.e[0].uris.contains_key("SITE_IMAGE")); + */ + } + Err(e) => match e.kind() { + Kind::HttpErrorStatus(error_string, _) => { + assert_eq!(error_string, "Forbidden"); + } + _ => panic!("Unexpected AccountsList response: {:?}", e), + }, + } +} + +#[test] +fn site_inventory_integration_test() { + let req = SiteInventoryReq::new(); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.inventory.meters.len(), 4); + assert_eq!(r.inventory.sensors.len(), 0); + assert_eq!(r.inventory.gateways.len(), 1); + assert_eq!(r.inventory.batteries.len(), 0); + assert_eq!(r.inventory.inverters.len(), 1); + assert_eq!(r.inventory.inverters[0].name, "Inverter 1"); + assert_eq!(r.inventory.inverters[0].manufacturer, "SolarEdge"); + assert_eq!(r.inventory.inverters[0].communication_method, "ZIGBEE"); + assert_eq!(r.inventory.inverters[0].connected_optimizers, 22); + } + Err(e) => { + panic!("Unexpected SiteInventory response: {:?}", e); + } + }; +} + +#[test] +fn site_equipment_change_log_integration_test() { + let req = SiteEquipmentChangeLogReq::new("7308CC3E-85"); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.change_log.count, 1); + assert_eq!(r.change_log.count as usize, r.change_log.list.len()); + assert_eq!(r.change_log.list[0].date, "2020-07-31"); + } + Err(e) => { + panic!("Unexpected SiteEquipmentChangeLog response: {:?}", e); + } + }; +} + +#[test] +fn site_inverter_technical_data_integration_test() { + let start_ndt = + match NaiveDateTime::parse_from_str("2022-01-01 00:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing start date: {}", error), + }; + + let end_ndt = + match NaiveDateTime::parse_from_str("2022-01-01 09:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SiteInverterTechnicalDataReq::new("7308CC3E-85", start_ndt, end_ndt); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.data.count, 14); + assert_eq!(r.data.count as usize, r.data.telemetries.t.len()); + assert_eq!( + r.data.telemetries.t[0].inverter_mode, + InverterMode::Sleeping + ); + assert_eq!(r.data.telemetries.t[0].operation_mode, 0); + assert_eq!(r.data.telemetries.t[0].l1_data.ac_voltage, 245.42); + assert_eq!(r.data.telemetries.t[1].inverter_mode, InverterMode::Mppt); + } + Err(e) => { + panic!("Unexpected SiteInverterTechnicalData response: {:?}", e); + } + }; +}