|
15 | 15 | from typing import Any, assert_never |
16 | 16 |
|
17 | 17 | from frequenz.api.common.v1.metrics import bounds_pb2, metric_sample_pb2 |
| 18 | +from frequenz.api.common.v1.microgrid.components import components_pb2 |
18 | 19 | from frequenz.api.microgrid.v1 import microgrid_pb2, microgrid_pb2_grpc |
19 | 20 | from frequenz.client.base import channel, client, conversion, retry, streaming |
20 | 21 | from frequenz.client.common.microgrid.components import ComponentId |
|
24 | 25 | from ._exception import ClientNotConnected |
25 | 26 | from ._microgrid_info import MicrogridInfo |
26 | 27 | from ._microgrid_info_proto import microgrid_info_from_proto |
| 28 | +from .component._category import ComponentCategory |
27 | 29 | from .component._component import Component |
| 30 | +from .component._component_proto import component_from_proto |
| 31 | +from .component._types import ComponentTypes |
28 | 32 | from .metrics._bounds import Bounds |
29 | 33 | from .metrics._metric import Metric |
30 | 34 |
|
@@ -162,6 +166,61 @@ async def get_microgrid_info( # noqa: DOC502 (raises ApiClientError indirectly) |
162 | 166 |
|
163 | 167 | return microgrid_info_from_proto(microgrid.microgrid) |
164 | 168 |
|
| 169 | + async def list_components( # noqa: DOC502 (raises ApiClientError indirectly) |
| 170 | + self, |
| 171 | + *, |
| 172 | + components: Iterable[ComponentId | Component] = (), |
| 173 | + categories: Iterable[ComponentCategory | int] = (), |
| 174 | + ) -> Iterable[ComponentTypes]: |
| 175 | + """Fetch all the components present in the local microgrid. |
| 176 | +
|
| 177 | + Electrical components are a part of a microgrid's electrical infrastructure |
| 178 | + are can be connected to each other to form an electrical circuit, which can |
| 179 | + then be represented as a graph. |
| 180 | +
|
| 181 | + If provided, the filters for component and categories have an `AND` |
| 182 | + relationship with one another, meaning that they are applied serially, |
| 183 | + but the elements within a single filter list have an `OR` relationship with |
| 184 | + each other. |
| 185 | +
|
| 186 | + Example: |
| 187 | + If `ids = {1, 2, 3}`, and `categories = {ComponentCategory.INVERTER, |
| 188 | + ComponentCategory.BATTERY}`, then the results will consist of elements that |
| 189 | + have: |
| 190 | +
|
| 191 | + * The IDs 1, `OR` 2, `OR` 3; `AND` |
| 192 | + * Are of the categories `ComponentCategory.INVERTER` `OR` |
| 193 | + `ComponentCategory.BATTERY`. |
| 194 | +
|
| 195 | + If a filter list is empty, then that filter is not applied. |
| 196 | +
|
| 197 | + Args: |
| 198 | + components: The components to fetch. See the method description for details. |
| 199 | + categories: The categories of the components to fetch. See the method |
| 200 | + description for details. |
| 201 | +
|
| 202 | + Returns: |
| 203 | + Iterator whose elements are all the components in the local microgrid. |
| 204 | +
|
| 205 | + Raises: |
| 206 | + ApiClientError: If the are any errors communicating with the Microgrid API, |
| 207 | + most likely a subclass of |
| 208 | + [GrpcError][frequenz.client.microgrid.GrpcError]. |
| 209 | + """ |
| 210 | + component_list = await client.call_stub_method( |
| 211 | + self, |
| 212 | + lambda: self.stub.ListComponents( |
| 213 | + microgrid_pb2.ListComponentsRequest( |
| 214 | + component_ids=map(_get_component_id, components), |
| 215 | + categories=map(_get_category_value, categories), |
| 216 | + ), |
| 217 | + timeout=DEFAULT_GRPC_CALL_TIMEOUT, |
| 218 | + ), |
| 219 | + method_name="ListComponents", |
| 220 | + ) |
| 221 | + |
| 222 | + return map(component_from_proto, component_list.components) |
| 223 | + |
165 | 224 | async def set_component_power_active( # noqa: DOC502 (raises ApiClientError indirectly) |
166 | 225 | self, |
167 | 226 | component: ComponentId | Component, |
@@ -456,6 +515,19 @@ def _get_metric_value(metric: Metric | int) -> metric_sample_pb2.Metric.ValueTyp |
456 | 515 | assert_never(unexpected) |
457 | 516 |
|
458 | 517 |
|
| 518 | +def _get_category_value( |
| 519 | + category: ComponentCategory | int, |
| 520 | +) -> components_pb2.ComponentCategory.ValueType: |
| 521 | + """Get the category value from a component or component category.""" |
| 522 | + match category: |
| 523 | + case ComponentCategory(): |
| 524 | + return components_pb2.ComponentCategory.ValueType(category.value) |
| 525 | + case int(): |
| 526 | + return components_pb2.ComponentCategory.ValueType(category) |
| 527 | + case unexpected: |
| 528 | + assert_never(unexpected) |
| 529 | + |
| 530 | + |
459 | 531 | def _delta_to_seconds(delta: timedelta | None) -> int | None: |
460 | 532 | """Convert a `timedelta` to seconds (or `None` if `None`).""" |
461 | 533 | return round(delta.total_seconds()) if delta is not None else None |
|
0 commit comments