|
5 | 5 |
|
6 | 6 | import logging |
7 | 7 |
|
8 | | -from frequenz.client.microgrid import ComponentCategory, ComponentMetricId |
| 8 | +from frequenz.client.microgrid import Component, ComponentCategory, ComponentMetricId |
9 | 9 |
|
10 | 10 | from ....microgrid import connection_manager |
11 | 11 | from ..._quantities import Power |
12 | 12 | from .._formula_engine import FormulaEngine |
13 | | -from ._formula_generator import NON_EXISTING_COMPONENT_ID, FormulaGenerator |
| 13 | +from ._fallback_formula_metric_fetcher import FallbackFormulaMetricFetcher |
| 14 | +from ._formula_generator import ( |
| 15 | + NON_EXISTING_COMPONENT_ID, |
| 16 | + FormulaGenerator, |
| 17 | + FormulaGeneratorConfig, |
| 18 | +) |
14 | 19 |
|
15 | 20 | _logger = logging.getLogger(__name__) |
16 | 21 |
|
@@ -58,18 +63,79 @@ def generate( # noqa: DOC502 |
58 | 63 | # frequency as the other streams. So we subscribe with a non-existing |
59 | 64 | # component id, just to get a `None` message at the resampling interval. |
60 | 65 | builder.push_component_metric( |
61 | | - NON_EXISTING_COMPONENT_ID, nones_are_zeros=True |
| 66 | + NON_EXISTING_COMPONENT_ID, |
| 67 | + nones_are_zeros=True, |
62 | 68 | ) |
63 | 69 | return builder.build() |
64 | 70 |
|
65 | | - for idx, component in enumerate(pv_components): |
66 | | - if idx > 0: |
67 | | - builder.push_oper("+") |
| 71 | + if self._config.allow_fallback: |
| 72 | + fallbacks = self._get_fallback_formulas(pv_components) |
68 | 73 |
|
69 | | - # should only be the case if the component is not a meter |
70 | | - builder.push_component_metric( |
71 | | - component.component_id, |
72 | | - nones_are_zeros=component.category != ComponentCategory.METER, |
73 | | - ) |
| 74 | + for idx, (primary_component, fallback_formula) in enumerate( |
| 75 | + fallbacks.items() |
| 76 | + ): |
| 77 | + if idx > 0: |
| 78 | + builder.push_oper("+") |
| 79 | + |
| 80 | + builder.push_component_metric( |
| 81 | + primary_component.component_id, |
| 82 | + nones_are_zeros=( |
| 83 | + primary_component.category != ComponentCategory.METER |
| 84 | + ), |
| 85 | + fallback=fallback_formula, |
| 86 | + ) |
| 87 | + else: |
| 88 | + for idx, component in enumerate(pv_components): |
| 89 | + if idx > 0: |
| 90 | + builder.push_oper("+") |
| 91 | + |
| 92 | + builder.push_component_metric( |
| 93 | + component.component_id, |
| 94 | + nones_are_zeros=component.category != ComponentCategory.METER, |
| 95 | + ) |
74 | 96 |
|
75 | 97 | return builder.build() |
| 98 | + |
| 99 | + def _get_fallback_formulas( |
| 100 | + self, components: set[Component] |
| 101 | + ) -> dict[Component, FallbackFormulaMetricFetcher[Power] | None]: |
| 102 | + """Find primary and fallback components and create fallback formulas. |
| 103 | +
|
| 104 | + The primary component is the one that will be used to calculate the PV power. |
| 105 | + If it is not available, the fallback formula will be used instead. |
| 106 | + Fallback formulas calculate the PV power using the fallback components. |
| 107 | + Fallback formulas are wrapped in `FallbackFormulaMetricFetcher`. |
| 108 | +
|
| 109 | + Args: |
| 110 | + components: The PV components. |
| 111 | +
|
| 112 | + Returns: |
| 113 | + A dictionary mapping primary components to their corresponding |
| 114 | + FallbackFormulaMetricFetcher. |
| 115 | + """ |
| 116 | + fallbacks = self._get_metric_fallback_components(components) |
| 117 | + |
| 118 | + fallback_formulas: dict[ |
| 119 | + Component, FallbackFormulaMetricFetcher[Power] | None |
| 120 | + ] = {} |
| 121 | + for primary_component, fallback_components in fallbacks.items(): |
| 122 | + if len(fallback_components) == 0: |
| 123 | + fallback_formulas[primary_component] = None |
| 124 | + continue |
| 125 | + fallback_ids = [c.component_id for c in fallback_components] |
| 126 | + |
| 127 | + generator = PVPowerFormula( |
| 128 | + f"{self._namespace}_fallback_{fallback_ids}", |
| 129 | + self._channel_registry, |
| 130 | + self._resampler_subscription_sender, |
| 131 | + FormulaGeneratorConfig( |
| 132 | + component_ids=set(fallback_ids), |
| 133 | + allow_fallback=False, |
| 134 | + ), |
| 135 | + ) |
| 136 | + |
| 137 | + fallback_formulas[primary_component] = FallbackFormulaMetricFetcher( |
| 138 | + generator |
| 139 | + ) |
| 140 | + |
| 141 | + return fallback_formulas |
0 commit comments