From 0a0a6248d118dfa7b1640d5a91382a03d8821c2c Mon Sep 17 00:00:00 2001 From: Ted <63551230+grtw@users.noreply.github.com> Date: Sat, 14 May 2022 16:05:04 -0400 Subject: [PATCH 01/11] Clippy pedantic --- src/current_version.rs | 17 +++++++++-------- src/error.rs | 15 ++++++++------- src/se_ms_api.rs | 20 +++++++++++++++----- src/site_details.rs | 17 +++++++++-------- src/site_energy_detailed.rs | 35 ++++++++++++++++++----------------- src/supported_versions.rs | 17 +++++++++-------- 6 files changed, 68 insertions(+), 53 deletions(-) diff --git a/src/current_version.rs b/src/current_version.rs index e2e2503..fdb6c0d 100644 --- a/src/current_version.rs +++ b/src/current_version.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; /// Current version request #[derive(Clone, Debug, PartialEq)] -pub struct CurrentVersionReq; +pub struct Req; /// Current version response #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] -pub struct CurrentVersionResp { +pub struct Resp { /// The API version running on the server pub version: Version, } @@ -21,14 +21,15 @@ pub struct Version { pub release: String, } -impl CurrentVersionReq { +impl Req { /// Create a current version request message that can be sent to SolarEdge. + #[must_use] pub fn new() -> Self { - CurrentVersionReq {} + Req {} } } -impl SendReq for CurrentVersionReq { +impl SendReq for Req { fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { format!( "{}version/current?{}", @@ -37,7 +38,7 @@ impl SendReq for CurrentVersionReq { } } -impl Default for CurrentVersionReq { +impl Default for Req { fn default() -> Self { Self::new() } @@ -50,8 +51,8 @@ mod tests { #[test] fn normal_types_unit_test() { - is_normal::(); - is_normal::(); + is_normal::(); + is_normal::(); is_normal::(); } } diff --git a/src/error.rs b/src/error.rs index d8c7a6e..bb44cf5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,16 +7,17 @@ use std::fmt; /// or when trying to parse the response from the server. #[derive(Debug)] pub struct Error { - kind: ErrorKind, + kind: Kind, } impl Error { - pub(crate) fn new(kind: ErrorKind) -> Error { + pub(crate) fn new(kind: Kind) -> Error { Error { kind } } /// Convenience function for getting the kind of error. - pub fn kind(&self) -> &ErrorKind { + #[must_use] + pub fn kind(&self) -> &Kind { &self.kind } } @@ -24,7 +25,7 @@ impl Error { /// The different kinds of errors that can occur. #[derive(Debug)] #[non_exhaustive] -pub enum ErrorKind { +pub enum Kind { /// An error returned from the reqwest crate. ReqwestError(reqwest::Error), } @@ -32,7 +33,7 @@ pub enum ErrorKind { impl error::Error for Error { fn description(&self) -> &str { match self.kind { - ErrorKind::ReqwestError(_) => "Reqwest error", + Kind::ReqwestError(_) => "Reqwest error", } } } @@ -40,13 +41,13 @@ impl error::Error for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.kind { - ErrorKind::ReqwestError(s) => write!(f, "Reqwest Error: HTTP status-code{}", s), + Kind::ReqwestError(s) => write!(f, "Reqwest Error: HTTP status-code{}", s), } } } impl From for Error { fn from(e: reqwest::Error) -> Self { - Error::new(ErrorKind::ReqwestError(e)) + Error::new(Kind::ReqwestError(e)) } } diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 8dd33e0..4e7b458 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -72,14 +72,20 @@ #![deny(unused_extern_crates)] #![warn(missing_docs)] #![warn(missing_debug_implementations)] +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::doc_markdown)] -pub use current_version::{CurrentVersionReq, CurrentVersionResp}; -pub use error::{Error, ErrorKind}; +pub use current_version::Req as CurrentVersionReq; +pub use current_version::Resp as CurrentVersionResp; +pub use error::{Error, Kind}; pub use meter_type::MeterType; use serde::Deserialize; -pub use site_details::{SiteDetailsReq, SiteDetailsResp}; -pub use site_energy_detailed::{SiteEnergyDetailedReq, SiteEnergyDetailedResp}; -pub use supported_versions::{SupportedVersionsReq, SupportedVersionsResp}; +pub use site_details::Req as SiteDetailsReq; +pub use site_details::Resp as SiteDetailsResp; +pub use site_energy_detailed::Req as SiteEnergyDetailedReq; +pub use site_energy_detailed::Resp as SiteEnergyDetailedResp; +pub use supported_versions::Req as SupportedVersionsReq; +pub use supported_versions::Resp as SupportedVersionsResp; mod current_version; mod date_value; @@ -115,6 +121,7 @@ pub struct SolaredgeCredentials { impl SolaredgeCredentials { /// Create a Solaredge destination for the requests from the given site id and api_key. + #[must_use] pub fn new(site_id: &str, api_key: &str) -> Self { let site_id = site_id.to_string(); let api_key = format!("api_key={}", api_key); @@ -123,6 +130,7 @@ impl SolaredgeCredentials { } /// See the site ID being used in the credentials. + #[must_use] pub fn site_id(&self) -> &str { &self.site_id } @@ -142,6 +150,8 @@ pub trait SendReq { /// /// # Returns /// The SolarEdge response or an error string. + /// + /// # Errors /// Errors can occur on the request send or when parsing the response. fn send(&self, solaredge: &SolaredgeCredentials) -> Result where diff --git a/src/site_details.rs b/src/site_details.rs index 3dda580..68148b9 100644 --- a/src/site_details.rs +++ b/src/site_details.rs @@ -9,11 +9,11 @@ use std::collections::HashMap; /// site_details request #[derive(Clone, Debug, PartialEq)] -pub struct SiteDetailsReq; +pub struct Req; /// site_details response #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] -pub struct SiteDetailsResp { +pub struct Resp { /// Detailed information about the monitoring site pub details: SiteDetails, } @@ -75,14 +75,15 @@ pub struct SiteDetails { pub public_settings: SitePublicSettings, } -impl SiteDetailsReq { +impl Req { /// Create a site details request message that can be sent to SolarEdge. + #[must_use] pub fn new() -> Self { - SiteDetailsReq {} + Req {} } } -impl SendReq for SiteDetailsReq { +impl SendReq for Req { fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { format!( "{}site/{}/details?{}", @@ -91,7 +92,7 @@ impl SendReq for SiteDetailsReq { } } -impl Default for SiteDetailsReq { +impl Default for Req { fn default() -> Self { Self::new() } @@ -104,8 +105,8 @@ mod tests { #[test] fn normal_types_unit_test() { - is_normal::(); - is_normal::(); + is_normal::(); + is_normal::(); is_normal::(); } } diff --git a/src/site_energy_detailed.rs b/src/site_energy_detailed.rs index c900eed..56e3a6d 100644 --- a/src/site_energy_detailed.rs +++ b/src/site_energy_detailed.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; /// site_energyDetails request #[derive(Clone, Debug, Default, PartialEq)] -pub struct SiteEnergyDetailedReq { +pub struct Req { start_time: String, end_time: String, time_unit: String, @@ -19,7 +19,7 @@ pub struct SiteEnergyDetailedReq { /// site_energyDetails response #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct SiteEnergyDetailedResp { +pub struct Resp { /// Energy details pub energy_details: EnergyDetails, } @@ -38,7 +38,7 @@ pub struct EnergyDetails { pub meters: Vec, } -impl SiteEnergyDetailedReq { +impl Req { /// Create an energy details request message that can be sent to SolarEdge. /// /// # Arguments @@ -49,6 +49,7 @@ impl SiteEnergyDetailedReq { /// 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, @@ -68,14 +69,14 @@ impl SiteEnergyDetailedReq { Some(m) => format!( "meters={}&", m.iter() - .map(|x| x.to_string()) + .map(MeterType::to_string) .collect::>() .join(",") ), None => "".to_string(), }; - SiteEnergyDetailedReq { + Req { start_time, end_time, time_unit, @@ -84,7 +85,7 @@ impl SiteEnergyDetailedReq { } } -impl SendReq for SiteEnergyDetailedReq { +impl SendReq for Req { fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { format!( "{}site/{}/energyDetails?{}{}{}{}{}", @@ -108,21 +109,21 @@ mod tests { #[test] fn site_energy_detailed_req_new_unit_test() { let dt = "2022-01-01 00:00:00"; - let ndt = match NaiveDateTime::parse_from_str(dt, "%Y-%m-%d %H:%M:%S") { - Ok(ndt) => ndt, - Err(_) => panic!("test failed"), - }; - let req = SiteEnergyDetailedReq::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, ""); + 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::(); is_normal::(); } } diff --git a/src/supported_versions.rs b/src/supported_versions.rs index 89ad71b..89f8fc2 100644 --- a/src/supported_versions.rs +++ b/src/supported_versions.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; /// Supported versions request #[derive(Clone, Debug, PartialEq)] -pub struct SupportedVersionsReq; +pub struct Req; /// Supported versions response #[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] -pub struct SupportedVersionsResp { +pub struct Resp { /// An array of all the API versions supported by the server pub supported: Vec, } @@ -21,14 +21,15 @@ pub struct Release { pub release: String, } -impl SupportedVersionsReq { +impl Req { /// Create a supported versions request message that can be sent to SolarEdge. + #[must_use] pub fn new() -> Self { - SupportedVersionsReq {} + Req {} } } -impl SendReq for SupportedVersionsReq { +impl SendReq for Req { fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { format!( "{}version/supported?{}", @@ -37,7 +38,7 @@ impl SendReq for SupportedVersionsReq { } } -impl Default for SupportedVersionsReq { +impl Default for Req { fn default() -> Self { Self::new() } @@ -50,8 +51,8 @@ mod tests { #[test] fn normal_types_unit_test() { - is_normal::(); - is_normal::(); + is_normal::(); + is_normal::(); is_normal::(); } } From 8b35fb930f27d43ce1a30e0e9094c4c4c39b964f Mon Sep 17 00:00:00 2001 From: grtw <63551230+grtw@users.noreply.github.com> Date: Sun, 15 May 2022 18:00:57 -0400 Subject: [PATCH 02/11] Added site power request message. --- src/se_ms_api.rs | 9 ++- src/site_power_detailed.rs | 115 +++++++++++++++++++++++++++++++++ tests/integration_reqs_test.rs | 41 +++++++++++- 3 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/site_power_detailed.rs diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 4e7b458..2744956 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -36,10 +36,11 @@ //! does not try to be performant. For example, it makes blocking HTTP requests. //! //! Supported API requests/responses include: -//! * [CurrentVersionReq]/[CurrentVersionResp] +//! * [CurrentVersionReq] / [CurrentVersionResp] //! * [SiteDetailsReq] / [SiteDetailsResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] -//! * [SupportedVersionsReq]/[SupportedVersionsResp] +//! * [SitePowerDetailedReq] / [SitePowerDetailedResp] +//! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: //! SitesList, @@ -53,7 +54,6 @@ //! SitePower bulk, //! SiteOverview, //! SiteOverview bulk, -//! SitePowerDetailed, //! SitePowerFlow, //! SiteStorageInformation, //! SiteImage, @@ -84,6 +84,8 @@ pub use site_details::Req as SiteDetailsReq; pub use site_details::Resp as SiteDetailsResp; pub use site_energy_detailed::Req as SiteEnergyDetailedReq; pub use site_energy_detailed::Resp as SiteEnergyDetailedResp; +pub use site_power_detailed::Req as SitePowerDetailedReq; +pub use site_power_detailed::Resp as SitePowerDetailedResp; pub use supported_versions::Req as SupportedVersionsReq; pub use supported_versions::Resp as SupportedVersionsResp; @@ -96,6 +98,7 @@ mod site_details; mod site_energy_detailed; mod site_location; mod site_module; +mod site_power_detailed; mod site_public_settings; mod supported_versions; mod time_unit; diff --git a/src/site_power_detailed.rs b/src/site_power_detailed.rs new file mode 100644 index 0000000..c83b04f --- /dev/null +++ b/src/site_power_detailed.rs @@ -0,0 +1,115 @@ +//! Module for detailed site power measurements from meters such as consumption, export (feed-in), import (purchase), etc. + +use crate::meter_type::MeterType; +use crate::meter_value::MeterValue; +use crate::URL_TIME_FORMAT; +use crate::{SendReq, SolaredgeCredentials, MONITORING_API_URL}; +use serde::{Deserialize, Serialize}; + +/// site_powerDetails request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + start_time: String, + end_time: String, + meters: String, +} + +/// site_powerDetails response +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Power details + pub power_details: PowerDetails, +} + +/// Power details +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PowerDetails { + /// Granularity of the power detail values (should match the request) + pub time_unit: String, + + /// Measurement unit (e.g. Wh) + pub unit: String, + + /// For the meter types requested, power values over the time period + pub meters: Vec, +} + +impl Req { + /// Create an power details request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * `start_time` - beginning of the time period for the power details + /// * `end_time` - end of the time period for the power details + /// * `meters` - meter types to collect power details for + #[must_use] + pub fn new( + start_time: chrono::NaiveDateTime, + end_time: chrono::NaiveDateTime, + meters: Option>, + ) -> Self { + let start_time = format!("startTime={}&", start_time.format(URL_TIME_FORMAT)); + + let end_time = format!("endTime={}&", end_time.format(URL_TIME_FORMAT)); + + let meters = match meters { + Some(m) => format!( + "meters={}&", + m.iter() + .map(MeterType::to_string) + .collect::>() + .join(",") + ), + None => "".to_string(), + }; + + Req { + start_time, + end_time, + meters, + } + } +} + +impl SendReq for Req { + fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { + format!( + "{}site/{}/powerDetails?{}{}{}{}", + *MONITORING_API_URL, + solaredge.site_id, + self.meters, + self.start_time, + self.end_time, + solaredge.api_key, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + use chrono::NaiveDateTime; + + #[test] + fn site_power_detailed_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); + assert_eq!(req.start_time, format!("startTime={}&", dt)); + assert_eq!(req.end_time, format!("endTime={}&", dt)); + assert_eq!(req.meters, ""); + } else { + panic!("test failed"); + } + } + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index 838d847..5d2baf7 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -7,7 +7,7 @@ mod common; use se_ms_api::{ CurrentVersionReq, MeterType, SendReq, SiteDetailsReq, SiteEnergyDetailedReq, - SupportedVersionsReq, + SitePowerDetailedReq, SupportedVersionsReq, }; #[test] @@ -105,3 +105,42 @@ fn site_details_integration_test() { } } } + +#[test] +fn site_power_detailed_integration_test() { + let start_ndt = match NaiveDateTime::parse_from_str("2022-01-01 00:00:00", common::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::TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SitePowerDetailedReq::new(start_ndt, end_ndt, Some(vec![MeterType::Purchased])); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.power_details.time_unit, "QUARTER_OF_AN_HOUR"); + assert_eq!(r.power_details.unit, "W"); + assert_eq!(r.power_details.meters.len(), 1); + assert_eq!(r.power_details.meters[0].meter_type, MeterType::Purchased); + assert_eq!(r.power_details.meters[0].values.len(), 2880); + + let mut self_consumption: f32 = 0.0; + for v in &r.power_details.meters[0].values { + if let Some(value) = v.value { + self_consumption += value; + } + } + assert!(self_consumption == 2277237.5); + } + Err(e) => { + panic!("Unexpected SitePowerDetailedReq response: {:?}", e); + } + }; +} From 417f09f1d541daf8f72c31827397e2ae8ee4f2c3 Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Tue, 17 May 2022 21:40:52 -0400 Subject: [PATCH 03/11] Added storage for bulk site IDs. Started site_data_period. Refactored SendReq trait and added new SenReqBulk trait. --- src/current_version.rs | 9 ++-- src/error.rs | 5 ++ src/se_ms_api.rs | 96 +++++++++++++++++++++++++++++-------- src/site_data_period.rs | 78 ++++++++++++++++++++++++++++++ src/site_details.rs | 6 +-- src/site_energy_detailed.rs | 8 ++-- src/site_power_detailed.rs | 11 ++--- src/supported_versions.rs | 9 ++-- 8 files changed, 176 insertions(+), 46 deletions(-) create mode 100644 src/site_data_period.rs diff --git a/src/current_version.rs b/src/current_version.rs index fdb6c0d..38ac4d6 100644 --- a/src/current_version.rs +++ b/src/current_version.rs @@ -1,6 +1,6 @@ //! Module for querying the current API version of the SolarEdge monitoring server. -use crate::{SendReq, SolaredgeCredentials, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// Current version request @@ -30,11 +30,8 @@ impl Req { } impl SendReq for Req { - fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { - format!( - "{}version/current?{}", - *MONITORING_API_URL, solaredge.api_key, - ) + fn build_url(&self, _: &str, api_key: &str) -> String { + format!("{}version/current?{}", *MONITORING_API_URL, api_key,) } } diff --git a/src/error.rs b/src/error.rs index bb44cf5..89a8da1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,12 +28,16 @@ impl Error { pub enum Kind { /// An error returned from the reqwest crate. ReqwestError(reqwest::Error), + + /// Attempted bulk operation, but bulk list is empty. + BulkListNone, } impl error::Error for Error { fn description(&self) -> &str { match self.kind { Kind::ReqwestError(_) => "Reqwest error", + Kind::BulkListNone => "Tried to use empty bulk list.", } } } @@ -42,6 +46,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.kind { Kind::ReqwestError(s) => write!(f, "Reqwest Error: HTTP status-code{}", s), + Kind::BulkListNone => write!(f, "Empty bulk list error"), } } } diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 2744956..c008931 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -44,16 +44,11 @@ //! //! TODO: //! SitesList, -//! SiteDataPeriod start/end dates, -//! SiteDataPeriod bulk, -//! SiteEnergy, -//! SiteEnergy bulk, -//! SiteTimeFrameEnergy, -//! SiteTimeFrameEnergy bulk, -//! SitePower, -//! SitePower bulk, -//! SiteOverview, -//! SiteOverview bulk, +//! SiteDataPeriod start/end dates & bulk, +//! SiteEnergy & bulk,, +//! SiteTimeFrameEnergy & bulk, +//! SitePower & bulk, +//! SiteOverview & bulk, //! SitePowerFlow, //! SiteStorageInformation, //! SiteImage, @@ -94,6 +89,7 @@ mod date_value; mod error; mod meter_type; mod meter_value; +mod site_data_period; mod site_details; mod site_energy_detailed; mod site_location; @@ -118,21 +114,46 @@ const URL_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; /// Used as the parameter for the send() function of all of the possible requests. #[derive(Clone, Debug, Default, PartialEq)] pub struct SolaredgeCredentials { + bulk_list: Option, site_id: String, api_key: String, } impl SolaredgeCredentials { /// Create a Solaredge destination for the requests from the given site id and api_key. + /// + /// # Arguments + /// + /// * `site_id` - ID used by SolarEdge to identify your site. + /// * `api_key` - API token used SolarEdge to authenticate user is allowed to access site data. + /// + /// # Returns + /// A credentials struct to be used in subsequent request sends. #[must_use] pub fn new(site_id: &str, api_key: &str) -> Self { + let bulk_list = None; let site_id = site_id.to_string(); let api_key = format!("api_key={}", api_key); - SolaredgeCredentials { site_id, api_key } + SolaredgeCredentials { + bulk_list, + site_id, + api_key, + } + } + + /// Populate the bulk site list used by the bulk option that some requests support. + /// + /// # Arguments + /// + /// * `bulk_list` - list of site IDs to query. 1 to 100 site IDs allowed. + pub fn set_bulk_list(&mut self, bulk_list: Vec<&str>) { + assert!((bulk_list.len() >= 1) && (bulk_list.len() <= 100)); + self.bulk_list = Some(bulk_list.join(",")); } /// See the site ID being used in the credentials. + /// Used by the integration test framework. #[must_use] pub fn site_id(&self) -> &str { &self.site_id @@ -143,7 +164,19 @@ impl SolaredgeCredentials { /// and getting the response is the same for all requests. pub trait SendReq { #[doc(hidden)] - fn build_url(&self, solaredge: &SolaredgeCredentials) -> String; + fn build_url(&self, site_id: &str, api_key: &str) -> String; + + #[doc(hidden)] + fn send_helper(&self, url: &str) -> Result + where + for<'de> Resp: Deserialize<'de>, + { + let res = REQWEST_CLIENT.get(url).send()?; + + let parsed = res.json::()?; + + Ok(parsed) + } /// Send the request to Solaredge and return the response. /// @@ -160,16 +193,34 @@ pub trait SendReq { where for<'de> Resp: Deserialize<'de>, { - let url = self.build_url(solaredge); + let url = self.build_url(&solaredge.site_id, &solaredge.api_key); - let res = REQWEST_CLIENT.get(&url).send()?; - - let parsed = res.json::()?; - - Ok(parsed) + self.send_helper(&url) } } +/// Some Solaredge requests implement this for sending bulk requests +/// (i.e. multiple sites in one request). +/// +/// Use SolaredgeCredentials.set_bulk_list() to specify the site list to use. +pub trait SendReqBulk: SendReq { + /// Send the bulk request to Solaredge and return the response. + /// + /// # Arguments + /// + /// * `solaredge` - SolarEdge credentials to use for sending + /// + /// # Returns + /// The SolarEdge response or an error string. + /// + /// # Errors + /// Errors can occur on the request send or when parsing the response. + /// Also it is an error if the bulk list is empty. + fn send_bulk(&self, solaredge: &SolaredgeCredentials) -> Result + where + for<'de> Resp: Deserialize<'de>; +} + #[cfg(test)] pub(crate) fn is_normal() {} @@ -179,10 +230,17 @@ mod tests { #[test] fn solaredge_credentials_unit_test() { - let se = SolaredgeCredentials::new("id", "key"); + let mut se = SolaredgeCredentials::new("id", "key"); assert_eq!(se.site_id, "id"); assert_eq!(se.site_id(), "id"); assert_eq!(se.api_key, "api_key=key"); + assert_eq!(se.bulk_list, None); + + se.set_bulk_list(vec!["1"]); + assert_eq!(se.bulk_list, Some("1".to_string())); + + se.set_bulk_list(vec!["2", "4"]); + assert_eq!(se.bulk_list, Some("2,4".to_string())); } #[test] diff --git a/src/site_data_period.rs b/src/site_data_period.rs new file mode 100644 index 0000000..0484140 --- /dev/null +++ b/src/site_data_period.rs @@ -0,0 +1,78 @@ +//! Module for querying the energy production start and end dates of the site. + +use crate::{Error, Kind, SendReq, SendReqBulk, SolaredgeCredentials, MONITORING_API_URL}; +use serde::{Deserialize, Serialize}; + +/// site_data_period request +#[derive(Clone, Debug, PartialEq)] +pub struct Req; + +/// site_data_period response +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Period of time site has been producing. + pub data_period: SiteDataPeriod, +} + +/// Period of time site has been producing. +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SiteDataPeriod { + /// Start date of energy production. + pub start_date: Option, + /// End date of energy production. + pub end_date: Option, +} + +impl Req { + /// Create a site_data_period 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/{}/dataPeriod?{}", + *MONITORING_API_URL, site_id, api_key, + ) + } +} + +impl SendReqBulk for Req { + fn send_bulk(&self, solaredge: &SolaredgeCredentials) -> Result + where + for<'de> Resp: Deserialize<'de>, + { + match &solaredge.bulk_list { + Some(b) => { + let url = self.build_url(&b, &solaredge.api_key); + + self.send_helper(&url) + } + None => Err(Error::new(Kind::BulkListNone)), + } + } +} + +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::(); + } +} diff --git a/src/site_details.rs b/src/site_details.rs index 68148b9..3ea4a81 100644 --- a/src/site_details.rs +++ b/src/site_details.rs @@ -3,7 +3,7 @@ use crate::site_location::SiteLocation; use crate::site_module::SiteModule; use crate::site_public_settings::SitePublicSettings; -use crate::{SendReq, SolaredgeCredentials, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -84,10 +84,10 @@ impl Req { } impl SendReq for Req { - fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { + fn build_url(&self, site_id: &str, api_key: &str) -> String { format!( "{}site/{}/details?{}", - *MONITORING_API_URL, solaredge.site_id, solaredge.api_key, + *MONITORING_API_URL, site_id, api_key, ) } } diff --git a/src/site_energy_detailed.rs b/src/site_energy_detailed.rs index 56e3a6d..8dd7ff7 100644 --- a/src/site_energy_detailed.rs +++ b/src/site_energy_detailed.rs @@ -4,7 +4,7 @@ use crate::meter_type::MeterType; use crate::meter_value::MeterValue; use crate::time_unit::TimeUnit; use crate::URL_TIME_FORMAT; -use crate::{SendReq, SolaredgeCredentials, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// site_energyDetails request @@ -86,16 +86,16 @@ impl Req { } impl SendReq for Req { - fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { + fn build_url(&self, site_id: &str, api_key: &str) -> String { format!( "{}site/{}/energyDetails?{}{}{}{}{}", *MONITORING_API_URL, - solaredge.site_id, + site_id, self.meters, self.time_unit, self.start_time, self.end_time, - solaredge.api_key, + api_key, ) } } diff --git a/src/site_power_detailed.rs b/src/site_power_detailed.rs index c83b04f..8ba9c5e 100644 --- a/src/site_power_detailed.rs +++ b/src/site_power_detailed.rs @@ -3,7 +3,7 @@ use crate::meter_type::MeterType; use crate::meter_value::MeterValue; use crate::URL_TIME_FORMAT; -use crate::{SendReq, SolaredgeCredentials, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// site_powerDetails request @@ -74,15 +74,10 @@ impl Req { } impl SendReq for Req { - fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { + fn build_url(&self, site_id: &str, api_key: &str) -> String { format!( "{}site/{}/powerDetails?{}{}{}{}", - *MONITORING_API_URL, - solaredge.site_id, - self.meters, - self.start_time, - self.end_time, - solaredge.api_key, + *MONITORING_API_URL, site_id, self.meters, self.start_time, self.end_time, api_key, ) } } diff --git a/src/supported_versions.rs b/src/supported_versions.rs index 89f8fc2..39b217e 100644 --- a/src/supported_versions.rs +++ b/src/supported_versions.rs @@ -1,6 +1,6 @@ //! Module for querying the API versions supported by the SolarEdge monitoring server. -use crate::{SendReq, SolaredgeCredentials, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// Supported versions request @@ -30,11 +30,8 @@ impl Req { } impl SendReq for Req { - fn build_url(&self, solaredge: &SolaredgeCredentials) -> String { - format!( - "{}version/supported?{}", - *MONITORING_API_URL, solaredge.api_key, - ) + fn build_url(&self, _: &str, api_key: &str) -> String { + format!("{}version/supported?{}", *MONITORING_API_URL, api_key,) } } From d4d83b6b05c67fc6a9e7465c469841cee7321888 Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Tue, 17 May 2022 22:15:24 -0400 Subject: [PATCH 04/11] Finished site_data_period. --- src/se_ms_api.rs | 6 ++++-- tests/integration_reqs_test.rs | 32 +++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index c008931..91c6025 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -37,6 +37,7 @@ //! //! Supported API requests/responses include: //! * [CurrentVersionReq] / [CurrentVersionResp] +//! * [SiteDataPeriodReq] / [SiteDataPeriodResp] //! * [SiteDetailsReq] / [SiteDetailsResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] @@ -44,8 +45,7 @@ //! //! TODO: //! SitesList, -//! SiteDataPeriod start/end dates & bulk, -//! SiteEnergy & bulk,, +//! SiteEnergy & bulk, //! SiteTimeFrameEnergy & bulk, //! SitePower & bulk, //! SiteOverview & bulk, @@ -75,6 +75,8 @@ pub use current_version::Resp as CurrentVersionResp; pub use error::{Error, Kind}; pub use meter_type::MeterType; use serde::Deserialize; +pub use site_data_period::Req as SiteDataPeriodReq; +pub use site_data_period::Resp as SiteDataPeriodResp; pub use site_details::Req as SiteDetailsReq; pub use site_details::Resp as SiteDetailsResp; pub use site_energy_detailed::Req as SiteEnergyDetailedReq; diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index 5d2baf7..65ada55 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -1,4 +1,4 @@ -use chrono::NaiveDateTime; +use chrono::{Local, NaiveDateTime}; #[macro_use] extern crate lazy_static; @@ -6,8 +6,8 @@ extern crate lazy_static; mod common; use se_ms_api::{ - CurrentVersionReq, MeterType, SendReq, SiteDetailsReq, SiteEnergyDetailedReq, - SitePowerDetailedReq, SupportedVersionsReq, + CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, + SiteEnergyDetailedReq, SitePowerDetailedReq, SupportedVersionsReq, }; #[test] @@ -144,3 +144,29 @@ fn site_power_detailed_integration_test() { } }; } + +#[test] +fn site_data_period_integration_test() { + let req = SiteDataPeriodReq::new(); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + if let Some(sd) = r.data_period.start_date { + assert_eq!(sd, "2018-02-08"); + } else { + panic!("SiteDataPeriod start date is none.") + } + if let Some(ed) = r.data_period.end_date { + let mut today = Local::today().to_string(); + today.truncate(10); + assert_eq!(ed, today); + } else { + panic!("SiteDataPeriod end date is none.") + } + } + Err(e) => { + panic!("Unexpected SiteDataPeriod response: {:?}", e); + } + } +} From 15d618d61961425f9670e639cb7ded16ee5f36ec Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Wed, 18 May 2022 22:06:32 -0400 Subject: [PATCH 05/11] Added site_energy request and response. --- src/se_ms_api.rs | 23 ++++++- src/site_data_period.rs | 18 +----- src/site_energy.rs | 87 ++++++++++++++++++++++++++ src/site_energy_detailed.rs | 6 +- src/site_power_detailed.rs | 6 +- tests/common/mod.rs | 3 +- tests/integration_reqs_test.rs | 108 ++++++++++++++++++++++++++------- 7 files changed, 202 insertions(+), 49 deletions(-) create mode 100644 src/site_energy.rs diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 91c6025..dd83ca0 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -39,13 +39,13 @@ //! * [CurrentVersionReq] / [CurrentVersionResp] //! * [SiteDataPeriodReq] / [SiteDataPeriodResp] //! * [SiteDetailsReq] / [SiteDetailsResp] +//! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: //! SitesList, -//! SiteEnergy & bulk, //! SiteTimeFrameEnergy & bulk, //! SitePower & bulk, //! SiteOverview & bulk, @@ -79,6 +79,8 @@ pub use site_data_period::Req as SiteDataPeriodReq; pub use site_data_period::Resp as SiteDataPeriodResp; pub use site_details::Req as SiteDetailsReq; pub use site_details::Resp as SiteDetailsResp; +pub use site_energy::Req as SiteEnergyReq; +pub use site_energy::Resp as SiteEnergyResp; pub use site_energy_detailed::Req as SiteEnergyDetailedReq; pub use site_energy_detailed::Resp as SiteEnergyDetailedResp; pub use site_power_detailed::Req as SitePowerDetailedReq; @@ -93,6 +95,7 @@ mod meter_type; mod meter_value; mod site_data_period; mod site_details; +mod site_energy; mod site_energy_detailed; mod site_location; mod site_module; @@ -109,7 +112,8 @@ lazy_static! { static ref MONITORING_API_URL: String = "https://monitoringapi.solaredge.com/".to_string(); } -const URL_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; +const URL_DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; +const URL_DATE_FORMAT: &str = "%Y-%m-%d"; /// Struct for accessing SolarEdge's monitoring server for a given site and api key. /// @@ -173,6 +177,9 @@ pub trait SendReq { where for<'de> Resp: Deserialize<'de>, { + //let res = reqwest::blocking::get(url)?.text()?; + //panic!("url is {}, res is: {:?}", url, res); + let res = REQWEST_CLIENT.get(url).send()?; let parsed = res.json::()?; @@ -220,7 +227,17 @@ pub trait SendReqBulk: SendReq { /// Also it is an error if the bulk list is empty. fn send_bulk(&self, solaredge: &SolaredgeCredentials) -> Result where - for<'de> Resp: Deserialize<'de>; + for<'de> Resp: Deserialize<'de>, + { + match &solaredge.bulk_list { + Some(b) => { + let url = self.build_url(&b, &solaredge.api_key); + + self.send_helper(&url) + } + None => Err(Error::new(Kind::BulkListNone)), + } + } } #[cfg(test)] diff --git a/src/site_data_period.rs b/src/site_data_period.rs index 0484140..8dee049 100644 --- a/src/site_data_period.rs +++ b/src/site_data_period.rs @@ -1,6 +1,6 @@ //! Module for querying the energy production start and end dates of the site. -use crate::{Error, Kind, SendReq, SendReqBulk, SolaredgeCredentials, MONITORING_API_URL}; +use crate::{SendReq, SendReqBulk, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// site_data_period request @@ -42,21 +42,7 @@ impl SendReq for Req { } } -impl SendReqBulk for Req { - fn send_bulk(&self, solaredge: &SolaredgeCredentials) -> Result - where - for<'de> Resp: Deserialize<'de>, - { - match &solaredge.bulk_list { - Some(b) => { - let url = self.build_url(&b, &solaredge.api_key); - - self.send_helper(&url) - } - None => Err(Error::new(Kind::BulkListNone)), - } - } -} +impl SendReqBulk for Req {} impl Default for Req { fn default() -> Self { diff --git a/src/site_energy.rs b/src/site_energy.rs new file mode 100644 index 0000000..dcce9cb --- /dev/null +++ b/src/site_energy.rs @@ -0,0 +1,87 @@ +//! Module for querying the site energy measurements. + +pub use crate::date_value::DateValue; + +use crate::time_unit::TimeUnit; +use crate::URL_DATE_FORMAT; +use crate::{SendReq, SendReqBulk, MONITORING_API_URL}; +use serde::{Deserialize, Serialize}; + +/// site_energy request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + start_date: String, + end_date: String, + time_unit: String, +} + +/// site_energy response +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Energy measurements. + pub energy: Energy, +} + +/// Energy measurements. +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Energy { + /// Granularity of the energy measurements (should match the request) + pub time_unit: String, + + /// Measurement unit (e.g. Wh) + pub unit: String, + + /// For the dates requested, measurements over the time period + pub values: Vec, +} + +impl Req { + /// Create a site_energy request message that can be sent to SolarEdge. + #[must_use] + pub fn new( + start_date: chrono::NaiveDate, + end_date: chrono::NaiveDate, + time_unit: Option, + ) -> Self { + let start_date = format!("startDate={}&", start_date.format(URL_DATE_FORMAT)); + + let end_date = format!("endDate={}&", end_date.format(URL_DATE_FORMAT)); + + let time_unit = match time_unit { + Some(t) => format!("timeUnit={}&", t), + None => "".to_string(), + }; + + Req { + start_date, + end_date, + time_unit, + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/energy?{}{}{}{}", + *MONITORING_API_URL, site_id, self.time_unit, self.start_date, self.end_date, api_key, + ) + } +} + +impl SendReqBulk for Req {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_energy_detailed.rs b/src/site_energy_detailed.rs index 8dd7ff7..6de1d2e 100644 --- a/src/site_energy_detailed.rs +++ b/src/site_energy_detailed.rs @@ -3,7 +3,7 @@ use crate::meter_type::MeterType; use crate::meter_value::MeterValue; use crate::time_unit::TimeUnit; -use crate::URL_TIME_FORMAT; +use crate::URL_DATE_TIME_FORMAT; use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; @@ -56,9 +56,9 @@ impl Req { time_unit: Option, meters: Option>, ) -> Self { - let start_time = format!("startTime={}&", start_time.format(URL_TIME_FORMAT)); + let start_time = format!("startTime={}&", start_time.format(URL_DATE_TIME_FORMAT)); - let end_time = format!("endTime={}&", end_time.format(URL_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), diff --git a/src/site_power_detailed.rs b/src/site_power_detailed.rs index 8ba9c5e..72e84a4 100644 --- a/src/site_power_detailed.rs +++ b/src/site_power_detailed.rs @@ -2,7 +2,7 @@ use crate::meter_type::MeterType; use crate::meter_value::MeterValue; -use crate::URL_TIME_FORMAT; +use crate::URL_DATE_TIME_FORMAT; use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; @@ -50,9 +50,9 @@ impl Req { end_time: chrono::NaiveDateTime, meters: Option>, ) -> Self { - let start_time = format!("startTime={}&", start_time.format(URL_TIME_FORMAT)); + let start_time = format!("startTime={}&", start_time.format(URL_DATE_TIME_FORMAT)); - let end_time = format!("endTime={}&", end_time.format(URL_TIME_FORMAT)); + let end_time = format!("endTime={}&", end_time.format(URL_DATE_TIME_FORMAT)); let meters = match meters { Some(m) => format!( diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a23a18e..1842db7 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,7 +2,8 @@ use se_ms_api::SolaredgeCredentials; use std::env; use std::fs; -pub const TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; +pub const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S"; +pub const DATE_FORMAT: &str = "%Y-%m-%d"; const TEST_CREDENTIALS_FILE: &str = "test_credentials.txt"; diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index 65ada55..b41066f 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -1,4 +1,4 @@ -use chrono::{Local, NaiveDateTime}; +use chrono::{Local, NaiveDate, NaiveDateTime}; #[macro_use] extern crate lazy_static; @@ -6,22 +6,23 @@ extern crate lazy_static; mod common; use se_ms_api::{ - CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SitePowerDetailedReq, SupportedVersionsReq, + CurrentVersionReq, Kind, MeterType, SendReq, SendReqBulk, SiteDataPeriodReq, SiteDetailsReq, + SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SupportedVersionsReq, }; #[test] fn site_energy_detailed_integration_test() { - let start_ndt = match NaiveDateTime::parse_from_str("2022-01-01 00:00:00", common::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::TIME_FORMAT) { - Ok(dt) => dt, - Err(error) => panic!("Error parsing end date: {}", error), - }; + 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 = SiteEnergyDetailedReq::new( start_ndt, @@ -108,16 +109,17 @@ fn site_details_integration_test() { #[test] fn site_power_detailed_integration_test() { - let start_ndt = match NaiveDateTime::parse_from_str("2022-01-01 00:00:00", common::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::TIME_FORMAT) { - Ok(dt) => dt, - Err(error) => panic!("Error parsing end date: {}", error), - }; + 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 = SitePowerDetailedReq::new(start_ndt, end_ndt, Some(vec![MeterType::Purchased])); @@ -169,4 +171,64 @@ fn site_data_period_integration_test() { panic!("Unexpected SiteDataPeriod response: {:?}", e); } } + + let resp = req.send_bulk(&common::TEST_CREDENTIALS); + match resp { + Ok(r) => { + panic!("SiteDataPeriod unexpected success: {:?}", r) + } + Err(e) => match e.kind() { + Kind::BulkListNone => assert!(true), + u => panic!("Unexpected SiteDataPeriod error: {:?}", u), + }, + } +} + +#[test] +fn site_energy_integration_test() { + let start_date = match NaiveDate::parse_from_str("2022-01-01", common::DATE_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing start date: {}", error), + }; + + let end_date = match NaiveDate::parse_from_str("2022-01-02", common::DATE_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SiteEnergyReq::new(start_date, end_date, None); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.energy.time_unit, "DAY"); + assert_eq!(r.energy.unit, "Wh"); + assert_eq!(r.energy.values.len(), 2); + assert_eq!(r.energy.values[0].date, "2022-01-01 00:00:00"); + assert_eq!(r.energy.values[1].date, "2022-01-02 00:00:00"); + + if let Some(v) = r.energy.values[0].value { + assert_eq!(v, 12926.0); + } else { + panic!("Missing value."); + } + if let Some(v) = r.energy.values[1].value { + assert_eq!(v, 4419.0); + } else { + panic!("Missing value."); + } + } + Err(e) => panic!("Unexpected SiteEnergy response: {:?}", e), + } + + let resp = req.send_bulk(&common::TEST_CREDENTIALS); + match resp { + Ok(r) => { + panic!("SiteEnergy unexpected success: {:?}", r) + } + Err(e) => match e.kind() { + Kind::BulkListNone => assert!(true), + u => panic!("Unexpected SiteEnergy error: {:?}", u), + }, + } } From 517ed4b05faff54015a00c7a056490a2f9bdba63 Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Thu, 19 May 2022 19:54:21 -0400 Subject: [PATCH 06/11] Added SiteTimeFrameEnergy request. Check response status before trying to parse json. --- src/error.rs | 5 +++ src/se_ms_api.rs | 28 ++++++++++--- src/site_time_frame_energy.rs | 72 ++++++++++++++++++++++++++++++++++ tests/integration_reqs_test.rs | 38 +++++++++++++++++- 4 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/site_time_frame_energy.rs diff --git a/src/error.rs b/src/error.rs index 89a8da1..aac2113 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,6 +31,9 @@ pub enum Kind { /// Attempted bulk operation, but bulk list is empty. BulkListNone, + + /// HTTP error from sending a request. + HttpErrorStatus(String, String), } impl error::Error for Error { @@ -38,6 +41,7 @@ impl error::Error for Error { match self.kind { Kind::ReqwestError(_) => "Reqwest error", Kind::BulkListNone => "Tried to use empty bulk list.", + Kind::HttpErrorStatus(_, _) => "HTTP error", } } } @@ -47,6 +51,7 @@ impl fmt::Display for Error { match &self.kind { Kind::ReqwestError(s) => write!(f, "Reqwest Error: HTTP status-code{}", s), Kind::BulkListNone => write!(f, "Empty bulk list error"), + Kind::HttpErrorStatus(s, t) => write!(f, "HTTP error: {}: {}", s, t), } } } diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index dd83ca0..8e1b62e 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -42,11 +42,11 @@ //! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] +//! * [SiteTimeFrameEnergyReq] / [SiteTimeFrameEnergyResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: //! SitesList, -//! SiteTimeFrameEnergy & bulk, //! SitePower & bulk, //! SiteOverview & bulk, //! SitePowerFlow, @@ -85,6 +85,8 @@ pub use site_energy_detailed::Req as SiteEnergyDetailedReq; pub use site_energy_detailed::Resp as SiteEnergyDetailedResp; pub use site_power_detailed::Req as SitePowerDetailedReq; pub use site_power_detailed::Resp as SitePowerDetailedResp; +pub use site_time_frame_energy::Req as SiteTimeFrameEnergyReq; +pub use site_time_frame_energy::Resp as SiteTimeFrameEnergyResp; pub use supported_versions::Req as SupportedVersionsReq; pub use supported_versions::Resp as SupportedVersionsResp; @@ -101,6 +103,7 @@ mod site_location; mod site_module; mod site_power_detailed; mod site_public_settings; +mod site_time_frame_energy; mod supported_versions; mod time_unit; @@ -177,14 +180,27 @@ pub trait SendReq { where for<'de> Resp: Deserialize<'de>, { - //let res = reqwest::blocking::get(url)?.text()?; - //panic!("url is {}, res is: {:?}", url, res); - let res = REQWEST_CLIENT.get(url).send()?; - let parsed = res.json::()?; + if res.status().is_success() { + let parsed = res.json::()?; + + Ok(parsed) + } else { + let reason: String; + match res.status().canonical_reason() { + Some(r) => reason = r.to_string(), + None => reason = res.status().as_str().to_string(), + }; - Ok(parsed) + let text: String; + match res.text() { + Ok(t) => text = t, + Err(_) => text = "".to_string(), + }; + + Err(Error::new(Kind::HttpErrorStatus(reason, text))) + } } /// Send the request to Solaredge and return the response. diff --git a/src/site_time_frame_energy.rs b/src/site_time_frame_energy.rs new file mode 100644 index 0000000..2188f9e --- /dev/null +++ b/src/site_time_frame_energy.rs @@ -0,0 +1,72 @@ +//! Module for querying the site total energy produced for a given period. + +pub use crate::date_value::DateValue; + +use crate::URL_DATE_FORMAT; +use crate::{SendReq, SendReqBulk, MONITORING_API_URL}; +use serde::{Deserialize, Serialize}; + +/// site_time_frame_energy request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + start_date: String, + end_date: String, +} + +/// site_time_frame_energy response +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Energy measurements. + pub time_frame_energy: TimeFrameEnergy, +} + +/// Energy measurements. +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct TimeFrameEnergy { + /// Energy produced during the time period + pub energy: f32, + + /// Measurement unit (e.g. Wh) + pub unit: String, +} + +impl Req { + /// Create a site_time_frame_energy request message that can be sent to SolarEdge. + #[must_use] + pub fn new(start_date: chrono::NaiveDate, end_date: chrono::NaiveDate) -> Self { + let start_date = format!("startDate={}&", start_date.format(URL_DATE_FORMAT)); + + let end_date = format!("endDate={}&", end_date.format(URL_DATE_FORMAT)); + + Req { + start_date, + end_date, + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/timeFrameEnergy?{}{}{}", + *MONITORING_API_URL, site_id, self.start_date, self.end_date, api_key, + ) + } +} + +impl SendReqBulk for Req {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index b41066f..bb1445d 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -7,7 +7,8 @@ mod common; use se_ms_api::{ CurrentVersionReq, Kind, MeterType, SendReq, SendReqBulk, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SupportedVersionsReq, + SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SiteTimeFrameEnergyReq, + SupportedVersionsReq, }; #[test] @@ -232,3 +233,38 @@ fn site_energy_integration_test() { }, } } + +#[test] +fn site_time_frame_energy_integration_test() { + let start_date = match NaiveDate::parse_from_str("2022-01-01", common::DATE_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing start date: {}", error), + }; + + let end_date = match NaiveDate::parse_from_str("2022-01-02", common::DATE_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SiteTimeFrameEnergyReq::new(start_date, end_date); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.time_frame_energy.unit, "Wh"); + assert_eq!(r.time_frame_energy.energy, 12896.0); + } + Err(e) => panic!("Unexpected SiteTimeFrameEnergy response: {:?}", e), + } + + let resp = req.send_bulk(&common::TEST_CREDENTIALS); + match resp { + Ok(r) => { + panic!("SiteTimeFrameEnergy unexpected success: {:?}", r) + } + Err(e) => match e.kind() { + Kind::BulkListNone => assert!(true), + u => panic!("Unexpected SiteTimeFrameEnergy error: {:?}", u), + }, + } +} From 49b4dd83a17b900b55bdf6c2d9914c0b18b7fa7d Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Fri, 20 May 2022 20:16:39 -0400 Subject: [PATCH 07/11] Added SitePowerReq/Resp. --- src/se_ms_api.rs | 8 +++- src/site_energy.rs | 14 ++++--- src/site_energy_detailed.rs | 6 +-- src/site_power.rs | 77 ++++++++++++++++++++++++++++++++++ src/site_power_detailed.rs | 7 +--- src/time_unit.rs | 5 ++- tests/integration_reqs_test.rs | 61 +++++++++++++++++++++++++-- 7 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 src/site_power.rs diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 8e1b62e..b0714b0 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -41,13 +41,13 @@ //! * [SiteDetailsReq] / [SiteDetailsResp] //! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] +//! * [SitePowerReq] / [SitePowerResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] //! * [SiteTimeFrameEnergyReq] / [SiteTimeFrameEnergyResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: //! SitesList, -//! SitePower & bulk, //! SiteOverview & bulk, //! SitePowerFlow, //! SiteStorageInformation, @@ -72,8 +72,10 @@ pub use current_version::Req as CurrentVersionReq; pub use current_version::Resp as CurrentVersionResp; +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_data_period::Req as SiteDataPeriodReq; pub use site_data_period::Resp as SiteDataPeriodResp; @@ -83,12 +85,15 @@ pub use site_energy::Req as SiteEnergyReq; pub use site_energy::Resp as SiteEnergyResp; pub use site_energy_detailed::Req as SiteEnergyDetailedReq; pub use site_energy_detailed::Resp as SiteEnergyDetailedResp; +pub use site_power::Req as SitePowerReq; +pub use site_power::Resp as SitePowerResp; pub use site_power_detailed::Req as SitePowerDetailedReq; pub use site_power_detailed::Resp as SitePowerDetailedResp; pub use site_time_frame_energy::Req as SiteTimeFrameEnergyReq; pub use site_time_frame_energy::Resp as SiteTimeFrameEnergyResp; pub use supported_versions::Req as SupportedVersionsReq; pub use supported_versions::Resp as SupportedVersionsResp; +pub use time_unit::TimeUnit; mod current_version; mod date_value; @@ -101,6 +106,7 @@ mod site_energy; mod site_energy_detailed; mod site_location; mod site_module; +mod site_power; mod site_power_detailed; mod site_public_settings; mod site_time_frame_energy; diff --git a/src/site_energy.rs b/src/site_energy.rs index dcce9cb..8524f1f 100644 --- a/src/site_energy.rs +++ b/src/site_energy.rs @@ -1,10 +1,6 @@ //! Module for querying the site energy measurements. -pub use crate::date_value::DateValue; - -use crate::time_unit::TimeUnit; -use crate::URL_DATE_FORMAT; -use crate::{SendReq, SendReqBulk, MONITORING_API_URL}; +use crate::{DateValue, SendReq, SendReqBulk, TimeUnit, MONITORING_API_URL, URL_DATE_FORMAT}; use serde::{Deserialize, Serialize}; /// site_energy request @@ -28,7 +24,7 @@ pub struct Resp { #[serde(rename_all = "camelCase")] pub struct Energy { /// Granularity of the energy measurements (should match the request) - pub time_unit: String, + pub time_unit: TimeUnit, /// Measurement unit (e.g. Wh) pub unit: String, @@ -39,6 +35,12 @@ pub struct Energy { impl Req { /// Create a site_energy request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * `start_date` - beginning date for the energy details + /// * `end_date` - end date for the energy details + /// * `time_unit` - size of time unit to collect over the date period #[must_use] pub fn new( start_date: chrono::NaiveDate, diff --git a/src/site_energy_detailed.rs b/src/site_energy_detailed.rs index 6de1d2e..4a9dbeb 100644 --- a/src/site_energy_detailed.rs +++ b/src/site_energy_detailed.rs @@ -1,10 +1,6 @@ //! Module for detailed site energy measurements from meters such as consumption, export (feed-in), import (purchase), etc. -use crate::meter_type::MeterType; -use crate::meter_value::MeterValue; -use crate::time_unit::TimeUnit; -use crate::URL_DATE_TIME_FORMAT; -use crate::{SendReq, MONITORING_API_URL}; +use crate::{MeterType, MeterValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; use serde::{Deserialize, Serialize}; /// site_energyDetails request diff --git a/src/site_power.rs b/src/site_power.rs new file mode 100644 index 0000000..dd9f78e --- /dev/null +++ b/src/site_power.rs @@ -0,0 +1,77 @@ +//! Module for querying the site power measurements in 15 minute resolution. + +use crate::{DateValue, SendReq, SendReqBulk, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; +use serde::{Deserialize, Serialize}; + +/// site_power request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + start_time: String, + end_time: String, +} + +/// site_power response +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Power measurements. + pub power: Power, +} + +/// Power measurements. +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Power { + /// Time unit of the Power measurements + pub time_unit: TimeUnit, + + /// Measurement unit (e.g. W) + pub unit: String, + + /// For the dates requested, measurements over the time period + pub values: Vec, +} + +impl Req { + /// Create a site_power 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 + #[must_use] + pub fn new(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 { + start_time, + end_time, + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/power?{}{}{}", + *MONITORING_API_URL, site_id, self.start_time, self.end_time, api_key, + ) + } +} + +impl SendReqBulk for Req {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn normal_types_unit_test() { + is_normal::(); + is_normal::(); + is_normal::(); + } +} diff --git a/src/site_power_detailed.rs b/src/site_power_detailed.rs index 72e84a4..6475262 100644 --- a/src/site_power_detailed.rs +++ b/src/site_power_detailed.rs @@ -1,9 +1,6 @@ //! Module for detailed site power measurements from meters such as consumption, export (feed-in), import (purchase), etc. -use crate::meter_type::MeterType; -use crate::meter_value::MeterValue; -use crate::URL_DATE_TIME_FORMAT; -use crate::{SendReq, MONITORING_API_URL}; +use crate::{MeterType, MeterValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; use serde::{Deserialize, Serialize}; /// site_powerDetails request @@ -27,7 +24,7 @@ pub struct Resp { #[serde(rename_all = "camelCase")] pub struct PowerDetails { /// Granularity of the power detail values (should match the request) - pub time_unit: String, + pub time_unit: TimeUnit, /// Measurement unit (e.g. Wh) pub unit: String, diff --git a/src/time_unit.rs b/src/time_unit.rs index c5d8864..1c0c88c 100644 --- a/src/time_unit.rs +++ b/src/time_unit.rs @@ -1,8 +1,11 @@ //! Module for handling units of time used by the SolarEdge server monitoring API. +use serde::{Deserialize, Serialize}; + /// Time units specified in SolarEdge server monitoring API requests and responses. /// Specifies the aggregation granularity of the data. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TimeUnit { /// 15 minutes QuarterOfAnHour, diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index bb1445d..8f1f700 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -7,8 +7,8 @@ mod common; use se_ms_api::{ CurrentVersionReq, Kind, MeterType, SendReq, SendReqBulk, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SiteTimeFrameEnergyReq, - SupportedVersionsReq, + SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SitePowerReq, + SiteTimeFrameEnergyReq, SupportedVersionsReq, TimeUnit, }; #[test] @@ -128,7 +128,7 @@ fn site_power_detailed_integration_test() { match resp { Ok(r) => { - assert_eq!(r.power_details.time_unit, "QUARTER_OF_AN_HOUR"); + assert_eq!(r.power_details.time_unit, TimeUnit::QuarterOfAnHour); assert_eq!(r.power_details.unit, "W"); assert_eq!(r.power_details.meters.len(), 1); assert_eq!(r.power_details.meters[0].meter_type, MeterType::Purchased); @@ -202,7 +202,7 @@ fn site_energy_integration_test() { match resp { Ok(r) => { - assert_eq!(r.energy.time_unit, "DAY"); + assert_eq!(r.energy.time_unit, TimeUnit::Day); assert_eq!(r.energy.unit, "Wh"); assert_eq!(r.energy.values.len(), 2); assert_eq!(r.energy.values[0].date, "2022-01-01 00:00:00"); @@ -268,3 +268,56 @@ fn site_time_frame_energy_integration_test() { }, } } + +#[test] +fn site_power_integration_test() { + let start_date = + match NaiveDateTime::parse_from_str("2022-01-01 12:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing start date: {}", error), + }; + + let end_date = + match NaiveDateTime::parse_from_str("2022-01-01 13:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SitePowerReq::new(start_date, end_date); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.power.time_unit, TimeUnit::QuarterOfAnHour); + assert_eq!(r.power.unit, "W"); + assert_eq!(r.power.values.len(), 4); + assert_eq!(r.power.values[0].date, "2022-01-01 12:00:00"); + assert_eq!(r.power.values[1].date, "2022-01-01 12:15:00"); + assert_eq!(r.power.values[2].date, "2022-01-01 12:30:00"); + assert_eq!(r.power.values[3].date, "2022-01-01 12:45:00"); + + if let Some(v) = r.power.values[0].value { + assert_eq!(v, 2013.872); + } else { + panic!("Missing value."); + } + if let Some(v) = r.power.values[3].value { + assert_eq!(v, 1670.7087); + } else { + panic!("Missing value."); + } + } + Err(e) => panic!("Unexpected SitePower response: {:?}", e), + } + + let resp = req.send_bulk(&common::TEST_CREDENTIALS); + match resp { + Ok(r) => { + panic!("SitePower unexpected success: {:?}", r) + } + Err(e) => match e.kind() { + Kind::BulkListNone => assert!(true), + u => panic!("Unexpected SitePower error: {:?}", u), + }, + } +} From efe358a9c03f72a3954be0ebb458ae56d65ce129 Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Fri, 20 May 2022 21:18:37 -0400 Subject: [PATCH 08/11] Remove incorrect bulk request implementation. --- src/error.rs | 5 --- src/se_ms_api.rs | 63 ++++++---------------------------- src/site_data_period.rs | 4 +-- src/site_energy.rs | 4 +-- src/site_power.rs | 4 +-- src/site_time_frame_energy.rs | 4 +-- tests/integration_reqs_test.rs | 46 +------------------------ 7 files changed, 16 insertions(+), 114 deletions(-) diff --git a/src/error.rs b/src/error.rs index aac2113..07734e8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,9 +29,6 @@ pub enum Kind { /// An error returned from the reqwest crate. ReqwestError(reqwest::Error), - /// Attempted bulk operation, but bulk list is empty. - BulkListNone, - /// HTTP error from sending a request. HttpErrorStatus(String, String), } @@ -40,7 +37,6 @@ impl error::Error for Error { fn description(&self) -> &str { match self.kind { Kind::ReqwestError(_) => "Reqwest error", - Kind::BulkListNone => "Tried to use empty bulk list.", Kind::HttpErrorStatus(_, _) => "HTTP error", } } @@ -50,7 +46,6 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.kind { Kind::ReqwestError(s) => write!(f, "Reqwest Error: HTTP status-code{}", s), - Kind::BulkListNone => write!(f, "Empty bulk list error"), Kind::HttpErrorStatus(s, t) => write!(f, "HTTP error: {}: {}", s, t), } } diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index b0714b0..925be79 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -48,7 +48,7 @@ //! //! TODO: //! SitesList, -//! SiteOverview & bulk, +//! SiteOverview, //! SitePowerFlow, //! SiteStorageInformation, //! SiteImage, @@ -68,31 +68,22 @@ #![warn(missing_docs)] #![warn(missing_debug_implementations)] #![warn(clippy::all, clippy::pedantic)] -#![allow(clippy::doc_markdown)] -pub use current_version::Req as CurrentVersionReq; -pub use current_version::Resp as CurrentVersionResp; +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_power::{Req as SitePowerReq, Resp as SitePowerResp}; +pub use site_power_detailed::{Req as SitePowerDetailedReq, Resp as SitePowerDetailedResp}; +pub use site_time_frame_energy::{Req as SiteTimeFrameEnergyReq, Resp as SiteTimeFrameEnergyResp}; +pub use supported_versions::{Req as SupportedVersionsReq, Resp as SupportedVersionsResp}; + 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_data_period::Req as SiteDataPeriodReq; -pub use site_data_period::Resp as SiteDataPeriodResp; -pub use site_details::Req as SiteDetailsReq; -pub use site_details::Resp as SiteDetailsResp; -pub use site_energy::Req as SiteEnergyReq; -pub use site_energy::Resp as SiteEnergyResp; -pub use site_energy_detailed::Req as SiteEnergyDetailedReq; -pub use site_energy_detailed::Resp as SiteEnergyDetailedResp; -pub use site_power::Req as SitePowerReq; -pub use site_power::Resp as SitePowerResp; -pub use site_power_detailed::Req as SitePowerDetailedReq; -pub use site_power_detailed::Resp as SitePowerDetailedResp; -pub use site_time_frame_energy::Req as SiteTimeFrameEnergyReq; -pub use site_time_frame_energy::Resp as SiteTimeFrameEnergyResp; -pub use supported_versions::Req as SupportedVersionsReq; -pub use supported_versions::Resp as SupportedVersionsResp; pub use time_unit::TimeUnit; mod current_version; @@ -230,38 +221,6 @@ pub trait SendReq { } } -/// Some Solaredge requests implement this for sending bulk requests -/// (i.e. multiple sites in one request). -/// -/// Use SolaredgeCredentials.set_bulk_list() to specify the site list to use. -pub trait SendReqBulk: SendReq { - /// Send the bulk request to Solaredge and return the response. - /// - /// # Arguments - /// - /// * `solaredge` - SolarEdge credentials to use for sending - /// - /// # Returns - /// The SolarEdge response or an error string. - /// - /// # Errors - /// Errors can occur on the request send or when parsing the response. - /// Also it is an error if the bulk list is empty. - fn send_bulk(&self, solaredge: &SolaredgeCredentials) -> Result - where - for<'de> Resp: Deserialize<'de>, - { - match &solaredge.bulk_list { - Some(b) => { - let url = self.build_url(&b, &solaredge.api_key); - - self.send_helper(&url) - } - None => Err(Error::new(Kind::BulkListNone)), - } - } -} - #[cfg(test)] pub(crate) fn is_normal() {} diff --git a/src/site_data_period.rs b/src/site_data_period.rs index 8dee049..6049ac1 100644 --- a/src/site_data_period.rs +++ b/src/site_data_period.rs @@ -1,6 +1,6 @@ //! Module for querying the energy production start and end dates of the site. -use crate::{SendReq, SendReqBulk, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// site_data_period request @@ -42,8 +42,6 @@ impl SendReq for Req { } } -impl SendReqBulk for Req {} - impl Default for Req { fn default() -> Self { Self::new() diff --git a/src/site_energy.rs b/src/site_energy.rs index 8524f1f..246cf36 100644 --- a/src/site_energy.rs +++ b/src/site_energy.rs @@ -1,6 +1,6 @@ //! Module for querying the site energy measurements. -use crate::{DateValue, SendReq, SendReqBulk, TimeUnit, MONITORING_API_URL, URL_DATE_FORMAT}; +use crate::{DateValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_FORMAT}; use serde::{Deserialize, Serialize}; /// site_energy request @@ -73,8 +73,6 @@ impl SendReq for Req { } } -impl SendReqBulk for Req {} - #[cfg(test)] mod tests { use super::*; diff --git a/src/site_power.rs b/src/site_power.rs index dd9f78e..88ef6af 100644 --- a/src/site_power.rs +++ b/src/site_power.rs @@ -1,6 +1,6 @@ //! Module for querying the site power measurements in 15 minute resolution. -use crate::{DateValue, SendReq, SendReqBulk, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; +use crate::{DateValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; use serde::{Deserialize, Serialize}; /// site_power request @@ -61,8 +61,6 @@ impl SendReq for Req { } } -impl SendReqBulk for Req {} - #[cfg(test)] mod tests { use super::*; diff --git a/src/site_time_frame_energy.rs b/src/site_time_frame_energy.rs index 2188f9e..1bd5d27 100644 --- a/src/site_time_frame_energy.rs +++ b/src/site_time_frame_energy.rs @@ -3,7 +3,7 @@ pub use crate::date_value::DateValue; use crate::URL_DATE_FORMAT; -use crate::{SendReq, SendReqBulk, MONITORING_API_URL}; +use crate::{SendReq, MONITORING_API_URL}; use serde::{Deserialize, Serialize}; /// site_time_frame_energy request @@ -56,8 +56,6 @@ impl SendReq for Req { } } -impl SendReqBulk for Req {} - #[cfg(test)] mod tests { use super::*; diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index 8f1f700..ef76d87 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -6,7 +6,7 @@ extern crate lazy_static; mod common; use se_ms_api::{ - CurrentVersionReq, Kind, MeterType, SendReq, SendReqBulk, SiteDataPeriodReq, SiteDetailsReq, + CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SitePowerReq, SiteTimeFrameEnergyReq, SupportedVersionsReq, TimeUnit, }; @@ -172,17 +172,6 @@ fn site_data_period_integration_test() { panic!("Unexpected SiteDataPeriod response: {:?}", e); } } - - let resp = req.send_bulk(&common::TEST_CREDENTIALS); - match resp { - Ok(r) => { - panic!("SiteDataPeriod unexpected success: {:?}", r) - } - Err(e) => match e.kind() { - Kind::BulkListNone => assert!(true), - u => panic!("Unexpected SiteDataPeriod error: {:?}", u), - }, - } } #[test] @@ -221,17 +210,6 @@ fn site_energy_integration_test() { } Err(e) => panic!("Unexpected SiteEnergy response: {:?}", e), } - - let resp = req.send_bulk(&common::TEST_CREDENTIALS); - match resp { - Ok(r) => { - panic!("SiteEnergy unexpected success: {:?}", r) - } - Err(e) => match e.kind() { - Kind::BulkListNone => assert!(true), - u => panic!("Unexpected SiteEnergy error: {:?}", u), - }, - } } #[test] @@ -256,17 +234,6 @@ fn site_time_frame_energy_integration_test() { } Err(e) => panic!("Unexpected SiteTimeFrameEnergy response: {:?}", e), } - - let resp = req.send_bulk(&common::TEST_CREDENTIALS); - match resp { - Ok(r) => { - panic!("SiteTimeFrameEnergy unexpected success: {:?}", r) - } - Err(e) => match e.kind() { - Kind::BulkListNone => assert!(true), - u => panic!("Unexpected SiteTimeFrameEnergy error: {:?}", u), - }, - } } #[test] @@ -309,15 +276,4 @@ fn site_power_integration_test() { } Err(e) => panic!("Unexpected SitePower response: {:?}", e), } - - let resp = req.send_bulk(&common::TEST_CREDENTIALS); - match resp { - Ok(r) => { - panic!("SitePower unexpected success: {:?}", r) - } - Err(e) => match e.kind() { - Kind::BulkListNone => assert!(true), - u => panic!("Unexpected SitePower error: {:?}", u), - }, - } } From 04690e0565751956405045d8d0f0239db37e8ebb Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Sat, 21 May 2022 17:20:30 -0400 Subject: [PATCH 09/11] Added SiteList request / response. --- src/current_version.rs | 6 +- src/date_value.rs | 4 +- src/meter_type.rs | 4 +- src/meter_value.rs | 7 +- src/se_ms_api.rs | 84 ++++------- src/site_data_period.rs | 6 +- src/site_details.rs | 11 +- src/site_energy.rs | 6 +- src/site_energy_detailed.rs | 6 +- src/site_list.rs | 248 +++++++++++++++++++++++++++++++++ src/site_location.rs | 4 +- src/site_module.rs | 4 +- src/site_power.rs | 6 +- src/site_power_detailed.rs | 6 +- src/site_public_settings.rs | 4 +- src/site_time_frame_energy.rs | 11 +- src/supported_versions.rs | 6 +- src/time_unit.rs | 4 +- tests/integration_reqs_test.rs | 22 ++- 19 files changed, 342 insertions(+), 107 deletions(-) create mode 100644 src/site_list.rs diff --git a/src/current_version.rs b/src/current_version.rs index 38ac4d6..e1a03c5 100644 --- a/src/current_version.rs +++ b/src/current_version.rs @@ -1,21 +1,21 @@ //! Module for querying the current API version of the SolarEdge monitoring server. use crate::{SendReq, MONITORING_API_URL}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Current version request #[derive(Clone, Debug, PartialEq)] pub struct Req; /// Current version response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] pub struct Resp { /// The API version running on the server pub version: Version, } /// The release version of the server -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] pub struct Version { /// The release number running on the server in format. pub release: String, diff --git a/src/date_value.rs b/src/date_value.rs index 48b8fd5..0f05a38 100644 --- a/src/date_value.rs +++ b/src/date_value.rs @@ -1,10 +1,10 @@ //! Module for handling generic date / value pairs returned by the SolarEdge server monitoring API. -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// A date and value pair returned from the monitoring API. The value units are specified by the unit /// field elsewhere in the response. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] pub struct DateValue { /// YYYY-mm-dd HH:MM:SS pub date: String, diff --git a/src/meter_type.rs b/src/meter_type.rs index dcdeff0..0c6258a 100644 --- a/src/meter_type.rs +++ b/src/meter_type.rs @@ -1,9 +1,9 @@ //! Module for specifying the meter type in SolarEdge server monitoring API requests and responses. -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Meters supported by SolarEdge. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[derive(Clone, Deserialize, Debug, PartialEq)] pub enum MeterType { /// Solar energy produced. Production, diff --git a/src/meter_value.rs b/src/meter_value.rs index 0fdc387..a187f3b 100644 --- a/src/meter_value.rs +++ b/src/meter_value.rs @@ -1,11 +1,10 @@ //! Module for holding values for a specified meter type in SolarEdge server monitoring API responses. -pub use crate::date_value::DateValue; -use crate::meter_type::MeterType; -use serde::{Deserialize, Serialize}; +use crate::{DateValue, MeterType}; +use serde::Deserialize; /// Values for the meter type over a range of dates. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[derive(Clone, Deserialize, Debug, PartialEq)] pub struct MeterValue { /// The meter type of the associated values. #[serde(rename = "type")] diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 925be79..10ae0af 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -41,13 +41,13 @@ //! * [SiteDetailsReq] / [SiteDetailsResp] //! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] +//! * [SiteListReq] / [SiteListResp] //! * [SitePowerReq] / [SitePowerResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] //! * [SiteTimeFrameEnergyReq] / [SiteTimeFrameEnergyResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: -//! SitesList, //! SiteOverview, //! SitePowerFlow, //! SiteStorageInformation, @@ -68,12 +68,14 @@ #![warn(missing_docs)] #![warn(missing_debug_implementations)] #![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_list::{Req as SiteListReq, Resp as SiteListResp}; pub use site_power::{Req as SitePowerReq, Resp as SitePowerResp}; pub use site_power_detailed::{Req as SitePowerDetailedReq, Resp as SitePowerDetailedResp}; pub use site_time_frame_energy::{Req as SiteTimeFrameEnergyReq, Resp as SiteTimeFrameEnergyResp}; @@ -84,6 +86,10 @@ 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_location::SiteLocation; +pub use site_module::SiteModule; +pub use site_public_settings::SitePublicSettings; pub use time_unit::TimeUnit; mod current_version; @@ -95,6 +101,7 @@ mod site_data_period; mod site_details; mod site_energy; mod site_energy_detailed; +mod site_list; mod site_location; mod site_module; mod site_power; @@ -120,7 +127,6 @@ const URL_DATE_FORMAT: &str = "%Y-%m-%d"; /// Used as the parameter for the send() function of all of the possible requests. #[derive(Clone, Debug, Default, PartialEq)] pub struct SolaredgeCredentials { - bulk_list: Option, site_id: String, api_key: String, } @@ -137,25 +143,10 @@ impl SolaredgeCredentials { /// A credentials struct to be used in subsequent request sends. #[must_use] pub fn new(site_id: &str, api_key: &str) -> Self { - let bulk_list = None; let site_id = site_id.to_string(); let api_key = format!("api_key={}", api_key); - SolaredgeCredentials { - bulk_list, - site_id, - api_key, - } - } - - /// Populate the bulk site list used by the bulk option that some requests support. - /// - /// # Arguments - /// - /// * `bulk_list` - list of site IDs to query. 1 to 100 site IDs allowed. - pub fn set_bulk_list(&mut self, bulk_list: Vec<&str>) { - assert!((bulk_list.len() >= 1) && (bulk_list.len() <= 100)); - self.bulk_list = Some(bulk_list.join(",")); + SolaredgeCredentials { site_id, api_key } } /// See the site ID being used in the credentials. @@ -172,34 +163,6 @@ pub trait SendReq { #[doc(hidden)] fn build_url(&self, site_id: &str, api_key: &str) -> String; - #[doc(hidden)] - fn send_helper(&self, url: &str) -> Result - where - for<'de> Resp: Deserialize<'de>, - { - let res = REQWEST_CLIENT.get(url).send()?; - - if res.status().is_success() { - let parsed = res.json::()?; - - Ok(parsed) - } else { - let reason: String; - match res.status().canonical_reason() { - Some(r) => reason = r.to_string(), - None => reason = res.status().as_str().to_string(), - }; - - let text: String; - match res.text() { - Ok(t) => text = t, - Err(_) => text = "".to_string(), - }; - - Err(Error::new(Kind::HttpErrorStatus(reason, text))) - } - } - /// Send the request to Solaredge and return the response. /// /// # Arguments @@ -217,7 +180,25 @@ pub trait SendReq { { let url = self.build_url(&solaredge.site_id, &solaredge.api_key); - self.send_helper(&url) + let res = REQWEST_CLIENT.get(url).send()?; + + if res.status().is_success() { + let parsed = res.json::()?; + + Ok(parsed) + } else { + let reason = match res.status().canonical_reason() { + Some(r) => r.to_string(), + None => res.status().as_str().to_string(), + }; + + let text = match res.text() { + Ok(t) => t, + Err(_) => "".to_string(), + }; + + Err(Error::new(Kind::HttpErrorStatus(reason, text))) + } } } @@ -230,17 +211,10 @@ mod tests { #[test] fn solaredge_credentials_unit_test() { - let mut se = SolaredgeCredentials::new("id", "key"); + let se = SolaredgeCredentials::new("id", "key"); assert_eq!(se.site_id, "id"); assert_eq!(se.site_id(), "id"); assert_eq!(se.api_key, "api_key=key"); - assert_eq!(se.bulk_list, None); - - se.set_bulk_list(vec!["1"]); - assert_eq!(se.bulk_list, Some("1".to_string())); - - se.set_bulk_list(vec!["2", "4"]); - assert_eq!(se.bulk_list, Some("2,4".to_string())); } #[test] diff --git a/src/site_data_period.rs b/src/site_data_period.rs index 6049ac1..9cf36cc 100644 --- a/src/site_data_period.rs +++ b/src/site_data_period.rs @@ -1,14 +1,14 @@ //! Module for querying the energy production start and end dates of the site. use crate::{SendReq, MONITORING_API_URL}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// site_data_period request #[derive(Clone, Debug, PartialEq)] pub struct Req; /// site_data_period response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Resp { /// Period of time site has been producing. @@ -16,7 +16,7 @@ pub struct Resp { } /// Period of time site has been producing. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiteDataPeriod { /// Start date of energy production. diff --git a/src/site_details.rs b/src/site_details.rs index 3ea4a81..b884658 100644 --- a/src/site_details.rs +++ b/src/site_details.rs @@ -1,10 +1,7 @@ //! Module for site details requests and responses exchanged with the SolarEdge server monitoring API. -use crate::site_location::SiteLocation; -use crate::site_module::SiteModule; -use crate::site_public_settings::SitePublicSettings; -use crate::{SendReq, MONITORING_API_URL}; -use serde::{Deserialize, Serialize}; +use crate::{SendReq, SiteLocation, SiteModule, SitePublicSettings, MONITORING_API_URL}; +use serde::Deserialize; use std::collections::HashMap; /// site_details request @@ -12,14 +9,14 @@ use std::collections::HashMap; pub struct Req; /// site_details response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] pub struct Resp { /// Detailed information about the monitoring site pub details: SiteDetails, } /// Detailed information for a single site. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiteDetails { /// ID of the site. Should match the site_id specified in the Solaredge request. diff --git a/src/site_energy.rs b/src/site_energy.rs index 246cf36..cdf4825 100644 --- a/src/site_energy.rs +++ b/src/site_energy.rs @@ -1,7 +1,7 @@ //! Module for querying the site energy measurements. use crate::{DateValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_FORMAT}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// site_energy request #[derive(Clone, Debug, PartialEq)] @@ -12,7 +12,7 @@ pub struct Req { } /// site_energy response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Resp { /// Energy measurements. @@ -20,7 +20,7 @@ pub struct Resp { } /// Energy measurements. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Energy { /// Granularity of the energy measurements (should match the request) diff --git a/src/site_energy_detailed.rs b/src/site_energy_detailed.rs index 4a9dbeb..ed5f50b 100644 --- a/src/site_energy_detailed.rs +++ b/src/site_energy_detailed.rs @@ -1,7 +1,7 @@ //! Module for detailed site energy measurements from meters such as consumption, export (feed-in), import (purchase), etc. use crate::{MeterType, MeterValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// site_energyDetails request #[derive(Clone, Debug, Default, PartialEq)] @@ -13,7 +13,7 @@ pub struct Req { } /// site_energyDetails response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Resp { /// Energy details @@ -21,7 +21,7 @@ pub struct Resp { } /// Energy details -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct EnergyDetails { /// Granularity of the energy detail values (should match the request) diff --git a/src/site_list.rs b/src/site_list.rs new file mode 100644 index 0000000..62dceb4 --- /dev/null +++ b/src/site_list.rs @@ -0,0 +1,248 @@ +//! 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 serde::Deserialize; + +/// site_list request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + size: String, + start_index: String, + search_text: String, + sort_property: String, + sort_order: String, + status: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SortProperty { + /// sort by site name + Name, + + /// sort by site country + Country, + + /// sort by site state + State, + + /// sort by site city + City, + + /// sort by site address + Address, + + /// sort by site zip code + Zip, + + /// sort by site status + Status, + + /// sort by peak power + PeakPower, + + /// sort by installation date + InstallationDate, + + /// sort by amount of alerts + Amount, + + /// sort by alert severity + MaxSeverity, + + /// sort by site creation time + CreationTime, +} + +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::State => write!(f, "state"), + SortProperty::City => write!(f, "city"), + SortProperty::Address => write!(f, "address"), + SortProperty::Zip => write!(f, "zip"), + SortProperty::Status => write!(f, "status"), + SortProperty::PeakPower => write!(f, "peakPower"), + SortProperty::InstallationDate => write!(f, "installationDate"), + SortProperty::Amount => write!(f, "amount"), + SortProperty::MaxSeverity => write!(f, "maxSeverity"), + SortProperty::CreationTime => write!(f, "creationTime"), + } + } +} + +#[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 + Active, + + /// Sites not active yet + Pending, + + /// Sites that are disabled + Disabled, + + /// All sites + All, +} + +impl std::fmt::Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + Status::Active => write!(f, "Active"), + Status::Pending => write!(f, "Pending"), + Status::Disabled => write!(f, "Disabled"), + Status::All => write!(f, "All"), + } + } +} + +/// site_list response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +pub struct Resp { + /// The sites matching the request. + pub sites: Sites, +} + +/// The sites matching the request. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +pub struct Sites { + /// The count of matching sites + pub count: u16, + + /// Array of matching sites + pub site: Entries, +} + +/// Array of matching sites +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct Entries { + pub e: Vec, +} + +impl Req { + /// Create a site_list request message that can be sent to SolarEdge. + /// + /// # Arguments + /// + /// * `size` - + /// * `start_index` - + /// * `search_text` - + /// * `sort_property` - + /// * `sort_order` - + /// * `status` - + #[must_use] + pub fn new( + size: Option, + start_index: Option, + search_text: Option, + sort_property: Option, + sort_order: Option, + status: 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(), + }; + + let status = match status { + Some(s) => format!( + "status={}&", + s.iter() + .map(Status::to_string) + .collect::>() + .join(",") + ), + None => "".to_string(), + }; + + Req { + size, + start_index, + search_text, + sort_property, + sort_order, + status, + } + } +} + +impl SendReq for Req { + fn build_url(&self, _site_id: &str, api_key: &str) -> String { + format!( + "{}sites/list?{}{}{}{}{}{}{}", + *MONITORING_API_URL, + self.size, + self.start_index, + self.search_text, + self.sort_property, + self.sort_order, + self.status, + 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/site_location.rs b/src/site_location.rs index aafe52a..826d866 100644 --- a/src/site_location.rs +++ b/src/site_location.rs @@ -1,9 +1,9 @@ //! Module for holding site location data returned in the SolarEdge server monitoring API responses. -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Location of the SolarEdge inverter. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiteLocation { /// Country of the SolarEdge inverter. diff --git a/src/site_module.rs b/src/site_module.rs index 9d51760..b954040 100644 --- a/src/site_module.rs +++ b/src/site_module.rs @@ -1,9 +1,9 @@ //! Module for reporting solar panel module information from the SolarEdge server monitoring API. -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Solar panel module information -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiteModule { /// solar panel manufacturer diff --git a/src/site_power.rs b/src/site_power.rs index 88ef6af..3547b51 100644 --- a/src/site_power.rs +++ b/src/site_power.rs @@ -1,7 +1,7 @@ //! Module for querying the site power measurements in 15 minute resolution. use crate::{DateValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// site_power request #[derive(Clone, Debug, PartialEq)] @@ -11,7 +11,7 @@ pub struct Req { } /// site_power response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Resp { /// Power measurements. @@ -19,7 +19,7 @@ pub struct Resp { } /// Power measurements. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Power { /// Time unit of the Power measurements diff --git a/src/site_power_detailed.rs b/src/site_power_detailed.rs index 6475262..b3c15de 100644 --- a/src/site_power_detailed.rs +++ b/src/site_power_detailed.rs @@ -1,7 +1,7 @@ //! Module for detailed site power measurements from meters such as consumption, export (feed-in), import (purchase), etc. use crate::{MeterType, MeterValue, SendReq, TimeUnit, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// site_powerDetails request #[derive(Clone, Debug, PartialEq)] @@ -12,7 +12,7 @@ pub struct Req { } /// site_powerDetails response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Resp { /// Power details @@ -20,7 +20,7 @@ pub struct Resp { } /// Power details -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct PowerDetails { /// Granularity of the power detail values (should match the request) diff --git a/src/site_public_settings.rs b/src/site_public_settings.rs index b4d9f2a..4c2a775 100644 --- a/src/site_public_settings.rs +++ b/src/site_public_settings.rs @@ -1,9 +1,9 @@ //! Module for reporting information about the web pages for the site served by SolarEdge. -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Information about the public web page for the site provided by SolarEdge. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SitePublicSettings { /// Optional name given to the web page fro the site. diff --git a/src/site_time_frame_energy.rs b/src/site_time_frame_energy.rs index 1bd5d27..863295e 100644 --- a/src/site_time_frame_energy.rs +++ b/src/site_time_frame_energy.rs @@ -1,10 +1,7 @@ //! Module for querying the site total energy produced for a given period. -pub use crate::date_value::DateValue; - -use crate::URL_DATE_FORMAT; -use crate::{SendReq, MONITORING_API_URL}; -use serde::{Deserialize, Serialize}; +use crate::{SendReq, MONITORING_API_URL, URL_DATE_FORMAT}; +use serde::Deserialize; /// site_time_frame_energy request #[derive(Clone, Debug, PartialEq)] @@ -14,7 +11,7 @@ pub struct Req { } /// site_time_frame_energy response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Resp { /// Energy measurements. @@ -22,7 +19,7 @@ pub struct Resp { } /// Energy measurements. -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] #[serde(rename_all = "camelCase")] pub struct TimeFrameEnergy { /// Energy produced during the time period diff --git a/src/supported_versions.rs b/src/supported_versions.rs index 39b217e..d403312 100644 --- a/src/supported_versions.rs +++ b/src/supported_versions.rs @@ -1,21 +1,21 @@ //! Module for querying the API versions supported by the SolarEdge monitoring server. use crate::{SendReq, MONITORING_API_URL}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Supported versions request #[derive(Clone, Debug, PartialEq)] pub struct Req; /// Supported versions response -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] pub struct Resp { /// An array of all the API versions supported by the server pub supported: Vec, } /// A release version supported by the server -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq)] +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] pub struct Release { /// A release number supported by the server in format. pub release: String, diff --git a/src/time_unit.rs b/src/time_unit.rs index 1c0c88c..1f2ec98 100644 --- a/src/time_unit.rs +++ b/src/time_unit.rs @@ -1,10 +1,10 @@ //! Module for handling units of time used by the SolarEdge server monitoring API. -use serde::{Deserialize, Serialize}; +use serde::Deserialize; /// Time units specified in SolarEdge server monitoring API requests and responses. /// Specifies the aggregation granularity of the data. -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[derive(Clone, Deserialize, Debug, PartialEq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TimeUnit { /// 15 minutes diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index ef76d87..4749e95 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -7,7 +7,7 @@ mod common; use se_ms_api::{ CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SiteEnergyReq, SitePowerDetailedReq, SitePowerReq, + SiteEnergyDetailedReq, SiteEnergyReq, SiteListReq, SitePowerDetailedReq, SitePowerReq, SiteTimeFrameEnergyReq, SupportedVersionsReq, TimeUnit, }; @@ -277,3 +277,23 @@ fn site_power_integration_test() { Err(e) => panic!("Unexpected SitePower response: {:?}", e), } } + +#[test] +fn site_list_integration_test() { + let req = SiteListReq::new(None, None, None, None, None, None); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.sites.count, 1); + assert_eq!(r.sites.site.e.len(), 1); + assert_eq!(r.sites.site.e[0].status, "Active"); + assert_eq!(r.sites.site.e[0].location.country_code, "US"); + assert!(r.sites.site.e[0].uris.contains_key("SITE_IMAGE")); + assert!(!r.sites.site.e[0].public_settings.is_public); + } + Err(e) => { + panic!("Unexpected SiteList response: {:?}", e); + } + } +} From 4902dbaa855182cf5dcc19d0a795e879dbc1fcc7 Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Sun, 22 May 2022 16:01:20 -0400 Subject: [PATCH 10/11] Added site_overview. Added site_power_flow. --- src/se_ms_api.rs | 8 ++- src/site_overview.rs | 98 ++++++++++++++++++++++++++ src/site_power_flow.rs | 121 +++++++++++++++++++++++++++++++++ tests/integration_reqs_test.rs | 63 ++++++++++++++++- 4 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 src/site_overview.rs create mode 100644 src/site_power_flow.rs diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 10ae0af..88dafab 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -42,14 +42,14 @@ //! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] //! * [SiteListReq] / [SiteListResp] +//! * [SiteOverviewReq] / [SiteOverviewResp] //! * [SitePowerReq] / [SitePowerResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] +//! * [SitePowerFlowReq] / [SitePowerFlowResp] //! * [SiteTimeFrameEnergyReq] / [SiteTimeFrameEnergyResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: -//! SiteOverview, -//! SitePowerFlow, //! SiteStorageInformation, //! SiteImage, //! SiteEnvironmentalBenefits, @@ -76,8 +76,10 @@ 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_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_time_frame_energy::{Req as SiteTimeFrameEnergyReq, Resp as SiteTimeFrameEnergyResp}; pub use supported_versions::{Req as SupportedVersionsReq, Resp as SupportedVersionsResp}; @@ -104,8 +106,10 @@ mod site_energy_detailed; mod site_list; mod site_location; mod site_module; +mod site_overview; mod site_power; mod site_power_detailed; +mod site_power_flow; mod site_public_settings; mod site_time_frame_energy; mod supported_versions; diff --git a/src/site_overview.rs b/src/site_overview.rs new file mode 100644 index 0000000..8539db8 --- /dev/null +++ b/src/site_overview.rs @@ -0,0 +1,98 @@ +//! Module for site overview requests and responses exchanged with the SolarEdge server monitoring API. + +use crate::{SendReq, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_overview request +#[derive(Clone, Debug, PartialEq)] +pub struct Req; + +/// site_overview response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +pub struct Resp { + /// Overview information about the monitoring site + pub overview: Overview, +} + +/// Overview information for the single site. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Overview { + /// Last time the site reported in to SolarEdge. + pub last_update_time: String, + + /// Total energy produced and revenue of the site + pub life_time_data: EnergyRevenue, + + /// Energy produced and revenue of the site for the previous year + pub last_year_data: EnergyRevenue, + + /// Energy produced and revenue of the site for the previous month + pub last_month_data: EnergyRevenue, + + /// Energy produced and revenue of the site for the previous day + pub last_day_data: EnergyRevenue, + + /// Power currently being produced by the site + pub current_power: CurrentPower, + + /// Source of reading + pub measured_by: String, +} + +/// Energy and revenue pair +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct EnergyRevenue { + /// Energy value + pub energy: f32, + + /// Revenue value + pub revenue: Option, +} + +/// Power currently being produced by the site +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CurrentPower { + /// Current power reading + pub power: f32, +} + +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!( + "{}site/{}/overview?{}", + *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_power_flow.rs b/src/site_power_flow.rs new file mode 100644 index 0000000..efd8720 --- /dev/null +++ b/src/site_power_flow.rs @@ -0,0 +1,121 @@ +//! Module for querying site power flow between all elements of the site including +//! PV array, storage (battery), loads (consumption) and grid. + +use crate::{SendReq, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_power_flow request +#[derive(Clone, Debug, PartialEq)] +pub struct Req; + +/// site_power_flow response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Site's current power flow status + pub site_current_power_flow: SiteCurrentPowerFlow, +} + +/// Site's current power flow status +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SiteCurrentPowerFlow { + /// undocumented + pub update_refresh_rate: u16, + + /// The measurement units (e.g. Watt) + pub unit: String, + + /// A table including all the relationships between the elements, + /// and the power flow directions (producing element and consuming element) + pub connections: Vec, + + /// Electric grid + #[serde(rename = "GRID")] + pub grid: Parameters, + + /// Site electricity consumers + #[serde(rename = "LOAD")] + pub load: Parameters, + + /// Photovoltaic array + #[serde(rename = "PV")] + pub pv: Option, + + /// Electric storage + #[serde(rename = "STORAGE")] + pub storage: Option, +} + +/// List of producers (from) adn consumers (to) of electricity. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Connections { + /// The element providing power + pub from: String, + + /// The element consuming power + pub to: String, +} + +/// Parameters for each site producer/consumer element. +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Parameters { + /// The current status of the element (Active / Idle Disabled) + pub status: String, + + /// The current power of the element. All numbers are positive; + /// power direction is determined by the “connections” section. + pub current_power: f32, + + /// (STORAGE only) The accumulated state of energy (% of charge) for all batteries. + pub charge_level: Option, + + /// (STORAGE only) If the accumulated storage charge level drops below + /// a configurable level (currently 10%), this flag is returned. + pub critical: Option, + + /// (STORAGE only) In Backup mode (GRID is Disabled), this property + /// is returned to specify the time left before the storage energy runs out + /// (estimated according to current load level). + pub time_left: Option, +} + +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!( + "{}site/{}/currentPowerFlow?{}", + *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/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index 4749e95..f571c36 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -7,8 +7,8 @@ mod common; use se_ms_api::{ CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SiteEnergyReq, SiteListReq, SitePowerDetailedReq, SitePowerReq, - SiteTimeFrameEnergyReq, SupportedVersionsReq, TimeUnit, + SiteEnergyDetailedReq, SiteEnergyReq, SiteListReq, SiteOverviewReq, SitePowerDetailedReq, + SitePowerFlowReq, SitePowerReq, SiteTimeFrameEnergyReq, SupportedVersionsReq, TimeUnit, }; #[test] @@ -297,3 +297,62 @@ fn site_list_integration_test() { } } } + +#[test] +fn site_overview_integration_test() { + let req = SiteOverviewReq::new(); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert!(!r.overview.last_update_time.is_empty()); + + assert!(r.overview.life_time_data.energy > 0.0); + if let Some(revenue) = r.overview.life_time_data.revenue { + assert!(revenue > 0.0); + } else { + panic!("Missing value."); + } + + assert!(r.overview.last_year_data.energy > 0.0); + if r.overview.last_year_data.revenue.is_some() { + panic!("Unexpected value."); + } + + assert!(r.overview.last_month_data.energy > 0.0); + if r.overview.last_month_data.revenue.is_some() { + panic!("Unexpected value."); + } + + assert!(r.overview.last_day_data.energy > 0.0); + if r.overview.last_day_data.revenue.is_some() { + panic!("Unexpected value."); + } + + assert!(r.overview.current_power.power > 0.0); + assert_eq!(r.overview.measured_by, "INVERTER".to_string()); + } + Err(e) => { + panic!("Unexpected SiteOverview response: {:?}", e); + } + } +} + +#[test] +fn site_power_flow_integration_test() { + let req = SitePowerFlowReq::new(); + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.site_current_power_flow.update_refresh_rate, 3); + assert_eq!(r.site_current_power_flow.unit, "kW"); + assert!(!r.site_current_power_flow.connections.is_empty()); + assert!(r.site_current_power_flow.pv.is_some()); + assert!(r.site_current_power_flow.storage.is_none()); + } + Err(e) => { + panic!("Unexpected SitePowerFlow response: {:?}", e); + } + } +} From 42da0a7f28c7c6e35a33579938a6af247a8ae961 Mon Sep 17 00:00:00 2001 From: grtwje <63551230+grtwje@users.noreply.github.com> Date: Mon, 23 May 2022 16:22:27 -0400 Subject: [PATCH 11/11] Added site_storage_data. Added site_environmental_benefits. --- src/se_ms_api.rs | 32 +++++++---- src/site_environmental_benefits.rs | 87 ++++++++++++++++++++++++++++ src/site_storage_data.rs | 91 ++++++++++++++++++++++++++++++ src/system_units.rs | 40 +++++++++++++ tests/integration_reqs_test.rs | 73 +++++++++++++++++++++++- 5 files changed, 309 insertions(+), 14 deletions(-) create mode 100644 src/site_environmental_benefits.rs create mode 100644 src/site_storage_data.rs create mode 100644 src/system_units.rs diff --git a/src/se_ms_api.rs b/src/se_ms_api.rs index 88dafab..2529fe1 100644 --- a/src/se_ms_api.rs +++ b/src/se_ms_api.rs @@ -41,27 +41,27 @@ //! * [SiteDetailsReq] / [SiteDetailsResp] //! * [SiteEnergyReq] / [SiteEnergyResp] //! * [SiteEnergyDetailedReq] / [SiteEnergyDetailedResp] +//! * [SiteEnvironmentalBenefitsReq] / [SiteEnvironmentalBenefitsResp] //! * [SiteListReq] / [SiteListResp] //! * [SiteOverviewReq] / [SiteOverviewResp] //! * [SitePowerReq] / [SitePowerResp] //! * [SitePowerDetailedReq] / [SitePowerDetailedResp] //! * [SitePowerFlowReq] / [SitePowerFlowResp] +//! * [SiteStorageDataReq] / [SiteStorageDataResp] //! * [SiteTimeFrameEnergyReq] / [SiteTimeFrameEnergyResp] //! * [SupportedVersionsReq] / [SupportedVersionsResp] //! //! TODO: -//! SiteStorageInformation, -//! SiteImage, -//! SiteEnvironmentalBenefits, -//! SiteInstallerImage, -//! SiteEquipmentList, -//! SiteInventory, -//! SiteInverterTechnicalData, -//! SiteEquipmentChangeLog, -//! AccountsList, -//! SiteMetersData, -//! SiteSensorList, -//! SiteSensorData +//! * SiteImage, +//! * SiteInstallerImage, +//! * SiteEquipmentList, +//! * SiteInventory, +//! * SiteInverterTechnicalData, +//! * SiteEquipmentChangeLog, +//! * AccountsList, +//! * SiteMetersData, +//! * SiteSensorList, +//! * SiteSensorData #![warn(unused_crate_dependencies)] #![deny(unused_extern_crates)] @@ -75,11 +75,15 @@ 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 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}; @@ -92,6 +96,7 @@ pub use site_details::SiteDetails; pub use site_location::SiteLocation; pub use site_module::SiteModule; pub use site_public_settings::SitePublicSettings; +pub use system_units::SystemUnits; pub use time_unit::TimeUnit; mod current_version; @@ -103,6 +108,7 @@ mod site_data_period; mod site_details; mod site_energy; mod site_energy_detailed; +mod site_environmental_benefits; mod site_list; mod site_location; mod site_module; @@ -111,8 +117,10 @@ mod site_power; mod site_power_detailed; mod site_power_flow; mod site_public_settings; +mod site_storage_data; mod site_time_frame_energy; mod supported_versions; +mod system_units; mod time_unit; #[macro_use] diff --git a/src/site_environmental_benefits.rs b/src/site_environmental_benefits.rs new file mode 100644 index 0000000..bc95fb0 --- /dev/null +++ b/src/site_environmental_benefits.rs @@ -0,0 +1,87 @@ +//! Module for getting all environmental benefits based on site energy production: +//! CO2 emissions saved, equivalent trees planted, and light bulbs powered for a day. + +use crate::{SendReq, SystemUnits, MONITORING_API_URL}; +use serde::Deserialize; + +/// site_environmental_benefits request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + /// The measurement system to use in the response. + system_units: String, +} + +/// site_environmental_benefits response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Environmental benefits of the monitoring site + pub env_benefits: EnvBenefits, +} + +/// Environmental benefits of the monitoring site +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct EnvBenefits { + /// Quantity of CO2 emissions that would have been generated by an equivalent fossil fuel system. + pub gas_emission_saved: GasEmissionSaved, + + /// Equivalent planting of new trees for reducing CO2 levels. + pub trees_planted: f32, + + /// Number of light bulbs that could have been powered by the site for a day. + pub light_bulbs: f32, +} + +/// Environmental benefits of the monitoring site +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct GasEmissionSaved { + /// Measurement unit of following gases. + pub units: String, + + /// Carbon Dioxide + pub co2: f32, + + /// Sulphur Dioxide + pub so2: f32, + + /// Nitrous Oxide + pub nox: f32, +} + +impl Req { + /// Create a site environmental benefits request message that can be sent to SolarEdge. + #[must_use] + pub fn new(system_units: Option) -> Self { + let system_units = match system_units { + Some(su) => format!("systemUnits={}&", su), + None => "".to_string(), + }; + + Req { system_units } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/envBenefits?{}{}", + *MONITORING_API_URL, site_id, self.system_units, 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_storage_data.rs b/src/site_storage_data.rs new file mode 100644 index 0000000..2990081 --- /dev/null +++ b/src/site_storage_data.rs @@ -0,0 +1,91 @@ +//! Module for detailed storage information from batteries: the state of energy, power and lifetime energy. + +use crate::{SendReq, MONITORING_API_URL, URL_DATE_TIME_FORMAT}; +use serde::Deserialize; + +/// site_storage_data request +#[derive(Clone, Debug, PartialEq)] +pub struct Req { + start_time: String, + end_time: String, + serials: String, +} + +/// site_storage_data response +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Resp { + /// Information about the site's storage + pub storage_data: StorageData, +} + +/// Information about the site's storage +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct StorageData { + pub battery_count: u16, + pub batteries: Batteries, +} + +/// Array of batteries +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(transparent)] +pub struct Batteries { + pub e: Vec, +} + +/// Data for a single battery +#[derive(Clone, Deserialize, Debug, Default, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Battery { + pub nameplate: u32, +} + +impl Req { + /// Create a site storage data request message that can be sent to SolarEdge. + #[must_use] + pub fn new( + start_time: chrono::NaiveDateTime, + end_time: chrono::NaiveDateTime, + serials: 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 serials = match serials { + Some(s) => format!("serials={}&", s.join(",")), + None => "".to_string(), + }; + + Req { + start_time, + end_time, + serials, + } + } +} + +impl SendReq for Req { + fn build_url(&self, site_id: &str, api_key: &str) -> String { + format!( + "{}site/{}/storageData?{}{}{}{}", + *MONITORING_API_URL, site_id, self.start_time, self.end_time, self.serials, 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::(); + } +} diff --git a/src/system_units.rs b/src/system_units.rs new file mode 100644 index 0000000..6be7ac7 --- /dev/null +++ b/src/system_units.rs @@ -0,0 +1,40 @@ +//! Module for the measurement units used with the SolarEdge server monitoring API. + +use serde::Deserialize; + +/// Time units specified in SolarEdge server monitoring API requests and responses. +/// Specifies the aggregation granularity of the data. +#[derive(Clone, Deserialize, Debug, PartialEq)] +pub enum SystemUnits { + /// Imperial measurement units + Imperial, + + /// Metric measurement units + Metrics, +} + +impl std::fmt::Display for SystemUnits { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + SystemUnits::Imperial => write!(f, "Imperial"), + SystemUnits::Metrics => write!(f, "Metrics"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::is_normal; + + #[test] + fn system_units_fmt_unit_test() { + let t = SystemUnits::Imperial; + assert_eq!(format!("{}", t), "Imperial"); + } + + #[test] + fn normal_types_unit_test() { + is_normal::(); + } +} diff --git a/tests/integration_reqs_test.rs b/tests/integration_reqs_test.rs index f571c36..701595e 100644 --- a/tests/integration_reqs_test.rs +++ b/tests/integration_reqs_test.rs @@ -7,8 +7,9 @@ mod common; use se_ms_api::{ CurrentVersionReq, MeterType, SendReq, SiteDataPeriodReq, SiteDetailsReq, - SiteEnergyDetailedReq, SiteEnergyReq, SiteListReq, SiteOverviewReq, SitePowerDetailedReq, - SitePowerFlowReq, SitePowerReq, SiteTimeFrameEnergyReq, SupportedVersionsReq, TimeUnit, + SiteEnergyDetailedReq, SiteEnergyReq, SiteEnvironmentalBenefitsReq, SiteListReq, + SiteOverviewReq, SitePowerDetailedReq, SitePowerFlowReq, SitePowerReq, SiteStorageDataReq, + SiteTimeFrameEnergyReq, SupportedVersionsReq, SystemUnits, TimeUnit, }; #[test] @@ -356,3 +357,71 @@ fn site_power_flow_integration_test() { } } } + +#[test] +fn site_storage_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-7 00:00:00", common::DATE_TIME_FORMAT) { + Ok(dt) => dt, + Err(error) => panic!("Error parsing end date: {}", error), + }; + + let req = SiteStorageDataReq::new(start_ndt, end_ndt, None); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.storage_data.battery_count, 0); + assert!(r.storage_data.batteries.e.is_empty()); + } + Err(e) => { + panic!("Unexpected SiteStorageData response: {:?}", e); + } + }; +} + +#[test] +fn site_environmental_benefits_integration_test() { + let req = SiteEnvironmentalBenefitsReq::new(Some(SystemUnits::Imperial)); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.env_benefits.gas_emission_saved.units, "lb"); + assert!(r.env_benefits.gas_emission_saved.co2 > 79975.0); + assert!(r.env_benefits.gas_emission_saved.so2 > 57791.0); + assert!(r.env_benefits.gas_emission_saved.nox > 18429.0); + assert!(r.env_benefits.trees_planted > 604.0); + assert!(r.env_benefits.light_bulbs > 156510.0); + } + Err(e) => { + panic!("Unexpected SiteEnvironmentalBenefits response: {:?}", e); + } + }; + + let req = SiteEnvironmentalBenefitsReq::new(Some(SystemUnits::Metrics)); + + let resp = req.send(&common::TEST_CREDENTIALS); + + match resp { + Ok(r) => { + assert_eq!(r.env_benefits.gas_emission_saved.units, "kg"); + assert!(r.env_benefits.gas_emission_saved.co2 > 36276.0); + assert!(r.env_benefits.gas_emission_saved.so2 > 26213.0); + assert!(r.env_benefits.gas_emission_saved.nox > 8359.0); + assert!(r.env_benefits.trees_planted > 604.0); + assert!(r.env_benefits.light_bulbs > 156510.0); + } + Err(e) => { + panic!("Unexpected SiteEnvironmentalBenefits response: {:?}", e); + } + }; +}