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 1/2] 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 2/2] 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); + } + }; +}