diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index c4a159841dc100..7ffee39eb5101f 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -9384,6 +9384,41 @@ endpoint 3 { ram attribute clusterRevision default = 2; } } +endpoint 4 { + device type ma_genericswitch = 15, version 3; + + + server cluster Identify { + ram attribute identifyTime default = 0x0000; + ram attribute identifyType default = 0x00; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute tagList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster Switch { + ram attribute numberOfPositions default = 2; + ram attribute currentPosition default = 0; + ram attribute multiPressMax default = 3; + ram attribute featureMap default = 30; + ram attribute clusterRevision default = 2; + } +} endpoint 65534 { device type ma_secondary_network_interface = 25, version 1; diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 2ed20d53a62605..9e0452b0c423fc 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -14781,7 +14781,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14797,7 +14797,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14845,7 +14845,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14861,7 +14861,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14877,7 +14877,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14893,7 +14893,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -14909,7 +14909,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -25510,6 +25510,417 @@ }, { "id": 5, + "name": "MA-genericswitch", + "deviceTypeRef": { + "code": 15, + "profileId": 259, + "label": "MA-genericswitch", + "name": "MA-genericswitch" + }, + "deviceTypes": [ + { + "code": 15, + "profileId": 259, + "label": "MA-genericswitch", + "name": "MA-genericswitch" + } + ], + "deviceVersions": [ + 3 + ], + "deviceIdentifiers": [ + 15 + ], + "deviceTypeName": "MA-genericswitch", + "deviceTypeCode": 15, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TagList", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Switch", + "code": 59, + "mfgCode": null, + "define": "SWITCH_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "NumberOfPositions", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CurrentPosition", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "MultiPressMax", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "3", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "30", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 6, "name": "Anonymous Endpoint Type", "deviceTypeRef": { "code": 25, @@ -26050,9 +26461,17 @@ "parentEndpointIdentifier": null }, { - "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeName": "MA-genericswitch", "endpointTypeIndex": 4, "profileId": 259, + "endpointId": 4, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 5, + "profileId": 259, "endpointId": 65534, "networkId": 0, "parentEndpointIdentifier": null diff --git a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp index 3c42eb1dd6a32e..45ab1dac61bebc 100644 --- a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp +++ b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp @@ -56,22 +56,23 @@ bool HasNumericField(Json::Value & jsonValue, const std::string & field) } /** - * Named pipe handler for simulated long press on an action switch. + * Named pipe handler for simulated long press * * Usage example: - * echo '{"Name": "SimulateActionSwitchLongPress", "EndpointId": 3, "ButtonId": 1, "LongPressDelayMillis": 800, + * echo '{"Name": "SimulateLongPress", "EndpointId": 3, "ButtonId": 1, "LongPressDelayMillis": 800, * "LongPressDurationMillis": 1000}' > /tmp/chip_all_clusters_fifo_1146610 * * JSON Arguments: - * - "Name": Must be "SimulateActionSwitchLongPress" + * - "Name": Must be "SimulateLongPress" * - "EndpointId": number of endpoint having a switch cluster * - "ButtonId": switch position in the switch cluster for "down" button (not idle) * - "LongPressDelayMillis": Time in milliseconds before the LongPress * - "LongPressDurationMillis": Total duration in milliseconds from start of the press to LongRelease + * - "FeatureMap": The feature map to simulate * * @param jsonValue - JSON payload from named pipe */ -void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue) +void HandleSimulateLongPress(Json::Value & jsonValue) { if (sButtonSimulatorInstance != nullptr) { @@ -83,13 +84,14 @@ void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue) bool hasButtonId = HasNumericField(jsonValue, "ButtonId"); bool hasLongPressDelayMillis = HasNumericField(jsonValue, "LongPressDelayMillis"); bool hasLongPressDurationMillis = HasNumericField(jsonValue, "LongPressDurationMillis"); - if (!hasEndpointId || !hasButtonId || !hasLongPressDelayMillis || !hasLongPressDurationMillis) + bool hasFeatureMap = HasNumericField(jsonValue, "FeatureMap"); + if (!hasEndpointId || !hasButtonId || !hasLongPressDelayMillis || !hasLongPressDurationMillis || !hasFeatureMap) { std::string inputJson = jsonValue.toStyledString(); - ChipLogError( - NotSpecified, - "Missing or invalid value for one of EndpointId, ButtonId, LongPressDelayMillis or LongPressDurationMillis in %s", - inputJson.c_str()); + ChipLogError(NotSpecified, + "Missing or invalid value for one of EndpointId, ButtonId, LongPressDelayMillis, LongPressDurationMillis or " + "FeatureMap in %s", + inputJson.c_str()); return; } @@ -97,6 +99,7 @@ void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue) uint8_t buttonId = static_cast(jsonValue["ButtonId"].asUInt()); System::Clock::Milliseconds32 longPressDelayMillis{ static_cast(jsonValue["LongPressDelayMillis"].asUInt()) }; System::Clock::Milliseconds32 longPressDurationMillis{ static_cast(jsonValue["LongPressDurationMillis"].asUInt()) }; + uint32_t featureMap = static_cast(jsonValue["FeatureMap"].asUInt()); auto buttonSimulator = std::make_unique(); bool success = buttonSimulator->SetMode(ButtonEventsSimulator::Mode::kModeLongPress) @@ -105,6 +108,7 @@ void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue) .SetIdleButtonId(0) .SetPressedButtonId(buttonId) .SetEndpointId(endpointId) + .SetFeatureMap(featureMap) .Execute([]() { sButtonSimulatorInstance.reset(); }); if (!success) @@ -117,11 +121,11 @@ void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue) } /** - * Named pipe handler for simulated multi-press on an action switch. + * Named pipe handler for simulated multi-press. * * Usage example: - * echo '{"Name": "SimulateActionSwitchMultiPress", "EndpointId": 3, "ButtonId": 1, "MultiPressPressedTimeMillis": 100, - * "MultiPressReleasedTimeMillis": 350, "MultiPressNumPresses": 2}' > /tmp/chip_all_clusters_fifo_1146610 + * echo '{"Name": "SimulateMultiPress", "EndpointId": 3, "ButtonId": 1, "MultiPressPressedTimeMillis": 100, + * "MultiPressReleasedTimeMillis": 350, "MultiPressNumPresses": 2, "FeatureMap": 58}' > /tmp/chip_all_clusters_fifo_1146610 * * JSON Arguments: * - "Name": Must be "SimulateActionSwitchMultiPress" @@ -130,10 +134,12 @@ void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue) * - "MultiPressPressedTimeMillis": Pressed time in milliseconds for each press * - "MultiPressReleasedTimeMillis": Released time in milliseconds after each press * - "MultiPressNumPresses": Number of presses to simulate + * - "FeatureMap": The feature map to simulate + * - "MultiPressMax": max number of presses (from attribute). * * @param jsonValue - JSON payload from named pipe */ -void HandleSimulateActionSwitchMultiPress(Json::Value & jsonValue) +void HandleSimulateMultiPress(Json::Value & jsonValue) { if (sButtonSimulatorInstance != nullptr) { @@ -146,13 +152,15 @@ void HandleSimulateActionSwitchMultiPress(Json::Value & jsonValue) bool hasMultiPressPressedTimeMillis = HasNumericField(jsonValue, "MultiPressPressedTimeMillis"); bool hasMultiPressReleasedTimeMillis = HasNumericField(jsonValue, "MultiPressReleasedTimeMillis"); bool hasMultiPressNumPresses = HasNumericField(jsonValue, "MultiPressNumPresses"); + bool hasFeatureMap = HasNumericField(jsonValue, "FeatureMap"); + bool hasMultiPressMax = HasNumericField(jsonValue, "MultiPressMax"); if (!hasEndpointId || !hasButtonId || !hasMultiPressPressedTimeMillis || !hasMultiPressReleasedTimeMillis || - !hasMultiPressNumPresses) + !hasMultiPressNumPresses || !hasFeatureMap || !hasMultiPressMax) { std::string inputJson = jsonValue.toStyledString(); ChipLogError(NotSpecified, "Missing or invalid value for one of EndpointId, ButtonId, MultiPressPressedTimeMillis, " - "MultiPressReleasedTimeMillis or MultiPressNumPresses in %s", + "MultiPressReleasedTimeMillis, MultiPressNumPresses, FeatureMap or MultiPressMax in %s", inputJson.c_str()); return; } @@ -164,6 +172,8 @@ void HandleSimulateActionSwitchMultiPress(Json::Value & jsonValue) System::Clock::Milliseconds32 multiPressReleasedTimeMillis{ static_cast( jsonValue["MultiPressReleasedTimeMillis"].asUInt()) }; uint8_t multiPressNumPresses = static_cast(jsonValue["MultiPressNumPresses"].asUInt()); + uint32_t featureMap = static_cast(jsonValue["FeatureMap"].asUInt()); + uint8_t multiPressMax = static_cast(jsonValue["MultiPressMax"].asUInt()); auto buttonSimulator = std::make_unique(); bool success = buttonSimulator->SetMode(ButtonEventsSimulator::Mode::kModeMultiPress) @@ -173,6 +183,8 @@ void HandleSimulateActionSwitchMultiPress(Json::Value & jsonValue) .SetIdleButtonId(0) .SetPressedButtonId(buttonId) .SetEndpointId(endpointId) + .SetFeatureMap(featureMap) + .SetMultiPressMax(multiPressMax) .Execute([]() { sButtonSimulatorInstance.reset(); }); if (!success) @@ -333,13 +345,13 @@ void AllClustersAppCommandHandler::HandleCommand(intptr_t context) std::string operation = self->mJsonValue["Operation"].asString(); self->OnOperationalStateChange(device, operation, self->mJsonValue["Param"]); } - else if (name == "SimulateActionSwitchLongPress") + else if (name == "SimulateLongPress") { - HandleSimulateActionSwitchLongPress(self->mJsonValue); + HandleSimulateLongPress(self->mJsonValue); } - else if (name == "SimulateActionSwitchMultiPress") + else if (name == "SimulateMultiPress") { - HandleSimulateActionSwitchMultiPress(self->mJsonValue); + HandleSimulateMultiPress(self->mJsonValue); } else { diff --git a/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp b/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp index 44bf5657f5c2f6..53a08672fdbc07 100644 --- a/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp +++ b/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp @@ -114,6 +114,42 @@ void EmitMultiPressComplete(EndpointId endpointId, uint8_t previousPosition, uin } } +void EmitShortRelease(EndpointId endpointId, uint8_t previousPosition) +{ + Clusters::Switch::Events::ShortRelease::Type event{}; + event.previousPosition = previousPosition; + EventNumber eventNumber = 0; + + CHIP_ERROR err = LogEvent(event, endpointId, eventNumber); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to log ShortRelease event: %" CHIP_ERROR_FORMAT, err.Format()); + } + else + { + ChipLogProgress(NotSpecified, "Logged ShortRelease on Endpoint %u", static_cast(endpointId)); + } +} + +void EmitMultiPressOngoing(EndpointId endpointId, uint8_t newPosition, uint8_t count) +{ + Clusters::Switch::Events::MultiPressOngoing::Type event{}; + event.newPosition = newPosition; + event.currentNumberOfPressesCounted = count; + EventNumber eventNumber = 0; + + CHIP_ERROR err = LogEvent(event, endpointId, eventNumber); + if (err != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to log MultiPressOngoing event: %" CHIP_ERROR_FORMAT, err.Format()); + } + else + { + ChipLogProgress(NotSpecified, "Logged MultiPressOngoing on Endpoint %u position %u, count %u", + static_cast(endpointId), newPosition, count); + } +} + } // namespace void ButtonEventsSimulator::OnTimerDone(System::Layer * layer, void * appState) @@ -186,19 +222,65 @@ void ButtonEventsSimulator::Next() } case ButtonEventsSimulator::State::kEmitLongRelease: { SetButtonPosition(mEndpointId, mIdleButtonId); - EmitLongRelease(mEndpointId, mPressedButtonId); + if (mFeatureMap & static_cast(Clusters::Switch::Feature::kMomentarySwitchLongPress)) + { + EmitLongRelease(mEndpointId, mPressedButtonId); + } + else if (mFeatureMap & static_cast(Clusters::Switch::Feature::kMomentarySwitchRelease)) + { + EmitShortRelease(mEndpointId, mPressedButtonId); + } SetState(ButtonEventsSimulator::State::kIdle); mDoneCallback(); break; } case ButtonEventsSimulator::State::kEmitStartOfMultiPress: { EmitInitialPress(mEndpointId, mPressedButtonId); - StartTimer(mMultiPressNumPresses * (mMultiPressPressedTimeMillis + mMultiPressReleasedTimeMillis)); - SetState(ButtonEventsSimulator::State::kEmitEndOfMultiPress); + if (mFeatureMap & static_cast(Clusters::Switch::Feature::kActionSwitch)) + { + StartTimer(mMultiPressNumPresses * (mMultiPressPressedTimeMillis + mMultiPressReleasedTimeMillis)); + SetState(ButtonEventsSimulator::State::kEmitEndOfMultiPress); + } + else + { + SetState(ButtonEventsSimulator::State::kMultiPressButtonRelease); + StartTimer(mMultiPressPressedTimeMillis); + } break; } + case ButtonEventsSimulator::State::kMultiPressButtonRelease: { + ++mMultiPressPressesDone; + if (mMultiPressPressesDone > 1) + { + EmitMultiPressOngoing(mEndpointId, mPressedButtonId, mMultiPressPressesDone); + } + + if (mMultiPressPressesDone == mMultiPressNumPresses) + { + SetState(ButtonEventsSimulator::State::kEmitEndOfMultiPress); + } + else + { + SetState(ButtonEventsSimulator::State::kEmitStartOfMultiPress); + } + + if (mFeatureMap & static_cast(Clusters::Switch::Feature::kMomentarySwitchRelease)) + { + EmitShortRelease(mEndpointId, mPressedButtonId); + } + StartTimer(mMultiPressReleasedTimeMillis); + break; + } + case ButtonEventsSimulator::State::kEmitEndOfMultiPress: { - EmitMultiPressComplete(mEndpointId, mPressedButtonId, mMultiPressNumPresses); + if (mFeatureMap & static_cast(Clusters::Switch::Feature::kActionSwitch) && mMultiPressNumPresses > mMultiPressMax) + { + EmitMultiPressComplete(mEndpointId, mPressedButtonId, 0); + } + else + { + EmitMultiPressComplete(mEndpointId, mPressedButtonId, mMultiPressNumPresses); + } SetState(ButtonEventsSimulator::State::kIdle); mDoneCallback(); break; diff --git a/examples/all-clusters-app/linux/ButtonEventsSimulator.h b/examples/all-clusters-app/linux/ButtonEventsSimulator.h index 658da98f14fefd..539b2010099bd6 100644 --- a/examples/all-clusters-app/linux/ButtonEventsSimulator.h +++ b/examples/all-clusters-app/linux/ButtonEventsSimulator.h @@ -42,7 +42,8 @@ class ButtonEventsSimulator enum class Mode { kModeLongPress, - kModeMultiPress + kModeMultiPress, + kModeMultiPressNonAs }; using DoneCallback = std::function; @@ -107,6 +108,18 @@ class ButtonEventsSimulator return *this; } + ButtonEventsSimulator & SetFeatureMap(uint32_t featureMap) + { + mFeatureMap = featureMap; + return *this; + } + + ButtonEventsSimulator & SetMultiPressMax(uint8_t multiPressMax) + { + mMultiPressMax = multiPressMax; + return *this; + } + private: enum class State { @@ -118,6 +131,8 @@ class ButtonEventsSimulator kEmitStartOfMultiPress = 4, kEmitEndOfMultiPress = 5, + + kMultiPressButtonRelease = 6, }; static void OnTimerDone(System::Layer * layer, void * appState); @@ -131,9 +146,12 @@ class ButtonEventsSimulator System::Clock::Milliseconds32 mMultiPressPressedTimeMillis{}; System::Clock::Milliseconds32 mMultiPressReleasedTimeMillis{}; uint8_t mMultiPressNumPresses{ 1 }; + uint8_t mMultiPressPressesDone{ 0 }; uint8_t mIdleButtonId{ 0 }; uint8_t mPressedButtonId{ 1 }; EndpointId mEndpointId{ 1 }; + uint32_t mFeatureMap{ 0 }; + uint8_t mMultiPressMax{ 0 }; Mode mMode{ Mode::kModeLongPress }; State mState{ State::kIdle }; diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index 1d1799ece3581b..ddbcaee06b889c 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -80,13 +80,19 @@ Clusters::ValveConfigurationAndControl::ValveControlDelegate sValveDelegate; Clusters::TimeSynchronization::ExtendedTimeSyncDelegate sTimeSyncDelegate; // Please refer to https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/namespaces -constexpr const uint8_t kNamespaceCommon = 7; +constexpr const uint8_t kNamespaceCommon = 7; +constexpr const uint8_t kNamespaceSwitches = 0x43; + // Common Number Namespace: 7, tag 0 (Zero) constexpr const uint8_t kTagCommonZero = 0; // Common Number Namespace: 7, tag 1 (One) constexpr const uint8_t kTagCommonOne = 1; // Common Number Namespace: 7, tag 2 (Two) constexpr const uint8_t kTagCommonTwo = 2; +// Switches namespace: 0x43, tag = 0x03 (Up) +constexpr const uint8_t kTagSwitchesUp = 0x03; +// Switches namespace: 0x43, tag = 0x04 (Down) +constexpr const uint8_t kTagSwitchesDown = 0x04; constexpr const uint8_t kNamespacePosition = 8; // Common Position Namespace: 8, tag: 0 (Left) @@ -104,6 +110,11 @@ const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp1TagList[] = { const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp2TagList[] = { { .namespaceID = kNamespaceCommon, .tag = kTagCommonTwo }, { .namespaceID = kNamespacePosition, .tag = kTagPositionRight } }; +// Endpoints 3 and 4 are an action switch and a non-action switch. On the device, they're tagged as up and down because why not. +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp3TagList[] = { { .namespaceID = kNamespaceSwitches, + .tag = kTagSwitchesUp } }; +const Clusters::Descriptor::Structs::SemanticTagStruct::Type gEp4TagList[] = { { .namespaceID = kNamespaceSwitches, + .tag = kTagSwitchesDown } }; } // namespace #ifdef MATTER_DM_PLUGIN_DISHWASHER_ALARM_SERVER @@ -238,6 +249,8 @@ void ApplicationInit() SetTagList(/* endpoint= */ 0, Span(gEp0TagList)); SetTagList(/* endpoint= */ 1, Span(gEp1TagList)); SetTagList(/* endpoint= */ 2, Span(gEp2TagList)); + SetTagList(/* endpoint= */ 3, Span(gEp3TagList)); + SetTagList(/* endpoint= */ 4, Span(gEp4TagList)); } void ApplicationShutdown() diff --git a/src/app/tests/suites/TestDescriptorCluster.yaml b/src/app/tests/suites/TestDescriptorCluster.yaml index 21a43fbe3c1cd0..39179e1143bd75 100644 --- a/src/app/tests/suites/TestDescriptorCluster.yaml +++ b/src/app/tests/suites/TestDescriptorCluster.yaml @@ -82,7 +82,7 @@ tests: command: "readAttribute" attribute: "PartsList" response: - value: [1, 2, 3] + value: [1, 2, 3, 4] - label: "Read attribute ClusterRevision" command: "readAttribute" diff --git a/src/python_testing/TC_SWTCH.py b/src/python_testing/TC_SWTCH.py index 5fb968b724e098..926379804eb8e3 100644 --- a/src/python_testing/TC_SWTCH.py +++ b/src/python_testing/TC_SWTCH.py @@ -23,7 +23,7 @@ # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json -# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --endpoint 3 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto --PICS src/app/tests/suites/certification/ci-pics-values # === END CI TEST ARGUMENTS === import json @@ -37,8 +37,9 @@ import test_plan_support from chip.clusters import ClusterObjects as ClusterObjects from chip.clusters.Attribute import EventReadResult, TypedAttributePath +from chip.tlv import uint from matter_testing_support import (AttributeValue, ClusterAttributeChangeAccumulator, EventChangeCallback, MatterBaseTest, - TestStep, async_test_body, default_matter_test_main) + TestStep, default_matter_test_main, has_feature, per_endpoint_test) from mobly import asserts logger = logging.getLogger(__name__) @@ -52,10 +53,6 @@ def desc_TC_SWTCH_2_4(self) -> str: """Returns a description of this test""" return "[TC-SWTCH-2.4] Momentary Switch Long Press Verification" - def pics_TC_SWTCH_2_4(self): - """ This function returns a list of PICS for this test case that must be True for the test to be run""" - return ["SWTCH.S", "SWTCH.S.F01"] - # def steps_TC_SWTCH_2_4(self) -> list[TestStep]: # steps = [ # TestStep("0", "Commissioning, already done", is_commissioning=True), @@ -86,24 +83,65 @@ def _ask_for_switch_idle(self): if not self._use_button_simulator(): self.wait_for_user_input(prompt_msg="Ensure switch is idle") - def _ask_for_long_press(self, endpoint_id: int, pressed_position: int): + 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): + command_dict = {"Name": 'SimulateMultiPress', "EndpointId": endpoint_id, + "ButtonId": pressed_position, "MultiPressPressedTimeMillis": 500, "MultiPressReleasedTimeMillis": 500, + "MultiPressNumPresses": number_of_presses, "FeatureMap": feature_map, "MultiPressMax": multi_press_max} + self._send_named_pipe_command(command_dict) + + def _send_long_press_named_pipe_command(self, endpoint_id: int, pressed_position: int, feature_map: int): + command_dict = {"Name": "SimulateLongPress", "EndpointId": endpoint_id, + "ButtonId": pressed_position, "LongPressDelayMillis": 5000, "LongPressDurationMillis": 5500, "FeatureMap": feature_map} + self._send_named_pipe_command(command_dict) + + def _ask_for_multi_press_short_long(self, endpoint_id: int, pressed_position: int, feature_map: uint, multi_press_max: uint): + if not self._use_button_simulator(): + msg = f""" + Actuate the switch in the following sequence: + 1. Operate switch (press briefly) associated with position {pressed_position} on the DUT then release switch from DUT + 2. Operate switch (keep pressed for long time, e.g. 5 seconds) on the DUT immediately after the previous step + 3. Release switch from the DUT + """ + self.wait_for_user_input(msg) + else: + # This is just a simulator, ignore the long press instruction for now, it doesn't matter for the CI. It does for cert. + self._send_multi_press_named_pipe_command(endpoint_id, 2, pressed_position, feature_map, multi_press_max) + + def _ask_for_multi_press_long_short(self, endpoint_id, pressed_position, feature_map: int): + if not self._use_button_simulator(): + msg = f""" + Actuate the switch in the following sequence: + 1. Operate switch (keep pressed for long time, e.g. 5 seconds) on the DUT + 2. Releases switch from the DUT + 3. Immediately after the previous step completes, operate switch (press briefly) associated with position {pressed_position} on the DUT then release switch from DUT + """ + self.wait_for_user_input(msg) + else: + # This is just the start of the sequence + # we'll need to send the short press after getting the LongRelease event because the simulator doesn't queue requests. + self._send_long_press_named_pipe_command(endpoint_id, pressed_position, feature_map) + + def _ask_for_multi_press(self, endpoint_id: int, number_of_presses: int, pressed_position: int, feature_map: uint, multi_press_max: uint): + if not self._use_button_simulator(): + self.wait_for_user_input( + f'Operate the switch (press briefly) associated with position {pressed_position} then release {number_of_presses} times') + else: + self._send_multi_press_named_pipe_command(endpoint_id, number_of_presses, + pressed_position, feature_map, multi_press_max) + + def _ask_for_long_press(self, endpoint_id: int, pressed_position: int, feature_map): if not self._use_button_simulator(): self.wait_for_user_input( prompt_msg=f"Press switch position {pressed_position} for a long time (around 5 seconds) on the DUT, then release it.") else: - command_dict = {"Name": "SimulateActionSwitchLongPress", "EndpointId": endpoint_id, - "ButtonId": pressed_position, "LongPressDelayMillis": 5000, "LongPressDurationMillis": 5500} - self._send_named_pipe_command(command_dict) + self._send_long_press_named_pipe_command(endpoint_id, pressed_position, feature_map) - def _ask_for_keep_pressed(self, endpoint_id: int, pressed_position: int): + def _ask_for_keep_pressed(self, endpoint_id: int, pressed_position: int, feature_map: int): if not self._use_button_simulator(): self.wait_for_user_input( prompt_msg=f"Press switch position {pressed_position} for a long time (around 5 seconds) on the DUT, then release it.") else: - # Using the long press here with a long duration so we can check the intermediate value. - command_dict = {"Name": "SimulateActionSwitchLongPress", "EndpointId": endpoint_id, - "ButtonId": pressed_position, "LongPressDelayMillis": 0, "LongPressDurationMillis": self.keep_pressed_delay} - self._send_named_pipe_command(command_dict) + self._send_long_press_named_pipe_command(endpoint_id, pressed_position, feature_map) def _ask_for_release(self): # Since we used a long press for this, "ask for release" on the button simulator just means waiting out the delay @@ -219,7 +257,7 @@ def _expect_no_events_for_cluster(self, event_queue: queue.Queue, endpoint_id: i logging.info(f"Successfully waited for no further events on {expected_cluster} for {elapsed:.1f} seconds") - @async_test_body + @per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch)) async def test_TC_SWTCH_2_4(self): # TODO: Make this come from PIXIT switch_pressed_position = 1 @@ -263,7 +301,7 @@ async def test_TC_SWTCH_2_4(self): # Step 4a: Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT, the release it self._placeholder_for_step("4a") - self._ask_for_long_press(endpoint_id, switch_pressed_position) + self._ask_for_long_press(endpoint_id, switch_pressed_position, feature_map) # Step 4b: TH expects report of CurrentPosition 1, followed by a report of Current Position 0. self._placeholder_for_step("4b") @@ -323,9 +361,6 @@ def _received_event(self, event_listener: EventChangeCallback, target_event: Clu remaining = end_time - datetime.now() return False - def pics_TC_SWTCH_2_3(self): - return ['SWTCH.S.F01'] - def steps_TC_SWTCH_2_3(self): return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True), TestStep(2, "Set up subscription to all events of Switch cluster on the endpoint"), @@ -335,13 +370,14 @@ def steps_TC_SWTCH_2_3(self): "Verify that the TH receives InitialPress event with NewPosition set to 1 on the DUT"), TestStep(6, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 1"), TestStep(7, "Operator releases switch on the DUT"), - TestStep("8a", "If the DUT implements the MSR feature, verify that the TH receives ShortRelease event with NewPosition set to 0 on the DUT", "Event received"), + TestStep("8a", "If the DUT implements the MSR feature and does not implement the MSL feature, verify that the TH receives ShortRelease event with NewPosition set to 0 on the DUT", "Event received"), + TestStep("8b", "If the DUT implements the MSR feature and the MSL feature, verify that the TH receives LongRelease event with NewPosition set to 0 on the DUT", "Event received"), TestStep( - "8b", "If the DUT implements the AS feature, verify that the TH does not receive ShortRelease event on the DUT", "No event received"), + "8c", "If the DUT implements the AS feature, verify that the TH does not receive ShortRelease event on the DUT", "No event received"), TestStep(9, "TH reads the CurrentPosition attribute from the DUT", "Verify that the value is 0"), ] - @async_test_body + @per_endpoint_test(has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitch)) async def test_TC_SWTCH_2_3(self): # Commissioning - already done self.step(1) @@ -349,6 +385,7 @@ async def test_TC_SWTCH_2_3(self): feature_map = await self.read_single_attribute_check_success(cluster, attribute=cluster.Attributes.FeatureMap) has_msr_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchRelease) != 0 + has_msl_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchLongPress) != 0 has_as_feature = (feature_map & cluster.Bitmaps.Feature.kActionSwitch) != 0 endpoint_id = self.matter_test_config.endpoint @@ -369,7 +406,7 @@ async def test_TC_SWTCH_2_3(self): # This is 1s larger than the subscription ceiling self.keep_pressed_delay = 6000 self.pressed_position = 1 - self._ask_for_keep_pressed(endpoint_id, self.pressed_position) + self._ask_for_keep_pressed(endpoint_id, self.pressed_position, feature_map) event_listener.wait_for_event_report(cluster.Events.InitialPress) self.step(6) @@ -380,13 +417,18 @@ async def test_TC_SWTCH_2_3(self): self._ask_for_release() self.step("8a") - if has_msr_feature: + if has_msr_feature and not has_msl_feature: asserts.assert_true(self._received_event(event_listener, cluster.Events.ShortRelease, 10), "Did not receive short release") else: self.mark_current_step_skipped() self.step("8b") + if has_msr_feature and has_msl_feature: + asserts.assert_true(self._received_event(event_listener, cluster.Events.LongRelease, 10), + "Did not receive long release") + + self.step("8c") if has_as_feature: asserts.assert_false(self._received_event(event_listener, cluster.Events.ShortRelease, 10), "Received short release") else: @@ -396,6 +438,355 @@ async def test_TC_SWTCH_2_3(self): button_val = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.CurrentPosition) asserts.assert_equal(button_val, 0, "Button value is not 0") + def steps_TC_SWTCH_2_5(self): + return [TestStep(1, test_plan_support.commission_if_required(), "", is_commissioning=True), + TestStep(2, "Set up a subscription to all Switch cluster events"), + TestStep(3, "Operate does not operate the switch on the DUT"), + TestStep("4a", "Operator operates switch (press briefly) associated with position 1 on the DUT then release switch from DUT", + """ + + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + """), + TestStep("4b", "Operator does not operate switch on the DUT", + "TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 1 from the DUT"), + TestStep("5a", "Operator repeat step 4a 2 times quickly", + """ + + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives MultiPressOngoing event with NewPosition set to 1 and CurrentNumberOfPressesCounted set to 2 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + + + The events sequence SHALL follow the same sequence as above + """), + TestStep("5b", "Operator does not operate switch on the DUT", + "Verify that the TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 2 from the DUT"), + TestStep("6a", "If MultiPressMax == 2 (see 2c of TC-SWTCH-2.1), skip steps 6b .. 6c"), + TestStep("6b", "Operator repeat step 4a 3 times quickly", + """ + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives MultiPressOngoing event with NewPosition set to 1 and CurrentNumberOfPressesCounted set to 2 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives MultiPressOngoing event with NewPosition set to 1 and CurrentNumberOfPressesCounted set to 3 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + + + The events sequence from the subscription SHALL follow the same sequence as expressed above, in the exact order of events specified. + """), + TestStep("6c", "Operator does not operate switch on the DUT for 5 seconds", + "Verify that the TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 3 from the DUT"), + TestStep(7, "Set up subscription to all Switch cluster events"), + TestStep("8a", + """ + Operator operates switch in below sequence: + 1. Operator operates switch (press briefly) associated with position 1 on the DUT then release switch from DUT + 2. Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT immediately after the previous step + 3. Operator releases switch from the DUT + """, + """ + + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + + * Verify that the TH receives MultiPressOngoing event with NewPosition set to 1 and CurrentNumberOfPressesCounted set to 2 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH does not receive LongPress event from the DUT + * Verify that the TH does not receive LongRelease event from the DUT + + The events sequence from the subscription SHALL follow the same sequence as expressed above, in the exact order of events specified. + """), + TestStep("8b", "Operator does not operate switch on the DUT", + "TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 2 from the DUT"), + TestStep("9a", + """ + Operator operates switch in below sequence: + 1. Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT + 2. Operator releases switch from the DUT + 3. Immediately after the previous step completes, Operator operates switch (press briefly) associated with position 1 on the DUT then release switch from DUT + """, + """ + + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives (one, not more than one) LongPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives LongRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives ShortRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH does not receive MultiPressOngoing event from the DUT + + The events sequence from the subscription SHALL follow the same sequence as expressed above, in the exact order of events specified. + """), + TestStep("9b", "Operator does not operate switch on the DUT", + "TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 2 from the DUT") + + ] + + @staticmethod + def should_run_SWTCH_2_5(wildcard, endpoint): + msm = has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitchMultiPress) + asf = has_feature(Clusters.Switch, 0x20) + return msm(wildcard, endpoint) and not asf(wildcard, endpoint) + + @per_endpoint_test(should_run_SWTCH_2_5) + async def test_TC_SWTCH_2_5(self): + # Commissioning - already done + self.step(1) + + cluster = Clusters.Switch + feature_map = await self.read_single_attribute_check_success(cluster, attribute=cluster.Attributes.FeatureMap) + has_msl_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchLongPress) + multi_press_max = await self.read_single_attribute_check_success(cluster, attribute=cluster.Attributes.MultiPressMax) + + endpoint_id = self.matter_test_config.endpoint + pressed_position = 1 + + self.step(2) + event_listener = EventChangeCallback(cluster) + await event_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id) + + self.step(3) + self._ask_for_switch_idle() + + def test_multi_press_sequence(starting_step: str, count: int, short_long: bool = False): + step = starting_step + self.step(step) + + if short_long: + self._ask_for_multi_press_short_long(endpoint_id, pressed_position, + feature_map=feature_map, multi_press_max=multi_press_max) + else: + self._ask_for_multi_press(endpoint_id, number_of_presses=count, pressed_position=pressed_position, + feature_map=feature_map, multi_press_max=multi_press_max) + for i in range(count): + event = event_listener.wait_for_event_report(cluster.Events.InitialPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on InitialEvent") + if i > 0: + event = event_listener.wait_for_event_report(cluster.Events.MultiPressOngoing) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on MultiPressOngoing") + asserts.assert_equal(event.currentNumberOfPressesCounted, i+1, + "Unexpected CurrentNumberOfPressesCounted on MultiPressOngoing") + event = event_listener.wait_for_event_report(cluster.Events.ShortRelease) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on ShortRelease") + + step = step[:-1] + chr(ord(step[-1])+1) + self.step(step) + self._ask_for_switch_idle() + event = event_listener.wait_for_event_report(cluster.Events.MultiPressComplete) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on MultiPressComplete") + asserts.assert_equal(event.totalNumberOfPressesCounted, count, "Unexpected count on MultiPressComplete") + + test_multi_press_sequence("4a", 1) + + test_multi_press_sequence("5a", 2) + + self.step("6a") + multi_press_max = await self.read_single_attribute_check_success(cluster=cluster, attribute=cluster.Attributes.MultiPressMax) + if multi_press_max == 2: + self.skip_step("6b") + self.skip_step("6c") + else: + test_multi_press_sequence("6b", 3) + + if not has_msl_feature: + self.skip_all_remaining_steps(7) + return + + self.step(7) + # subscription is already set up + + test_multi_press_sequence("8a", 2, short_long=True) + + self.step("9a") + self._ask_for_multi_press_long_short(endpoint_id, pressed_position, feature_map) + + event = event_listener.wait_for_event_report(cluster.Events.InitialPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on InitialEvent") + event = event_listener.wait_for_event_report(cluster.Events.LongPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on LongPress") + event = event_listener.wait_for_event_report(cluster.Events.LongRelease) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on LongRelease") + if self._use_button_simulator: + # simulator can't sequence so we need to help it along here + self._send_multi_press_named_pipe_command(endpoint_id, number_of_presses=1, + pressed_position=1, feature_map=feature_map, multi_press_max=multi_press_max) + + event = event_listener.wait_for_event_report(cluster.Events.InitialPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on InitialEvent") + event = event_listener.wait_for_event_report(cluster.Events.ShortRelease) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on ShortRelease") + + # Because this is a queue, we verify that no multipress ongoing is received by verifying that the next event is the multipress complete + + self.step("9b") + self._ask_for_switch_idle() + event = event_listener.wait_for_event_report(cluster.Events.MultiPressComplete) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on MultiPressComplete") + asserts.assert_equal(event.totalNumberOfPressesCounted, 1, "Unexpected count on MultiPressComplete") + + def steps_TC_SWTCH_2_6(self): + return [TestStep(1, test_plan_support.commission_if_required(), is_commissioning=True), + TestStep(2, "Set up subscription to all Switch cluster events"), + TestStep(3, "Operator does not operate switch on the DUT"), + TestStep("4a", "Operator operates switch (press briefly) associated with position 1 on the DUT then release switch from DUT", + """ + + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH does not receive ShortRelease event from the DUT + """), + TestStep("4b", "Operator does not operate switch on the DUT", + "TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 1 from the DUT"), + TestStep("5a", "Operator repeat step 4a 2 times quickly", + """ + + * Verify that the TH receives InitialPress(one, not more than one) event with NewPosition set to 1 from the DUT + * Verify that the TH does not receive ShortRelease event from the DUT + * Verify that the TH does not receive MultiPressOngoing event from the DUT + """), + TestStep("5b", "Operator does not operate switch on the DUT", + "Verify that the TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 2 from the DUT"), + TestStep("6a", "Operator repeat step 4a MultiPressMax + 1(see 2c of TC-SWTCH-2.1) times quickly", + """ + + * Verify that the TH receives InitialPress(one, not more than one) event with NewPosition set to 1 from the DUT + * Verify that the TH does not receive ShortRelease event from the DUT + * Verify that the TH does not receive MultiPressOngoing event from the DUT + """ + ), + TestStep("6b", "Operator does not operate switch on the DUT", + "Verify that the TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 0 from the DUT"), + TestStep("7a", "If the switch cluster does not implement the MomentarySwitchLongPress (MSL) feature, skip the remaining steps"), + TestStep("7b", "Set up subscription to all Switch cluster events"), + TestStep("8a", + """ + Operator operates switch in below sequence: + 1. Operator operates switch (press briefly) associated with position 1 on the DUT then release switch from DUT + 2. Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT immediately after the previous step + 3. Operator releases switch from the DUT + """, + """ + + * Verify that the TH receives InitialPress(one, not more than one) event with NewPosition set to 1 from the DUT + * Verify that the TH does not receive ShortRelease event from the DUT + * Verify that the TH does not receive MultiPressOngoing event from the DUT + * Verify that the TH does not receive LongPress event from the DUT + * Verify that the TH does not receive LongRelease event from the DUT + """), + TestStep("8b", "Operator does not operate switch on the DUT", + "TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 2 from the DUT"), + TestStep("9a", + """ + Operator operates switch in below sequence: + + 1. Operator operates switch (keep pressed for long time, e.g. 5 seconds) on the DUT + 2. Operator releases switch from the DUT + 3. Immediately after the previous step complete, Operator operates switch (press briefly) associated with position 1 on the DUT then release switch from DUT + """, + """ + + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives (one, not more than one) LongPress event with NewPosition set to 1 from the DUT + * Verify that the TH receives LongRelease event with PreviousPosition set to 1 from the DUT + * Verify that the TH receives InitialPress event with NewPosition set to 1 from the DUT + * Verify that the TH does not receive MultiPressOngoing event from the DUT + * Verify that the TH does not receive ShortRelease event from the DUT + + The events sequence from the subscription SHALL follow the same sequence as expressed above, in the exact order of events specified. + """), + TestStep("9b", "Operator does not operate switch on the DUT" + "Verify that the TH receives MultiPressComplete event with PreviousPosition set to 1 and TotalNumberOfPressesCounted set to 1 from the DUT"), + ] + + @staticmethod + def should_run_SWTCH_2_6(wildcard, endpoint): + msm = has_feature(Clusters.Switch, Clusters.Switch.Bitmaps.Feature.kMomentarySwitchMultiPress) + asf = has_feature(Clusters.Switch, 0x20) + return msm(wildcard, endpoint) and asf(wildcard, endpoint) + + @per_endpoint_test(should_run_SWTCH_2_6) + async def test_TC_SWTCH_2_6(self): + # Commissioning - already done + self.step(1) + + cluster = Clusters.Switch + feature_map = await self.read_single_attribute_check_success(cluster, attribute=cluster.Attributes.FeatureMap) + has_msl_feature = (feature_map & cluster.Bitmaps.Feature.kMomentarySwitchLongPress) + multi_press_max = await self.read_single_attribute_check_success(cluster, attribute=cluster.Attributes.MultiPressMax) + + endpoint_id = self.matter_test_config.endpoint + pressed_position = 1 + + self.step(2) + event_listener = EventChangeCallback(cluster) + await event_listener.start(self.default_controller, self.dut_node_id, endpoint=endpoint_id) + + self.step(3) + self._ask_for_switch_idle() + + def test_multi_press_sequence(starting_step: str, count: int, short_long: bool = False): + step = starting_step + self.step(step) + + if short_long: + self._ask_for_multi_press_short_long(endpoint_id, pressed_position, + feature_map=feature_map, multi_press_max=multi_press_max) + else: + self._ask_for_multi_press(endpoint_id, number_of_presses=count, pressed_position=pressed_position, + feature_map=feature_map, multi_press_max=multi_press_max) + + event = event_listener.wait_for_event_report(cluster.Events.InitialPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on InitialEvent") + + step = step[:-1] + chr(ord(step[-1])+1) + self.step(step) + self._ask_for_switch_idle() + event = event_listener.wait_for_event_report(cluster.Events.MultiPressComplete) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on MultiPressComplete") + expected_count = 0 if count > multi_press_max else count + asserts.assert_equal(event.totalNumberOfPressesCounted, expected_count, "Unexpected count on MultiPressComplete") + + test_multi_press_sequence("4a", 1) + + test_multi_press_sequence("5a", 2) + + test_multi_press_sequence("6a", multi_press_max + 1) + + self.step("7a") + if not has_msl_feature: + self.skip_all_remaining_steps("7b") + + # subscription is already established + self.step("7b") + + test_multi_press_sequence("8a", 2, short_long=True) + + self.step("9a") + self._ask_for_multi_press_long_short(endpoint_id, pressed_position, feature_map) + + event = event_listener.wait_for_event_report(cluster.Events.InitialPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on InitialEvent") + event = event_listener.wait_for_event_report(cluster.Events.LongPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on LongPress") + event = event_listener.wait_for_event_report(cluster.Events.LongRelease) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on LongRelease") + if self._use_button_simulator: + # simulator can't sequence so we need to help it along here + self._send_multi_press_named_pipe_command(endpoint_id, number_of_presses=1, + pressed_position=1, feature_map=feature_map, multi_press_max=multi_press_max) + + event = event_listener.wait_for_event_report(cluster.Events.InitialPress) + asserts.assert_equal(event.newPosition, pressed_position, "Unexpected NewPosition on InitialEvent") + + # Verify that we don't receive the multi-press ongoing or short release by verifying that the next event in the sequence is the multi-press complete + self.step("9b") + self._ask_for_switch_idle() + event = event_listener.wait_for_event_report(cluster.Events.MultiPressComplete) + asserts.assert_equal(event.previousPosition, pressed_position, "Unexpected PreviousPosition on MultiPressComplete") + asserts.assert_equal(event.totalNumberOfPressesCounted, 1, "Unexpected count on MultiPressComplete") + if __name__ == "__main__": default_matter_test_main() diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 3d235e49873a8a..812713f243c953 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -33,7 +33,7 @@ from dataclasses import asdict as dataclass_asdict from dataclasses import dataclass, field from datetime import datetime, timedelta, timezone -from enum import Enum +from enum import Enum, IntFlag from functools import partial from typing import Any, List, Optional, Tuple @@ -1093,7 +1093,7 @@ def mark_current_step_skipped(self): steps = self.get_test_steps(self.current_test_info.name) if self.current_step_index == 0: asserts.fail("Script error: mark_current_step_skipped cannot be called before step()") - num = steps[self.current_step_index-1].test_plan_number + num = steps[self.current_step_index - 1].test_plan_number except KeyError: num = self.current_step_index @@ -1730,6 +1730,38 @@ def has_attribute(attribute: ClusterObjects.ClusterAttributeDescriptor) -> Endpo return partial(_has_attribute, attribute=attribute) +def _has_feature(wildcard, endpoint, cluster: ClusterObjects.ClusterObjectDescriptor, feature: IntFlag) -> bool: + try: + feature_map = wildcard.attributes[endpoint][cluster][cluster.Attributes.FeatureMap] + return (feature & feature_map) != 0 + except KeyError: + return False + + +def has_feature(cluster: ClusterObjects.ClusterObjectDescriptor, feature: IntFlag) -> EndpointCheckFunction: + """ EndpointCheckFunction that can be passed as a parameter to the per_endpoint_test decorator. + + Use this function with the per_endpoint_test decorator to run this test on all endpoints with + the specified feature. For example, given a device with the following conformance + + EP0: cluster A, B, C + EP1: cluster D with feature F0 + EP2, cluster D with feature F0 + EP3, cluster D without feature F0 + + And the following test specification: + @per_endpoint_test(has_feature(Clusters.D.Bitmaps.Feature.F0)) + test_mytest(self): + ... + + The test would be run on endpoint 1 and on endpoint 2. + + If the cluster is not found on any endpoint the decorator will call the on_skip function to + notify the test harness that the test is not applicable to this node and the test will not be run. + """ + return partial(_has_feature, cluster=cluster, feature=feature) + + async def get_accepted_endpoints_for_test(self: MatterBaseTest, accept_function: EndpointCheckFunction) -> list[uint]: """ Helper function for the per_endpoint_test decorator. diff --git a/src/python_testing/test_testing/TestDecorators.py b/src/python_testing/test_testing/TestDecorators.py index 60a75bfca466ef..2ce418d6c5e43a 100644 --- a/src/python_testing/test_testing/TestDecorators.py +++ b/src/python_testing/test_testing/TestDecorators.py @@ -30,15 +30,14 @@ import chip.clusters as Clusters from chip.clusters import Attribute -from chip.clusters import ClusterObjects as ClusterObjects try: from matter_testing_support import (MatterBaseTest, async_test_body, get_accepted_endpoints_for_test, has_attribute, - has_cluster, per_endpoint_test, per_node_test) + has_cluster, has_feature, per_endpoint_test, per_node_test) except ImportError: sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from matter_testing_support import (MatterBaseTest, async_test_body, get_accepted_endpoints_for_test, has_attribute, - has_cluster, per_endpoint_test, per_node_test) + has_cluster, has_feature, per_endpoint_test, per_node_test) from typing import Optional @@ -204,6 +203,16 @@ async def test_endpoint_attribute_supported_cluster_no(self): async def test_endpoint_attribute_unsupported_cluster_no(self): pass + # This test should be run once per endpoint + @per_endpoint_test(has_feature(Clusters.OnOff, Clusters.OnOff.Bitmaps.Feature.kLighting)) + async def test_endpoint_feature_yes(self): + pass + + # This test should be skipped since this attribute is part of an unsupported cluster + @per_endpoint_test(has_feature(Clusters.TimeSynchronization, Clusters.TimeSynchronization.Bitmaps.Feature.kNTPClient)) + async def test_endpoint_feature_unsupported_cluster_no(self): + pass + # This test should be run since both are present @per_endpoint_test(has_attribute(Clusters.OnOff.Attributes.OnOff) and has_cluster(Clusters.OnOff)) async def test_endpoint_boolean_yes(self): @@ -292,6 +301,8 @@ def check_skipped(test_name: str): check_once_per_endpoint('test_endpoint_attribute_yes') check_skipped('test_endpoint_attribute_supported_cluster_no') check_skipped('test_endpoint_attribute_unsupported_cluster_no') + check_once_per_endpoint('test_endpoint_feature_yes') + check_skipped('test_endpoint_feature_unsupported_cluster_no') check_once_per_endpoint('test_endpoint_boolean_yes') check_skipped('test_endpoint_boolean_no')