Skip to content

Commit 9dfb5a6

Browse files
Add BatteryPool.temperature metrics receiver
Signed-off-by: Christian Parpart <[email protected]>
1 parent 09932bd commit 9dfb5a6

File tree

2 files changed

+120
-4
lines changed

2 files changed

+120
-4
lines changed

src/frequenz/sdk/timeseries/battery_pool/_metric_calculator.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ...microgrid.component import ComponentCategory, ComponentMetricId, InverterType
1515
from ...timeseries import Energy, Percentage, Sample, Temperature
1616
from ._component_metrics import ComponentMetricsData
17-
from ._result_types import Bound, PowerMetrics
17+
from ._result_types import Bound, PowerMetrics, TemperatureMetrics
1818

1919
_logger = logging.getLogger(__name__)
2020
_MIN_TIMESTAMP = datetime.min.replace(tzinfo=timezone.utc)
@@ -59,7 +59,7 @@ def battery_inverter_mapping(batteries: Iterable[int]) -> dict[int, int]:
5959

6060
# Formula output types class have no common interface
6161
# Print all possible types here.
62-
T = TypeVar("T", Sample[Percentage], Sample[Energy], PowerMetrics)
62+
T = TypeVar("T", Sample[Percentage], Sample[Energy], PowerMetrics, TemperatureMetrics)
6363

6464

6565
class MetricCalculator(ABC, Generic[T]):
@@ -234,6 +234,99 @@ def calculate(
234234
)
235235

236236

237+
class TemperatureCalculator(MetricCalculator[TemperatureMetrics]):
238+
"""Define how to calculate temperature metrics."""
239+
240+
def __init__(self, batteries: Set[int]) -> None:
241+
"""Create class instance.
242+
243+
Args:
244+
batteries: What batteries should be used for calculation.
245+
"""
246+
super().__init__(batteries)
247+
248+
self._metrics = [
249+
ComponentMetricId.TEMPERATURE_MAX,
250+
]
251+
252+
@classmethod
253+
def name(cls) -> str:
254+
"""Return name of the calculator.
255+
256+
Returns:
257+
Name of the calculator
258+
"""
259+
return "temperature"
260+
261+
@property
262+
def battery_metrics(self) -> Mapping[int, list[ComponentMetricId]]:
263+
"""Return what metrics are needed for each battery.
264+
265+
Returns:
266+
Map between battery id and set of required metrics id.
267+
"""
268+
return {bid: self._metrics for bid in self._batteries}
269+
270+
@property
271+
def inverter_metrics(self) -> Mapping[int, list[ComponentMetricId]]:
272+
"""Return what metrics are needed for each inverter.
273+
274+
Returns:
275+
Map between inverter id and set of required metrics id.
276+
"""
277+
return {}
278+
279+
def calculate(
280+
self,
281+
metrics_data: dict[int, ComponentMetricsData],
282+
working_batteries: set[int],
283+
) -> TemperatureMetrics | None:
284+
"""Aggregate the metrics_data and calculate high level metric for temperature.
285+
286+
Missing components will be ignored. Formula will be calculated for all
287+
working batteries that are in metrics_data.
288+
289+
Args:
290+
metrics_data: Components metrics data, that should be used to calculate the
291+
result.
292+
working_batteries: working batteries. These batteries will be used
293+
to calculate the result. It should be subset of the batteries given in a
294+
constructor.
295+
296+
Returns:
297+
High level metric calculated from the given metrics.
298+
Return None if there are no component metrics.
299+
"""
300+
timestamp = _MIN_TIMESTAMP
301+
temperature_sum: float = 0
302+
temperature_min: float = float("inf")
303+
temperature_max: float = float("-inf")
304+
temperature_count: int = 0
305+
for battery_id in working_batteries:
306+
if battery_id not in metrics_data:
307+
continue
308+
metrics = metrics_data[battery_id]
309+
temperature = metrics.get(ComponentMetricId.TEMPERATURE_MAX)
310+
if temperature is None:
311+
continue
312+
timestamp = max(timestamp, metrics.timestamp)
313+
temperature_sum += temperature
314+
temperature_min = min(temperature_min, temperature)
315+
temperature_max = max(temperature_max, temperature)
316+
temperature_count += 1
317+
if timestamp == _MIN_TIMESTAMP:
318+
return None
319+
320+
temperature_avg = temperature_sum / temperature_count
321+
322+
return TemperatureMetrics(
323+
timestamp=timestamp,
324+
min=Temperature.from_celsius(value=temperature_min),
325+
avg=Temperature.from_celsius(value=temperature_avg),
326+
max=Temperature.from_celsius(value=temperature_max),
327+
)
328+
329+
237330
class SoCCalculator(MetricCalculator[Sample[Percentage]]):
238331
"""Define how to calculate SoC metrics."""
239332

src/frequenz/sdk/timeseries/battery_pool/battery_pool.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@
2929
)
3030
from .._quantities import Energy, Percentage, Power
3131
from ._methods import MetricAggregator, SendOnUpdate
32-
from ._metric_calculator import CapacityCalculator, PowerBoundsCalculator, SoCCalculator
33-
from ._result_types import PowerMetrics
32+
from ._metric_calculator import (
33+
CapacityCalculator,
34+
PowerBoundsCalculator,
35+
SoCCalculator,
36+
TemperatureCalculator,
37+
)
38+
from ._result_types import PowerMetrics, TemperatureMetrics
3439

3540

3641
class BatteryPool:
@@ -373,6 +378,24 @@ def soc(self) -> MetricAggregator[Sample[Percentage]]:
373378

374379
return self._active_methods[method_name]
375380

381+
@property
382+
def temperature(self) -> MetricAggregator[TemperatureMetrics]:
383+
"""Fetch the maximum temperature of the batteries in the pool.
384+
385+
Returns:
386+
A MetricAggregator that will calculate and stream the maximum temperature
387+
of all batteries in the pool.
388+
"""
389+
method_name = SendOnUpdate.name() + "_" + TemperatureCalculator.name()
390+
if method_name not in self._active_methods:
391+
calculator = TemperatureCalculator(self._batteries)
392+
self._active_methods[method_name] = SendOnUpdate(
393+
metric_calculator=calculator,
394+
working_batteries=self._working_batteries,
395+
min_update_interval=self._min_update_interval,
396+
)
397+
return self._active_methods[method_name]
398+
376399
@property
377400
def capacity(self) -> MetricAggregator[Sample[Energy]]:
378401
"""Get receiver to receive new capacity metrics when they change.

0 commit comments

Comments
 (0)