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