Skip to content

Commit 8601109

Browse files
Fix test TC-SWTCH-2.2 (project-chip#34655)
* WIP * Fix test TC-SWTCH-2.2 - TC-SWTCH-2.2 is updated from YAML to Python by this PR - TC-SWTCH-2.2 latest test plan steps implemented - Added latching switch simulation to all clusters app - All switch cluster simulation features are now done in all-clusters-app Fixes project-chip#34304 Fixes project-chip#34305 Testing done: - New test passes with all-clusters - Existing tests also pass * Force restyle * Force restyle * Restyled * Add follow-up issue
1 parent 51e47f1 commit 8601109

File tree

3 files changed

+183
-18
lines changed

3 files changed

+183
-18
lines changed

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

+58-3
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ bool HasNumericField(Json::Value & jsonValue, const std::string & field)
6464
*
6565
* JSON Arguments:
6666
* - "Name": Must be "SimulateLongPress"
67-
* - "EndpointId": number of endpoint having a switch cluster
67+
* - "EndpointId": ID of endpoint having a switch cluster
6868
* - "ButtonId": switch position in the switch cluster for "down" button (not idle)
6969
* - "LongPressDelayMillis": Time in milliseconds before the LongPress
7070
* - "LongPressDurationMillis": Total duration in milliseconds from start of the press to LongRelease
@@ -129,7 +129,7 @@ void HandleSimulateLongPress(Json::Value & jsonValue)
129129
*
130130
* JSON Arguments:
131131
* - "Name": Must be "SimulateActionSwitchMultiPress"
132-
* - "EndpointId": number of endpoint having a switch cluster
132+
* - "EndpointId": ID of endpoint having a switch cluster
133133
* - "ButtonId": switch position in the switch cluster for "down" button (not idle)
134134
* - "MultiPressPressedTimeMillis": Pressed time in milliseconds for each press
135135
* - "MultiPressReleasedTimeMillis": Released time in milliseconds after each press
@@ -196,6 +196,56 @@ void HandleSimulateMultiPress(Json::Value & jsonValue)
196196
sButtonSimulatorInstance = std::move(buttonSimulator);
197197
}
198198

199+
/**
200+
* Named pipe handler for simulating a latched switch movement.
201+
*
202+
* Usage example:
203+
* echo '{"Name": "SimulateLatchPosition", "EndpointId": 3, "PositionId": 1}' > /tmp/chip_all_clusters_fifo_1146610
204+
*
205+
* JSON Arguments:
206+
* - "Name": Must be "SimulateLatchPosition"
207+
* - "EndpointId": ID of endpoint having a switch cluster
208+
* - "PositionId": switch position for new CurrentPosition to set in switch cluster
209+
*
210+
* @param jsonValue - JSON payload from named pipe
211+
*/
212+
213+
void HandleSimulateLatchPosition(Json::Value & jsonValue)
214+
{
215+
bool hasEndpointId = HasNumericField(jsonValue, "EndpointId");
216+
bool hasPositionId = HasNumericField(jsonValue, "PositionId");
217+
218+
if (!hasEndpointId || !hasPositionId)
219+
{
220+
std::string inputJson = jsonValue.toStyledString();
221+
ChipLogError(NotSpecified, "Missing or invalid value for one of EndpointId, PositionId in %s", inputJson.c_str());
222+
return;
223+
}
224+
225+
EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
226+
uint8_t positionId = static_cast<uint8_t>(jsonValue["PositionId"].asUInt());
227+
228+
uint8_t previousPositionId = 0;
229+
Protocols::InteractionModel::Status status = Switch::Attributes::CurrentPosition::Get(endpointId, &previousPositionId);
230+
VerifyOrReturn(Protocols::InteractionModel::Status::Success == status,
231+
ChipLogError(NotSpecified, "Failed to get CurrentPosition attribute"));
232+
233+
if (positionId != previousPositionId)
234+
{
235+
status = Switch::Attributes::CurrentPosition::Set(endpointId, positionId);
236+
VerifyOrReturn(Protocols::InteractionModel::Status::Success == status,
237+
ChipLogError(NotSpecified, "Failed to set CurrentPosition attribute"));
238+
ChipLogDetail(NotSpecified, "The latching switch is moved to a new position: %u", static_cast<unsigned>(positionId));
239+
240+
Clusters::SwitchServer::Instance().OnSwitchLatch(endpointId, positionId);
241+
}
242+
else
243+
{
244+
ChipLogDetail(NotSpecified, "Not moving latching switch to a new position, already at %u",
245+
static_cast<unsigned>(positionId));
246+
}
247+
}
248+
199249
} // namespace
200250

201251
AllClustersAppCommandHandler * AllClustersAppCommandHandler::FromJSON(const char * json)
@@ -353,9 +403,14 @@ void AllClustersAppCommandHandler::HandleCommand(intptr_t context)
353403
{
354404
HandleSimulateMultiPress(self->mJsonValue);
355405
}
406+
else if (name == "SimulateLatchPosition")
407+
{
408+
HandleSimulateLatchPosition(self->mJsonValue);
409+
}
356410
else
357411
{
358-
ChipLogError(NotSpecified, "Unhandled command: Should never happens");
412+
ChipLogError(NotSpecified, "Unhandled command '%s': this hould never happen", name.c_str());
413+
VerifyOrDie(false && "Named pipe command not supported, see log above.");
359414
}
360415

361416
exit:

src/python_testing/TC_SWTCH.py

+103-7
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def desc_TC_SWTCH_2_4(self) -> str:
5353
"""Returns a description of this test"""
5454
return "[TC-SWTCH-2.4] Momentary Switch Long Press Verification"
5555

56+
# TODO(#34656): Fill test steps
5657
# def steps_TC_SWTCH_2_4(self) -> list[TestStep]:
5758
# steps = [
5859
# TestStep("0", "Commissioning, already done", is_commissioning=True),
@@ -79,10 +80,6 @@ def _send_named_pipe_command(self, command_dict: dict[str, Any]):
7980
def _use_button_simulator(self) -> bool:
8081
return self.check_pics("PICS_SDK_CI_ONLY") or self.user_params.get("use_button_simulator", False)
8182

82-
def _ask_for_switch_idle(self):
83-
if not self._use_button_simulator():
84-
self.wait_for_user_input(prompt_msg="Ensure switch is idle")
85-
8683
def _send_multi_press_named_pipe_command(self, endpoint_id: int, number_of_presses: int, pressed_position: int, feature_map: uint, multi_press_max: uint):
8784
command_dict = {"Name": 'SimulateMultiPress', "EndpointId": endpoint_id,
8885
"ButtonId": pressed_position, "MultiPressPressedTimeMillis": 500, "MultiPressReleasedTimeMillis": 500,
@@ -94,6 +91,20 @@ def _send_long_press_named_pipe_command(self, endpoint_id: int, pressed_position
9491
"ButtonId": pressed_position, "LongPressDelayMillis": 5000, "LongPressDurationMillis": 5500, "FeatureMap": feature_map}
9592
self._send_named_pipe_command(command_dict)
9693

94+
def _send_latching_switch_named_pipe_command(self, endpoint_id: int, new_position: int):
95+
command_dict = {"Name": "SimulateLatchPosition", "EndpointId": endpoint_id, "PositionId": new_position}
96+
self._send_named_pipe_command(command_dict)
97+
98+
def _ask_for_switch_idle(self):
99+
if not self._use_button_simulator():
100+
self.wait_for_user_input(prompt_msg="Ensure switch is idle")
101+
102+
def _ask_for_switch_position(self, endpoint_id: int, new_position: int):
103+
if not self._use_button_simulator():
104+
self.wait_for_user_input(prompt_msg=f"Move latched switch to position {new_position}, if it is not already there.")
105+
else:
106+
self._send_latching_switch_named_pipe_command(endpoint_id, new_position)
107+
97108
def _ask_for_multi_press_short_long(self, endpoint_id: int, pressed_position: int, feature_map: uint, multi_press_max: uint):
98109
if not self._use_button_simulator():
99110
msg = f"""
@@ -105,7 +116,8 @@ def _ask_for_multi_press_short_long(self, endpoint_id: int, pressed_position: in
105116
self.wait_for_user_input(msg)
106117
else:
107118
# This is just a simulator, ignore the long press instruction for now, it doesn't matter for the CI. It does for cert.
108-
self._send_multi_press_named_pipe_command(endpoint_id, 2, pressed_position, feature_map, multi_press_max)
119+
self._send_multi_press_named_pipe_command(
120+
endpoint_id, number_of_presses=2, pressed_position=pressed_position, feature_map=feature_map, multi_press_max=multi_press_max)
109121

110122
def _ask_for_multi_press_long_short(self, endpoint_id, pressed_position, feature_map: int):
111123
if not self._use_button_simulator():
@@ -153,7 +165,7 @@ def _ask_for_release(self):
153165
time.sleep(self.keep_pressed_delay/1000)
154166

155167
def _placeholder_for_step(self, step_id: str):
156-
# TODO: Global search an replace of `self._placeholder_for_step` with `self.step` when done.
168+
# TODO(#34656): Global search an replace of `self._placeholder_for_step` with `self.step` when done.
157169
logging.info(f"Step {step_id}")
158170
pass
159171

@@ -259,7 +271,7 @@ def _expect_no_events_for_cluster(self, event_queue: queue.Queue, endpoint_id: i
259271

260272
@per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch))
261273
async def test_TC_SWTCH_2_4(self):
262-
# TODO: Make this come from PIXIT
274+
# TODO(#34656): Make this come from PIXIT
263275
switch_pressed_position = 1
264276
post_prompt_settle_delay_seconds = 10.0
265277

@@ -361,6 +373,90 @@ def _received_event(self, event_listener: EventChangeCallback, target_event: Clu
361373
remaining = end_time - datetime.now()
362374
return False
363375

376+
def steps_TC_SWTCH_2_2(self):
377+
return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True),
378+
TestStep(2, "Set up subscription to all events of Switch cluster on the endpoint"),
379+
TestStep(3, "Operator sets switch to first position on the DUT"),
380+
TestStep(4, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"),
381+
TestStep(5, "Operator sets switch to second position (one) on the DUT",
382+
"Verify that the TH receives SwitchLatched event with NewPosition set to 1 from the DUT"),
383+
TestStep(6, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1"),
384+
TestStep(7, "If there are more than 2 positions, test subsequent positions of the DUT"),
385+
TestStep(8, "Operator sets switch to first position on the DUT."),
386+
TestStep(9, "Wait 10 seconds for event reports stable." "Verify that last SwitchLatched event received is for NewPosition 0."),
387+
TestStep(10, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"),
388+
]
389+
390+
@per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kLatchingSwitch))
391+
async def test_TC_SWTCH_2_2(self):
392+
post_prompt_settle_delay_seconds = 10.0
393+
394+
# Step 1: Commissioning - already done
395+
self.step(1)
396+
397+
cluster = Clusters.Switch
398+
endpoint_id = self.matter_test_config.endpoint
399+
400+
# Step 2: Set up subscription to all events of Switch cluster on the endpoint.
401+
self.step(2)
402+
event_listener = EventChangeCallback(cluster)
403+
await event_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id)
404+
405+
# Step 3: Operator sets switch to first position on the DUT.
406+
self.step(3)
407+
self._ask_for_switch_position(endpoint_id, new_position=0)
408+
event_listener.flush_events()
409+
410+
# Step 4: TH reads the CurrentPosition attribute from the DUT.
411+
# Verify that the value is 0.
412+
self.step(4)
413+
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
414+
asserts.assert_equal(button_val, 0, "Switch position value is not 0")
415+
416+
# Step 5: Operator sets switch to second position (one) on the DUT",
417+
# Verify that the TH receives SwitchLatched event with NewPosition set to 1 from the DUT
418+
self.step(5)
419+
expected_switch_position = 1
420+
self._ask_for_switch_position(endpoint_id, expected_switch_position)
421+
422+
data = event_listener.wait_for_event_report(cluster.Events.SwitchLatched, timeout_sec=post_prompt_settle_delay_seconds)
423+
logging.info(f"-> SwitchLatched event last received: {data}")
424+
asserts.assert_equal(data, cluster.Events.SwitchLatched(
425+
newPosition=expected_switch_position), "Did not get expected switch position")
426+
427+
# Step 6: TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1
428+
self.step(6)
429+
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
430+
asserts.assert_equal(button_val, expected_switch_position, f"Switch position is not {expected_switch_position}")
431+
432+
# Step 7: If there are more than 2 positions, test subsequent positions of the DUT
433+
# # TODO(#34656): Implement loop for > 2 total positions
434+
self.skip_step(7)
435+
436+
# Step 8: Operator sets switch to first position on the DUT.
437+
self.step(8)
438+
event_listener.flush_events()
439+
self._ask_for_switch_position(endpoint_id, new_position=0)
440+
441+
# Step 9: Wait 10 seconds for event reports stable.
442+
# Verify that last SwitchLatched event received is for NewPosition 0.
443+
self.step(9)
444+
time.sleep(10.0)
445+
446+
expected_switch_position = 0
447+
last_event = event_listener.get_last_event()
448+
asserts.assert_is_not_none(last_event, "Did not get SwitchLatched events since last operator action.")
449+
last_event_data = last_event.Data
450+
logging.info(f"-> SwitchLatched event last received: {last_event_data}")
451+
asserts.assert_equal(last_event_data, cluster.Events.SwitchLatched(
452+
newPosition=expected_switch_position), "Did not get expected switch position")
453+
454+
# Step 10: TH reads the CurrentPosition attribute from the DUT.
455+
# Verify that the value is 0
456+
self.step(10)
457+
button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition)
458+
asserts.assert_equal(button_val, 0, "Button value is not 0")
459+
364460
def steps_TC_SWTCH_2_3(self):
365461
return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True),
366462
TestStep(2, "Set up subscription to all events of Switch cluster on the endpoint"),

src/python_testing/matter_testing_support.py

+22-8
Original file line numberDiff line numberDiff line change
@@ -257,28 +257,42 @@ def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction):
257257
f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}')
258258
self._q.put(res)
259259

260-
def wait_for_event_report(self, expected_event: ClusterObjects.ClusterEvent, timeoutS: int = 10):
261-
"""This function allows a test script to block waiting for the specific event to arrive with a timeout
262-
(specified in seconds). It returns the event data so that the values can be checked."""
260+
def wait_for_event_report(self, expected_event: ClusterObjects.ClusterEvent, timeout_sec: float = 10.0) -> Any:
261+
"""This function allows a test script to block waiting for the specific event to be the next event
262+
to arrive within a timeout (specified in seconds). It returns the event data so that the values can be checked."""
263263
try:
264-
res = self._q.get(block=True, timeout=timeoutS)
264+
res = self._q.get(block=True, timeout=timeout_sec)
265265
except queue.Empty:
266266
asserts.fail("Failed to receive a report for the event {}".format(expected_event))
267267

268268
asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report")
269269
asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report")
270270
return res.Data
271271

272-
def wait_for_event_expect_no_report(self, timeoutS: int = 10):
273-
"""This function succceeds/returns if an event does not arrive within the timeout specified in seconds.
274-
If an event does arrive, an assert is called."""
272+
def wait_for_event_expect_no_report(self, timeout_sec: float = 10.0):
273+
"""This function returns if an event does not arrive within the timeout specified in seconds.
274+
If any event does arrive, an assert failure occurs."""
275275
try:
276-
res = self._q.get(block=True, timeout=timeoutS)
276+
res = self._q.get(block=True, timeout=timeout_sec)
277277
except queue.Empty:
278278
return
279279

280280
asserts.fail(f"Event reported when not expected {res}")
281281

282+
def get_last_event(self) -> Optional[Any]:
283+
"""Flush entire queue, returning last (newest) event only."""
284+
last_event: Optional[Any] = None
285+
while True:
286+
try:
287+
last_event = self._q.get(block=False)
288+
except queue.Empty:
289+
return last_event
290+
291+
def flush_events(self) -> None:
292+
"""Flush entire queue, returning nothing."""
293+
_ = self.get_last_event()
294+
return
295+
282296
@property
283297
def event_queue(self) -> queue.Queue:
284298
return self._q

0 commit comments

Comments
 (0)