Skip to content

Commit 58e6bf3

Browse files
committed
Updated TC_DEM_2.10 (Q Quality) Basic structure is there - steps need tidying up and fails due to powerAdjustmentCapability not being updated as expected.
1 parent 7012080 commit 58e6bf3

File tree

1 file changed

+199
-58
lines changed

1 file changed

+199
-58
lines changed

src/python_testing/TC_DEM_2_10.py

+199-58
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import logging
3434
import time
35+
import sys
3536

3637
import chip.clusters as Clusters
3738
from chip.interaction_model import Status
@@ -53,107 +54,247 @@ def desc_TC_DEM_2_10(self) -> str:
5354
def pics_TC_DEM_2_10(self):
5455
"""Return the PICS definitions associated with this test."""
5556
pics = [
56-
# Depends on Feature 05 (ForecastAdjustment) & Feature 02 (StateForecastReporting)
57-
"DEM.S.F05", "DEM.S.F02"
57+
"DEM.S"
5858
]
5959
return pics
6060

6161
def steps_TC_DEM_2_10(self) -> list[TestStep]:
6262
"""Execute the test steps."""
6363
steps = [
64-
TestStep("1", "Commission DUT to TH"),
65-
TestStep("2", "TH reads from the DUT the Featuremap attribute",
66-
"Verify that the DUT response contains the Featuremap attribute. Verify ForecastAdjustment and StateForecastReporting is supported. Verify PowerForecastReporting is not supported."),
64+
TestStep("1", "Commission DUT to TH (can be skipped if done in a preceding test)"),
65+
TestStep("2", "TH reads from the DUT the FeatureMap attribute",
66+
"Verify that the DUT response contains the FeatureMap attribute. Store the value as FeatureMap."),
6767
TestStep("3", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster",
6868
"Value has to be 1 (True)"),
69-
TestStep("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for Forecast Adjustment Test Event",
69+
TestStep("4", "Set up a subscription to the DeviceEnergyManagement cluster, with MinIntervalFloor set to 0, MaxIntervalCeiling set to 10 and KeepSubscriptions set to false",
70+
"Subscription successfully established"),
71+
TestStep("5", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for User Opt-out Test Event Clear",
7072
"Verify DUT responds w/ status SUCCESS(0x00)"),
71-
TestStep("4a", "TH reads from the DUT the ESAState",
72-
"Value has to be 0x01 (Online)"),
73-
TestStep("4b", "TH reads from the DUT the Forecast",
74-
"Value has to include slots[0].MinDurationAdjustment, slots[0].MaxDurationAdjustment"),
75-
TestStep("4c", "TH reads from the DUT the OptOutState",
73+
TestStep("5a", "TH reads from the DUT the OptOutState",
7674
"Value has to be 0x00 (NoOptOut)"),
77-
TestStep("5", "Set up a subscription to the Forecast attribute, with MinIntervalFloor set to 0, MaxIntervalCeiling set to 10 and KeepSubscriptions set to false",
78-
"Subscription successfully established"),
79-
TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for User Opt-out Local Optimization Test Event",
75+
TestStep("6", "If {PICS_S_FA} {featIsNotSupported} skip to step 14",
76+
"Value has to be 0x00 (NoOptOut)"),
77+
78+
TestStep("7", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for Forecast Adjustment Test Event",
8079
"Verify DUT responds w/ status SUCCESS(0x00)"),
81-
TestStep("6a", "TH reads from the DUT the ESAState",
80+
TestStep("7a", "TH reads from the DUT the ESAState",
8281
"Value has to be 0x01 (Online)"),
83-
TestStep("6b", "TH reads from the DUT the OptOutState",
84-
"Value has to be 0x02 (LocalOptOut)"),
85-
TestStep("7", "TH sends command ModifyForecastRequest with ForecastID=Forecast.ForecastID, SlotAdjustments[0].{SlotIndex=0, Duration=Forecast.Slots[0].MinDurationAdjustment}, Cause=GridOptimization",
82+
TestStep("8", "Reset all accumulated report counts, then wait 12 seconds"),
83+
TestStep("9", "TH counts all report transactions with an attribute report for the Forecast attribute",
84+
"TH verifies that numberOfReportsReceived \<= 2"),
85+
TestStep("10", "TH reads from the DUT the Forecast",
86+
"Value has to include slots[0].MinDurationAdjustment, slots[0].MaxDurationAdjustment"),
87+
TestStep("11", "TH sends command ModifyForecastRequest... TODO",
8688
"Verify DUT responds w/ status SUCCESS(0x00)"),
87-
TestStep("8", "TH counts all report transactions with an attribute report for the Forecast attribute over the next Forecast.Slots[0].MinDurationAdjustment}",
88-
"TH verifies that numberOfReportsReceived <= 2 + Forecast.Slots[0].MinDurationAdjustment}"),
89-
TestStep("9", "Cancel the subscription to the Forecast attribute",
90-
"The subscription is cancelled successfully"),
91-
TestStep("10", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for Forecast Adjustment Test Event Clear",
89+
90+
TestStep("12", "TH resets all accumulated report counts, then TH sends command CancelRequest//// TODO"),
91+
92+
TestStep("13", "Wait 5 seconds"),
93+
94+
TestStep("13a", "TH counts all report transactions with an attribute report for the Forecast attribute_",
95+
"TH verifies that numberOfReportsReceived >= 1 and Value has to include ForecastUpdateReason=InternalOptimization in the last attribute report received."),
96+
97+
TestStep("14", "Clear forecast trigger --- TODO"),
98+
TestStep("15", "If {PICS_S_PA} {featIsNotSupported} skip to step 22 - TODO", ""),
99+
TestStep("16", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for Power Adjustment Test Event",
100+
"Verify DUT responds w/ status SUCCESS(0x00)"),
101+
TestStep("16b", "TH reads from the DUT the PowerAdjustmentCapability",
102+
"Value has to include Cause=NoAdjustment."),
103+
TestStep("17", "TH resets all accumulated report counts, then TH sends command PowerAdjustRequest with Power=PowerAdjustmentCapability[0].MaxPower, Duration=20, Cause=LocalOptimization",
104+
"Verify DUT responds w/ status SUCCESS(0x00)"),
105+
TestStep("18", "Wait 12 seconds"),
106+
TestStep("18a", "TH counts all report transactions with an attribute report for the PowerAdjustmentCapability attribute",
107+
"TH verifies that numberOfReportsReceived \<= 2"),
108+
TestStep("19", "TH resets all accumulated report counts, then TH sends command CancelPowerAdjustment",
109+
"Verify DUT responds w/ status SUCCESS(0x00)"),
110+
TestStep("20", "Wait 5 seconds"),
111+
TestStep("20a", "TH counts all report transactions with an attribute report for the PowerAdjustmentCapability attribute",
112+
"TH verifies that numberOfReportsReceived >=1"),
113+
114+
TestStep("21", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.DEM.TESTEVENT_TRIGGERKEY and EventTrigger field set to PIXIT.DEM.TESTEVENTTRIGGER for Power Adjustment Test Event Clear",
92115
"Verify DUT responds w/ status SUCCESS(0x00)"),
116+
TestStep("22", "Cancel the subscription to the Device Energy Management cluster",
117+
"The subscription is cancelled successfully"),
93118
]
94119

95120
return steps
96121

97-
@async_test_body
122+
@ async_test_body
98123
async def test_TC_DEM_2_10(self):
99124
# pylint: disable=too-many-locals, too-many-statements
100125
"""Run the test steps."""
101126
self.step("1")
102127
# Commission DUT - already done
103128

104129
self.step("2")
105-
await self.validate_feature_map([Clusters.DeviceEnergyManagement.Bitmaps.Feature.kForecastAdjustment,
106-
Clusters.DeviceEnergyManagement.Bitmaps.Feature.kStateForecastReporting],
107-
[Clusters.DeviceEnergyManagement.Bitmaps.Feature.kPowerForecastReporting])
130+
feature_map = await self.read_dem_attribute_expect_success(attribute="FeatureMap")
131+
logger.info(f"FeatureMap: {feature_map}")
132+
133+
has_pfr = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kPowerForecastReporting
134+
has_sfr = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kStateForecastReporting
135+
has_pa = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kPowerAdjustment
136+
has_sta = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kStartTimeAdjustment
137+
has_pau = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kPausable
138+
has_fa = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kForecastAdjustment
139+
has_con = feature_map & Clusters.DeviceEnergyManagement.Bitmaps.Feature.kConstraintBasedAdjustment
140+
has_any_forecast_adjustment = has_sta | has_pau | has_fa | has_con
141+
if has_any_forecast_adjustment:
142+
# check it has pfr or sfr (one not both)
143+
asserts.assert_false(has_pfr and has_sfr, "Not allowed to have both PFR and SFR features enabled!")
144+
asserts.assert_true(has_pfr or has_sfr, "Must have either PFR or SFR with a forecast adjustment feature")
145+
146+
if has_pa:
147+
asserts.assert_true(has_pfr, "Since PowerAdjustment is supported, PFR should be used, not SFR")
108148

109149
self.step("3")
110150
await self.check_test_event_triggers_enabled()
111151

112152
self.step("4")
113-
await self.send_test_event_trigger_forecast_adjustment()
153+
sub_handler = ClusterAttributeChangeAccumulator(Clusters.DeviceEnergyManagement)
154+
await sub_handler.start(self.default_controller, self.dut_node_id,
155+
self.matter_test_config.endpoint,
156+
min_interval_sec=0,
157+
max_interval_sec=10, keepSubscriptions=False)
114158

115-
self.step("4a")
116-
await self.check_dem_attribute("ESAState", Clusters.DeviceEnergyManagement.Enums.ESAStateEnum.kOnline)
159+
def accumulate_reports(wait_time):
160+
logging.info(f"Test will now wait {wait_time} seconds to accumulate reports")
161+
time.sleep(wait_time)
117162

118-
self.step("4b")
119-
forecast = await self.read_dem_attribute_expect_success(attribute="Forecast")
120-
asserts.assert_is_not_none(forecast.slots[0].minDurationAdjustment)
121-
asserts.assert_is_not_none(forecast.slots[0].maxDurationAdjustment)
163+
self.step("5")
164+
await self.send_test_event_trigger_user_opt_out_clear_all()
122165

123-
self.step("4c")
166+
self.step("5a")
124167
await self.check_dem_attribute("OptOutState", Clusters.DeviceEnergyManagement.Enums.OptOutStateEnum.kNoOptOut)
125168

126-
self.step("5")
127-
sub_handler = ClusterAttributeChangeAccumulator(Clusters.DeviceEnergyManagement)
128-
await sub_handler.start(self.default_controller, self.dut_node_id, self.matter_test_config.endpoint, keepSubscriptions=False)
129-
sub_handler.reset()
130-
131169
self.step("6")
132-
await self.send_test_event_trigger_user_opt_out_local()
170+
if not has_fa:
171+
self.skip_step("7")
172+
self.skip_step("7a")
173+
self.skip_step("8")
174+
self.skip_step("9")
175+
self.skip_step("10")
176+
self.skip_step("11")
177+
self.skip_step("12")
178+
self.skip_step("13")
179+
self.skip_step("13a")
180+
self.skip_step("14")
181+
else:
182+
self.step("7")
183+
await self.send_test_event_trigger_forecast_adjustment()
133184

134-
self.step("6a")
135-
await self.check_dem_attribute("ESAState", Clusters.DeviceEnergyManagement.Enums.ESAStateEnum.kOnline)
185+
self.step("7a")
186+
await self.check_dem_attribute("ESAState", Clusters.DeviceEnergyManagement.Enums.ESAStateEnum.kOnline)
136187

137-
self.step("6b")
138-
await self.check_dem_attribute("OptOutState", Clusters.DeviceEnergyManagement.Enums.OptOutStateEnum.kLocalOptOut)
188+
self.step("8")
189+
wait = 12 # Wait 12 seconds - the spec says we should only get reports every 10s at most, unless a command changes it
190+
sub_handler.reset()
191+
accumulate_reports(wait)
139192

140-
self.step("7")
141-
slotAdjustments = [Clusters.DeviceEnergyManagement.Structs.SlotAdjustmentStruct(
142-
slotIndex=0, duration=forecast.slots[0].minDurationAdjustment)]
143-
await self.send_modify_forecast_request_command(forecast.forecastID, slotAdjustments, Clusters.DeviceEnergyManagement.Enums.AdjustmentCauseEnum.kGridOptimization, expected_status=Status.Success)
193+
self.step("9")
194+
count = sub_handler.attribute_report_counts[Clusters.DeviceEnergyManagement.Attributes.Forecast]
195+
logging.info(f"Received {count} Forecast updates in {wait} seconds")
196+
asserts.assert_less_equal(count, 2, f"Expected <= 2 Forecast updates in {wait} seconds")
144197

145-
self.step("8")
146-
time.sleep(forecast.slots[0].minDurationAdjustment)
198+
self.step("10")
199+
forecast = await self.read_dem_attribute_expect_success(attribute="Forecast")
200+
asserts.assert_is_not_none(forecast.slots[0].minDurationAdjustment)
201+
asserts.assert_is_not_none(forecast.slots[0].maxDurationAdjustment)
147202

148-
count = sub_handler.attribute_report_counts[Clusters.DeviceEnergyManagement.Attributes.Forecast]
149-
logging.info(f"Number of Forecast updates {count}")
150-
asserts.assert_less_equal(count, 10, "More than 10 reports received")
203+
self.step("11")
204+
if has_pfr:
205+
# we include nominalPower
206+
slotAdjustments = [Clusters.DeviceEnergyManagement.Structs.SlotAdjustmentStruct(
207+
slotIndex=0, duration=forecast.slots[0].maxDurationAdjustment,
208+
nominalPower=forecast.slots[0].minPowerAdjustment)]
209+
else:
210+
# SFR we don't provide nominalPower
211+
slotAdjustments = [Clusters.DeviceEnergyManagement.Structs.SlotAdjustmentStruct(
212+
slotIndex=0, duration=forecast.slots[0].maxDurationAdjustment)]
213+
await self.send_modify_forecast_request_command(forecast.forecastID, slotAdjustments, Clusters.DeviceEnergyManagement.Enums.AdjustmentCauseEnum.kGridOptimization, expected_status=Status.Success)
151214

152-
self.step("9")
153-
await sub_handler.cancel()
215+
self.step("12")
216+
sub_handler.reset()
217+
await self.send_cancel_request_command()
218+
219+
self.step("13")
220+
wait = 5 # We expect a change to the forecast attribute after the cancel, Wait 5 seconds - allow time for the report to come in
221+
accumulate_reports(wait)
222+
223+
self.step("13a")
224+
count = sub_handler.attribute_report_counts[Clusters.DeviceEnergyManagement.Attributes.Forecast]
225+
logging.info(f"Received {count} Forecast updates in {wait} seconds")
226+
asserts.assert_greater_equal(count, 1, "Expected >= 1 Forecast updates after a cancelled operation")
227+
228+
self.step("14")
229+
await self.send_test_event_trigger_forecast_adjustment_clear()
230+
231+
self.step("15")
232+
if not has_pa:
233+
self.skip_step("16")
234+
self.skip_step("16b")
235+
self.skip_step("17")
236+
self.skip_step("18")
237+
self.skip_step("18a")
238+
self.skip_step("19")
239+
self.skip_step("20")
240+
self.skip_step("20a")
241+
self.skip_step("21")
242+
else:
243+
244+
self.step("16")
245+
await self.send_test_event_trigger_power_adjustment()
246+
247+
self.step("16b")
248+
powerAdjustCapabilityStruct = await self.read_dem_attribute_expect_success(attribute="PowerAdjustmentCapability")
249+
asserts.assert_greater_equal(len(powerAdjustCapabilityStruct.powerAdjustCapability), 1)
250+
logging.info(powerAdjustCapabilityStruct)
251+
asserts.assert_equal(powerAdjustCapabilityStruct.cause,
252+
Clusters.DeviceEnergyManagement.Enums.PowerAdjustReasonEnum.kNoAdjustment)
253+
254+
# we should expect powerAdjustCapabilityStruct to have multiple entries with different max powers, min powers, max and min durations
255+
min_power = sys.maxsize
256+
max_power = 0
154257

155-
self.step("10")
156-
await self.send_test_event_trigger_forecast_adjustment_clear()
258+
for entry in powerAdjustCapabilityStruct.powerAdjustCapability:
259+
min_power = min(min_power, entry.minPower)
260+
max_power = max(max_power, entry.maxPower)
261+
262+
result = f"min_power {min_power} max_power {max_power}"
263+
logging.info(result)
264+
265+
self.step("17")
266+
sub_handler.reset()
267+
await self.send_power_adjustment_command(power=powerAdjustCapabilityStruct.powerAdjustCapability[0].maxPower,
268+
duration=20,
269+
cause=Clusters.DeviceEnergyManagement.Enums.AdjustmentCauseEnum.kLocalOptimization)
270+
271+
self.step("18")
272+
wait = 12 # Wait 12 seconds - the spec says we should only get reports every 10s at most, unless a command changes it
273+
accumulate_reports(wait)
274+
275+
self.step("18a")
276+
count = sub_handler.attribute_report_counts[Clusters.DeviceEnergyManagement.Attributes.PowerAdjustmentCapability]
277+
logging.info(f"Received {count} PowerAdjustmentCapability updates in {wait} seconds")
278+
asserts.assert_less_equal(count, 2, f"Expected <= 2 PowerAdjustmentCapability updates in {wait} seconds")
279+
280+
self.step("19")
281+
sub_handler.reset()
282+
await self.send_cancel_power_adjustment_command()
283+
284+
self.step("20")
285+
wait = 5 # We expect a change to the forecast attribute after the cancel, Wait 5 seconds - allow time for the report to come in
286+
accumulate_reports(wait)
287+
288+
self.step("20a")
289+
count = sub_handler.attribute_report_counts[Clusters.DeviceEnergyManagement.Attributes.PowerAdjustmentCapability]
290+
logging.info(f"Received {count} PowerAdjustmentCapability updates in {wait} seconds")
291+
asserts.assert_greater_equal(count, 1, "Expected >= 1 PowerAdjustmentCapability updates after a cancelled operation")
292+
293+
self.step("21")
294+
await self.send_test_event_trigger_power_adjustment_clear()
295+
296+
self.step("22")
297+
await sub_handler.cancel()
157298

158299

159300
if __name__ == "__main__":

0 commit comments

Comments
 (0)