From af84da6f6aa9db3ea6a74a3e734aa43574e72508 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Tue, 3 Dec 2024 03:08:51 -0300 Subject: [PATCH] feat(matter): Adds a new Matter Endpoint: Generic Switch (smart button) (#10662) * feat(matter): adds new matter generic switch endpoint * fix(matter): no need of arduino preferences here * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- CMakeLists.txt | 1 + .../MatterSmartButon/MatterSmartButon.ino | 115 ++++++++++++++++++ .../Matter/examples/MatterSmartButon/ci.json | 7 ++ libraries/Matter/keywords.txt | 3 +- libraries/Matter/src/Matter.h | 2 + .../MatterEndpoints/MatterGenericSwitch.cpp | 100 +++++++++++++++ .../src/MatterEndpoints/MatterGenericSwitch.h | 39 ++++++ 7 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 libraries/Matter/examples/MatterSmartButon/MatterSmartButon.ino create mode 100644 libraries/Matter/examples/MatterSmartButon/ci.json create mode 100644 libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.cpp create mode 100644 libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bbef502143..59035e50774 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,7 @@ set(ARDUINO_LIBRARY_OpenThread_SRCS libraries/OpenThread/src/OThreadCLI_Util.cpp) set(ARDUINO_LIBRARY_Matter_SRCS + libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.cpp libraries/Matter/src/MatterEndpoints/MatterOnOffLight.cpp libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp diff --git a/libraries/Matter/examples/MatterSmartButon/MatterSmartButon.ino b/libraries/Matter/examples/MatterSmartButon/MatterSmartButon.ino new file mode 100644 index 00000000000..1d71f2123a6 --- /dev/null +++ b/libraries/Matter/examples/MatterSmartButon/MatterSmartButon.ino @@ -0,0 +1,115 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +// Matter Manager +#include +#include + +// List of Matter Endpoints for this Node +// Generic Switch Endpoint - works as a smart button with a single click +MatterGenericSwitch SmartButton; + +// set your board USER BUTTON pin here +const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9. + +// WiFi is manually set and started +const char *ssid = "your-ssid"; // Change this to your WiFi SSID +const char *password = "your-password"; // Change this to your WiFi password + +void setup() { + // Initialize the USER BUTTON (Boot button) GPIO that will act as a toggle switch + pinMode(buttonPin, INPUT_PULLUP); + + Serial.begin(115200); + while (!Serial) { + delay(100); + } + + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + // enable IPv6 + WiFi.enableIPv6(true); + // Manually connect to WiFi + WiFi.begin(ssid, password); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("\r\nWiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + delay(500); + + // Initialize the Matter EndPoint + SmartButton.begin(); + + // Matter beginning - Last step, after all EndPoints are initialized + Matter.begin(); + // This may be a restart of a already commissioned Matter accessory + if (Matter.isDeviceCommissioned()) { + Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); + } +} +// Button control +uint32_t button_time_stamp = 0; // debouncing control +bool button_state = false; // false = released | true = pressed +const uint32_t debouceTime = 250; // button debouncing time (ms) +const uint32_t decommissioningTimeout = 10000; // keep the button pressed for 10s to decommission the Matter Fabric + +void loop() { + // Check Matter Accessory Commissioning state, which may change during execution of loop() + if (!Matter.isDeviceCommissioned()) { + Serial.println(""); + Serial.println("Matter Node is not commissioned yet."); + Serial.println("Initiate the device discovery in your Matter environment."); + Serial.println("Commission it to your Matter hub with the manual pairing code or QR code"); + Serial.printf("Manual pairing code: %s\r\n", Matter.getManualPairingCode().c_str()); + Serial.printf("QR code URL: %s\r\n", Matter.getOnboardingQRCodeUrl().c_str()); + // waits for Matter Generic Switch Commissioning. + uint32_t timeCount = 0; + while (!Matter.isDeviceCommissioned()) { + delay(100); + if ((timeCount++ % 50) == 0) { // 50*100ms = 5 sec + Serial.println("Matter Node not commissioned yet. Waiting for commissioning."); + } + } + Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); + } + + // A builtin button is used to trigger a command to the Matter Controller + // Check if the button has been pressed + if (digitalRead(buttonPin) == LOW && !button_state) { + // deals with button debouncing + button_time_stamp = millis(); // record the time while the button is pressed. + button_state = true; // pressed. + } + + // Onboard User Button is used as a smart button or to decommission it + uint32_t time_diff = millis() - button_time_stamp; + if (button_state && time_diff > debouceTime && digitalRead(buttonPin) == HIGH) { + button_state = false; // released + // builtin button is released - send a click event to the Matter Controller + Serial.println("User button released. Sending Click to the Matter Controller!"); + // Matter Controller will receive an event and, if programmed, it will trigger an action + SmartButton.click(); + + // Factory reset is triggered if the button is pressed longer than 10 seconds + if (time_diff > decommissioningTimeout) { + Serial.println("Decommissioning the Generic Switch Matter Accessory. It shall be commissioned again."); + Matter.decommission(); + } + } +} diff --git a/libraries/Matter/examples/MatterSmartButon/ci.json b/libraries/Matter/examples/MatterSmartButon/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterSmartButon/ci.json @@ -0,0 +1,7 @@ +{ + "fqbn_append": "PartitionScheme=huge_app", + "requires": [ + "CONFIG_SOC_WIFI_SUPPORTED=y", + "CONFIG_ESP_MATTER_ENABLE_DATA_MODEL=y" + ] +} diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index 663cc2e8327..597bbac657b 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -8,6 +8,7 @@ Matter KEYWORD1 ArduinoMatter KEYWORD1 +MatterGenericSwitch KEYWORD1 MatterOnOffLight KEYWORD1 MatterDimmableLight KEYWORD1 MatterColorTemperatureLight KEYWORD1 @@ -46,7 +47,7 @@ onChangeOnOff KEYWORD2 onChangeBrightness KEYWORD2 onChangeColorTemperature KEYWORD2 onChangeColorHSV KEYWORD2 - +click KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index 5c68572640a..9136eead048 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ class ArduinoMatter { static void decommission(); // list of Matter EndPoints Friend Classes + friend class MatterGenericSwitch; friend class MatterOnOffLight; friend class MatterDimmableLight; friend class MatterColorTemperatureLight; diff --git a/libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.cpp b/libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.cpp new file mode 100644 index 00000000000..f5c6c9d750f --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.cpp @@ -0,0 +1,100 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// 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 +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include +#include + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace esp_matter::cluster; +using namespace chip::app::Clusters; + +MatterGenericSwitch::MatterGenericSwitch() {} + +MatterGenericSwitch::~MatterGenericSwitch() { + end(); +} + +bool MatterGenericSwitch::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { + if (!started) { + log_e("Matter Generic Switch device has not begun."); + return false; + } + + log_d("Generic Switch Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + return true; +} + +bool MatterGenericSwitch::begin() { + ArduinoMatter::_init(); + generic_switch::config_t switch_config; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = generic_switch::create(node::get(), &switch_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create Generic swtich endpoint"); + return false; + } + // Add group cluster to the switch endpoint + cluster::groups::config_t groups_config; + cluster::groups::create(endpoint, &groups_config, CLUSTER_FLAG_SERVER | CLUSTER_FLAG_CLIENT); + + cluster_t *aCluster = cluster::get(endpoint, Descriptor::Id); + esp_matter::cluster::descriptor::feature::taglist::add(aCluster); + + cluster::fixed_label::config_t fl_config; + cluster::fixed_label::create(endpoint, &fl_config, CLUSTER_FLAG_SERVER); + + cluster::user_label::config_t ul_config; + cluster::user_label::create(endpoint, &ul_config, CLUSTER_FLAG_SERVER); + + aCluster = cluster::get(endpoint, Switch::Id); + switch_cluster::feature::momentary_switch::add(aCluster); + switch_cluster::event::create_initial_press(aCluster); + + switch_cluster::feature::momentary_switch::add(aCluster); + + switch_cluster::attribute::create_current_position(aCluster, 0); + switch_cluster::attribute::create_number_of_positions(aCluster, 2); + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Generic Switch created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterGenericSwitch::end() { + started = false; +} + +void MatterGenericSwitch::click() { + if (!started) { + log_e("Matter Generic Switch device has not begun."); + return; + } + + int switch_endpoint_id = getEndPointId(); + uint8_t newPosition = 1; + // Press moves Position from 0 (off) to 1 (on) + chip::DeviceLayer::SystemLayer().ScheduleLambda([switch_endpoint_id, newPosition]() { + // InitialPress event takes newPosition as event data + switch_cluster::event::send_initial_press(switch_endpoint_id, newPosition); + }); +} + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.h b/libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.h new file mode 100644 index 00000000000..14118462932 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterGenericSwitch.h @@ -0,0 +1,39 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// 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 +#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL + +#include +#include + +// Matter Generic Switch Endpoint that works as a single click smart button +class MatterGenericSwitch : public MatterEndPoint { +public: + MatterGenericSwitch(); + ~MatterGenericSwitch(); + virtual bool begin(); + void end(); // this will just stop processing Matter events + + // send a simple click event to the Matter Controller + void click(); + + // this function is called by Matter internal event processor. It could be overwritten by the application, if necessary. + bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val); + +protected: + bool started = false; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */