Skip to content

Commit 7ed29a8

Browse files
Add BatteryPoolStatus module to store status for set of batteries
Signed-off-by: ela-kotulska-frequenz <[email protected]>
1 parent 35c6e30 commit 7ed29a8

File tree

2 files changed

+1061
-0
lines changed

2 files changed

+1061
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# License: MIT
2+
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH
3+
"""Class that stores pool of batteries and manage them."""
4+
5+
import asyncio
6+
import logging
7+
from typing import Set
8+
9+
from ...microgrid._battery import BatteryStatus, StatusTracker
10+
from .result import PartialFailure, Result, Success
11+
12+
_logger = logging.getLogger(__name__)
13+
14+
15+
class BatteryPoolStatus:
16+
"""Holds pool of batteries and returns data from them."""
17+
18+
def __init__(
19+
self,
20+
battery_ids: Set[int],
21+
max_data_age_sec: float,
22+
max_blocking_duration_sec: float,
23+
) -> None:
24+
"""Create partially initialized object instance.
25+
26+
Note:
27+
Please call `async_init` method to fully initialize BatteryPoolStatus. Otherwise
28+
it is not possible to use BatteryPoolStatus.
29+
30+
Args:
31+
battery_ids: set of batteries ids that should be stored in pool.
32+
max_data_age_sec: If component stopped sending data, then
33+
this is the maximum time when its last message should be considered as
34+
valid. After that time, component won't be used until it starts sending
35+
data.
36+
max_blocking_duration_sec: This value tell what should be the maximum
37+
timeout used for blocking failing component.
38+
"""
39+
self._batteries = {
40+
id: StatusTracker(id, max_data_age_sec, max_blocking_duration_sec)
41+
for id in battery_ids
42+
}
43+
self._init_method_called: bool = False
44+
45+
async def async_init(self) -> None:
46+
"""Init battery pool."""
47+
await asyncio.gather(
48+
*[bat.async_init() for bat in self._batteries.values()],
49+
return_exceptions=True,
50+
)
51+
self._init_method_called = True
52+
53+
def get_working_batteries(self, battery_ids: Set[int]) -> Set[int]:
54+
"""Get subset of battery_ids with working batteries.
55+
56+
Args:
57+
battery_ids: batteries ids
58+
59+
Raises:
60+
RuntimeError: If `async_init` method was not called at the beginning to
61+
initialized object.
62+
KeyError: If any battery in the given batteries is not in the pool.
63+
64+
Returns:
65+
Subset of given batteries with working batteries.
66+
"""
67+
if not self._init_method_called:
68+
raise RuntimeError(
69+
"`async_init` method not called or not awaited. Run it before first use"
70+
)
71+
72+
working: Set[int] = set()
73+
uncertain: Set[int] = set()
74+
for bat_id in battery_ids:
75+
if bat_id not in battery_ids:
76+
ids = str(self._batteries.keys())
77+
raise KeyError(f"No battery {bat_id} in pool. All batteries: {ids}")
78+
battery_status = self._batteries[bat_id].get_status()
79+
if battery_status == BatteryStatus.WORKING:
80+
working.add(bat_id)
81+
elif battery_status == BatteryStatus.UNCERTAIN:
82+
uncertain.add(bat_id)
83+
84+
if len(working) > 0:
85+
return working
86+
87+
_logger.warning(
88+
"There are no working batteries in %s. Falling back to using uncertain batteries %s.",
89+
str(battery_ids),
90+
str(uncertain),
91+
)
92+
return uncertain
93+
94+
def update_last_request_status(self, result: Result):
95+
"""Update batteries in pool based on the last result from the request.
96+
97+
Args:
98+
result: Summary of what batteries failed and succeed in last request.
99+
100+
Raises:
101+
RuntimeError: If `async_init` method was not called at the beginning to
102+
initialize object.
103+
"""
104+
if not self._init_method_called:
105+
raise RuntimeError(
106+
"`async_init` method not called or not awaited. Run it before first use"
107+
)
108+
109+
if isinstance(result, Success):
110+
for bat_id in result.used_batteries:
111+
self._batteries[bat_id].unblock()
112+
113+
elif isinstance(result, PartialFailure):
114+
for bat_id in result.failed_batteries:
115+
duration = self._batteries[bat_id].block()
116+
if duration > 0:
117+
_logger.warning(
118+
"Battery %d failed last response. Block it for %f sec",
119+
bat_id,
120+
duration,
121+
)
122+
123+
for bat_id in result.succeed_batteries:
124+
self._batteries[bat_id].unblock()

0 commit comments

Comments
 (0)