From bf1cef193744db70879e3939855e7f6aac2a0043 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 2 May 2023 13:43:18 +0200 Subject: [PATCH] Fetch PV power from PV meters, rather than PV inverters This is because the microgrid API currently doesn't have drivers for reading data from PV inverters. We have plans to update our generated formulas to have fallback components, when a certain selection of components is unable to stream data, tracked here: #184. Reading PV power from a meter is also going to be more accurate, because it measures the actual power coming out of the PV inverter, unaffected by the inverter's self consumption. Signed-off-by: Sahas Subramanian --- .../_formula_generators/_pv_power_formula.py | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py index 5bf03ba0d..4375cc907 100644 --- a/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py +++ b/src/frequenz/sdk/timeseries/_formula_engine/_formula_generators/_pv_power_formula.py @@ -3,12 +3,20 @@ """Formula generator for PV Power, from the component graph.""" +from __future__ import annotations + import logging +from collections import abc from ....microgrid import connection_manager from ....microgrid.component import ComponentCategory, ComponentMetricId, InverterType from .._formula_engine import FormulaEngine -from ._formula_generator import NON_EXISTING_COMPONENT_ID, FormulaGenerator, FormulaType +from ._formula_generator import ( + NON_EXISTING_COMPONENT_ID, + FormulaGenerationError, + FormulaGenerator, + FormulaType, +) _logger = logging.getLogger(__name__) @@ -27,15 +35,8 @@ def generate(self) -> FormulaEngine: """ builder = self._get_builder("pv-power", ComponentMetricId.ACTIVE_POWER) - component_graph = connection_manager.get().component_graph - pv_inverters = list( - comp - for comp in component_graph.components() - if comp.category == ComponentCategory.INVERTER - and comp.type == InverterType.SOLAR - ) - - if not pv_inverters: + pv_meters = self._get_pv_meters() + if not pv_meters: _logger.warning( "Unable to find any PV inverters in the component graph. " "Subscribing to the resampling actor with a non-existing " @@ -51,11 +52,11 @@ def generate(self) -> FormulaEngine: builder.push_oper("(") builder.push_oper("(") - for idx, comp in enumerate(pv_inverters): + for idx, comp_id in enumerate(pv_meters): if idx > 0: builder.push_oper("+") - builder.push_component_metric(comp.component_id, nones_are_zeros=True) + builder.push_component_metric(comp_id, nones_are_zeros=True) builder.push_oper(")") if self._config.formula_type == FormulaType.PRODUCTION: builder.push_oper("*") @@ -66,3 +67,40 @@ def generate(self) -> FormulaEngine: builder.push_clipper(0.0, None) return builder.build() + + def _get_pv_meters(self) -> abc.Set[int]: + component_graph = connection_manager.get().component_graph + + pv_inverters = list( + comp + for comp in component_graph.components() + if comp.category == ComponentCategory.INVERTER + and comp.type == InverterType.SOLAR + ) + pv_meters: set[int] = set() + + if not pv_inverters: + return pv_meters + + for pv_inverter in pv_inverters: + predecessors = component_graph.predecessors(pv_inverter.component_id) + if len(predecessors) != 1: + raise FormulaGenerationError( + "Expected exactly one predecessor for PV inverter " + f"{pv_inverter.component_id}, but found {len(predecessors)}." + ) + meter = next(iter(predecessors)) + if meter.category != ComponentCategory.METER: + raise FormulaGenerationError( + f"Expected predecessor of PV inverter {pv_inverter.component_id} " + f"to be a meter, but found {meter.category}." + ) + meter_successors = component_graph.successors(meter.component_id) + if len(meter_successors) != 1: + raise FormulaGenerationError( + f"Expected exactly one successor for meter {meter.component_id}" + f", connected to PV inverter {pv_inverter.component_id}" + f", but found {len(meter_successors)}." + ) + pv_meters.add(meter.component_id) + return pv_meters