Skip to content

Commit c3b874e

Browse files
PowerDistributor should not block user command. (#92)
This is workaround for the bug. Previously if all components were considered as broken, then all user commands were blocked for 30 seconds. With this workaround if all components are considered as broken then we will try to use them anyway.
2 parents 20203ca + 6807433 commit c3b874e

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

src/frequenz/sdk/actor/power_distributing.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,34 @@ def update_retry(self, timeout_sec: float) -> None:
9191
"""
9292
self._timeout_sec = timeout_sec
9393

94+
def get_working_subset(self, components_ids: Set[int]) -> Set[int]:
95+
"""Get subset of batteries that are not marked as broken.
96+
97+
If all given batteries are broken, then mark them as working and return them.
98+
This is temporary workaround to not block user command.
99+
100+
Args:
101+
components_ids: set of component ids
102+
103+
Returns:
104+
Subset of given components_ids with working components.
105+
"""
106+
working = set(filter(lambda cid: not self.is_broken(cid), components_ids))
107+
108+
if len(working) == 0:
109+
_logger.warning(
110+
"All requested components: %s are marked as broken. "
111+
"Marking them as working to not block command.",
112+
str(components_ids),
113+
)
114+
115+
for cid in components_ids:
116+
self._broken.pop(cid, None)
117+
118+
working = components_ids
119+
120+
return working
121+
94122
def is_broken(self, component_id: int) -> bool:
95123
"""Check if component is marked as broken.
96124
@@ -595,18 +623,15 @@ def _get_components_data(self, batteries: Iterable[int]) -> List[InvBatPair]:
595623
Pairs of battery and adjacent inverter data.
596624
"""
597625
pairs_data: List[InvBatPair] = []
598-
for battery_id in batteries:
626+
627+
for battery_id in self._broken_components.get_working_subset(batteries):
599628
if battery_id not in self._battery_receivers:
600629
raise KeyError(
601630
f"No battery {battery_id}, "
602631
f"available batteries: {list(self._battery_receivers.keys())}"
603632
)
604633

605634
inverter_id: int = self._bat_inv_map[battery_id]
606-
if self._broken_components.is_broken(
607-
battery_id
608-
) or self._broken_components.is_broken(inverter_id):
609-
continue
610635

611636
battery_data: Optional[BatteryData] = self._battery_receivers[
612637
battery_id

tests/actor/test_power_distributing.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
from frequenz.channels import Bidirectional, Broadcast, Receiver, Sender
1515
from google.protobuf.empty_pb2 import Empty # pylint: disable=no-name-in-module
16+
from pytest_mock import MockerFixture
1617

1718
from frequenz.sdk.actor.power_distributing import (
1819
PowerDistributingActor,
1920
Request,
2021
Result,
22+
_BrokenComponents,
2123
)
2224
from frequenz.sdk.microgrid._graph import _MicrogridComponentGraph
2325
from frequenz.sdk.microgrid.client import Connection
@@ -738,3 +740,43 @@ async def test_power_distributor_stale_all_components_message(
738740
assert result.status == Result.Status.ERROR
739741
# User is interested in batteries only.
740742
assert result.error_message == "No data for the given batteries {106, 206}"
743+
744+
745+
class TestBrokenComponents:
746+
"""Test if BrokenComponents class is working as expected."""
747+
748+
def test_broken_components(self, mocker: MockerFixture) -> None:
749+
"""Check if components are blocked for 30 seconds.
750+
751+
Args:
752+
mocker: pytest mocker
753+
"""
754+
datetime_mock = mocker.patch("frequenz.sdk.actor.power_distributing.datetime")
755+
756+
expected_datetime = [
757+
datetime.fromisoformat("2001-01-01T00:00:00+00:00"),
758+
datetime.fromisoformat("2001-01-01T00:00:10+00:00"),
759+
datetime.fromisoformat("2001-01-01T00:00:20+00:00"),
760+
]
761+
expected_datetime.extend(
762+
20 * [datetime.fromisoformat("2001-01-01T00:00:31+00:00")]
763+
)
764+
765+
datetime_mock.now.side_effect = expected_datetime
766+
767+
# After 30 seconds components should be considered as working
768+
broken = _BrokenComponents(30)
769+
770+
for component_id in range(3):
771+
broken.mark_as_broken(component_id)
772+
773+
# Component 0 was marked as broken 30 seconds ago. Other components, not.
774+
assert not broken.is_broken(0)
775+
assert broken.is_broken(1)
776+
assert broken.is_broken(2)
777+
assert broken.get_working_subset({0, 1}) == {0}
778+
assert broken.get_working_subset({0}) == {0}
779+
780+
# If all requested components are marked as broken,
781+
# then we should mark them as working to not block user command.
782+
assert broken.get_working_subset({1, 2}) == {1, 2}

0 commit comments

Comments
 (0)