Skip to content

Commit e3d6aaf

Browse files
authored
Interface improvements (#23)
This PR expands the public interface of the crate by introducing a generic `Bounds` type and a new client API to augment metric bounds, while also refining metric/component typing and adding convenience impls for better ergonomics. **Changes:** - Add `Bounds<Q>` and a `MicrogridClientHandle::augment_electrical_component_bounds()` API (plus actor/instruction/proto plumbing). - Improve component/metric typing (e.g., `ElectricalComponentCategory` filters, move `metric` to crate root) and add proto extension/graph trait impls. - Add `Display` for `Sample` and extend quantity helpers (const ctors/getters, min/max, etc.).
2 parents 1ea273c + 5637f80 commit e3d6aaf

18 files changed

Lines changed: 427 additions & 45 deletions

RELEASE_NOTES.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,16 @@
22

33
## Summary
44

5-
This is the first release of Frequenz Microgrid.
5+
This release makes incremental improvements to the client and the quantity types.
6+
7+
## Upgrading
8+
9+
- The `MicrogridClientHandle::list_electrical_components` method now expects `ElectricalComponentCategory` enum values instead of `i32`, to filter by component category.
10+
11+
## New Features
12+
13+
- The new `MicrogridClientHandle::augment_electrical_component_bounds` method can be used to augment the bounds for specific metrics of electrical components.
14+
15+
- All methods on `Quantity` types are now `const`.
16+
17+
- `Quantity` types have two new methods `min` and `max`, similar to the `min` and `max` methods on fundamental numerical types.

src/bounds.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// License: MIT
2+
// Copyright © 2026 Frequenz Energy-as-a-Service GmbH
3+
4+
//! A representation of Bounds for any metric.
5+
6+
use crate::proto::common::metrics::Bounds as PbBounds;
7+
use crate::quantity::{Current, Power, Quantity, ReactivePower};
8+
9+
/// A set of lower and upper bounds for any metric.
10+
pub struct Bounds<Q: Quantity> {
11+
/// The lower bound.
12+
/// If None, there is no lower bound.
13+
lower: Option<Q>,
14+
/// The upper bound.
15+
/// If None, there is no upper bound.
16+
upper: Option<Q>,
17+
}
18+
19+
impl<Q: Quantity> Bounds<Q> {
20+
/// Creates a new `Bounds` with the given lower and upper bounds.
21+
pub fn new(lower: Option<Q>, upper: Option<Q>) -> Self {
22+
Self { lower, upper }
23+
}
24+
25+
/// Returns the lower bound.
26+
pub fn lower(&self) -> Option<Q> {
27+
self.lower
28+
}
29+
30+
/// Returns the upper bound.
31+
pub fn upper(&self) -> Option<Q> {
32+
self.upper
33+
}
34+
}
35+
36+
impl<Q: Quantity> From<(Option<Q>, Option<Q>)> for Bounds<Q> {
37+
fn from(bounds: (Option<Q>, Option<Q>)) -> Self {
38+
Self::new(bounds.0, bounds.1)
39+
}
40+
}
41+
42+
impl From<Bounds<Power>> for PbBounds {
43+
fn from(bounds: Bounds<Power>) -> Self {
44+
PbBounds {
45+
lower: bounds.lower.map(|q| q.as_watts()),
46+
upper: bounds.upper.map(|q| q.as_watts()),
47+
}
48+
}
49+
}
50+
51+
impl From<Bounds<Current>> for PbBounds {
52+
fn from(bounds: Bounds<Current>) -> Self {
53+
PbBounds {
54+
lower: bounds.lower.map(|q| q.as_amperes()),
55+
upper: bounds.upper.map(|q| q.as_amperes()),
56+
}
57+
}
58+
}
59+
60+
impl From<Bounds<ReactivePower>> for PbBounds {
61+
fn from(bounds: Bounds<ReactivePower>) -> Self {
62+
PbBounds {
63+
lower: bounds.lower.map(|q| q.as_volt_amperes_reactive()),
64+
upper: bounds.upper.map(|q| q.as_volt_amperes_reactive()),
65+
}
66+
}
67+
}

src/client.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ mod microgrid_client_actor;
88
mod retry_tracker;
99

1010
mod microgrid_api_client;
11-
pub use microgrid_api_client::MicrogridApiClient;
11+
pub(crate) use microgrid_api_client::MicrogridApiClient;
1212

1313
mod microgrid_client_handle;
1414
pub use microgrid_client_handle::MicrogridClientHandle;
1515

16+
pub(crate) mod proto;
17+
pub use proto::common::microgrid::electrical_components::{
18+
ElectricalComponent, ElectricalComponentCategory,
19+
};
20+
1621
#[cfg(test)]
1722
pub(crate) mod test_utils;

src/client/instruction.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33

44
//! Instructions that can be sent to the client actor from client handles.
55
6+
use chrono::TimeDelta;
67
use tokio::sync::{broadcast, oneshot};
78

89
use crate::{
910
Error,
10-
proto::common::microgrid::electrical_components::{
11-
ElectricalComponent, ElectricalComponentConnection, ElectricalComponentTelemetry,
11+
proto::common::{
12+
metrics::{Bounds, Metric},
13+
microgrid::electrical_components::{
14+
ElectricalComponent, ElectricalComponentCategory, ElectricalComponentConnection,
15+
ElectricalComponentTelemetry,
16+
},
1217
},
1318
};
1419

@@ -21,12 +26,19 @@ pub(super) enum Instruction {
2126
},
2227
ListElectricalComponents {
2328
electrical_component_ids: Vec<u64>,
24-
electrical_component_categories: Vec<i32>,
29+
electrical_component_categories: Vec<ElectricalComponentCategory>,
2530
response_tx: oneshot::Sender<Result<Vec<ElectricalComponent>, Error>>,
2631
},
2732
ListElectricalComponentConnections {
2833
source_electrical_component_ids: Vec<u64>,
2934
destination_electrical_component_ids: Vec<u64>,
3035
response_tx: oneshot::Sender<Result<Vec<ElectricalComponentConnection>, Error>>,
3136
},
37+
AugmentElectricalComponentBounds {
38+
electrical_component_id: u64,
39+
target_metric: Metric,
40+
bounds: Vec<Bounds>,
41+
request_lifetime: Option<TimeDelta>,
42+
response_tx: oneshot::Sender<Result<Option<chrono::DateTime<chrono::Utc>>, Error>>,
43+
},
3244
}

src/client/microgrid_api_client.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use tonic::transport::Channel;
88

99
use crate::proto::microgrid::{
10+
AugmentElectricalComponentBoundsRequest, AugmentElectricalComponentBoundsResponse,
1011
ListElectricalComponentConnectionsRequest, ListElectricalComponentConnectionsResponse,
1112
ListElectricalComponentsRequest, ListElectricalComponentsResponse,
1213
ReceiveElectricalComponentTelemetryStreamRequest,
@@ -44,6 +45,11 @@ pub trait MicrogridApiClient: Send + Sync + 'static {
4445
&mut self,
4546
request: impl tonic::IntoRequest<ReceiveElectricalComponentTelemetryStreamRequest> + Send,
4647
) -> std::result::Result<tonic::Response<Self::TelemetryStream>, tonic::Status>;
48+
49+
async fn augment_electrical_component_bounds(
50+
&mut self,
51+
request: impl tonic::IntoRequest<AugmentElectricalComponentBoundsRequest> + Send,
52+
) -> std::result::Result<tonic::Response<AugmentElectricalComponentBoundsResponse>, tonic::Status>;
4753
}
4854

4955
/// Implement the MicrogridApiClient trait for the generated gRPC client.
@@ -78,4 +84,12 @@ impl MicrogridApiClient for crate::proto::microgrid::microgrid_client::Microgrid
7884
self.receive_electrical_component_telemetry_stream(request)
7985
.await
8086
}
87+
88+
async fn augment_electrical_component_bounds(
89+
&mut self,
90+
request: impl tonic::IntoRequest<AugmentElectricalComponentBoundsRequest> + Send,
91+
) -> std::result::Result<tonic::Response<AugmentElectricalComponentBoundsResponse>, tonic::Status>
92+
{
93+
self.augment_electrical_component_bounds(request).await
94+
}
8195
}

src/client/microgrid_client_actor.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use crate::{
1111
ReceiveElectricalComponentTelemetryStreamResponse,
1212
},
1313
};
14-
use std::collections::HashMap;
15-
14+
use chrono::DateTime;
1615
use futures::{Stream, StreamExt};
16+
use std::collections::HashMap;
1717
use tokio::{
1818
select,
1919
sync::{broadcast, mpsc},
@@ -149,7 +149,10 @@ async fn handle_instruction<T: MicrogridApiClient>(
149149
let components = client
150150
.list_electrical_components(ListElectricalComponentsRequest {
151151
electrical_component_ids,
152-
electrical_component_categories,
152+
electrical_component_categories: electrical_component_categories
153+
.into_iter()
154+
.map(|c| c as i32)
155+
.collect(),
153156
})
154157
.await
155158
.map_err(|e| Error::connection_failure(format!("list_components failed: {e}")))
@@ -177,6 +180,53 @@ async fn handle_instruction<T: MicrogridApiClient>(
177180
.send(connections)
178181
.map_err(|_| Error::internal("failed to send response"))?;
179182
}
183+
Some(Instruction::AugmentElectricalComponentBounds {
184+
electrical_component_id,
185+
target_metric,
186+
bounds,
187+
request_lifetime,
188+
response_tx,
189+
}) => {
190+
let response = client
191+
.augment_electrical_component_bounds(
192+
crate::proto::microgrid::AugmentElectricalComponentBoundsRequest {
193+
electrical_component_id,
194+
target_metric: target_metric as i32,
195+
bounds,
196+
request_lifetime: request_lifetime.and_then(|d| {
197+
let secs = d.num_seconds();
198+
u64::try_from(secs).ok()
199+
}),
200+
},
201+
)
202+
.await
203+
.map_err(|e| {
204+
Error::api_server_error(format!(
205+
"augment_electrical_component_bounds failed: {e}"
206+
))
207+
})
208+
.map(|r| {
209+
r.into_inner().valid_until_time.and_then(|t| {
210+
match DateTime::from_timestamp(t.seconds, t.nanos as u32) {
211+
dt @ Some(_) => dt,
212+
None => {
213+
tracing::error!(
214+
concat!(
215+
"Received invalid valid_until_time in ",
216+
"AugmentElectricalComponentBoundsResponse: {:?}"
217+
),
218+
t
219+
);
220+
None
221+
}
222+
}
223+
})
224+
});
225+
226+
response_tx
227+
.send(response)
228+
.map_err(|_| Error::internal("failed to send response"))?;
229+
}
180230
None => {}
181231
}
182232

0 commit comments

Comments
 (0)