Implement TC-SWTCH-2.4 and all-clusters button simulator (#34406)
* Implement TC-SWTCH-2.4 and all-clusters button simulator
- Implement TC-SWTCH-2.4
- Implement action switch button simulator to drive the test
Testing done:
- New test (TC-SWTCH-2.4) works using the button simulator
* Restyled by clang-format
* Restyled by autopep8
* Restyled by isort
* Enable TC_SWTCH.py in CI
* Fix lint
* Fix typo that arose on file saving
* Handle EOF in reading user input in matter testing support
* Do not provide a stdin on python test runner and handle EOF on matter_testing_support
* Fix up PICS execution for CI
---------
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Andrei Litvin <andreilitvin@google.com>
diff --git a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
index 8effe64..3c42eb1 100644
--- a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
+++ b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
@@ -28,6 +28,7 @@
#include <app/util/attribute-storage.h>
#include <platform/PlatformManager.h>
+#include "ButtonEventsSimulator.h"
#include <air-quality-instance.h>
#include <dishwasher-mode.h>
#include <laundry-washer-mode.h>
@@ -36,13 +37,155 @@
#include <oven-operational-state-delegate.h>
#include <rvc-modes.h>
+#include <memory>
#include <string>
+#include <utility>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::DeviceLayer;
+namespace {
+
+std::unique_ptr<ButtonEventsSimulator> sButtonSimulatorInstance{ nullptr };
+
+bool HasNumericField(Json::Value & jsonValue, const std::string & field)
+{
+ return jsonValue.isMember(field) && jsonValue[field].isNumeric();
+}
+
+/**
+ * Named pipe handler for simulated long press on an action switch.
+ *
+ * Usage example:
+ * echo '{"Name": "SimulateActionSwitchLongPress", "EndpointId": 3, "ButtonId": 1, "LongPressDelayMillis": 800,
+ * "LongPressDurationMillis": 1000}' > /tmp/chip_all_clusters_fifo_1146610
+ *
+ * JSON Arguments:
+ * - "Name": Must be "SimulateActionSwitchLongPress"
+ * - "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
+ *
+ * @param jsonValue - JSON payload from named pipe
+ */
+void HandleSimulateActionSwitchLongPress(Json::Value & jsonValue)
+{
+ if (sButtonSimulatorInstance != nullptr)
+ {
+ ChipLogError(NotSpecified, "Button simulation already in progress! Ignoring request.");
+ return;
+ }
+
+ bool hasEndpointId = HasNumericField(jsonValue, "EndpointId");
+ bool hasButtonId = HasNumericField(jsonValue, "ButtonId");
+ bool hasLongPressDelayMillis = HasNumericField(jsonValue, "LongPressDelayMillis");
+ bool hasLongPressDurationMillis = HasNumericField(jsonValue, "LongPressDurationMillis");
+ if (!hasEndpointId || !hasButtonId || !hasLongPressDelayMillis || !hasLongPressDurationMillis)
+ {
+ std::string inputJson = jsonValue.toStyledString();
+ ChipLogError(
+ NotSpecified,
+ "Missing or invalid value for one of EndpointId, ButtonId, LongPressDelayMillis or LongPressDurationMillis in %s",
+ inputJson.c_str());
+ return;
+ }
+
+ EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
+ uint8_t buttonId = static_cast<uint8_t>(jsonValue["ButtonId"].asUInt());
+ System::Clock::Milliseconds32 longPressDelayMillis{ static_cast<unsigned>(jsonValue["LongPressDelayMillis"].asUInt()) };
+ System::Clock::Milliseconds32 longPressDurationMillis{ static_cast<unsigned>(jsonValue["LongPressDurationMillis"].asUInt()) };
+ auto buttonSimulator = std::make_unique<ButtonEventsSimulator>();
+
+ bool success = buttonSimulator->SetMode(ButtonEventsSimulator::Mode::kModeLongPress)
+ .SetLongPressDelayMillis(longPressDelayMillis)
+ .SetLongPressDurationMillis(longPressDurationMillis)
+ .SetIdleButtonId(0)
+ .SetPressedButtonId(buttonId)
+ .SetEndpointId(endpointId)
+ .Execute([]() { sButtonSimulatorInstance.reset(); });
+
+ if (!success)
+ {
+ ChipLogError(NotSpecified, "Failed to start execution of button simulator!");
+ return;
+ }
+
+ sButtonSimulatorInstance = std::move(buttonSimulator);
+}
+
+/**
+ * Named pipe handler for simulated multi-press on an action switch.
+ *
+ * Usage example:
+ * echo '{"Name": "SimulateActionSwitchMultiPress", "EndpointId": 3, "ButtonId": 1, "MultiPressPressedTimeMillis": 100,
+ * "MultiPressReleasedTimeMillis": 350, "MultiPressNumPresses": 2}' > /tmp/chip_all_clusters_fifo_1146610
+ *
+ * JSON Arguments:
+ * - "Name": Must be "SimulateActionSwitchMultiPress"
+ * - "EndpointId": number of endpoint having a switch cluster
+ * - "ButtonId": switch position in the switch cluster for "down" button (not idle)
+ * - "MultiPressPressedTimeMillis": Pressed time in milliseconds for each press
+ * - "MultiPressReleasedTimeMillis": Released time in milliseconds after each press
+ * - "MultiPressNumPresses": Number of presses to simulate
+ *
+ * @param jsonValue - JSON payload from named pipe
+ */
+void HandleSimulateActionSwitchMultiPress(Json::Value & jsonValue)
+{
+ if (sButtonSimulatorInstance != nullptr)
+ {
+ ChipLogError(NotSpecified, "Button simulation already in progress! Ignoring request.");
+ return;
+ }
+
+ bool hasEndpointId = HasNumericField(jsonValue, "EndpointId");
+ bool hasButtonId = HasNumericField(jsonValue, "ButtonId");
+ bool hasMultiPressPressedTimeMillis = HasNumericField(jsonValue, "MultiPressPressedTimeMillis");
+ bool hasMultiPressReleasedTimeMillis = HasNumericField(jsonValue, "MultiPressReleasedTimeMillis");
+ bool hasMultiPressNumPresses = HasNumericField(jsonValue, "MultiPressNumPresses");
+ if (!hasEndpointId || !hasButtonId || !hasMultiPressPressedTimeMillis || !hasMultiPressReleasedTimeMillis ||
+ !hasMultiPressNumPresses)
+ {
+ std::string inputJson = jsonValue.toStyledString();
+ ChipLogError(NotSpecified,
+ "Missing or invalid value for one of EndpointId, ButtonId, MultiPressPressedTimeMillis, "
+ "MultiPressReleasedTimeMillis or MultiPressNumPresses in %s",
+ inputJson.c_str());
+ return;
+ }
+
+ EndpointId endpointId = static_cast<EndpointId>(jsonValue["EndpointId"].asUInt());
+ uint8_t buttonId = static_cast<uint8_t>(jsonValue["ButtonId"].asUInt());
+ System::Clock::Milliseconds32 multiPressPressedTimeMillis{ static_cast<unsigned>(
+ jsonValue["MultiPressPressedTimeMillis"].asUInt()) };
+ System::Clock::Milliseconds32 multiPressReleasedTimeMillis{ static_cast<unsigned>(
+ jsonValue["MultiPressReleasedTimeMillis"].asUInt()) };
+ uint8_t multiPressNumPresses = static_cast<uint8_t>(jsonValue["MultiPressNumPresses"].asUInt());
+ auto buttonSimulator = std::make_unique<ButtonEventsSimulator>();
+
+ bool success = buttonSimulator->SetMode(ButtonEventsSimulator::Mode::kModeMultiPress)
+ .SetMultiPressPressedTimeMillis(multiPressPressedTimeMillis)
+ .SetMultiPressReleasedTimeMillis(multiPressReleasedTimeMillis)
+ .SetMultiPressNumPresses(multiPressNumPresses)
+ .SetIdleButtonId(0)
+ .SetPressedButtonId(buttonId)
+ .SetEndpointId(endpointId)
+ .Execute([]() { sButtonSimulatorInstance.reset(); });
+
+ if (!success)
+ {
+ ChipLogError(NotSpecified, "Failed to start execution of button simulator!");
+ return;
+ }
+
+ sButtonSimulatorInstance = std::move(buttonSimulator);
+}
+
+} // namespace
+
AllClustersAppCommandHandler * AllClustersAppCommandHandler::FromJSON(const char * json)
{
Json::Reader reader;
@@ -190,6 +333,14 @@
std::string operation = self->mJsonValue["Operation"].asString();
self->OnOperationalStateChange(device, operation, self->mJsonValue["Param"]);
}
+ else if (name == "SimulateActionSwitchLongPress")
+ {
+ HandleSimulateActionSwitchLongPress(self->mJsonValue);
+ }
+ else if (name == "SimulateActionSwitchMultiPress")
+ {
+ HandleSimulateActionSwitchMultiPress(self->mJsonValue);
+ }
else
{
ChipLogError(NotSpecified, "Unhandled command: Should never happens");
diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn
index 3d52ef7..a2f4ff0 100644
--- a/examples/all-clusters-app/linux/BUILD.gn
+++ b/examples/all-clusters-app/linux/BUILD.gn
@@ -66,7 +66,10 @@
"${chip_root}/examples/energy-management-app/energy-management-common/src/device-energy-management-mode.cpp",
"${chip_root}/examples/energy-management-app/energy-management-common/src/energy-evse-mode.cpp",
"AllClustersCommandDelegate.cpp",
+ "AllClustersCommandDelegate.h",
"AppOptions.cpp",
+ "ButtonEventsSimulator.cpp",
+ "ButtonEventsSimulator.h",
"ValveControlDelegate.cpp",
"WindowCoveringManager.cpp",
"include/tv-callbacks.cpp",
diff --git a/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp b/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp
new file mode 100644
index 0000000..44bf565
--- /dev/null
+++ b/examples/all-clusters-app/linux/ButtonEventsSimulator.cpp
@@ -0,0 +1,210 @@
+/*
+ *
+ * Copyright (c) 2024 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ButtonEventsSimulator.h"
+
+#include <functional>
+#include <inttypes.h>
+#include <stdint.h>
+#include <utility>
+
+#include <app-common/zap-generated/attributes/Accessors.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/EventLogging.h>
+#include <lib/core/CHIPError.h>
+#include <lib/core/DataModelTypes.h>
+#include <lib/support/CodeUtils.h>
+#include <lib/support/logging/CHIPLogging.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <system/SystemClock.h>
+#include <system/SystemLayer.h>
+
+namespace chip {
+namespace app {
+
+namespace {
+
+void SetButtonPosition(EndpointId endpointId, uint8_t position)
+{
+ Clusters::Switch::Attributes::CurrentPosition::Set(endpointId, position);
+}
+
+void EmitInitialPress(EndpointId endpointId, uint8_t newPosition)
+{
+ Clusters::Switch::Events::InitialPress::Type event{ newPosition };
+ EventNumber eventNumber = 0;
+
+ CHIP_ERROR err = LogEvent(event, endpointId, eventNumber);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(NotSpecified, "Failed to log InitialPress event: %" CHIP_ERROR_FORMAT, err.Format());
+ }
+ else
+ {
+ ChipLogProgress(NotSpecified, "Logged InitialPress(%u) on Endpoint %u", static_cast<unsigned>(newPosition),
+ static_cast<unsigned>(endpointId));
+ }
+}
+
+void EmitLongPress(EndpointId endpointId, uint8_t newPosition)
+{
+ Clusters::Switch::Events::LongPress::Type event{ newPosition };
+ EventNumber eventNumber = 0;
+
+ CHIP_ERROR err = LogEvent(event, endpointId, eventNumber);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(NotSpecified, "Failed to log LongPress event: %" CHIP_ERROR_FORMAT, err.Format());
+ }
+ else
+ {
+ ChipLogProgress(NotSpecified, "Logged LongPress(%u) on Endpoint %u", static_cast<unsigned>(newPosition),
+ static_cast<unsigned>(endpointId));
+ }
+}
+
+void EmitLongRelease(EndpointId endpointId, uint8_t previousPosition)
+{
+ Clusters::Switch::Events::LongRelease::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 LongRelease event: %" CHIP_ERROR_FORMAT, err.Format());
+ }
+ else
+ {
+ ChipLogProgress(NotSpecified, "Logged LongRelease on Endpoint %u", static_cast<unsigned>(endpointId));
+ }
+}
+
+void EmitMultiPressComplete(EndpointId endpointId, uint8_t previousPosition, uint8_t count)
+{
+ Clusters::Switch::Events::MultiPressComplete::Type event{};
+ event.previousPosition = previousPosition;
+ event.totalNumberOfPressesCounted = count;
+ EventNumber eventNumber = 0;
+
+ CHIP_ERROR err = LogEvent(event, endpointId, eventNumber);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(NotSpecified, "Failed to log MultiPressComplete event: %" CHIP_ERROR_FORMAT, err.Format());
+ }
+ else
+ {
+ ChipLogProgress(NotSpecified, "Logged MultiPressComplete(count=%u) on Endpoint %u", static_cast<unsigned>(count),
+ static_cast<unsigned>(endpointId));
+ }
+}
+
+} // namespace
+
+void ButtonEventsSimulator::OnTimerDone(System::Layer * layer, void * appState)
+{
+ ButtonEventsSimulator * that = reinterpret_cast<ButtonEventsSimulator *>(appState);
+ that->Next();
+}
+
+bool ButtonEventsSimulator::Execute(DoneCallback && doneCallback)
+{
+ VerifyOrReturnValue(mIdleButtonId != mPressedButtonId, false);
+
+ switch (mMode)
+ {
+ case Mode::kModeLongPress:
+ VerifyOrReturnValue(mLongPressDurationMillis > mLongPressDelayMillis, false);
+ SetState(State::kEmitStartOfLongPress);
+ break;
+ case Mode::kModeMultiPress:
+ VerifyOrReturnValue(mMultiPressPressedTimeMillis.count() > 0, false);
+ VerifyOrReturnValue(mMultiPressReleasedTimeMillis.count() > 0, false);
+ VerifyOrReturnValue(mMultiPressNumPresses > 0, false);
+ SetState(State::kEmitStartOfMultiPress);
+ break;
+ default:
+ return false;
+ }
+ mDoneCallback = std::move(doneCallback);
+ Next();
+ return true;
+}
+
+void ButtonEventsSimulator::SetState(ButtonEventsSimulator::State newState)
+{
+ ButtonEventsSimulator::State oldState = mState;
+ if (oldState != newState)
+ {
+ ChipLogProgress(NotSpecified, "ButtonEventsSimulator state change %u -> %u", static_cast<unsigned>(oldState),
+ static_cast<unsigned>(newState));
+ }
+
+ mState = newState;
+}
+
+void ButtonEventsSimulator::StartTimer(System::Clock::Timeout duration)
+{
+ chip::DeviceLayer::SystemLayer().StartTimer(duration, &ButtonEventsSimulator::OnTimerDone, this);
+}
+
+void ButtonEventsSimulator::Next()
+{
+ switch (mState)
+ {
+ case ButtonEventsSimulator::State::kIdle: {
+ ChipLogError(NotSpecified, "Found idle state where not expected!");
+ break;
+ }
+ case ButtonEventsSimulator::State::kEmitStartOfLongPress: {
+ SetButtonPosition(mEndpointId, mPressedButtonId);
+ EmitInitialPress(mEndpointId, mPressedButtonId);
+ SetState(ButtonEventsSimulator::State::kEmitLongPress);
+ StartTimer(mLongPressDelayMillis);
+ break;
+ }
+ case ButtonEventsSimulator::State::kEmitLongPress: {
+ EmitLongPress(mEndpointId, mPressedButtonId);
+ SetState(ButtonEventsSimulator::State::kEmitLongRelease);
+ StartTimer(mLongPressDurationMillis - mLongPressDelayMillis);
+ break;
+ }
+ case ButtonEventsSimulator::State::kEmitLongRelease: {
+ SetButtonPosition(mEndpointId, mIdleButtonId);
+ EmitLongRelease(mEndpointId, mPressedButtonId);
+ SetState(ButtonEventsSimulator::State::kIdle);
+ mDoneCallback();
+ break;
+ }
+ case ButtonEventsSimulator::State::kEmitStartOfMultiPress: {
+ EmitInitialPress(mEndpointId, mPressedButtonId);
+ StartTimer(mMultiPressNumPresses * (mMultiPressPressedTimeMillis + mMultiPressReleasedTimeMillis));
+ SetState(ButtonEventsSimulator::State::kEmitEndOfMultiPress);
+ break;
+ }
+ case ButtonEventsSimulator::State::kEmitEndOfMultiPress: {
+ EmitMultiPressComplete(mEndpointId, mPressedButtonId, mMultiPressNumPresses);
+ SetState(ButtonEventsSimulator::State::kIdle);
+ mDoneCallback();
+ break;
+ }
+ }
+}
+
+} // namespace app
+} // namespace chip
diff --git a/examples/all-clusters-app/linux/ButtonEventsSimulator.h b/examples/all-clusters-app/linux/ButtonEventsSimulator.h
new file mode 100644
index 0000000..658da98
--- /dev/null
+++ b/examples/all-clusters-app/linux/ButtonEventsSimulator.h
@@ -0,0 +1,143 @@
+/*
+ *
+ * Copyright (c) 2024 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+#include <stdbool.h>
+
+#include <lib/core/DataModelTypes.h>
+#include <system/SystemClock.h>
+#include <system/SystemLayer.h>
+
+namespace chip {
+namespace app {
+
+/**
+ * State machine to emit button sequences. Configure with `SetXxx()` methods
+ * and then call `Execute()` with a functor to be called when done.
+ *
+ * The implementation has dependencies on SystemLayer (to start timers) and on
+ * EventLogging.
+ *
+ */
+class ButtonEventsSimulator
+{
+public:
+ enum class Mode
+ {
+ kModeLongPress,
+ kModeMultiPress
+ };
+
+ using DoneCallback = std::function<void()>;
+
+ ButtonEventsSimulator() = default;
+
+ // Returns true on success to start execution, false on something going awry.
+ // `doneCallback` is called only if execution got started.
+ bool Execute(DoneCallback && doneCallback);
+
+ ButtonEventsSimulator & SetLongPressDelayMillis(System::Clock::Milliseconds32 longPressDelayMillis)
+ {
+ mLongPressDelayMillis = longPressDelayMillis;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetLongPressDurationMillis(System::Clock::Milliseconds32 longPressDurationMillis)
+ {
+ mLongPressDurationMillis = longPressDurationMillis;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetMultiPressPressedTimeMillis(System::Clock::Milliseconds32 multiPressPressedTimeMillis)
+ {
+ mMultiPressPressedTimeMillis = multiPressPressedTimeMillis;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetMultiPressReleasedTimeMillis(System::Clock::Milliseconds32 multiPressReleasedTimeMillis)
+ {
+ mMultiPressReleasedTimeMillis = multiPressReleasedTimeMillis;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetMultiPressNumPresses(uint8_t multiPressNumPresses)
+ {
+ mMultiPressNumPresses = multiPressNumPresses;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetIdleButtonId(uint8_t idleButtonId)
+ {
+ mIdleButtonId = idleButtonId;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetPressedButtonId(uint8_t pressedButtonId)
+ {
+ mPressedButtonId = pressedButtonId;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetMode(Mode mode)
+ {
+ mMode = mode;
+ return *this;
+ }
+
+ ButtonEventsSimulator & SetEndpointId(EndpointId endpointId)
+ {
+ mEndpointId = endpointId;
+ return *this;
+ }
+
+private:
+ enum class State
+ {
+ kIdle = 0,
+
+ kEmitStartOfLongPress = 1,
+ kEmitLongPress = 2,
+ kEmitLongRelease = 3,
+
+ kEmitStartOfMultiPress = 4,
+ kEmitEndOfMultiPress = 5,
+ };
+
+ static void OnTimerDone(System::Layer * layer, void * appState);
+ void SetState(State newState);
+ void Next();
+ void StartTimer(System::Clock::Timeout duration);
+
+ DoneCallback mDoneCallback;
+ System::Clock::Milliseconds32 mLongPressDelayMillis{};
+ System::Clock::Milliseconds32 mLongPressDurationMillis{};
+ System::Clock::Milliseconds32 mMultiPressPressedTimeMillis{};
+ System::Clock::Milliseconds32 mMultiPressReleasedTimeMillis{};
+ uint8_t mMultiPressNumPresses{ 1 };
+ uint8_t mIdleButtonId{ 0 };
+ uint8_t mPressedButtonId{ 1 };
+ EndpointId mEndpointId{ 1 };
+
+ Mode mMode{ Mode::kModeLongPress };
+ State mState{ State::kIdle };
+};
+
+} // namespace app
+} // namespace chip