diff --git a/CMakeLists.txt b/CMakeLists.txt index 322824f11ab..6f89f3b8715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS libraries/Matter/src/MatterEndpoints/MatterContactSensor.cpp libraries/Matter/src/MatterEndpoints/MatterPressureSensor.cpp libraries/Matter/src/MatterEndpoints/MatterOccupancySensor.cpp + libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.cpp libraries/Matter/src/Matter.cpp) set(ARDUINO_LIBRARY_PPP_SRCS diff --git a/libraries/Matter/examples/MatterOnOffPlugin/MatterOnOffPlugin.ino b/libraries/Matter/examples/MatterOnOffPlugin/MatterOnOffPlugin.ino new file mode 100644 index 00000000000..1950b5bb9f3 --- /dev/null +++ b/libraries/Matter/examples/MatterOnOffPlugin/MatterOnOffPlugin.ino @@ -0,0 +1,142 @@ +// 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 +#include + +// List of Matter Endpoints for this Node +// On/Off Plugin Endpoint +MatterOnOffPlugin OnOffPlugin; + +// it will keep last OnOff state stored, using Preferences +Preferences matterPref; +const char *onOffPrefKey = "OnOff"; + +// set your board Power Relay pin here - this example uses the built-in LED for easy visualization +#ifdef LED_BUILTIN +const uint8_t onoffPin = LED_BUILTIN; +#else +const uint8_t onoffPin = 2; // Set your pin here - usually a power relay +#warning "Do not forget to set the Power Relay pin" +#endif + +// board USER BUTTON pin necessary for Decommissioning +const uint8_t buttonPin = BOOT_PIN; // Set your pin here. Using BOOT Button. + +// Button control +uint32_t button_time_stamp = 0; // debouncing control +bool button_state = false; // false = released | true = pressed +const uint32_t decommissioningTimeout = 5000; // keep the button pressed for 5s, or longer, to decommission + +// 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 + +// Matter Protocol Endpoint Callback +bool setPluginOnOff(bool state) { + Serial.printf("User Callback :: New Plugin State = %s\r\n", state ? "ON" : "OFF"); + if (state) { + digitalWrite(onoffPin, HIGH); + } else { + digitalWrite(onoffPin, LOW); + } + // store last OnOff state for when the Plugin is restarted / power goes off + matterPref.putBool(onOffPrefKey, state); + // This callback must return the success state to Matter core + return true; +} + +void setup() { + // Initialize the USER BUTTON + pinMode(buttonPin, INPUT_PULLUP); + // Initialize the Power Relay (plugin) GPIO + pinMode(onoffPin, OUTPUT); + + Serial.begin(115200); + + // We start by connecting to a WiFi network + Serial.print("Connecting to "); + Serial.println(ssid); + 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 Matter EndPoint + matterPref.begin("MatterPrefs", false); + bool lastOnOffState = matterPref.getBool(onOffPrefKey, false); + OnOffPlugin.begin(lastOnOffState); + OnOffPlugin.onChange(setPluginOnOff); + + // 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."); + Serial.printf("Initial state: %s\r\n", OnOffPlugin.getOnOff() ? "ON" : "OFF"); + OnOffPlugin.updateAccessory(); // configure the Plugin based on initial state + } +} + +void loop() { + // Check Matter Plugin 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 Plugin 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.printf("Initial state: %s\r\n", OnOffPlugin.getOnOff() ? "ON" : "OFF"); + OnOffPlugin.updateAccessory(); // configure the Plugin based on initial state + Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); + } + + // 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 to decommission the Matter Node + if (button_state && digitalRead(buttonPin) == HIGH) { + button_state = false; // released + } + + // Onboard User Button is kept pressed for longer than 5 seconds in order to decommission matter node + uint32_t time_diff = millis() - button_time_stamp; + if (button_state && time_diff > decommissioningTimeout) { + Serial.println("Decommissioning the Plugin Matter Accessory. It shall be commissioned again."); + OnOffPlugin.setOnOff(false); // turn the plugin off + Matter.decommission(); + button_time_stamp = millis(); // avoid running decommissining again, reboot takes a second or so + } +} diff --git a/libraries/Matter/examples/MatterOnOffPlugin/ci.json b/libraries/Matter/examples/MatterOnOffPlugin/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterOnOffPlugin/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 1daf65974ed..3f40e598ada 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -23,6 +23,7 @@ MatterHumiditySensor KEYWORD1 MatterContactSensor KEYWORD1 MatterPressureSensor KEYWORD1 MatterOccupancySensor KEYWORD1 +MatterOnOffPlugin KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index bc0a0ec1cc7..7fcab363f11 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -31,6 +31,7 @@ #include #include #include +#include using namespace esp_matter; @@ -68,6 +69,7 @@ class ArduinoMatter { friend class MatterContactSensor; friend class MatterPressureSensor; friend class MatterOccupancySensor; + friend class MatterOnOffPlugin; protected: static void _init(); diff --git a/libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.cpp b/libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.cpp new file mode 100644 index 00000000000..6b5e5e630e0 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.cpp @@ -0,0 +1,136 @@ +// 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 chip::app::Clusters; + +bool MatterOnOffPlugin::attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) { + bool ret = true; + if (!started) { + log_e("Matter On-Off Plugin device has not begun."); + return false; + } + + log_d("OnOff Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + + if (endpoint_id == getEndPointId()) { + log_d("OnOffPlugin state changed to %d", val->val.b); + if (cluster_id == OnOff::Id) { + if (attribute_id == OnOff::Attributes::OnOff::Id) { + if (_onChangeOnOffCB != NULL) { + ret &= _onChangeOnOffCB(val->val.b); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(val->val.b); + } + if (ret == true) { + onOffState = val->val.b; + } + } + } + } + return ret; +} + +MatterOnOffPlugin::MatterOnOffPlugin() {} + +MatterOnOffPlugin::~MatterOnOffPlugin() { + end(); +} + +bool MatterOnOffPlugin::begin(bool initialState) { + ArduinoMatter::_init(); + on_off_plugin_unit::config_t plugin_config; + + plugin_config.on_off.on_off = initialState; + plugin_config.on_off.lighting.start_up_on_off = nullptr; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = on_off_plugin_unit::create(node::get(), &plugin_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create on-off plugin endpoint"); + return false; + } + onOffState = initialState; + setEndPointId(endpoint::get_id(endpoint)); + log_i("On-Off Plugin created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterOnOffPlugin::end() { + started = false; +} + +void MatterOnOffPlugin::updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(onOffState); + } +} + +bool MatterOnOffPlugin::setOnOff(bool newState) { + if (!started) { + log_e("Matter On-Off Plugin device has not begun."); + return false; + } + + // avoid processing the a "no-change" + if (onOffState == newState) { + return true; + } + + esp_matter_attr_val_t onoffVal = esp_matter_invalid(NULL); + + if (!getAttributeVal(OnOff::Id, OnOff::Attributes::OnOff::Id, &onoffVal)) { + log_e("Failed to get Pressure Sensor Attribute."); + return false; + } + if (onoffVal.val.b != newState) { + onoffVal.val.b = newState; + bool ret; + ret = updateAttributeVal(OnOff::Id, OnOff::Attributes::OnOff::Id, &onoffVal); + if (!ret) { + log_e("Failed to update Pressure Sensor Measurement Attribute."); + return false; + } + onOffState = newState; + } + log_v("Plugin OnOff state set to %s", newState ? "ON" : "OFF"); + return true; +} + +bool MatterOnOffPlugin::getOnOff() { + return onOffState; +} + +bool MatterOnOffPlugin::toggle() { + return setOnOff(!onOffState); +} + +MatterOnOffPlugin::operator bool() { + return getOnOff(); +} + +void MatterOnOffPlugin::operator=(bool newState) { + setOnOff(newState); +} +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.h b/libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.h new file mode 100644 index 00000000000..241726a3a46 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterOnOffPlugin.h @@ -0,0 +1,56 @@ +// 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 + +class MatterOnOffPlugin : public MatterEndPoint { +public: + MatterOnOffPlugin(); + ~MatterOnOffPlugin(); + virtual bool begin(bool initialState = false); // default initial state is off + void end(); // this will just stop processing Plugin Matter events + + bool setOnOff(bool newState); // returns true if successful + bool getOnOff(); // returns current plugin state + bool toggle(); // returns true if successful + + // used to update the state of the plugin using the current Matter Plugin internal state + // It is necessary to set a user callback function using onChange() to handle the physical plugin state + void updateAccessory(); + + operator bool(); // returns current plugin state + void operator=(bool state); // turns plugin on or off + // 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); + // User Callback for whenever the Plugin state is changed by the Matter Controller + using EndPointCB = std::function; + void onChange(EndPointCB onChangeCB) { + _onChangeCB = onChangeCB; + } + void onChangeOnOff(EndPointCB onChangeCB) { + _onChangeOnOffCB = onChangeCB; + } + +protected: + bool started = false; + bool onOffState = false; // default initial state is off, but it can be changed by begin(bool) + EndPointCB _onChangeCB = NULL; + EndPointCB _onChangeOnOffCB = NULL; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */