Skip to content

Commit c9115e0

Browse files
Fix PV power distribution (#1015)
To exclude PV inverters that didn't send any data. The power distributor crashes because it tried to get data from a component that didn't send any data since the application startup.
2 parents 9c54f8d + 894fcf2 commit c9115e0

File tree

3 files changed

+42
-8
lines changed

3 files changed

+42
-8
lines changed

RELEASE_NOTES.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
## Bug Fixes
44

5-
- Fix getting reactive power from meters, inverters and EV chargers.
5+
- Fix PV power distribution excluding inverters that haven't sent any data since the application started.

src/frequenz/sdk/actor/power_distributing/_component_managers/_pv_inverter_manager/_pv_inverter_manager.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,19 @@ async def distribute_power(self, request: Request) -> None:
113113
raise ValueError(
114114
"Cannot distribute power to PV inverters without any inverters"
115115
)
116-
working_components = list(
117-
self._component_pool_status_tracker.get_working_components(
118-
request.component_ids
119-
)
120-
)
116+
117+
working_components: list[int] = []
118+
for inv_id in self._component_pool_status_tracker.get_working_components(
119+
request.component_ids
120+
):
121+
if self._component_data_caches[inv_id].has_value():
122+
working_components.append(inv_id)
123+
else:
124+
_logger.warning(
125+
"Exclude inverter %s from distribution, because it didn't "
126+
"send any data since the application startup.",
127+
inv_id,
128+
)
121129

122130
# When sorting by lower bounds, which are negative for PV inverters, we have to
123131
# reverse the order, so that the inverters with the higher bounds i.e., the
@@ -130,13 +138,18 @@ async def distribute_power(self, request: Request) -> None:
130138
)
131139

132140
num_components = len(working_components)
141+
if num_components == 0:
142+
_logger.error("No inverters available for power distribution. Aborting.")
143+
return
144+
133145
for idx, inv_id in enumerate(working_components):
134146
# Request powers are negative for PV inverters. When remaining power is
135147
# greater than 0.0, we can stop allocating further.
136148
if remaining_power > Power.zero() or is_close_to_zero(
137149
remaining_power.as_watts()
138150
):
139-
break
151+
allocations[inv_id] = Power.zero()
152+
continue
140153
distribution = remaining_power / float(num_components - idx)
141154
inv_data = self._component_data_caches[inv_id]
142155
if not inv_data.has_value():

tests/timeseries/_pv_pool/test_pv_pool_control_methods.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ async def _recv_reports_until(
131131
if check(report):
132132
break
133133

134-
async def test_setting_power(
134+
async def test_setting_power( # pylint: disable=too-many-statements
135135
self,
136136
mocks: _Mocks,
137137
mocker: MockerFixture,
@@ -260,3 +260,24 @@ async def test_setting_power(
260260
mocker.call(inv_ids[2], -30000.0),
261261
mocker.call(inv_ids[3], -30000.0),
262262
]
263+
264+
# Setting 0 power should set all inverters to 0
265+
set_power.reset_mock()
266+
await pv_pool.propose_power(Power.zero())
267+
await self._recv_reports_until(
268+
bounds_rx,
269+
lambda x: x.target_power is not None and x.target_power.as_watts() == 0.0,
270+
)
271+
self._assert_report(
272+
await bounds_rx.receive(), power=0.0, lower=-100000.0, upper=0.0
273+
)
274+
await asyncio.sleep(0.0)
275+
276+
assert set_power.call_count == 4
277+
inv_ids = mocks.microgrid.pv_inverter_ids
278+
assert sorted(set_power.call_args_list, key=lambda x: x.args[0]) == [
279+
mocker.call(inv_ids[0], 0.0),
280+
mocker.call(inv_ids[1], 0.0),
281+
mocker.call(inv_ids[2], 0.0),
282+
mocker.call(inv_ids[3], 0.0),
283+
]

0 commit comments

Comments
 (0)