@@ -77,3 +77,69 @@ async def test_pv_power_no_pv_components(self, mocker: MockerFixture) -> None:
7777
7878 await mockgrid .mock_resampler .send_non_existing_component_value ()
7979 assert (await pv_power_receiver .receive ()).value == Power .zero ()
80+
81+ async def test_pv_power_with_failing_meter (self , mocker : MockerFixture ) -> None :
82+ """Test the pv power formula."""
83+ mockgrid = MockMicrogrid (grid_meter = False , mocker = mocker )
84+ mockgrid .add_solar_inverters (2 )
85+
86+ async with mockgrid , AsyncExitStack () as stack :
87+ pv_pool = microgrid .new_pv_pool (priority = 5 )
88+ stack .push_async_callback (pv_pool .stop )
89+ pv_power_receiver = pv_pool .power .new_receiver ()
90+
91+ # Note: PvPowerFormula has a "nones-are-zero" rule, that says:
92+ # * if the meter value is None, it should be treated as None.
93+ # * for other components None is treated as 0.
94+
95+ expected_input_output : list [
96+ tuple [list [float | None ], list [float | None ], Power | None ]
97+ ] = [
98+ # ([meter_power], [pv_inverter_power], expected_power)
99+ #
100+ # Case 1: Both meters are available, so inverters are not used.
101+ ([- 1.0 , - 2.0 ], [None , - 5.0 ], Power .from_watts (- 3.0 )),
102+ ([- 1.0 , - 2.0 ], [- 10.0 , - 20.0 ], Power .from_watts (- 3.0 )),
103+ # Case 2: The first meter is unavailable (None).
104+ # Subscribe to the fallback inverter, but return None as the result,
105+ # according to the "nones-are-zero" rule
106+ ([None , - 2.0 ], [- 10.0 , - 20.0 ], None ),
107+ # Case 3: First meter is unavailable (None). Fallback inverter provides
108+ # a value.
109+ ([None , - 2.0 ], [- 10.0 , - 20.0 ], Power .from_watts (- 12.0 )),
110+ ([None , - 2.0 ], [- 11.0 , - 20.0 ], Power .from_watts (- 13.0 )),
111+ # Case 4: Both first meter and its fallback inverter are unavailable
112+ # (None). Return 0 according to the "nones-are-zero" rule.
113+ ([None , - 2.0 ], [None , - 20.0 ], Power .from_watts (- 2.0 )),
114+ ([None , - 2.0 ], [- 11.0 , - 20.0 ], Power .from_watts (- 13.0 )),
115+ # Case 5: Both meters are unavailable (None).
116+ # Subscribe to the fallback inverter, but return None as the result,
117+ # according "nones-are-zero" rule
118+ ([None , None ], [- 5.0 , - 20.0 ], None ),
119+ # Case 6: Both meters are unavailable (None). Fallback inverter provides
120+ # a values.
121+ ([None , None ], [- 5.0 , - 20.0 ], Power .from_watts (- 25.0 )),
122+ # Case 7: All components are unavailable (None).
123+ # Return 0 according to the "nones-are-zero" rule.
124+ ([None , None ], [None , None ], Power .from_watts (0.0 )),
125+ ([None , None ], [- 5.0 , - 20.0 ], Power .from_watts (- 25.0 )),
126+ # Case 8: Meters becomes available and inverter values are not used.
127+ ([- 10.0 , None ], [- 5.0 , - 20.0 ], Power .from_watts (- 30.0 )),
128+ ([- 10.0 , - 2.0 ], [- 5.0 , - 20.0 ], Power .from_watts (- 12.0 )),
129+ ]
130+
131+ for idx , (meter_power , pv_inverter_power , expected_power ) in enumerate (
132+ expected_input_output
133+ ):
134+ await mockgrid .mock_resampler .send_meter_power (meter_power )
135+ await mockgrid .mock_resampler .send_pv_inverter_power (pv_inverter_power )
136+ mockgrid .mock_resampler .next_ts ()
137+
138+ result = await pv_power_receiver .receive ()
139+ assert result .value == expected_power , (
140+ f"Test case { idx } failed:"
141+ + f" meter_power: { meter_power } "
142+ + f" pv_inverter_power { pv_inverter_power } "
143+ + f" expected_power: { expected_power } "
144+ + f" actual_power: { result .value } "
145+ )
0 commit comments