Skip to content

Commit 4938fab

Browse files
Move grid power and current metrics to Grid (#758)
Fixes #729
2 parents 338dee5 + a5eae9c commit 4938fab

File tree

17 files changed

+470
-311
lines changed

17 files changed

+470
-311
lines changed

RELEASE_NOTES.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@
2020

2121
- The `Fuse` class has been moved to the `frequenz.sdk.timeseries` module.
2222

23+
- `microgrid.grid()`
24+
- A `Grid` object is always instantiated now, even if the microgrid is not connected to the grid (islanded microgrids).
25+
- The rated current of the grid fuse is set to `Current.zero()` in case of islanded microgrids.
26+
- The grid fuse is set to `None` when the grid connection component metadata lacks information about the fuse.
27+
- Grid power and current metrics were moved from `microgrid.logical_meter()` to `microgrid.grid()`.
28+
29+
Previously,
30+
31+
```python
32+
logical_meter = microgrid.logical_meter()
33+
grid_power_recv = logical_meter.grid_power.new_receiver()
34+
grid_current_recv = logical_meter.grid_current.new_receiver()
35+
```
36+
37+
Now,
38+
39+
```python
40+
grid = microgrid.grid()
41+
grid_power_recv = grid.power.new_receiver()
42+
grid_current_recv = grid.current.new_receiver()
43+
```
44+
2345
## New Features
2446

2547
<!-- Here goes the main new features and examples or instructions on how to use them -->

docs/tutorials/getting_started.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ async def run() -> None:
8787
...
8888

8989
# Define your application logic here
90-
grid_meter = microgrid.logical_meter().grid_power.new_receiver()
90+
grid_meter = microgrid.grid().power.new_receiver()
9191

9292
async for power in grid_meter:
9393
print(power.value)
@@ -117,7 +117,7 @@ async def run() -> None:
117117
)
118118

119119
# Define your application logic here
120-
grid_meter = microgrid.logical_meter().grid_power.new_receiver()
120+
grid_meter = microgrid.grid().power.new_receiver()
121121

122122
async for power in grid_meter:
123123
print(power.value)

docs/user-guide/glossary.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ microgrid are connected with each other. Some of the ways in which the SDK uses
166166
the component graph are:
167167

168168
- figure out how to calculate high level metrics like
169-
[`grid_power`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.grid_power],
169+
[`grid_power`][frequenz.sdk.timeseries.grid.Grid.power],
170170
[`consumer_power`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.consumer_power],
171171
etc. for a microgrid, using the available components.
172172
- identify the available {{glossary("battery", "batteries")}} or

src/frequenz/sdk/microgrid/__init__.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
6060
This refers to a microgrid's connection to the external Grid. The power flowing through
6161
this connection can be streamed through
62-
[`grid_power`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.grid_power].
62+
[`grid_power`][frequenz.sdk.timeseries.grid.Grid.power].
6363
6464
In locations without a grid connection, this method remains accessible, and streams zero
6565
values.
@@ -123,7 +123,6 @@
123123
""" # noqa: D205, D400
124124

125125
from ..actor import ResamplerConfig
126-
from ..timeseries.grid import initialize as initialize_grid
127126
from . import _data_pipeline, client, component, connection_manager, metadata
128127
from ._data_pipeline import (
129128
battery_pool,
@@ -143,11 +142,6 @@ async def initialize(host: str, port: int, resampler_config: ResamplerConfig) ->
143142
resampler_config: Configuration for the resampling actor.
144143
"""
145144
await connection_manager.initialize(host, port)
146-
147-
api_client = connection_manager.get().api_client
148-
components = await api_client.components()
149-
initialize_grid(components)
150-
151145
await _data_pipeline.initialize(resampler_config)
152146

153147

src/frequenz/sdk/microgrid/_data_pipeline.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from ..timeseries._grid_frequency import GridFrequency
2525
from ..timeseries.grid import Grid
2626
from ..timeseries.grid import get as get_grid
27+
from ..timeseries.grid import initialize as initialize_grid
2728
from . import connection_manager
2829
from .component import ComponentCategory
2930

@@ -115,6 +116,7 @@ def __init__(
115116
self._power_managing_actor: _power_managing.PowerManagingActor | None = None
116117

117118
self._logical_meter: LogicalMeter | None = None
119+
self._grid: Grid | None = None
118120
self._ev_charger_pools: dict[frozenset[int], EVChargerPool] = {}
119121
self._battery_pools: dict[frozenset[int], BatteryPoolReferenceStore] = {}
120122
self._frequency_pool: dict[int, GridFrequency] = {}
@@ -189,17 +191,22 @@ def ev_charger_pool(
189191
)
190192
return self._ev_charger_pools[key]
191193

192-
def grid(
193-
self,
194-
) -> Grid | None:
194+
def grid(self) -> Grid:
195195
"""Return the grid instance.
196196
197197
If a Grid instance doesn't exist, a new one is created and returned.
198198
199199
Returns:
200200
A Grid instance.
201201
"""
202-
return get_grid()
202+
if self._grid is None:
203+
initialize_grid(
204+
channel_registry=self._channel_registry,
205+
resampler_subscription_sender=self._resampling_request_sender(),
206+
)
207+
self._grid = get_grid()
208+
209+
return self._grid
203210

204211
def battery_pool(
205212
self,
@@ -470,7 +477,7 @@ def battery_pool(
470477
return _get().battery_pool(battery_ids, name, priority)
471478

472479

473-
def grid() -> Grid | None:
480+
def grid() -> Grid:
474481
"""Return the grid instance.
475482
476483
Returns:

src/frequenz/sdk/timeseries/_periodic_feature_extractor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class PeriodicFeatureExtractor:
8282
8383
async with MovingWindow(
8484
size=timedelta(days=35),
85-
resampled_data_recv=microgrid.logical_meter().grid_power.new_receiver(),
85+
resampled_data_recv=microgrid.grid().power.new_receiver(),
8686
input_sampling_period=timedelta(seconds=1),
8787
) as moving_window:
8888
feature_extractor = PeriodicFeatureExtractor(

src/frequenz/sdk/timeseries/formula_engine/_formula_engine.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ class FormulaEngine(
231231
resampled data streams.
232232
233233
They are used in the SDK to calculate and stream metrics like
234-
[`grid_power`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.grid_power],
234+
[`grid_power`][frequenz.sdk.timeseries.grid.Grid.power],
235235
[`consumer_power`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.consumer_power],
236236
etc., which are building blocks of the
237237
[Frequenz SDK Microgrid Model][frequenz.sdk.microgrid--frequenz-sdk-microgrid-model].
@@ -267,7 +267,7 @@ class FormulaEngine(
267267
[`battery_pool().power`][frequenz.sdk.timeseries.battery_pool.BatteryPool.power] and
268268
[`ev_charger_pool().power`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool]
269269
from the
270-
[`logical_meter().grid_power`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.grid_power],
270+
[`grid().power`][frequenz.sdk.timeseries.grid.Grid.power],
271271
we can build a `FormulaEngine` that provides a stream of this calculated metric as
272272
follows:
273273
@@ -277,11 +277,12 @@ class FormulaEngine(
277277
logical_meter = microgrid.logical_meter()
278278
battery_pool = microgrid.battery_pool()
279279
ev_charger_pool = microgrid.ev_charger_pool()
280+
grid = microgrid.grid()
280281
281282
# apply operations on formula engines to create a formula engine that would
282283
# apply these operations on the corresponding data streams.
283284
net_power = (
284-
logical_meter.grid_power - (battery_pool.power + ev_charger_pool.power)
285+
grid.power - (battery_pool.power + ev_charger_pool.power)
285286
).build("net_power")
286287
287288
async for power in net_power.new_receiver():
@@ -442,7 +443,7 @@ class FormulaEngine3Phase(
442443
[`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine], except that
443444
they stream [3-phase samples][frequenz.sdk.timeseries.Sample3Phase]. All the
444445
current formulas (like
445-
[`LogicalMeter.grid_current`][frequenz.sdk.timeseries.logical_meter.LogicalMeter.grid_current],
446+
[`Grid.current`][frequenz.sdk.timeseries.grid.Grid.current],
446447
[`EVChargerPool.current`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool.current],
447448
etc.) are implemented as 3-phase formulas.
448449
@@ -474,9 +475,10 @@ class FormulaEngine3Phase(
474475
475476
logical_meter = microgrid.logical_meter()
476477
ev_charger_pool = microgrid.ev_charger_pool()
478+
grid = microgrid.grid()
477479
478480
# Calculate grid consumption current that's not used by the EV chargers
479-
other_current = (logical_meter.grid_current - ev_charger_pool.current).build("other_current")
481+
other_current = (grid.current - ev_charger_pool.current).build("other_current")
480482
481483
async for sample in other_current.new_receiver():
482484
print(f"Other current: {sample}")

src/frequenz/sdk/timeseries/formula_engine/_formula_engine_pool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
from frequenz.channels import Sender
1111

12-
from ...actor import ChannelRegistry, ComponentMetricRequest
1312
from ...microgrid.component import ComponentMetricId
1413
from .._quantities import Current, Power, Quantity
1514
from ._formula_generators._formula_generator import (
@@ -20,6 +19,7 @@
2019

2120
if TYPE_CHECKING:
2221
# Break circular import
22+
from ...actor import ChannelRegistry, ComponentMetricRequest
2323
from ..formula_engine import FormulaEngine, FormulaEngine3Phase
2424

2525

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_formula_generator.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@
33

44
"""Base class for formula generators that use the component graphs."""
55

6+
from __future__ import annotations
67

78
import sys
89
from abc import ABC, abstractmethod
910
from collections import abc
1011
from collections.abc import Callable
1112
from dataclasses import dataclass
12-
from typing import Generic
13+
from typing import TYPE_CHECKING, Generic
1314

1415
from frequenz.channels import Sender
1516

16-
from ....actor import ChannelRegistry, ComponentMetricRequest
1717
from ....microgrid import component, connection_manager
1818
from ....microgrid.component import ComponentMetricId
1919
from ..._quantities import QuantityT
2020
from .._formula_engine import FormulaEngine, FormulaEngine3Phase
2121
from .._resampled_formula_builder import ResampledFormulaBuilder
2222

23+
if TYPE_CHECKING:
24+
# Break circular import
25+
from ....actor import ChannelRegistry, ComponentMetricRequest
26+
2327

2428
class FormulaGenerationError(Exception):
2529
"""An error encountered during formula generation from the component graph."""

src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33

44
"""A builder for creating formula engines that operate on resampled component metrics."""
55

6+
from __future__ import annotations
67

78
from collections.abc import Callable
8-
from typing import Generic
9+
from typing import TYPE_CHECKING, Generic
910

1011
from frequenz.channels import Receiver, Sender
1112

12-
from ...actor import ChannelRegistry, ComponentMetricRequest
1313
from ...microgrid.component import ComponentMetricId
1414
from .. import Sample
1515
from .._quantities import QuantityT
1616
from ._formula_engine import FormulaBuilder, FormulaEngine
1717
from ._tokenizer import Tokenizer, TokenType
1818

19+
if TYPE_CHECKING:
20+
# Break circular import
21+
from ...actor import ChannelRegistry, ComponentMetricRequest
22+
1923

2024
class ResampledFormulaBuilder(Generic[QuantityT], FormulaBuilder[QuantityT]):
2125
"""Provides a way to build a FormulaEngine from resampled data streams."""
@@ -63,6 +67,9 @@ def _get_resampled_receiver(
6367
Returns:
6468
A receiver to stream resampled data for the given component id.
6569
"""
70+
# pylint: disable=import-outside-toplevel
71+
from frequenz.sdk.actor import ComponentMetricRequest
72+
6673
request = ComponentMetricRequest(self._namespace, component_id, metric_id, None)
6774
self._resampler_requests.append(request)
6875
return self._channel_registry.new_receiver(request.get_channel_name())

0 commit comments

Comments
 (0)