-
Notifications
You must be signed in to change notification settings - Fork 2
Interface improvements #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v0.x.x
Are you sure you want to change the base?
Changes from all commits
90b0c76
27bf2fe
6570878
63deb53
773fe54
35b40a3
afda675
d847c38
6a06637
abbfdff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| // License: MIT | ||
| // Copyright © 2026 Frequenz Energy-as-a-Service GmbH | ||
|
|
||
| //! A representation of Bounds for any metric. | ||
| use crate::proto::common::metrics::Bounds as PbBounds; | ||
| use crate::quantity::{Current, Power, Quantity, ReactivePower}; | ||
|
|
||
| /// A set of lower and upper bounds for any metric. | ||
| pub struct Bounds<Q: Quantity> { | ||
| /// The lower bound. | ||
| /// If None, there is no lower bound. | ||
| lower: Option<Q>, | ||
| /// The upper bound. | ||
| /// If None, there is no upper bound. | ||
| upper: Option<Q>, | ||
| } | ||
|
|
||
| impl<Q: Quantity> Bounds<Q> { | ||
| /// Creates a new `Bounds` with the given lower and upper bounds. | ||
| pub fn new(lower: Option<Q>, upper: Option<Q>) -> Self { | ||
| Self { lower, upper } | ||
| } | ||
|
|
||
| /// Returns the lower bound. | ||
| pub fn lower(&self) -> Option<Q> { | ||
| self.lower | ||
| } | ||
|
|
||
| /// Returns the upper bound. | ||
| pub fn upper(&self) -> Option<Q> { | ||
| self.upper | ||
| } | ||
| } | ||
|
|
||
| impl<Q: Quantity> From<(Option<Q>, Option<Q>)> for Bounds<Q> { | ||
| fn from(bounds: (Option<Q>, Option<Q>)) -> Self { | ||
| Self::new(bounds.0, bounds.1) | ||
| } | ||
| } | ||
|
|
||
| impl From<Bounds<Power>> for PbBounds { | ||
| fn from(bounds: Bounds<Power>) -> Self { | ||
| PbBounds { | ||
| lower: bounds.lower.map(|q| q.as_watts()), | ||
| upper: bounds.upper.map(|q| q.as_watts()), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl From<Bounds<Current>> for PbBounds { | ||
| fn from(bounds: Bounds<Current>) -> Self { | ||
| PbBounds { | ||
| lower: bounds.lower.map(|q| q.as_amperes()), | ||
| upper: bounds.upper.map(|q| q.as_amperes()), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl From<Bounds<ReactivePower>> for PbBounds { | ||
| fn from(bounds: Bounds<ReactivePower>) -> Self { | ||
| PbBounds { | ||
| lower: bounds.lower.map(|q| q.as_volt_amperes_reactive()), | ||
| upper: bounds.upper.map(|q| q.as_volt_amperes_reactive()), | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,9 +11,9 @@ use crate::{ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ReceiveElectricalComponentTelemetryStreamResponse, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use chrono::DateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use futures::{Stream, StreamExt}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tokio::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| select, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sync::{broadcast, mpsc}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -149,7 +149,10 @@ async fn handle_instruction<T: MicrogridApiClient>( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let components = client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .list_electrical_components(ListElectricalComponentsRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| electrical_component_ids, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| electrical_component_categories, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| electrical_component_categories: electrical_component_categories | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .into_iter() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(|c| c as i32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .collect(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(|e| Error::connection_failure(format!("list_components failed: {e}"))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -177,6 +180,39 @@ async fn handle_instruction<T: MicrogridApiClient>( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .send(connections) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(|_| Error::internal("failed to send response"))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(Instruction::AugmentElectricalComponentBounds { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| electrical_component_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_metric, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bounds, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request_lifetime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response_tx, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let response = client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .augment_electrical_component_bounds( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| crate::proto::microgrid::AugmentElectricalComponentBoundsRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| electrical_component_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_metric: target_metric as i32, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bounds: bounds, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request_lifetime: request_lifetime.map(|d| d.as_seconds_f64() as u64), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request_lifetime: request_lifetime.map(|d| d.as_seconds_f64() as u64), | |
| request_lifetime: request_lifetime.and_then(|d| { | |
| let secs = d.num_seconds(); | |
| u64::try_from(secs).ok() | |
| }), |
Copilot
AI
Mar 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timestamp conversion casts t.nanos with as u32 and then drops invalid values by returning None (via from_timestamp(...).flatten()). Use a checked conversion for nanos (and ideally validate the 0..=999_999_999 range) and consider logging/returning an error if the server returns an invalid timestamp instead of silently discarding it.
| .map(|r| { | |
| r.into_inner() | |
| .valid_until_time | |
| .map(|t| DateTime::from_timestamp(t.seconds, t.nanos as u32)) | |
| .flatten() | |
| .and_then(|r| { | |
| let valid_until = r.into_inner().valid_until_time; | |
| match valid_until { | |
| None => Ok(None), | |
| Some(t) => { | |
| let nanos_u32 = u32::try_from(t.nanos).map_err(|_| { | |
| tracing::error!( | |
| "augment_electrical_component_bounds: invalid nanos value {} from server", | |
| t.nanos | |
| ); | |
| Error::api_server_error( | |
| "server returned invalid timestamp (nanos out of range)", | |
| ) | |
| })?; | |
| if !(0..=999_999_999).contains(&nanos_u32) { | |
| tracing::error!( | |
| "augment_electrical_component_bounds: nanos value {} out of allowed range", | |
| nanos_u32 | |
| ); | |
| return Err(Error::api_server_error( | |
| "server returned invalid timestamp (nanos out of allowed range)", | |
| )); | |
| } | |
| DateTime::from_timestamp(t.seconds, nanos_u32) | |
| .ok_or_else(|| { | |
| tracing::error!( | |
| "augment_electrical_component_bounds: invalid timestamp (seconds={}, nanos={}) from server", | |
| t.seconds, | |
| nanos_u32, | |
| ); | |
| Error::api_server_error( | |
| "server returned invalid timestamp", | |
| ) | |
| }) | |
| .map(Some) | |
| } | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,15 +6,19 @@ | |||||||||||||||||||||||||||||
| //! Instructions received by this handle are sent to the microgrid client actor, | ||||||||||||||||||||||||||||||
| //! which owns the connection to the microgrid API service. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| use chrono::TimeDelta; | ||||||||||||||||||||||||||||||
| use tokio::sync::{broadcast, mpsc, oneshot}; | ||||||||||||||||||||||||||||||
| use tonic::transport::Channel; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| use crate::{ | ||||||||||||||||||||||||||||||
| Error, | ||||||||||||||||||||||||||||||
| Bounds, Error, | ||||||||||||||||||||||||||||||
| client::MicrogridApiClient, | ||||||||||||||||||||||||||||||
| metric::Metric, | ||||||||||||||||||||||||||||||
| proto::{ | ||||||||||||||||||||||||||||||
| common::metrics::Bounds as PbBounds, | ||||||||||||||||||||||||||||||
| common::microgrid::electrical_components::{ | ||||||||||||||||||||||||||||||
| ElectricalComponent, ElectricalComponentConnection, ElectricalComponentTelemetry, | ||||||||||||||||||||||||||||||
| ElectricalComponent, ElectricalComponentCategory, ElectricalComponentConnection, | ||||||||||||||||||||||||||||||
| ElectricalComponentTelemetry, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| microgrid::microgrid_client::MicrogridClient, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
|
|
@@ -99,7 +103,7 @@ impl MicrogridClientHandle { | |||||||||||||||||||||||||||||
| pub async fn list_electrical_components( | ||||||||||||||||||||||||||||||
| &self, | ||||||||||||||||||||||||||||||
| electrical_component_ids: Vec<u64>, | ||||||||||||||||||||||||||||||
| electrical_component_categories: Vec<i32>, | ||||||||||||||||||||||||||||||
| electrical_component_categories: Vec<ElectricalComponentCategory>, | ||||||||||||||||||||||||||||||
| ) -> Result<Vec<ElectricalComponent>, Error> { | ||||||||||||||||||||||||||||||
|
Comment on lines
103
to
107
|
||||||||||||||||||||||||||||||
| let (response_tx, response_rx) = oneshot::channel(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -154,6 +158,55 @@ impl MicrogridClientHandle { | |||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||
| .map_err(|e| Error::internal(format!("failed to receive response: {e}")))? | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /// Augments the overall bounds for a given metric of a given electrical | ||||||||||||||||||||||||||||||
| /// component with the provided bounds. | ||||||||||||||||||||||||||||||
| /// Returns the UTC time at which the provided bounds will expire and its | ||||||||||||||||||||||||||||||
| /// effects will no longer be visible in the overall bounds for the | ||||||||||||||||||||||||||||||
| /// given metric. | ||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||
| /// The request parameters allows users to select a duration until | ||||||||||||||||||||||||||||||
| /// which the bounds will stay in effect. If no duration is provided, then the | ||||||||||||||||||||||||||||||
| /// bounds will be removed after a default duration of 5 seconds. | ||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||
| /// Inclusion bounds give the range that the system will try to keep the | ||||||||||||||||||||||||||||||
| /// metric within. If the metric goes outside of these bounds, the system will | ||||||||||||||||||||||||||||||
| /// try to bring it back within the bounds. | ||||||||||||||||||||||||||||||
| /// If the bounds for a metric are [Symbol’s value as variable is void: lower_1End. | ||||||||||||||||||||||||||||||
| /// ---- values here are considered out of range. | ||||||||||||||||||||||||||||||
| /// ==== values here are considered within range. | ||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||
|
Comment on lines
+175
to
+178
|
||||||||||||||||||||||||||||||
| /// If the bounds for a metric are [Symbol’s value as variable is void: lower_1End. | |
| /// ---- values here are considered out of range. | |
| /// ==== values here are considered within range. | |
| /// | |
| /// | |
| /// For example, if the bounds for a metric are: | |
| /// ```text | |
| /// -∞ lower upper +∞ | |
| /// ---- [====] ---- | |
| /// ``` | |
| /// then: | |
| /// - `----` values are considered out of range. | |
| /// - `====` values are considered within range. | |
| /// |
Copilot
AI
Mar 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The target_metric parameter is intentionally unused (suppressed via #[allow(unused_variables)]), which is a confusing API surface. Prefer removing the parameter entirely and relying on the type parameter M, or rename it to _target_metric/use PhantomData<M> so callers aren't required to pass a value that is ignored.
| #[allow(unused_variables)] target_metric: M, | |
| _target_metric: M, |
Copilot
AI
Mar 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
augment_electrical_component_bounds takes &mut self even though it only sends on an mpsc::Sender and does not mutate handle state. Making this &self would avoid unnecessary caller-side mutability and match the other handle APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boundsis documented as working "for any metric", butFrom<Bounds<Q>> for PbBoundsis only implemented forPower,Current, andReactivePower. As a result,augment_electrical_component_boundswon’t compile for metrics whoseQuantityTypeisVoltage,Frequency, etc. Either add the missingFrom<Bounds<...>>impls (if supported by the API) or narrow the docs/API bounds to the supported quantity types.