Skip to content

Commit

Permalink
Add tests for 3-phase active power streaming
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Zullo <daniel.zullo@frequenz.com>
  • Loading branch information
daniel-zullo-frequenz committed Feb 1, 2024
1 parent 4681446 commit 16e2030
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 1 deletion.
3 changes: 3 additions & 0 deletions tests/microgrid/test_component_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,17 @@ def test_inverter_data() -> None:
phase_1=electrical_pb2.AC.ACPhase(
current=metrics_pb2.Metric(value=12.3),
voltage=metrics_pb2.Metric(value=229.8),
power_active=metrics_pb2.Metric(value=3109.8),
),
phase_2=electrical_pb2.AC.ACPhase(
current=metrics_pb2.Metric(value=23.4),
voltage=metrics_pb2.Metric(value=230.0),
power_active=metrics_pb2.Metric(value=3528.3),
),
phase_3=electrical_pb2.AC.ACPhase(
current=metrics_pb2.Metric(value=34.5),
voltage=metrics_pb2.Metric(value=230.2),
power_active=metrics_pb2.Metric(value=3680.1),
),
),
),
Expand Down
24 changes: 23 additions & 1 deletion tests/timeseries/mock_resampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
NON_EXISTING_COMPONENT_ID,
)

# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes disable=too-many-locals


class MockResampler:
Expand Down Expand Up @@ -159,13 +159,26 @@ def voltage_senders(ids: list[int]) -> list[list[Sender[Sample[Quantity]]]]:
),
)

def active_power_senders(
ids: list[int],
) -> list[list[Sender[Sample[Quantity]]]]:
return multi_phase_senders(
ids,
(
ComponentMetricId.ACTIVE_POWER_PHASE_1,
ComponentMetricId.ACTIVE_POWER_PHASE_2,
ComponentMetricId.ACTIVE_POWER_PHASE_3,
),
)

self._bat_inverter_current_senders = current_senders(bat_inverter_ids)
self._pv_inverter_current_senders = current_senders(pv_inverter_ids)
self._ev_current_senders = current_senders(evc_ids)
self._chp_current_senders = current_senders(chp_ids)
self._meter_current_senders = current_senders(meter_ids)

self._meter_voltage_senders = voltage_senders(meter_ids)
self._meter_active_power_senders = active_power_senders(meter_ids)

self._next_ts = datetime.now()

Expand Down Expand Up @@ -325,3 +338,12 @@ async def send_meter_voltage(self, values: list[list[float | None]]) -> None:
for phase, value in enumerate(meter_values):
sample = self.make_sample(value)
await chan[phase].send(sample)

async def send_meter_active_power(self, values: list[list[float | None]]) -> None:
"""Send the given values as resampler output for meter active power."""
assert len(values) == len(self._meter_active_power_senders)
for chan, meter_values in zip(self._meter_active_power_senders, values):
assert len(meter_values) == 3 # 3 values for phases
for phase, value in enumerate(meter_values):
sample = self.make_sample(value)
await chan[phase].send(sample)
117 changes: 117 additions & 0 deletions tests/timeseries/test_power_streamer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# License: MIT
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH

"""Tests for fetching and streaming the 3-phase active power."""


import asyncio

from pytest_mock import MockerFixture

from frequenz.sdk import microgrid

from .mock_microgrid import MockMicrogrid

# pylint: disable=protected-access


async def test_active_power_1(mocker: MockerFixture) -> None:
"""Test the 3-phase active power with a grid side meter."""
mockgrid = MockMicrogrid(grid_meter=True, mocker=mocker)
mockgrid.add_batteries(1, no_meter=True)
mockgrid.add_batteries(1, no_meter=False)

async with mockgrid:
active_power = microgrid._active_power()
active_power_recv = active_power.new_receiver()

assert active_power._task is not None
# Wait for active power requests to be sent, one request per phase.
for _ in range(3):
await asyncio.sleep(0)

for count in range(10):
watts_delta = 1 if count % 2 == 0 else -1
watts_phases: list[float | None] = [
220.0 * watts_delta,
219.8 * watts_delta,
220.2 * watts_delta,
]

await mockgrid.mock_resampler.send_meter_active_power(
[watts_phases, watts_phases]
)

val = await active_power_recv.receive()
assert val is not None
assert val.value_p1 and val.value_p2 and val.value_p3
assert val.value_p1.as_watts() == watts_phases[0]
assert val.value_p2.as_watts() == watts_phases[1]
assert val.value_p3.as_watts() == watts_phases[2]


async def test_active_power_2(mocker: MockerFixture) -> None:
"""Test the 3-phase active power without a grid side meter."""
mockgrid = MockMicrogrid(grid_meter=False, mocker=mocker)
mockgrid.add_batteries(1, no_meter=False)
mockgrid.add_batteries(1, no_meter=True)

async with mockgrid:
active_power = microgrid._active_power()
active_power_recv = active_power.new_receiver()

assert active_power._task is not None
# Wait for active power requests to be sent, one request per phase.
for _ in range(3):
await asyncio.sleep(0)

for count in range(10):
watts_delta = 1 if count % 2 == 0 else -1
watts_phases: list[float | None] = [
220.0 * watts_delta,
219.8 * watts_delta,
220.2 * watts_delta,
]

await mockgrid.mock_resampler.send_meter_active_power([watts_phases])

val = await active_power_recv.receive()
assert val is not None
assert val.value_p1 and val.value_p2 and val.value_p3
assert val.value_p1.as_watts() == watts_phases[0]
assert val.value_p2.as_watts() == watts_phases[1]
assert val.value_p3.as_watts() == watts_phases[2]


async def test_active_power_3(mocker: MockerFixture) -> None:
"""Test the 3-phase active power with None values."""
mockgrid = MockMicrogrid(grid_meter=True, mocker=mocker)
mockgrid.add_batteries(2, no_meter=False)

async with mockgrid:
active_power = microgrid._active_power()
active_power_recv = active_power.new_receiver()

assert active_power._task is not None
# Wait for active power requests to be sent, one request per phase.
for _ in range(3):
await asyncio.sleep(0)

for count in range(10):
watts_delta = 1 if count % 2 == 0 else -1
watts_phases: list[float | None] = [
220.0 * watts_delta,
219.8 * watts_delta,
220.2 * watts_delta,
]

await mockgrid.mock_resampler.send_meter_active_power(
[watts_phases, [None, None, None], [None, 219.8, 220.2]]
)

val = await active_power_recv.receive()
assert val is not None
assert val.value_p1 and val.value_p2 and val.value_p3
assert val.value_p1.as_watts() == watts_phases[0]
assert val.value_p2.as_watts() == watts_phases[1]
assert val.value_p3.as_watts() == watts_phases[2]
18 changes: 18 additions & 0 deletions tests/utils/component_data_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ def __init__( # pylint: disable=too-many-arguments
active_power: float = math.nan,
current_per_phase: tuple[float, float, float] = (math.nan, math.nan, math.nan),
voltage_per_phase: tuple[float, float, float] = (math.nan, math.nan, math.nan),
active_power_per_phase: tuple[float, float, float] = (
math.nan,
math.nan,
math.nan,
),
active_power_inclusion_lower_bound: float = math.nan,
active_power_exclusion_lower_bound: float = math.nan,
active_power_inclusion_upper_bound: float = math.nan,
Expand All @@ -124,6 +129,7 @@ def __init__( # pylint: disable=too-many-arguments
active_power=active_power,
current_per_phase=current_per_phase,
voltage_per_phase=voltage_per_phase,
active_power_per_phase=active_power_per_phase,
active_power_inclusion_lower_bound=active_power_inclusion_lower_bound,
active_power_exclusion_lower_bound=active_power_exclusion_lower_bound,
active_power_inclusion_upper_bound=active_power_inclusion_upper_bound,
Expand Down Expand Up @@ -163,6 +169,11 @@ def __init__( # pylint: disable=too-many-arguments
active_power_exclusion_lower_bound: float = math.nan,
active_power_inclusion_upper_bound: float = math.nan,
active_power_exclusion_upper_bound: float = math.nan,
active_power_per_phase: tuple[float, float, float] = (
math.nan,
math.nan,
math.nan,
),
frequency: float = 50.0,
cable_state: EVChargerCableState = EVChargerCableState.UNSPECIFIED,
component_state: EVChargerComponentState = EVChargerComponentState.UNSPECIFIED,
Expand All @@ -182,6 +193,7 @@ def __init__( # pylint: disable=too-many-arguments
active_power_exclusion_lower_bound=active_power_exclusion_lower_bound,
active_power_inclusion_upper_bound=active_power_inclusion_upper_bound,
active_power_exclusion_upper_bound=active_power_exclusion_upper_bound,
active_power_per_phase=active_power_per_phase,
frequency=frequency,
cable_state=cable_state,
component_state=component_state,
Expand Down Expand Up @@ -213,6 +225,11 @@ def __init__( # pylint: disable=too-many-arguments
active_power: float = math.nan,
current_per_phase: tuple[float, float, float] = (math.nan, math.nan, math.nan),
voltage_per_phase: tuple[float, float, float] = (math.nan, math.nan, math.nan),
active_power_per_phase: tuple[float, float, float] = (
math.nan,
math.nan,
math.nan,
),
frequency: float = math.nan,
) -> None:
"""Initialize the MeterDataWrapper.
Expand All @@ -226,6 +243,7 @@ def __init__( # pylint: disable=too-many-arguments
active_power=active_power,
current_per_phase=current_per_phase,
voltage_per_phase=voltage_per_phase,
active_power_per_phase=active_power_per_phase,
frequency=frequency,
)

Expand Down

0 comments on commit 16e2030

Please sign in to comment.