Skip to content

Commit 933e4ae

Browse files
committed
Add grid_current and ev_charger_current methods to LogicalMeter
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent f8d0334 commit 933e4ae

File tree

2 files changed

+81
-4
lines changed

2 files changed

+81
-4
lines changed

src/frequenz/sdk/timeseries/logical_meter/_logical_meter.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@
1515
from ...actor import ChannelRegistry, ComponentMetricRequest
1616
from ...microgrid import ComponentGraph
1717
from ...microgrid.component import ComponentMetricId
18-
from ._formula_engine import FormulaEngine, FormulaReceiver
18+
from ._formula_engine import (
19+
FormulaEngine,
20+
FormulaEngine3Phase,
21+
FormulaReceiver,
22+
FormulaReceiver3Phase,
23+
_GenericEngine,
24+
_GenericFormulaReceiver,
25+
)
1926
from ._formula_generators import (
2027
BatteryPowerFormula,
2128
BatterySoCFormula,
29+
EVChargerCurrentFormula,
2230
FormulaGenerator,
31+
GridCurrentFormula,
2332
GridPowerFormula,
2433
PVPowerFormula,
2534
)
@@ -110,7 +119,7 @@ def __init__(
110119
# meter to use when communicating with the resampling actor.
111120
self._namespace = f"logical-meter-{uuid.uuid4()}"
112121
self._component_graph = component_graph
113-
self._engines: Dict[str, FormulaEngine] = {}
122+
self._engines: Dict[str, FormulaEngine | FormulaEngine3Phase] = {}
114123
self._tasks: List[asyncio.Task[None]] = []
115124

116125
async def _engine_from_formula_string(
@@ -163,8 +172,8 @@ async def start_formula(
163172
async def _get_formula_stream(
164173
self,
165174
channel_key: str,
166-
generator: Type[FormulaGenerator],
167-
) -> FormulaReceiver:
175+
generator: Type[FormulaGenerator[_GenericEngine]],
176+
) -> _GenericFormulaReceiver:
168177
if channel_key in self._engines:
169178
return self._engines[channel_key].new_receiver()
170179

@@ -187,6 +196,34 @@ async def grid_power(self) -> FormulaReceiver:
187196
"""
188197
return await self._get_formula_stream("grid_power", GridPowerFormula)
189198

199+
async def grid_current(self) -> FormulaReceiver3Phase:
200+
"""Fetch the grid power for the microgrid.
201+
202+
If a formula engine to calculate grid current is not already running, it
203+
will be started. Else, we'll just get a new receiver to the already
204+
existing data stream.
205+
206+
Returns:
207+
A *new* receiver that will stream grid_current values.
208+
209+
"""
210+
return await self._get_formula_stream("grid_current", GridCurrentFormula)
211+
212+
async def ev_charger_current(self) -> FormulaReceiver3Phase:
213+
"""Fetch the cumulative ev charger current for the microgrid.
214+
215+
If a formula engine to calculate ev charger current is not already
216+
running, it will be started. Else, we'll just get a new receiver to the
217+
already existing data stream.
218+
219+
Returns:
220+
A *new* receiver that will stream ev_charger_current values.
221+
222+
"""
223+
return await self._get_formula_stream(
224+
"ev_charger_current", EVChargerCurrentFormula
225+
)
226+
190227
async def battery_power(self) -> FormulaReceiver:
191228
"""Fetch the cumulative battery power in the microgrid.
192229

tests/timeseries/test_logical_meter.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,43 @@ async def test_formula_composition_missing_bat(self, mocker: MockerFixture) -> N
362362
await engine._stop() # pylint: disable=protected-access
363363

364364
assert count == 10
365+
366+
async def test_3_phase_formulas(self, mocker: MockerFixture) -> None:
367+
"""Test 3 phase formulas current formulas and their composition."""
368+
mockgrid = await MockMicrogrid.new(mocker, grid_side_meter=False)
369+
mockgrid.add_batteries(3)
370+
mockgrid.add_ev_chargers(1)
371+
request_sender, channel_registry = await mockgrid.start()
372+
logical_meter = LogicalMeter(
373+
channel_registry,
374+
request_sender,
375+
microgrid.get().component_graph,
376+
)
377+
grid_current_recv = await logical_meter.grid_current()
378+
ev_current_recv = await logical_meter.ev_charger_current()
379+
380+
engine = (grid_current_recv.clone() - ev_current_recv.clone()).build(
381+
"net_current"
382+
)
383+
net_current_recv = engine.new_receiver()
384+
for _ in range(10):
385+
grid_amps = await grid_current_recv.receive()
386+
ev_amps = await ev_current_recv.receive()
387+
net_amps = await net_current_recv.receive()
388+
389+
assert grid_amps.value_p1 is not None and grid_amps.value_p1 > 0.0
390+
assert grid_amps.value_p2 is not None and grid_amps.value_p2 > 0.0
391+
assert grid_amps.value_p3 is not None and grid_amps.value_p3 > 0.0
392+
assert ev_amps.value_p1 is not None and ev_amps.value_p1 > 0.0
393+
assert ev_amps.value_p2 is not None and ev_amps.value_p2 > 0.0
394+
assert ev_amps.value_p3 is not None and ev_amps.value_p3 > 0.0
395+
assert net_amps.value_p1 is not None and net_amps.value_p1 > 0.0
396+
assert net_amps.value_p2 is not None and net_amps.value_p2 > 0.0
397+
assert net_amps.value_p3 is not None and net_amps.value_p3 > 0.0
398+
399+
assert net_amps.value_p1 == grid_amps.value_p1 - ev_amps.value_p1
400+
assert net_amps.value_p2 == grid_amps.value_p2 - ev_amps.value_p2
401+
assert net_amps.value_p3 == grid_amps.value_p3 - ev_amps.value_p3
402+
403+
await mockgrid.cleanup()
404+
await engine._stop() # pylint: disable=protected-access

0 commit comments

Comments
 (0)