Skip to content

Commit 4616f83

Browse files
## Automation REFALM_2_2 ## (#37108)
* Proposal for Refalm 2.2 This test verify the basic functionality of the DUT as a server. Added custom manual actions for Dooropen. Implemented Fake Methods to run disallowd commands. Assert Status Attribute. Assert Status attribute from the Notify event. Added TODO for FakeMethods. Issue raised to add method to run arbitraty commands. * Restyled by clang-format * Restyled by autopep8 * Restyled by isort * Fix linting issues * Fix example * Removed not used line. * Added PIXIT.REFALM.AlarmThreshold variable to replace static variable for wait threshold. Added missing copyright. Added missing docstring. * Use variable is_pics_sdk_ci_only, updated prompt text. Updated usage example. * Updated eventcallback using EventChangeCallback. Added expectations to test steps. Removed ci check to command send. Added 32bit value check for State attribute. Reduced tests steps as the test plan. * Updated wait time form 5 to 1 in the CI. Remove not needed sleep. --------- Co-authored-by: Restyled.io <commits@restyled.io>
1 parent 1739c88 commit 4616f83

File tree

2 files changed

+334
-1
lines changed

2 files changed

+334
-1
lines changed

examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp

+47-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <app/EventLogging.h>
2323
#include <app/clusters/general-diagnostics-server/general-diagnostics-server.h>
2424
#include <app/clusters/occupancy-sensor-server/occupancy-sensor-server.h>
25+
#include <app/clusters/refrigerator-alarm-server/refrigerator-alarm-server.h>
2526
#include <app/clusters/smoke-co-alarm-server/smoke-co-alarm-server.h>
2627
#include <app/clusters/software-diagnostics-server/software-diagnostics-server.h>
2728
#include <app/clusters/switch-server/switch-server.h>
@@ -284,6 +285,47 @@ void HandleSimulateLatchPosition(Json::Value & jsonValue)
284285
}
285286
}
286287

288+
/**
289+
* Named pipe handler for simulating a Door Opening.
290+
*
291+
* Usage example:
292+
* echo '{"Name":"SetRefrigeratorDoorStatus", "EndpointId": 1, "DoorOpen": 1}' > /tmp/chip_all_clusters_fifo_1146610
293+
*
294+
* JSON Arguments:
295+
* - "Name": Must be "SetRefrigeratorDoorStatus"
296+
* - "EndpointId": ID of endpoint
297+
* - "DoorOpen": Status of the Door, open or closed.
298+
*
299+
* @param jsonValue - JSON payload from named pipe
300+
*/
301+
void SetRefrigeratorDoorStatusHandler(Json::Value & jsonValue)
302+
{
303+
bool hasEndpointId = HasNumericField(jsonValue, "EndpointId");
304+
bool hasDoorStatus = HasNumericField(jsonValue, "DoorOpen");
305+
306+
if (!hasEndpointId || !hasDoorStatus)
307+
{
308+
std::string inputJson = jsonValue.toStyledString();
309+
ChipLogError(NotSpecified, "Missing or invalid value for one of EndpointId, DoorOpen in %s", inputJson.c_str());
310+
return;
311+
}
312+
// values to update the door status
313+
EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
314+
bool doorStatus = static_cast<bool>(jsonValue["DoorOpen"].asBool());
315+
ChipLogDetail(NotSpecified, "SetRefrigeratorDoorStatusHandler State -> %d.", doorStatus);
316+
if (!doorStatus)
317+
{
318+
RefrigeratorAlarmServer::Instance().SetMaskValue(endpointId, doorStatus);
319+
ChipLogDetail(NotSpecified, "Refrigeratoralarm status updated to :%d", doorStatus);
320+
}
321+
else
322+
{
323+
ChipLogDetail(NotSpecified, "Refrigeratoralarm status updated to :%d", doorStatus);
324+
RefrigeratorAlarmServer::Instance().SetMaskValue(endpointId, doorStatus);
325+
RefrigeratorAlarmServer::Instance().SetStateValue(endpointId, doorStatus);
326+
}
327+
}
328+
287329
/**
288330
* Named pipe handler for simulating switch is idle
289331
*
@@ -509,9 +551,13 @@ void AllClustersAppCommandHandler::HandleCommand(intptr_t context)
509551
ChipLogError(NotSpecified, "Invalid Occupancy state to set.");
510552
}
511553
}
554+
else if (name == "SetRefrigeratorDoorStatus")
555+
{
556+
SetRefrigeratorDoorStatusHandler(self->mJsonValue);
557+
}
512558
else
513559
{
514-
ChipLogError(NotSpecified, "Unhandled command '%s': this hould never happen", name.c_str());
560+
ChipLogError(NotSpecified, "Unhandled command '%s': this should never happen", name.c_str());
515561
VerifyOrDie(false && "Named pipe command not supported, see log above.");
516562
}
517563

src/python_testing/TC_REFALM_2_2.py

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
#
2+
# Copyright (c) 2025 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments
19+
# for details about the block below.
20+
#
21+
# === BEGIN CI TEST ARGUMENTS ===
22+
# test-runner-runs:
23+
# run1:
24+
# app: ${ALL_CLUSTERS_APP}
25+
# factory-reset: true
26+
# quiet: true
27+
# app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json
28+
# script-args: >
29+
# --storage-path admin_storage.json
30+
# --commissioning-method on-network
31+
# --discriminator 1234
32+
# --passcode 20202021
33+
# --PICS src/app/tests/suites/certification/ci-pics-values
34+
# --int-arg PIXIT.REFALM.AlarmThreshold:1
35+
# --trace-to json:${TRACE_TEST_JSON}.json
36+
# --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto
37+
# === END CI TEST ARGUMENTS ===
38+
39+
import json
40+
import logging
41+
import typing
42+
from dataclasses import dataclass
43+
from time import sleep
44+
from typing import Any
45+
46+
import chip.clusters as Clusters
47+
from chip import ChipUtility
48+
from chip.clusters.ClusterObjects import ClusterCommand, ClusterObjectDescriptor, ClusterObjectFieldDescriptor
49+
from chip.interaction_model import InteractionModelError, Status
50+
from chip.testing import matter_asserts
51+
from chip.testing.matter_testing import EventChangeCallback, MatterBaseTest, TestStep, async_test_body, default_matter_test_main
52+
from chip.tlv import uint
53+
from mobly import asserts
54+
55+
logger = logging.getLogger(__name__)
56+
57+
# TODO(#37217) Refactor using generic method.
58+
# Implement fake commands for the RefrigeratorAlarm Cluster
59+
# These comands are Disallowed (X) in the spec.
60+
# When running those commands must return 0x81 (UNSUPPORTED COMMAND)
61+
62+
63+
@dataclass
64+
class FakeReset(ClusterCommand):
65+
cluster_id: typing.ClassVar[int] = 0x00000057
66+
command_id: typing.ClassVar[int] = 0x00000000
67+
is_client: typing.ClassVar[bool] = True
68+
response_type: typing.ClassVar[typing.Optional[str]] = None
69+
70+
@ChipUtility.classproperty
71+
def descriptor(cls) -> ClusterObjectDescriptor:
72+
return ClusterObjectDescriptor(
73+
Fields=[
74+
ClusterObjectFieldDescriptor(Label="alarms", Tag=0, Type=uint),
75+
])
76+
77+
alarms: uint = 0
78+
79+
80+
@dataclass
81+
class FakeModifyEnabledAlarms(ClusterCommand):
82+
cluster_id: typing.ClassVar[int] = 0x00000057
83+
command_id: typing.ClassVar[int] = 0x00000001
84+
is_client: typing.ClassVar[bool] = True
85+
response_type: typing.ClassVar[typing.Optional[str]] = None
86+
87+
@ChipUtility.classproperty
88+
def descriptor(cls) -> ClusterObjectDescriptor:
89+
return ClusterObjectDescriptor(
90+
Fields=[
91+
ClusterObjectFieldDescriptor(Label="mask", Tag=0, Type=uint),
92+
])
93+
94+
mask: uint = 0
95+
96+
97+
class TC_REFALM_2_2(MatterBaseTest):
98+
"""Implementation of test case TC_REFALM_2_2."""
99+
100+
def desc_TC_REFALM_2_2(self) -> str:
101+
return "223.2.2. [TC-REFALM-2.2] Primary functionality with DUT as Server"
102+
103+
def pics_TC_REFALM_2_2(self):
104+
"""Return PICS definitions asscociated with this test."""
105+
pics = [
106+
"REFALM.S"
107+
]
108+
return pics
109+
110+
def steps_TC_REFALM_2_2(self) -> list[TestStep]:
111+
"""Execute the test steps."""
112+
steps = [
113+
TestStep(1, "Commission DUT to TH (can be skipped if done in a preceding test)", is_commissioning=True),
114+
TestStep(2, "Ensure that the door on the DUT is closed"),
115+
TestStep(3, "TH reads from the DUT the State attribute",
116+
"Verify that the DUT response contains a 32-bit value with bit 0 set to 0"),
117+
TestStep(4, "Manually open the door on the DUT"),
118+
TestStep(5, "Wait for the time defined in PIXIT.REFALM.AlarmThreshold"),
119+
TestStep(6, "TH reads from the DUT the State attribute",
120+
"Verify that the DUT response contains a 32-bit value with bit 0 set to 1"),
121+
TestStep(7, "Ensure that the door on the DUT is closed"),
122+
TestStep(8, "TH reads from the DUT the State attribute",
123+
"Verify that the DUT response contains a 32-bit value with bit 0 set to 0"),
124+
TestStep(9, "TH sends Reset command to the DUT", "Verify DUT responds w/ status UNSUPPORTED_COMMAND(0x81)"),
125+
TestStep(10, "TH sends ModifyEnabledAlarms command to the DUT",
126+
"Verify DUT responds w/ status UNSUPPORTED_COMMAND(0x81)"),
127+
TestStep(11, "Set up subscription to the Notify event"),
128+
TestStep(12, "Repeat steps 4 and then 5",
129+
"After step 5 (repeated), receive a Notify event with the State attribute bit 0 set to 1."),
130+
TestStep(13, "Repeat step 7",
131+
"Receive a Notify event with the State attribute bit 0 set to 0."),
132+
]
133+
134+
return steps
135+
136+
async def _get_command_status(self, cmd: ClusterCommand):
137+
"""Return the status of the executed command. By default the status is 0x0 unless a different
138+
status on InteractionModel is returned. For this test we consider the status 0x0 as not succesfull."""
139+
cmd_status = Status.Success
140+
try:
141+
await self.default_controller.SendCommand(nodeid=self.dut_node_id, endpoint=self.endpoint, payload=cmd)
142+
except InteractionModelError as uc:
143+
cmd_status = uc.status
144+
return cmd_status
145+
146+
def _ask_for_closed_door(self):
147+
if self.is_pics_sdk_ci_only:
148+
self._send_close_door_commnad()
149+
else:
150+
user_response = self.wait_for_user_input(prompt_msg="Ensure that the door on the DUT is closed. Enter 'y' or 'n' after completition",
151+
default_value="y")
152+
asserts.assert_equal(user_response.lower(), "y")
153+
154+
def _ask_for_open_door(self):
155+
if self.is_pics_sdk_ci_only:
156+
self._send_open_door_command()
157+
else:
158+
user_response = self.wait_for_user_input(prompt_msg="Manually open the door on the DUT. Enter 'y' or 'n' after completition",
159+
default_value="y")
160+
asserts.assert_equal(user_response.lower(), "y")
161+
162+
async def _read_refalm_state_attribute(self):
163+
cluster = Clusters.Objects.RefrigeratorAlarm
164+
return await self.read_single_attribute_check_success(
165+
endpoint=self.endpoint,
166+
cluster=cluster,
167+
attribute=Clusters.RefrigeratorAlarm.Attributes.State
168+
)
169+
170+
def _wait_thresshold(self):
171+
"""Wait the defined time at the PIXIT.REFALM.AlarmThreshold to trigger it."""
172+
logger.info(f"Sleeping for {self.refalm_threshold_seconds} seconds defined at PIXIT.REFALM.AlarmThreshold")
173+
sleep(self.refalm_threshold_seconds)
174+
175+
def _send_named_pipe_command(self, command_dict: dict[str, Any]):
176+
app_pid = self.matter_test_config.app_pid
177+
if app_pid == 0:
178+
asserts.fail("The --app-pid flag must be set when usage of door state simulation named pipe is required (e.g. CI)")
179+
180+
app_pipe = f"/tmp/chip_all_clusters_fifo_{app_pid}"
181+
command = json.dumps(command_dict)
182+
183+
# Sends an out-of-band command to the sample app
184+
with open(app_pipe, "w") as outfile:
185+
logging.info(f"Sending named pipe command to {app_pipe}: '{command}'")
186+
outfile.write(command + "\n")
187+
# Delay for pipe command to be processed (otherwise tests may be flaky).
188+
sleep(0.1)
189+
190+
def _send_open_door_command(self):
191+
command_dict = {"Name": "SetRefrigeratorDoorStatus", "EndpointId": self.endpoint,
192+
"DoorOpen": Clusters.RefrigeratorAlarm.Bitmaps.AlarmBitmap.kDoorOpen}
193+
self._send_named_pipe_command(command_dict)
194+
195+
def _send_close_door_commnad(self):
196+
command_dict = {"Name": "SetRefrigeratorDoorStatus", "EndpointId": self.endpoint, "DoorOpen": 0}
197+
self._send_named_pipe_command(command_dict)
198+
199+
@async_test_body
200+
async def test_TC_REFALM_2_2(self):
201+
"""Run the test steps."""
202+
self.endpoint = self.get_endpoint(default=1)
203+
cluster = Clusters.RefrigeratorAlarm
204+
logger.info(f"Default endpoint {self.endpoint}")
205+
# Commision the device.
206+
# Read required variables.
207+
self.step(1)
208+
asserts.assert_true('PIXIT.REFALM.AlarmThreshold' in self.matter_test_config.global_test_params, "PIXIT.REFALM.AlarmThreshold must be included on the command line in "
209+
"the --int-arg flag as PIXIT.REFALM.AlarmThreshold:<AlarmThreshold> in seconds.")
210+
self.refalm_threshold_seconds = self.matter_test_config.global_test_params['PIXIT.REFALM.AlarmThreshold']
211+
212+
# check is closed
213+
self.step(2)
214+
self._ask_for_closed_door()
215+
216+
self.step(3)
217+
# reads the state attribute , must be a bitMap32 ( list wich values are 32 bits)
218+
device_state = await self._read_refalm_state_attribute()
219+
logger.info(f"The device state is {device_state}")
220+
matter_asserts.assert_valid_uint32(device_state, "State")
221+
asserts.assert_equal(device_state, 0)
222+
223+
# # open the door manually
224+
self.step(4)
225+
self._ask_for_open_door()
226+
227+
# wait PIXIT.REFALM.AlarmThreshold (1s)
228+
self.step(5)
229+
self._wait_thresshold()
230+
231+
# # read the status
232+
self.step(6)
233+
device_state = await self._read_refalm_state_attribute()
234+
logger.info(f"The device state is {device_state}")
235+
matter_asserts.assert_valid_uint32(device_state, "State")
236+
asserts.assert_equal(device_state, 1)
237+
238+
# # # ensure the door is closed
239+
self.step(7)
240+
self._ask_for_closed_door()
241+
242+
# # read from the state attr
243+
self.step(8)
244+
device_status = await self._read_refalm_state_attribute()
245+
logger.info(f"The device state is {device_state}")
246+
asserts.assert_equal(device_status, 0)
247+
matter_asserts.assert_valid_uint32(device_state, "State")
248+
249+
self.step(9)
250+
cmd_status = await self._get_command_status(cmd=FakeReset())
251+
asserts.assert_equal(Status.UnsupportedCommand, cmd_status,
252+
msg=f"Command status is not {Status.UnsupportedCommand}, is {cmd_status}")
253+
254+
self.step(10)
255+
cmd_status = await self._get_command_status(cmd=FakeModifyEnabledAlarms())
256+
asserts.assert_equal(Status.UnsupportedCommand, cmd_status,
257+
msg=f"Command status is not {Status.UnsupportedCommand}, is {cmd_status}")
258+
259+
# Subscribe to Notify Event
260+
self.step(11)
261+
event_callback = EventChangeCallback(Clusters.RefrigeratorAlarm)
262+
await event_callback.start(self.default_controller,
263+
self.dut_node_id,
264+
self.get_endpoint(1))
265+
266+
self.step(12)
267+
# repeat step 4 and 5
268+
logger.info("Manually open the door on the DUT")
269+
self._ask_for_open_door()
270+
logger.info("Wait for the time defined in PIXIT.REFALM.AlarmThreshold")
271+
self._wait_thresshold()
272+
# Wait for the Notify event with the State value.
273+
event_data = event_callback.wait_for_event_report(cluster.Events.Notify, timeout_sec=5)
274+
logger.info(f"Event data {event_data}")
275+
asserts.assert_equal(event_data.state, 1, "Unexpected value for State returned")
276+
277+
self.step(13)
278+
logger.info("Ensure that the door on the DUT is closed")
279+
self._ask_for_closed_door()
280+
# Wait for the Notify event with the State value.
281+
event_data = event_callback.wait_for_event_report(cluster.Events.Notify, timeout_sec=5)
282+
logger.info(f"Event data {event_data}")
283+
asserts.assert_equal(event_data.state, 0, "Unexpected value for State returned")
284+
285+
286+
if __name__ == "__main__":
287+
default_matter_test_main()

0 commit comments

Comments
 (0)