From bc1c55f499e7e35e09af1a489915fc7927d5c700 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Fri, 6 Dec 2024 18:19:22 -0300 Subject: [PATCH 1/4] feat(matter): creates new matter fan controller endpoint --- CMakeLists.txt | 1 + .../Matter/examples/MatterFan/MatterFan.ino | 173 +++++++++++++ libraries/Matter/examples/MatterFan/ci.json | 7 + libraries/Matter/keywords.txt | 34 ++- libraries/Matter/src/Matter.h | 2 + libraries/Matter/src/MatterEndPoint.h | 72 ++++++ .../Matter/src/MatterEndpoints/MatterFan.cpp | 233 ++++++++++++++++++ .../Matter/src/MatterEndpoints/MatterFan.h | 163 ++++++++++++ 8 files changed, 684 insertions(+), 1 deletion(-) create mode 100644 libraries/Matter/examples/MatterFan/MatterFan.ino create mode 100644 libraries/Matter/examples/MatterFan/ci.json create mode 100644 libraries/Matter/src/MatterEndpoints/MatterFan.cpp create mode 100644 libraries/Matter/src/MatterEndpoints/MatterFan.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 59035e50774..e4991d558d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,7 @@ set(ARDUINO_LIBRARY_Matter_SRCS libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp libraries/Matter/src/MatterEndpoints/MatterColorLight.cpp libraries/Matter/src/MatterEndpoints/MatterEnhancedColorLight.cpp + libraries/Matter/src/MatterEndpoints/MatterFan.cpp libraries/Matter/src/Matter.cpp) set(ARDUINO_LIBRARY_PPP_SRCS diff --git a/libraries/Matter/examples/MatterFan/MatterFan.ino b/libraries/Matter/examples/MatterFan/MatterFan.ino new file mode 100644 index 00000000000..a6113b2d621 --- /dev/null +++ b/libraries/Matter/examples/MatterFan/MatterFan.ino @@ -0,0 +1,173 @@ +// 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 +// Fan Endpoint - On/Off control + Speed Percent Control + Fan Modes +MatterFan Fan; + +// set your board USER BUTTON pin here - used for toggling On/Off +const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9. + +// set your board Analog Pin here - used for changing the Fan speed +const uint8_t analogPin = A0; // Analog Pin depends on each board + +// 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 toggle the Fan (On/Off) + pinMode(buttonPin, INPUT_PULLUP); + // Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly + pinMode(analogPin, INPUT); + analogReadResolution(10); // 10 bits resolution reading 0..1023 + + 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); + + // On Boot or Reset, Fan is set at 0% speed, OFF, changing between OFF, ON, SMART and HIGH + Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH); + + // callback functions would control Fan motor + // the Matter Controller will send new data whenver the User APP or Automation request + + // single feature callbacks take place before the generic (all features) callback + // This callback will be executed whenever the speed percent matter attribute is updated + Fan.onChangeSpeedPercent([](uint8_t speedPercent) { + // setting speed to Zero, while the Fan is ON, shall turn the Fan OFF + if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) { + // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + return Fan.setOnOff(false, Fan.ATTR_SET); + } + // changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON + if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) { + // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + return Fan.setOnOff(true, Fan.ATTR_SET); + } + // for other case, just return true + return true; + }); + + // This callback will be executed whenever the fan mode matter attribute is updated + // This will take action when user APP starts the Fan by changing the mode + Fan.onChangeMode([](MatterFan::FanMode_t fanMode) { + // when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50% + if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) { + Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode)); + // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + return Fan.setSpeedPercent(50, Fan.ATTR_SET); + } + return true; + }); + + // Generic callback will be executed as soon as a single feature callback is done + // In this example, it will just print status messages + Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) { + // just report state + Serial.printf("Fan State: Mode %s | %d%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent); + // returns success + return true; + }); + + // 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."); + } +} + +// Builtin 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 and send 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 + // button is released - toggle Fan On/Off + Fan.toggle(); + Serial.printf("User button released. Setting the Fan %s.\r\n", Fan > 0 ? "ON" : "OFF"); + + // 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(); + } + } + + // checks Analog pin and adjust the speed only if it has changed + static int lastRead = 0; + // analog values (0..1023) / 103 => mapped into 10 steps (0..9) + int anaVal = analogRead(analogPin) / 103; + if (lastRead != anaVal) { + // speed percent moves in steps of 10. Range is 10..100 + if (Fan.setSpeedPercent((anaVal + 1) * 10)) { + lastRead = anaVal; + } + } +} diff --git a/libraries/Matter/examples/MatterFan/ci.json b/libraries/Matter/examples/MatterFan/ci.json new file mode 100644 index 00000000000..0665800b12b --- /dev/null +++ b/libraries/Matter/examples/MatterFan/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 597bbac657b..4668054d12e 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -15,6 +15,9 @@ MatterColorTemperatureLight KEYWORD1 MatterColorLight KEYWORD1 MatterEnhancedColorLight KEYWORD1 MatterEndPoint KEYWORD1 +MatterFan KEYWORD1 +FanMode_t KEYWORD1 +FanModeSequence_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -32,6 +35,7 @@ decommission KEYWORD2 attributeChangeCB KEYWORD2 setOnOff KEYWORD2 getOnOff KEYWORD2 +toggle KEYWORD2 setBrightness KEYWORD2 getBrightness KEYWORD2 setColorTemperature KEYWORD2 @@ -40,7 +44,6 @@ setColorRGB KEYWORD2 getColorRGB KEYWORD2 setColorHSV KEYWORD2 getColorHSV KEYWORD2 -toggle KEYWORD2 updateAccessory KEYWORD2 onChange KEYWORD2 onChangeOnOff KEYWORD2 @@ -48,6 +51,17 @@ onChangeBrightness KEYWORD2 onChangeColorTemperature KEYWORD2 onChangeColorHSV KEYWORD2 click KEYWORD2 +getAttribute KEYWORD2 +getAttributeVal KEYWORD2 +setAttributeVal KEYWORD2 +updateAttributeVal KEYWORD2 +getFanModeString KEYWORD2 +setSpeedPercent KEYWORD2 +getSpeedPercent KEYWORD2 +setMode KEYWORD2 +getMode KEYWORD2 +onChangeMode KEYWORD2 +onChangeSpeedPercent KEYWORD2 ####################################### # Constants (LITERAL1) @@ -56,3 +70,21 @@ click KEYWORD2 MAX_BRIGHTNESS LITERAL1 MAX_COLOR_TEMPERATURE LITERAL1 MIN_COLOR_TEMPERATURE LITERAL1 +ATTR_SET LITERAL1 +ATTR_UPDATE LITERAL1 +MAX_SPEED LITERAL1 +MIN_SPEED LITERAL1 +OFF_SPEED LITERAL1 +FAN_MODE_OFF LITERAL1 +FAN_MODE_LOW LITERAL1 +FAN_MODE_MEDIUM LITERAL1 +FAN_MODE_HIGH LITERAL1 +FAN_MODE_ON LITERAL1 +FAN_MODE_AUTO LITERAL1 +FAN_MODE_SMART LITERAL1 +FAN_MODE_SEQ_OFF_LOW_MED_HIGH LITERAL1 +FAN_MODE_SEQ_OFF_LOW_HIGH LITERAL1 +FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO LITERAL1 +FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO LITERAL1 +FAN_MODE_SEQ_OFF_HIGH_AUTO LITERAL1 +FAN_MODE_SEQ_OFF_HIGH LITERAL1 diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index 9136eead048..06edf32288f 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -25,6 +25,7 @@ #include #include #include +#include using namespace esp_matter; @@ -56,6 +57,7 @@ class ArduinoMatter { friend class MatterColorTemperatureLight; friend class MatterColorLight; friend class MatterEnhancedColorLight; + friend class MatterFan; protected: static void _init(); diff --git a/libraries/Matter/src/MatterEndPoint.h b/libraries/Matter/src/MatterEndPoint.h index 02577957e8e..5c352d7ef57 100644 --- a/libraries/Matter/src/MatterEndPoint.h +++ b/libraries/Matter/src/MatterEndPoint.h @@ -19,15 +19,87 @@ #include #include +using namespace esp_matter; + // Matter Endpoint Base Class. Controls the endpoint ID and allows the child class to overwrite attribute change call class MatterEndPoint { public: + + enum attrOperation_t { + ATTR_SET = false, + ATTR_UPDATE = true + }; + uint16_t getEndPointId() { return endpoint_id; } + void setEndPointId(uint16_t ep) { endpoint_id = ep; } + + // helper functions for attribute manipulation + attribute_t * getAttribute(uint32_t cluster_id, uint32_t attribute_id){ + if (endpoint_id == 0) { + log_e("Endpoint ID is not set"); + return NULL; + } + endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id); + if (endpoint == NULL) { + log_e("Endpoint [%d] not found", endpoint_id); + return NULL; + } + cluster_t *cluster = cluster::get(endpoint, cluster_id); + if (cluster == NULL) { + log_e("Cluster [%d] not found", cluster_id); + return NULL; + } + attribute_t *attribute = attribute::get(cluster, attribute_id); + if (attribute == NULL) { + log_e("Attribute [%d] not found", attribute_id); + return NULL; + } + return attribute; + } + + // get the value of an attribute from its cluster id and attribute it + bool getAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){ + attribute_t *attribute = getAttribute(cluster_id, attribute_id); + if (attribute == NULL) { + return false; + } + if (attribute::get_val(attribute, attrVal) == ESP_OK) { + log_v("GET_VAL Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + return true; + } + log_e("GET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + return false; + } + + // set the value of an attribute from its cluster id and attribute it + bool setAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){ + attribute_t *attribute = getAttribute(cluster_id, attribute_id); + if (attribute == NULL) { + return false; + } + if (attribute::set_val(attribute, attrVal) == ESP_OK) { + log_v("SET_VAL Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + return true; + } + log_e("SET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + return false; + } + + // update the value of an attribute from its cluster id and attribute it + bool updateAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){ + if (attribute::update(endpoint_id, cluster_id, attribute_id, attrVal) == ESP_OK) { + log_v("Update Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + return true; + } + log_e("Update FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + return false; + } + // this function is called by Matter internal event processor. It could be overwritten by the application, if necessary. virtual bool attributeChangeCB(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val) = 0; diff --git a/libraries/Matter/src/MatterEndpoints/MatterFan.cpp b/libraries/Matter/src/MatterEndpoints/MatterFan.cpp new file mode 100644 index 00000000000..3bd7727794d --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterFan.cpp @@ -0,0 +1,233 @@ +// 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 + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace esp_matter::cluster; +using namespace chip::app::Clusters; + +// string helper for the FAN MODE +const char *MatterFan::fanModeString[7] = {"OFF", "LOW", "MEDIUM", "HIGH", "ON", "AUTO", "SMART"}; +// bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums +const uint8_t MatterFan::fanModeSequence[6] = { + fanSeqModeOffLowMedHigh, fanSeqModeOffLowHigh, fanSeqModeOffLowMedHighAuto, + fanSeqModeOffLowHighAuto, fanSeqModeOffHighAuto, fanSeqModeOffHigh +}; + +// Constructor and Method Definitions +MatterFan::MatterFan() { +} + +MatterFan::~MatterFan() { + end(); +} + +bool MatterFan::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 Fan device has not begun."); + return false; + } + + log_d("Fan Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + + if (endpoint_id == getEndPointId() && cluster_id == FanControl::Id) { + switch (attribute_id) { + case FanControl::Attributes::FanMode::Id: + log_v("FanControl Fan Mode changed to %s (%x)", val->val.u8 < 7 ? fanModeString[val->val.u8] : "Unknown", val->val.u8); + if (_onChangeModeCB != NULL) { + ret &= _onChangeModeCB((FanMode_t) val->val.u8); + } + if ( _onChangeCB != NULL) { + ret &= _onChangeCB((FanMode_t) val->val.u8, currentPercent); + } + if (ret == true) { + currentFanMode = (FanMode_t) val->val.u8; + } + break; + case FanControl::Attributes::PercentSetting::Id: + case FanControl::Attributes::PercentCurrent::Id: + log_v("FanControl Percent %s changed to %d", attribute_id == FanControl::Attributes::PercentSetting::Id ? "SETTING" : "CURRENT", val->val.u8); + if (_onChangeSpeedCB != NULL) { + ret &= _onChangeSpeedCB(val->val.u8); + } + if ( _onChangeCB != NULL) { + ret &= _onChangeCB(currentFanMode, val->val.u8); + } + if (ret == true) { + // change setting speed percent + currentPercent = val->val.u8; + setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, val); + setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, val); + } + break; + } + } + + return ret; +} + +bool MatterFan::begin(uint8_t percent, FanMode_t fanMode, FanModeSequence_t fanModeSeq) { + ArduinoMatter::_init(); + + // endpoint handles can be used to add/modify clusters. + fan::config_t fan_config; + fan_config.fan_control.fan_mode = fanMode; + fan_config.fan_control.percent_current = percent; + fan_config.fan_control.percent_setting = percent; + fan_config.fan_control.fan_mode_sequence = fanModeSeq; + validFanModes = fanModeSequence[fanModeSeq]; + + endpoint_t *endpoint = fan::create(node::get(), &fan_config, ENDPOINT_FLAG_NONE, (void *)this); + + if (endpoint == nullptr) { + log_e("Failed to create Generic swtich endpoint"); + return false; + } + + currentFanMode = fanMode; + currentPercent = percent; + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Fan created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterFan::end() { + started = false; +} + +bool MatterFan::setMode(FanMode_t newMode, bool performUpdate) { + if (!started) { + log_w("Matter Fan device has not begun."); + return false; + } + // avoid processing the a "no-change" + if (currentFanMode == newMode) { + return true; + } + + // check if the mode is valid based on the sequence used in its creation + if (!(validFanModes & (1 << newMode))) { + log_e("Invalid Fan Mode %s for the current Fan Mode Sequence.", fanModeString[newMode]); + return false; + } + + esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL); + if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) { + log_e("Failed to get Fan Mode Attribute."); + return false; + } + if (modeVal.val.u8 != (uint8_t) newMode) { + modeVal.val.u8 = (uint8_t) newMode; + bool ret; + if (performUpdate) { + ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal); + } else { + ret = setAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal); + } + if (!ret) { + log_e("Failed to %s Fan Mode Attribute.", performUpdate ? "update" : "set"); + return false; + } + } + currentFanMode = newMode; + log_v("Fan Mode %s to %s ==> onOffState[%s]", performUpdate ? "updated" : "set", fanModeString[currentFanMode], getOnOff() ? "ON" : "OFF"); + return true; + +} + +// this function will change the Fan Speed by callin the user application callback +// it is up to the application to decide to turn on, off or change the speed of the fan +bool MatterFan::setSpeedPercent(uint8_t newPercent, bool performUpdate) { + if (!started) { + log_w("Matter Fan device has not begun."); + return false; + } + // avoid processing the a "no-change" + if (currentPercent == newPercent) { + return true; + } + + esp_matter_attr_val_t speedVal = esp_matter_invalid(NULL); + if (!getAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal)) { + log_e("Failed to get Fan Speed Percent Attribute."); + return false; + } + if (speedVal.val.u8 != newPercent) { + speedVal.val.u8 = newPercent; + bool ret; + if (performUpdate) { + ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal); + } else { + ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal); + ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, &speedVal); + } + if (!ret) { + log_e("Failed to %s Fan Speed Percent Attribute.", performUpdate ? "update" : "set"); + return false; + } + } + currentPercent = newPercent; + log_v("Fan Speed %s to %d ==> onOffState[%s]", performUpdate ? "updated" : "set", currentPercent, getOnOff() ? "ON" : "OFF"); + return true; +} + +bool MatterFan::setOnOff(bool newState, bool performUpdate) { + if (!started) { + log_w("Matter Fan device has not begun."); + return false; + } + // avoid processing the a "no-change" + if (getOnOff() == newState) { + return true; + } + + esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL); + if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) { + log_e("Failed to get Fan Mode Attribute."); + return false; + } + if (modeVal.val.u8 != (uint8_t) newState) { + FanMode_t newMode = newState ? FAN_MODE_ON : FAN_MODE_OFF; + if (!setMode(newMode, performUpdate)) { + return false; + } + } + log_v("Fan State %s to %s :: Mode[%s]|Speed[%d]", performUpdate ? "updated" : "set", getOnOff() ? "ON" : "OFF", fanModeString[currentFanMode], currentPercent); + return true; +} + +bool MatterFan::getOnOff() { + return currentFanMode == FAN_MODE_OFF ? false : true; +} + +bool MatterFan::toggle(bool performUpdate) { + if (getOnOff() == true) { + return setOnOff(false, performUpdate); + } else { + return setOnOff(true, performUpdate); + } +} + + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterFan.h b/libraries/Matter/src/MatterEndpoints/MatterFan.h new file mode 100644 index 00000000000..c9da10c5073 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterFan.h @@ -0,0 +1,163 @@ +// 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 +#include + +using namespace chip::app::Clusters::FanControl; + +// Matter Fan endpoint with On/Off, Mode and Speed control + +class MatterFan : public MatterEndPoint { +public: + // Fan feature constants + static const uint8_t MAX_SPEED = 100; // maximum High speed + static const uint8_t MIN_SPEED = 1; // minimum Low speed + static const uint8_t OFF_SPEED = 0; // speed set by Matter when FAN_MODE_OFF + + // Default Fan Modes: ON, SMART, HIGH and OFF + + // Other mode will depend on what is the configured Fan Mode Sequence + enum FanMode_t { + FAN_MODE_OFF = (uint8_t) FanModeEnum::kOff, + FAN_MODE_LOW = (uint8_t) FanModeEnum::kLow, + FAN_MODE_MEDIUM = (uint8_t) FanModeEnum::kMedium, + FAN_MODE_HIGH = (uint8_t) FanModeEnum::kHigh, + FAN_MODE_ON = (uint8_t) FanModeEnum::kOn, + FAN_MODE_AUTO = (uint8_t) FanModeEnum::kAuto, + FAN_MODE_SMART = (uint8_t) FanModeEnum::kSmart + }; + + // Menu will always have ON, OFF, HIGH and SMART. + // AUTO will show up only when a AUTO SEQ is CONFIGURED + // LOW and MEDIUM depend on the SEQ MODE configuration + enum FanModeSequence_t { + FAN_MODE_SEQ_OFF_LOW_MED_HIGH = (uint8_t) FanModeSequenceEnum::kOffLowMedHigh, + FAN_MODE_SEQ_OFF_LOW_HIGH = (uint8_t) FanModeSequenceEnum::kOffLowHigh, + FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO = (uint8_t) FanModeSequenceEnum::kOffLowMedHighAuto, + FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO = (uint8_t) FanModeSequenceEnum::kOffLowHighAuto, + FAN_MODE_SEQ_OFF_HIGH_AUTO = (uint8_t) FanModeSequenceEnum::kOffHighAuto, + FAN_MODE_SEQ_OFF_HIGH = (uint8_t) FanModeSequenceEnum::kOffHigh + }; + + MatterFan(); + ~MatterFan(); + virtual bool begin(uint8_t percent = 0, FanMode_t fanMode = FAN_MODE_OFF, FanModeSequence_t fanModeSeq = FAN_MODE_SEQ_OFF_HIGH); + void end(); // this will just stop processing Matter events + + // returns a friendly string for the Fan Mode + static const char *getFanModeString(uint8_t mode) { + return fanModeString[mode]; + } + + // Fan Control of current On/Off state + + bool setOnOff(bool newState, bool performUpdate = true); // sets Fan On/Off state + bool getOnOff(); // returns current Fan state + bool toggle(bool performUpdate = true); // toggle Fun On/Off state + + // Fan Control of current speed percent + + bool setSpeedPercent(uint8_t newPercent, bool performUpdate = true); // returns true if successful + uint8_t getSpeedPercent() { // returns current Fan Speed Percent + return currentPercent; + } + + // Fan Control of current Fan Mode + + bool setMode(FanMode_t newMode, bool performUpdate = true); // returns true if successful + FanMode_t getMode() { // returns current Fan Mode + return currentFanMode; + } + // used to update the state of the Fan using the current Matter Fan internal state + // It is necessary to set a user callback function using onChange() to handle the physical Fan motor state + + void updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(currentFanMode, currentPercent); + } + } + + // returns current Fan speed percent + operator uint8_t() { + return getSpeedPercent(); + } + // sets Fan speed percent + void operator=(uint8_t speedPercent) { + setSpeedPercent(speedPercent); + } + + // 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 Fan Mode (state) is changed by the Matter Controller + using EndPointModeCB = std::function; + void onChangeMode(EndPointModeCB onChangeCB) { + _onChangeModeCB = onChangeCB; + } + + // User Callback for whenever the Fan Speed Percentage value [0..100] is changed by the Matter Controller + using EndPointSpeedCB = std::function; + void onChangeSpeedPercent(EndPointSpeedCB onChangeCB) { + _onChangeSpeedCB = onChangeCB; + } + + // User Callback for whenever any parameter is changed by the Matter Controller + using EndPointCB = std::function; + void onChange(EndPointCB onChangeCB) { + _onChangeCB = onChangeCB; + } + +protected: + bool started = false; + uint8_t validFanModes = 0; // bitmap for valid Fan Modes - index of fanModeSequence[] + + uint8_t currentPercent = 0; // current speed percent + FanMode_t currentFanMode = FAN_MODE_OFF; // current Fan Mode + EndPointModeCB _onChangeModeCB = NULL; + EndPointSpeedCB _onChangeSpeedCB = NULL; + EndPointCB _onChangeCB = NULL; + + // bitmap for Fan Sequence Modes (OFF, LOW, MEDIUM, HIGH, AUTO) + static const uint8_t fanSeqModeOff = 0x01; + static const uint8_t fanSeqModeLow = 0x02; + static const uint8_t fanSeqModeMedium = 0x04; + static const uint8_t fanSeqModeHigh = 0x08; + static const uint8_t fanSeqModeOn = 0x10; + static const uint8_t fanSeqModeAuto = 0x20; + static const uint8_t fanSeqModeSmart = 0x40; + + // bitmap for common modes: ON, OFF, HIGH and SMART + static const uint8_t fanSeqCommonModes = fanSeqModeOff | fanSeqModeOn | fanSeqModeHigh | fanSeqModeSmart; + + static const uint8_t fanSeqModeOffLowMedHigh = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium; + static const uint8_t fanSeqModeOffLowHigh = fanSeqCommonModes | fanSeqModeLow; + static const uint8_t fanSeqModeOffLowMedHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium | fanSeqModeAuto; + static const uint8_t fanSeqModeOffLowHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeAuto; + static const uint8_t fanSeqModeOffHighAuto = fanSeqCommonModes | fanSeqModeAuto; + static const uint8_t fanSeqModeOffHigh = fanSeqCommonModes; + + // bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums + static const uint8_t fanModeSequence[6]; + + // string helper for the FAN MODE + static const char *fanModeString[7]; +}; + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ From 435f21750a1fd6046f789a0b5b5c0a8e0f2baed4 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 9 Dec 2024 09:10:14 -0300 Subject: [PATCH 2/4] feat(matter): adds fan motor driver routine to the example --- .../Matter/examples/MatterFan/MatterFan.ino | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/libraries/Matter/examples/MatterFan/MatterFan.ino b/libraries/Matter/examples/MatterFan/MatterFan.ino index a6113b2d621..20b42bc8c9c 100644 --- a/libraries/Matter/examples/MatterFan/MatterFan.ino +++ b/libraries/Matter/examples/MatterFan/MatterFan.ino @@ -26,17 +26,44 @@ const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use // set your board Analog Pin here - used for changing the Fan speed const uint8_t analogPin = A0; // Analog Pin depends on each board +// set your board PWM Pin here - used for controlling the Fan speed (DC motor example) +// for this example, it will use the builtin board RGB LED to simulate the Fan DC motor using its brightness +#ifdef RGB_BUILTIN +const uint8_t dcMotorPin = RGB_BUILTIN; +#else +const uint8_t dcMotorPin = 2; // Set your pin here if your board has not defined LED_BUILTIN +#warning "Do not forget to set the RGB LED pin" +#endif + // 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 fanDCMotorDrive(bool fanState, uint8_t speedPercent) { + // drive the Fan DC motor + if (fanState == false) { + // turn off the Fan + digitalWrite(dcMotorPin, LOW); + } else { + // set the Fan speed + uint8_t fanDCMotorPWM = map(speedPercent, 0, 100, 0, 255); +#ifdef RGB_BUILTIN + rgbLedWrite(dcMotorPin, fanDCMotorPWM, fanDCMotorPWM, fanDCMotorPWM); +#else + analogWrite(dcMotorPin, fanDCMotorPWM); +#endif + } +} + void setup() { // Initialize the USER BUTTON (Boot button) GPIO that will toggle the Fan (On/Off) pinMode(buttonPin, INPUT_PULLUP); // Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly pinMode(analogPin, INPUT); analogReadResolution(10); // 10 bits resolution reading 0..1023 - + // Initialize the PWM output pin for a Fan DC motor + pinMode(dcMotorPin, OUTPUT); + Serial.begin(115200); while (!Serial) { delay(100); @@ -99,6 +126,8 @@ void setup() { Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) { // just report state Serial.printf("Fan State: Mode %s | %d%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent); + // drive the Fan DC motor + fanDCMotorDrive(fanMode != MatterFan::FAN_MODE_OFF, speedPercent); // returns success return true; }); From 0fd4cce733bf59ccb3b9f89176cdecf4df17daae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:27:15 +0000 Subject: [PATCH 3/4] ci(pre-commit): Apply automatic fixes --- .../Matter/examples/MatterFan/MatterFan.ino | 404 +++++++-------- libraries/Matter/src/MatterEndPoint.h | 13 +- .../Matter/src/MatterEndpoints/MatterFan.cpp | 463 +++++++++--------- .../Matter/src/MatterEndpoints/MatterFan.h | 326 ++++++------ 4 files changed, 601 insertions(+), 605 deletions(-) diff --git a/libraries/Matter/examples/MatterFan/MatterFan.ino b/libraries/Matter/examples/MatterFan/MatterFan.ino index 20b42bc8c9c..d124e8a9232 100644 --- a/libraries/Matter/examples/MatterFan/MatterFan.ino +++ b/libraries/Matter/examples/MatterFan/MatterFan.ino @@ -1,202 +1,202 @@ -// 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 -// Fan Endpoint - On/Off control + Speed Percent Control + Fan Modes -MatterFan Fan; - -// set your board USER BUTTON pin here - used for toggling On/Off -const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9. - -// set your board Analog Pin here - used for changing the Fan speed -const uint8_t analogPin = A0; // Analog Pin depends on each board - -// set your board PWM Pin here - used for controlling the Fan speed (DC motor example) -// for this example, it will use the builtin board RGB LED to simulate the Fan DC motor using its brightness -#ifdef RGB_BUILTIN -const uint8_t dcMotorPin = RGB_BUILTIN; -#else -const uint8_t dcMotorPin = 2; // Set your pin here if your board has not defined LED_BUILTIN -#warning "Do not forget to set the RGB LED pin" -#endif - -// 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 fanDCMotorDrive(bool fanState, uint8_t speedPercent) { - // drive the Fan DC motor - if (fanState == false) { - // turn off the Fan - digitalWrite(dcMotorPin, LOW); - } else { - // set the Fan speed - uint8_t fanDCMotorPWM = map(speedPercent, 0, 100, 0, 255); -#ifdef RGB_BUILTIN - rgbLedWrite(dcMotorPin, fanDCMotorPWM, fanDCMotorPWM, fanDCMotorPWM); -#else - analogWrite(dcMotorPin, fanDCMotorPWM); -#endif - } -} - -void setup() { - // Initialize the USER BUTTON (Boot button) GPIO that will toggle the Fan (On/Off) - pinMode(buttonPin, INPUT_PULLUP); - // Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly - pinMode(analogPin, INPUT); - analogReadResolution(10); // 10 bits resolution reading 0..1023 - // Initialize the PWM output pin for a Fan DC motor - pinMode(dcMotorPin, OUTPUT); - - 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); - - // On Boot or Reset, Fan is set at 0% speed, OFF, changing between OFF, ON, SMART and HIGH - Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH); - - // callback functions would control Fan motor - // the Matter Controller will send new data whenver the User APP or Automation request - - // single feature callbacks take place before the generic (all features) callback - // This callback will be executed whenever the speed percent matter attribute is updated - Fan.onChangeSpeedPercent([](uint8_t speedPercent) { - // setting speed to Zero, while the Fan is ON, shall turn the Fan OFF - if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) { - // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop - return Fan.setOnOff(false, Fan.ATTR_SET); - } - // changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON - if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) { - // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop - return Fan.setOnOff(true, Fan.ATTR_SET); - } - // for other case, just return true - return true; - }); - - // This callback will be executed whenever the fan mode matter attribute is updated - // This will take action when user APP starts the Fan by changing the mode - Fan.onChangeMode([](MatterFan::FanMode_t fanMode) { - // when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50% - if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) { - Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode)); - // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop - return Fan.setSpeedPercent(50, Fan.ATTR_SET); - } - return true; - }); - - // Generic callback will be executed as soon as a single feature callback is done - // In this example, it will just print status messages - Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) { - // just report state - Serial.printf("Fan State: Mode %s | %d%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent); - // drive the Fan DC motor - fanDCMotorDrive(fanMode != MatterFan::FAN_MODE_OFF, speedPercent); - // returns success - return true; - }); - - // 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."); - } -} - -// Builtin 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 and send 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 - // button is released - toggle Fan On/Off - Fan.toggle(); - Serial.printf("User button released. Setting the Fan %s.\r\n", Fan > 0 ? "ON" : "OFF"); - - // 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(); - } - } - - // checks Analog pin and adjust the speed only if it has changed - static int lastRead = 0; - // analog values (0..1023) / 103 => mapped into 10 steps (0..9) - int anaVal = analogRead(analogPin) / 103; - if (lastRead != anaVal) { - // speed percent moves in steps of 10. Range is 10..100 - if (Fan.setSpeedPercent((anaVal + 1) * 10)) { - lastRead = anaVal; - } - } -} +// 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 +// Fan Endpoint - On/Off control + Speed Percent Control + Fan Modes +MatterFan Fan; + +// set your board USER BUTTON pin here - used for toggling On/Off +const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9. + +// set your board Analog Pin here - used for changing the Fan speed +const uint8_t analogPin = A0; // Analog Pin depends on each board + +// set your board PWM Pin here - used for controlling the Fan speed (DC motor example) +// for this example, it will use the builtin board RGB LED to simulate the Fan DC motor using its brightness +#ifdef RGB_BUILTIN +const uint8_t dcMotorPin = RGB_BUILTIN; +#else +const uint8_t dcMotorPin = 2; // Set your pin here if your board has not defined LED_BUILTIN +#warning "Do not forget to set the RGB LED pin" +#endif + +// 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 fanDCMotorDrive(bool fanState, uint8_t speedPercent) { + // drive the Fan DC motor + if (fanState == false) { + // turn off the Fan + digitalWrite(dcMotorPin, LOW); + } else { + // set the Fan speed + uint8_t fanDCMotorPWM = map(speedPercent, 0, 100, 0, 255); +#ifdef RGB_BUILTIN + rgbLedWrite(dcMotorPin, fanDCMotorPWM, fanDCMotorPWM, fanDCMotorPWM); +#else + analogWrite(dcMotorPin, fanDCMotorPWM); +#endif + } +} + +void setup() { + // Initialize the USER BUTTON (Boot button) GPIO that will toggle the Fan (On/Off) + pinMode(buttonPin, INPUT_PULLUP); + // Initialize the Analog Pin A0 used to read input voltage and to set the Fan speed accordingly + pinMode(analogPin, INPUT); + analogReadResolution(10); // 10 bits resolution reading 0..1023 + // Initialize the PWM output pin for a Fan DC motor + pinMode(dcMotorPin, OUTPUT); + + 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); + + // On Boot or Reset, Fan is set at 0% speed, OFF, changing between OFF, ON, SMART and HIGH + Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH); + + // callback functions would control Fan motor + // the Matter Controller will send new data whenver the User APP or Automation request + + // single feature callbacks take place before the generic (all features) callback + // This callback will be executed whenever the speed percent matter attribute is updated + Fan.onChangeSpeedPercent([](uint8_t speedPercent) { + // setting speed to Zero, while the Fan is ON, shall turn the Fan OFF + if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) { + // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + return Fan.setOnOff(false, Fan.ATTR_SET); + } + // changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON + if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) { + // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + return Fan.setOnOff(true, Fan.ATTR_SET); + } + // for other case, just return true + return true; + }); + + // This callback will be executed whenever the fan mode matter attribute is updated + // This will take action when user APP starts the Fan by changing the mode + Fan.onChangeMode([](MatterFan::FanMode_t fanMode) { + // when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50% + if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) { + Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode)); + // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + return Fan.setSpeedPercent(50, Fan.ATTR_SET); + } + return true; + }); + + // Generic callback will be executed as soon as a single feature callback is done + // In this example, it will just print status messages + Fan.onChange([](MatterFan::FanMode_t fanMode, uint8_t speedPercent) { + // just report state + Serial.printf("Fan State: Mode %s | %d%% speed.\r\n", Fan.getFanModeString(fanMode), speedPercent); + // drive the Fan DC motor + fanDCMotorDrive(fanMode != MatterFan::FAN_MODE_OFF, speedPercent); + // returns success + return true; + }); + + // 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."); + } +} + +// Builtin 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 and send 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 + // button is released - toggle Fan On/Off + Fan.toggle(); + Serial.printf("User button released. Setting the Fan %s.\r\n", Fan > 0 ? "ON" : "OFF"); + + // 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(); + } + } + + // checks Analog pin and adjust the speed only if it has changed + static int lastRead = 0; + // analog values (0..1023) / 103 => mapped into 10 steps (0..9) + int anaVal = analogRead(analogPin) / 103; + if (lastRead != anaVal) { + // speed percent moves in steps of 10. Range is 10..100 + if (Fan.setSpeedPercent((anaVal + 1) * 10)) { + lastRead = anaVal; + } + } +} diff --git a/libraries/Matter/src/MatterEndPoint.h b/libraries/Matter/src/MatterEndPoint.h index 5c352d7ef57..dd0d91b1ca2 100644 --- a/libraries/Matter/src/MatterEndPoint.h +++ b/libraries/Matter/src/MatterEndPoint.h @@ -24,22 +24,21 @@ using namespace esp_matter; // Matter Endpoint Base Class. Controls the endpoint ID and allows the child class to overwrite attribute change call class MatterEndPoint { public: - enum attrOperation_t { - ATTR_SET = false, + ATTR_SET = false, ATTR_UPDATE = true }; uint16_t getEndPointId() { return endpoint_id; } - + void setEndPointId(uint16_t ep) { endpoint_id = ep; } // helper functions for attribute manipulation - attribute_t * getAttribute(uint32_t cluster_id, uint32_t attribute_id){ + attribute_t *getAttribute(uint32_t cluster_id, uint32_t attribute_id) { if (endpoint_id == 0) { log_e("Endpoint ID is not set"); return NULL; @@ -63,7 +62,7 @@ class MatterEndPoint { } // get the value of an attribute from its cluster id and attribute it - bool getAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){ + bool getAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) { attribute_t *attribute = getAttribute(cluster_id, attribute_id); if (attribute == NULL) { return false; @@ -77,7 +76,7 @@ class MatterEndPoint { } // set the value of an attribute from its cluster id and attribute it - bool setAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){ + bool setAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) { attribute_t *attribute = getAttribute(cluster_id, attribute_id); if (attribute == NULL) { return false; @@ -91,7 +90,7 @@ class MatterEndPoint { } // update the value of an attribute from its cluster id and attribute it - bool updateAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal){ + bool updateAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) { if (attribute::update(endpoint_id, cluster_id, attribute_id, attrVal) == ESP_OK) { log_v("Update Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); return true; diff --git a/libraries/Matter/src/MatterEndpoints/MatterFan.cpp b/libraries/Matter/src/MatterEndpoints/MatterFan.cpp index 3bd7727794d..3992be61c09 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterFan.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterFan.cpp @@ -1,233 +1,230 @@ -// 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 - -using namespace esp_matter; -using namespace esp_matter::endpoint; -using namespace esp_matter::cluster; -using namespace chip::app::Clusters; - -// string helper for the FAN MODE -const char *MatterFan::fanModeString[7] = {"OFF", "LOW", "MEDIUM", "HIGH", "ON", "AUTO", "SMART"}; -// bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums -const uint8_t MatterFan::fanModeSequence[6] = { - fanSeqModeOffLowMedHigh, fanSeqModeOffLowHigh, fanSeqModeOffLowMedHighAuto, - fanSeqModeOffLowHighAuto, fanSeqModeOffHighAuto, fanSeqModeOffHigh -}; - -// Constructor and Method Definitions -MatterFan::MatterFan() { -} - -MatterFan::~MatterFan() { - end(); -} - -bool MatterFan::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 Fan device has not begun."); - return false; - } - - log_d("Fan Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); - - if (endpoint_id == getEndPointId() && cluster_id == FanControl::Id) { - switch (attribute_id) { - case FanControl::Attributes::FanMode::Id: - log_v("FanControl Fan Mode changed to %s (%x)", val->val.u8 < 7 ? fanModeString[val->val.u8] : "Unknown", val->val.u8); - if (_onChangeModeCB != NULL) { - ret &= _onChangeModeCB((FanMode_t) val->val.u8); - } - if ( _onChangeCB != NULL) { - ret &= _onChangeCB((FanMode_t) val->val.u8, currentPercent); - } - if (ret == true) { - currentFanMode = (FanMode_t) val->val.u8; - } - break; - case FanControl::Attributes::PercentSetting::Id: - case FanControl::Attributes::PercentCurrent::Id: - log_v("FanControl Percent %s changed to %d", attribute_id == FanControl::Attributes::PercentSetting::Id ? "SETTING" : "CURRENT", val->val.u8); - if (_onChangeSpeedCB != NULL) { - ret &= _onChangeSpeedCB(val->val.u8); - } - if ( _onChangeCB != NULL) { - ret &= _onChangeCB(currentFanMode, val->val.u8); - } - if (ret == true) { - // change setting speed percent - currentPercent = val->val.u8; - setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, val); - setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, val); - } - break; - } - } - - return ret; -} - -bool MatterFan::begin(uint8_t percent, FanMode_t fanMode, FanModeSequence_t fanModeSeq) { - ArduinoMatter::_init(); - - // endpoint handles can be used to add/modify clusters. - fan::config_t fan_config; - fan_config.fan_control.fan_mode = fanMode; - fan_config.fan_control.percent_current = percent; - fan_config.fan_control.percent_setting = percent; - fan_config.fan_control.fan_mode_sequence = fanModeSeq; - validFanModes = fanModeSequence[fanModeSeq]; - - endpoint_t *endpoint = fan::create(node::get(), &fan_config, ENDPOINT_FLAG_NONE, (void *)this); - - if (endpoint == nullptr) { - log_e("Failed to create Generic swtich endpoint"); - return false; - } - - currentFanMode = fanMode; - currentPercent = percent; - - setEndPointId(endpoint::get_id(endpoint)); - log_i("Fan created with endpoint_id %d", getEndPointId()); - started = true; - return true; -} - -void MatterFan::end() { - started = false; -} - -bool MatterFan::setMode(FanMode_t newMode, bool performUpdate) { - if (!started) { - log_w("Matter Fan device has not begun."); - return false; - } - // avoid processing the a "no-change" - if (currentFanMode == newMode) { - return true; - } - - // check if the mode is valid based on the sequence used in its creation - if (!(validFanModes & (1 << newMode))) { - log_e("Invalid Fan Mode %s for the current Fan Mode Sequence.", fanModeString[newMode]); - return false; - } - - esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL); - if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) { - log_e("Failed to get Fan Mode Attribute."); - return false; - } - if (modeVal.val.u8 != (uint8_t) newMode) { - modeVal.val.u8 = (uint8_t) newMode; - bool ret; - if (performUpdate) { - ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal); - } else { - ret = setAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal); - } - if (!ret) { - log_e("Failed to %s Fan Mode Attribute.", performUpdate ? "update" : "set"); - return false; - } - } - currentFanMode = newMode; - log_v("Fan Mode %s to %s ==> onOffState[%s]", performUpdate ? "updated" : "set", fanModeString[currentFanMode], getOnOff() ? "ON" : "OFF"); - return true; - -} - -// this function will change the Fan Speed by callin the user application callback -// it is up to the application to decide to turn on, off or change the speed of the fan -bool MatterFan::setSpeedPercent(uint8_t newPercent, bool performUpdate) { - if (!started) { - log_w("Matter Fan device has not begun."); - return false; - } - // avoid processing the a "no-change" - if (currentPercent == newPercent) { - return true; - } - - esp_matter_attr_val_t speedVal = esp_matter_invalid(NULL); - if (!getAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal)) { - log_e("Failed to get Fan Speed Percent Attribute."); - return false; - } - if (speedVal.val.u8 != newPercent) { - speedVal.val.u8 = newPercent; - bool ret; - if (performUpdate) { - ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal); - } else { - ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal); - ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, &speedVal); - } - if (!ret) { - log_e("Failed to %s Fan Speed Percent Attribute.", performUpdate ? "update" : "set"); - return false; - } - } - currentPercent = newPercent; - log_v("Fan Speed %s to %d ==> onOffState[%s]", performUpdate ? "updated" : "set", currentPercent, getOnOff() ? "ON" : "OFF"); - return true; -} - -bool MatterFan::setOnOff(bool newState, bool performUpdate) { - if (!started) { - log_w("Matter Fan device has not begun."); - return false; - } - // avoid processing the a "no-change" - if (getOnOff() == newState) { - return true; - } - - esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL); - if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) { - log_e("Failed to get Fan Mode Attribute."); - return false; - } - if (modeVal.val.u8 != (uint8_t) newState) { - FanMode_t newMode = newState ? FAN_MODE_ON : FAN_MODE_OFF; - if (!setMode(newMode, performUpdate)) { - return false; - } - } - log_v("Fan State %s to %s :: Mode[%s]|Speed[%d]", performUpdate ? "updated" : "set", getOnOff() ? "ON" : "OFF", fanModeString[currentFanMode], currentPercent); - return true; -} - -bool MatterFan::getOnOff() { - return currentFanMode == FAN_MODE_OFF ? false : true; -} - -bool MatterFan::toggle(bool performUpdate) { - if (getOnOff() == true) { - return setOnOff(false, performUpdate); - } else { - return setOnOff(true, performUpdate); - } -} - - -#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ +// 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 + +using namespace esp_matter; +using namespace esp_matter::endpoint; +using namespace esp_matter::cluster; +using namespace chip::app::Clusters; + +// string helper for the FAN MODE +const char *MatterFan::fanModeString[7] = {"OFF", "LOW", "MEDIUM", "HIGH", "ON", "AUTO", "SMART"}; +// bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums +const uint8_t MatterFan::fanModeSequence[6] = {fanSeqModeOffLowMedHigh, fanSeqModeOffLowHigh, fanSeqModeOffLowMedHighAuto, + fanSeqModeOffLowHighAuto, fanSeqModeOffHighAuto, fanSeqModeOffHigh}; + +// Constructor and Method Definitions +MatterFan::MatterFan() {} + +MatterFan::~MatterFan() { + end(); +} + +bool MatterFan::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 Fan device has not begun."); + return false; + } + + log_d("Fan Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + + if (endpoint_id == getEndPointId() && cluster_id == FanControl::Id) { + switch (attribute_id) { + case FanControl::Attributes::FanMode::Id: + log_v("FanControl Fan Mode changed to %s (%x)", val->val.u8 < 7 ? fanModeString[val->val.u8] : "Unknown", val->val.u8); + if (_onChangeModeCB != NULL) { + ret &= _onChangeModeCB((FanMode_t)val->val.u8); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB((FanMode_t)val->val.u8, currentPercent); + } + if (ret == true) { + currentFanMode = (FanMode_t)val->val.u8; + } + break; + case FanControl::Attributes::PercentSetting::Id: + case FanControl::Attributes::PercentCurrent::Id: + log_v("FanControl Percent %s changed to %d", attribute_id == FanControl::Attributes::PercentSetting::Id ? "SETTING" : "CURRENT", val->val.u8); + if (_onChangeSpeedCB != NULL) { + ret &= _onChangeSpeedCB(val->val.u8); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(currentFanMode, val->val.u8); + } + if (ret == true) { + // change setting speed percent + currentPercent = val->val.u8; + setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, val); + setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, val); + } + break; + } + } + + return ret; +} + +bool MatterFan::begin(uint8_t percent, FanMode_t fanMode, FanModeSequence_t fanModeSeq) { + ArduinoMatter::_init(); + + // endpoint handles can be used to add/modify clusters. + fan::config_t fan_config; + fan_config.fan_control.fan_mode = fanMode; + fan_config.fan_control.percent_current = percent; + fan_config.fan_control.percent_setting = percent; + fan_config.fan_control.fan_mode_sequence = fanModeSeq; + validFanModes = fanModeSequence[fanModeSeq]; + + endpoint_t *endpoint = fan::create(node::get(), &fan_config, ENDPOINT_FLAG_NONE, (void *)this); + + if (endpoint == nullptr) { + log_e("Failed to create Generic swtich endpoint"); + return false; + } + + currentFanMode = fanMode; + currentPercent = percent; + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Fan created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterFan::end() { + started = false; +} + +bool MatterFan::setMode(FanMode_t newMode, bool performUpdate) { + if (!started) { + log_w("Matter Fan device has not begun."); + return false; + } + // avoid processing the a "no-change" + if (currentFanMode == newMode) { + return true; + } + + // check if the mode is valid based on the sequence used in its creation + if (!(validFanModes & (1 << newMode))) { + log_e("Invalid Fan Mode %s for the current Fan Mode Sequence.", fanModeString[newMode]); + return false; + } + + esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL); + if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) { + log_e("Failed to get Fan Mode Attribute."); + return false; + } + if (modeVal.val.u8 != (uint8_t)newMode) { + modeVal.val.u8 = (uint8_t)newMode; + bool ret; + if (performUpdate) { + ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal); + } else { + ret = setAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal); + } + if (!ret) { + log_e("Failed to %s Fan Mode Attribute.", performUpdate ? "update" : "set"); + return false; + } + } + currentFanMode = newMode; + log_v("Fan Mode %s to %s ==> onOffState[%s]", performUpdate ? "updated" : "set", fanModeString[currentFanMode], getOnOff() ? "ON" : "OFF"); + return true; +} + +// this function will change the Fan Speed by callin the user application callback +// it is up to the application to decide to turn on, off or change the speed of the fan +bool MatterFan::setSpeedPercent(uint8_t newPercent, bool performUpdate) { + if (!started) { + log_w("Matter Fan device has not begun."); + return false; + } + // avoid processing the a "no-change" + if (currentPercent == newPercent) { + return true; + } + + esp_matter_attr_val_t speedVal = esp_matter_invalid(NULL); + if (!getAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal)) { + log_e("Failed to get Fan Speed Percent Attribute."); + return false; + } + if (speedVal.val.u8 != newPercent) { + speedVal.val.u8 = newPercent; + bool ret; + if (performUpdate) { + ret = updateAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal); + } else { + ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentSetting::Id, &speedVal); + ret = setAttributeVal(FanControl::Id, FanControl::Attributes::PercentCurrent::Id, &speedVal); + } + if (!ret) { + log_e("Failed to %s Fan Speed Percent Attribute.", performUpdate ? "update" : "set"); + return false; + } + } + currentPercent = newPercent; + log_v("Fan Speed %s to %d ==> onOffState[%s]", performUpdate ? "updated" : "set", currentPercent, getOnOff() ? "ON" : "OFF"); + return true; +} + +bool MatterFan::setOnOff(bool newState, bool performUpdate) { + if (!started) { + log_w("Matter Fan device has not begun."); + return false; + } + // avoid processing the a "no-change" + if (getOnOff() == newState) { + return true; + } + + esp_matter_attr_val_t modeVal = esp_matter_invalid(NULL); + if (!getAttributeVal(FanControl::Id, FanControl::Attributes::FanMode::Id, &modeVal)) { + log_e("Failed to get Fan Mode Attribute."); + return false; + } + if (modeVal.val.u8 != (uint8_t)newState) { + FanMode_t newMode = newState ? FAN_MODE_ON : FAN_MODE_OFF; + if (!setMode(newMode, performUpdate)) { + return false; + } + } + log_v( + "Fan State %s to %s :: Mode[%s]|Speed[%d]", performUpdate ? "updated" : "set", getOnOff() ? "ON" : "OFF", fanModeString[currentFanMode], currentPercent + ); + return true; +} + +bool MatterFan::getOnOff() { + return currentFanMode == FAN_MODE_OFF ? false : true; +} + +bool MatterFan::toggle(bool performUpdate) { + if (getOnOff() == true) { + return setOnOff(false, performUpdate); + } else { + return setOnOff(true, performUpdate); + } +} + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterFan.h b/libraries/Matter/src/MatterEndpoints/MatterFan.h index c9da10c5073..232577b7bef 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterFan.h +++ b/libraries/Matter/src/MatterEndpoints/MatterFan.h @@ -1,163 +1,163 @@ -// 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 -#include - -using namespace chip::app::Clusters::FanControl; - -// Matter Fan endpoint with On/Off, Mode and Speed control - -class MatterFan : public MatterEndPoint { -public: - // Fan feature constants - static const uint8_t MAX_SPEED = 100; // maximum High speed - static const uint8_t MIN_SPEED = 1; // minimum Low speed - static const uint8_t OFF_SPEED = 0; // speed set by Matter when FAN_MODE_OFF - - // Default Fan Modes: ON, SMART, HIGH and OFF - - // Other mode will depend on what is the configured Fan Mode Sequence - enum FanMode_t { - FAN_MODE_OFF = (uint8_t) FanModeEnum::kOff, - FAN_MODE_LOW = (uint8_t) FanModeEnum::kLow, - FAN_MODE_MEDIUM = (uint8_t) FanModeEnum::kMedium, - FAN_MODE_HIGH = (uint8_t) FanModeEnum::kHigh, - FAN_MODE_ON = (uint8_t) FanModeEnum::kOn, - FAN_MODE_AUTO = (uint8_t) FanModeEnum::kAuto, - FAN_MODE_SMART = (uint8_t) FanModeEnum::kSmart - }; - - // Menu will always have ON, OFF, HIGH and SMART. - // AUTO will show up only when a AUTO SEQ is CONFIGURED - // LOW and MEDIUM depend on the SEQ MODE configuration - enum FanModeSequence_t { - FAN_MODE_SEQ_OFF_LOW_MED_HIGH = (uint8_t) FanModeSequenceEnum::kOffLowMedHigh, - FAN_MODE_SEQ_OFF_LOW_HIGH = (uint8_t) FanModeSequenceEnum::kOffLowHigh, - FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO = (uint8_t) FanModeSequenceEnum::kOffLowMedHighAuto, - FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO = (uint8_t) FanModeSequenceEnum::kOffLowHighAuto, - FAN_MODE_SEQ_OFF_HIGH_AUTO = (uint8_t) FanModeSequenceEnum::kOffHighAuto, - FAN_MODE_SEQ_OFF_HIGH = (uint8_t) FanModeSequenceEnum::kOffHigh - }; - - MatterFan(); - ~MatterFan(); - virtual bool begin(uint8_t percent = 0, FanMode_t fanMode = FAN_MODE_OFF, FanModeSequence_t fanModeSeq = FAN_MODE_SEQ_OFF_HIGH); - void end(); // this will just stop processing Matter events - - // returns a friendly string for the Fan Mode - static const char *getFanModeString(uint8_t mode) { - return fanModeString[mode]; - } - - // Fan Control of current On/Off state - - bool setOnOff(bool newState, bool performUpdate = true); // sets Fan On/Off state - bool getOnOff(); // returns current Fan state - bool toggle(bool performUpdate = true); // toggle Fun On/Off state - - // Fan Control of current speed percent - - bool setSpeedPercent(uint8_t newPercent, bool performUpdate = true); // returns true if successful - uint8_t getSpeedPercent() { // returns current Fan Speed Percent - return currentPercent; - } - - // Fan Control of current Fan Mode - - bool setMode(FanMode_t newMode, bool performUpdate = true); // returns true if successful - FanMode_t getMode() { // returns current Fan Mode - return currentFanMode; - } - // used to update the state of the Fan using the current Matter Fan internal state - // It is necessary to set a user callback function using onChange() to handle the physical Fan motor state - - void updateAccessory() { - if (_onChangeCB != NULL) { - _onChangeCB(currentFanMode, currentPercent); - } - } - - // returns current Fan speed percent - operator uint8_t() { - return getSpeedPercent(); - } - // sets Fan speed percent - void operator=(uint8_t speedPercent) { - setSpeedPercent(speedPercent); - } - - // 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 Fan Mode (state) is changed by the Matter Controller - using EndPointModeCB = std::function; - void onChangeMode(EndPointModeCB onChangeCB) { - _onChangeModeCB = onChangeCB; - } - - // User Callback for whenever the Fan Speed Percentage value [0..100] is changed by the Matter Controller - using EndPointSpeedCB = std::function; - void onChangeSpeedPercent(EndPointSpeedCB onChangeCB) { - _onChangeSpeedCB = onChangeCB; - } - - // User Callback for whenever any parameter is changed by the Matter Controller - using EndPointCB = std::function; - void onChange(EndPointCB onChangeCB) { - _onChangeCB = onChangeCB; - } - -protected: - bool started = false; - uint8_t validFanModes = 0; // bitmap for valid Fan Modes - index of fanModeSequence[] - - uint8_t currentPercent = 0; // current speed percent - FanMode_t currentFanMode = FAN_MODE_OFF; // current Fan Mode - EndPointModeCB _onChangeModeCB = NULL; - EndPointSpeedCB _onChangeSpeedCB = NULL; - EndPointCB _onChangeCB = NULL; - - // bitmap for Fan Sequence Modes (OFF, LOW, MEDIUM, HIGH, AUTO) - static const uint8_t fanSeqModeOff = 0x01; - static const uint8_t fanSeqModeLow = 0x02; - static const uint8_t fanSeqModeMedium = 0x04; - static const uint8_t fanSeqModeHigh = 0x08; - static const uint8_t fanSeqModeOn = 0x10; - static const uint8_t fanSeqModeAuto = 0x20; - static const uint8_t fanSeqModeSmart = 0x40; - - // bitmap for common modes: ON, OFF, HIGH and SMART - static const uint8_t fanSeqCommonModes = fanSeqModeOff | fanSeqModeOn | fanSeqModeHigh | fanSeqModeSmart; - - static const uint8_t fanSeqModeOffLowMedHigh = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium; - static const uint8_t fanSeqModeOffLowHigh = fanSeqCommonModes | fanSeqModeLow; - static const uint8_t fanSeqModeOffLowMedHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium | fanSeqModeAuto; - static const uint8_t fanSeqModeOffLowHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeAuto; - static const uint8_t fanSeqModeOffHighAuto = fanSeqCommonModes | fanSeqModeAuto; - static const uint8_t fanSeqModeOffHigh = fanSeqCommonModes; - - // bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums - static const uint8_t fanModeSequence[6]; - - // string helper for the FAN MODE - static const char *fanModeString[7]; -}; - -#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ +// 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 +#include + +using namespace chip::app::Clusters::FanControl; + +// Matter Fan endpoint with On/Off, Mode and Speed control + +class MatterFan : public MatterEndPoint { +public: + // Fan feature constants + static const uint8_t MAX_SPEED = 100; // maximum High speed + static const uint8_t MIN_SPEED = 1; // minimum Low speed + static const uint8_t OFF_SPEED = 0; // speed set by Matter when FAN_MODE_OFF + + // Default Fan Modes: ON, SMART, HIGH and OFF + + // Other mode will depend on what is the configured Fan Mode Sequence + enum FanMode_t { + FAN_MODE_OFF = (uint8_t)FanModeEnum::kOff, + FAN_MODE_LOW = (uint8_t)FanModeEnum::kLow, + FAN_MODE_MEDIUM = (uint8_t)FanModeEnum::kMedium, + FAN_MODE_HIGH = (uint8_t)FanModeEnum::kHigh, + FAN_MODE_ON = (uint8_t)FanModeEnum::kOn, + FAN_MODE_AUTO = (uint8_t)FanModeEnum::kAuto, + FAN_MODE_SMART = (uint8_t)FanModeEnum::kSmart + }; + + // Menu will always have ON, OFF, HIGH and SMART. + // AUTO will show up only when a AUTO SEQ is CONFIGURED + // LOW and MEDIUM depend on the SEQ MODE configuration + enum FanModeSequence_t { + FAN_MODE_SEQ_OFF_LOW_MED_HIGH = (uint8_t)FanModeSequenceEnum::kOffLowMedHigh, + FAN_MODE_SEQ_OFF_LOW_HIGH = (uint8_t)FanModeSequenceEnum::kOffLowHigh, + FAN_MODE_SEQ_OFF_LOW_MED_HIGH_AUTO = (uint8_t)FanModeSequenceEnum::kOffLowMedHighAuto, + FAN_MODE_SEQ_OFF_LOW_HIGH_AUTO = (uint8_t)FanModeSequenceEnum::kOffLowHighAuto, + FAN_MODE_SEQ_OFF_HIGH_AUTO = (uint8_t)FanModeSequenceEnum::kOffHighAuto, + FAN_MODE_SEQ_OFF_HIGH = (uint8_t)FanModeSequenceEnum::kOffHigh + }; + + MatterFan(); + ~MatterFan(); + virtual bool begin(uint8_t percent = 0, FanMode_t fanMode = FAN_MODE_OFF, FanModeSequence_t fanModeSeq = FAN_MODE_SEQ_OFF_HIGH); + void end(); // this will just stop processing Matter events + + // returns a friendly string for the Fan Mode + static const char *getFanModeString(uint8_t mode) { + return fanModeString[mode]; + } + + // Fan Control of current On/Off state + + bool setOnOff(bool newState, bool performUpdate = true); // sets Fan On/Off state + bool getOnOff(); // returns current Fan state + bool toggle(bool performUpdate = true); // toggle Fun On/Off state + + // Fan Control of current speed percent + + bool setSpeedPercent(uint8_t newPercent, bool performUpdate = true); // returns true if successful + uint8_t getSpeedPercent() { // returns current Fan Speed Percent + return currentPercent; + } + + // Fan Control of current Fan Mode + + bool setMode(FanMode_t newMode, bool performUpdate = true); // returns true if successful + FanMode_t getMode() { // returns current Fan Mode + return currentFanMode; + } + // used to update the state of the Fan using the current Matter Fan internal state + // It is necessary to set a user callback function using onChange() to handle the physical Fan motor state + + void updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(currentFanMode, currentPercent); + } + } + + // returns current Fan speed percent + operator uint8_t() { + return getSpeedPercent(); + } + // sets Fan speed percent + void operator=(uint8_t speedPercent) { + setSpeedPercent(speedPercent); + } + + // 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 Fan Mode (state) is changed by the Matter Controller + using EndPointModeCB = std::function; + void onChangeMode(EndPointModeCB onChangeCB) { + _onChangeModeCB = onChangeCB; + } + + // User Callback for whenever the Fan Speed Percentage value [0..100] is changed by the Matter Controller + using EndPointSpeedCB = std::function; + void onChangeSpeedPercent(EndPointSpeedCB onChangeCB) { + _onChangeSpeedCB = onChangeCB; + } + + // User Callback for whenever any parameter is changed by the Matter Controller + using EndPointCB = std::function; + void onChange(EndPointCB onChangeCB) { + _onChangeCB = onChangeCB; + } + +protected: + bool started = false; + uint8_t validFanModes = 0; // bitmap for valid Fan Modes - index of fanModeSequence[] + + uint8_t currentPercent = 0; // current speed percent + FanMode_t currentFanMode = FAN_MODE_OFF; // current Fan Mode + EndPointModeCB _onChangeModeCB = NULL; + EndPointSpeedCB _onChangeSpeedCB = NULL; + EndPointCB _onChangeCB = NULL; + + // bitmap for Fan Sequence Modes (OFF, LOW, MEDIUM, HIGH, AUTO) + static const uint8_t fanSeqModeOff = 0x01; + static const uint8_t fanSeqModeLow = 0x02; + static const uint8_t fanSeqModeMedium = 0x04; + static const uint8_t fanSeqModeHigh = 0x08; + static const uint8_t fanSeqModeOn = 0x10; + static const uint8_t fanSeqModeAuto = 0x20; + static const uint8_t fanSeqModeSmart = 0x40; + + // bitmap for common modes: ON, OFF, HIGH and SMART + static const uint8_t fanSeqCommonModes = fanSeqModeOff | fanSeqModeOn | fanSeqModeHigh | fanSeqModeSmart; + + static const uint8_t fanSeqModeOffLowMedHigh = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium; + static const uint8_t fanSeqModeOffLowHigh = fanSeqCommonModes | fanSeqModeLow; + static const uint8_t fanSeqModeOffLowMedHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeMedium | fanSeqModeAuto; + static const uint8_t fanSeqModeOffLowHighAuto = fanSeqCommonModes | fanSeqModeLow | fanSeqModeAuto; + static const uint8_t fanSeqModeOffHighAuto = fanSeqCommonModes | fanSeqModeAuto; + static const uint8_t fanSeqModeOffHigh = fanSeqCommonModes; + + // bitmap for valid Fan Modes based on order defined in Zap Generated Cluster Enums + static const uint8_t fanModeSequence[6]; + + // string helper for the FAN MODE + static const char *fanModeString[7]; +}; + +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ From dadf2c2232d483514af6f7344e04bcac798caed4 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 9 Dec 2024 09:47:18 -0300 Subject: [PATCH 4/4] fix(matter): ci codespell fixes to commentaties --- libraries/Matter/examples/MatterFan/MatterFan.ino | 8 ++++---- libraries/Matter/src/MatterEndPoint.h | 6 +++--- libraries/Matter/src/MatterEndpoints/MatterFan.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/Matter/examples/MatterFan/MatterFan.ino b/libraries/Matter/examples/MatterFan/MatterFan.ino index d124e8a9232..ac26550f2b6 100644 --- a/libraries/Matter/examples/MatterFan/MatterFan.ino +++ b/libraries/Matter/examples/MatterFan/MatterFan.ino @@ -90,19 +90,19 @@ void setup() { Fan.begin(0, MatterFan::FAN_MODE_OFF, MatterFan::FAN_MODE_SEQ_OFF_HIGH); // callback functions would control Fan motor - // the Matter Controller will send new data whenver the User APP or Automation request + // the Matter Controller will send new data whenever the User APP or Automation request // single feature callbacks take place before the generic (all features) callback // This callback will be executed whenever the speed percent matter attribute is updated Fan.onChangeSpeedPercent([](uint8_t speedPercent) { // setting speed to Zero, while the Fan is ON, shall turn the Fan OFF if (speedPercent == MatterFan::OFF_SPEED && Fan.getMode() != MatterFan::FAN_MODE_OFF) { - // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + // ATTR_SET do not update the attribute, just SET it to avoid infinite loop return Fan.setOnOff(false, Fan.ATTR_SET); } // changing the speed to higher than Zero, while the Fan is OFF, shall turn the Fan ON if (speedPercent > MatterFan::OFF_SPEED && Fan.getMode() == MatterFan::FAN_MODE_OFF) { - // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + // ATTR_SET do not update the attribute, just SET it to avoid infinite loop return Fan.setOnOff(true, Fan.ATTR_SET); } // for other case, just return true @@ -115,7 +115,7 @@ void setup() { // when the Fan is turned ON using Mode Selection, while it is OFF, shall start it by setting the speed to 50% if (Fan.getSpeedPercent() == MatterFan::OFF_SPEED && fanMode != MatterFan::FAN_MODE_OFF) { Serial.printf("Fan set to %s mode -- speed percentage will go to 50%%\r\n", Fan.getFanModeString(fanMode)); - // ATTR_SET do not update the attribute, just SET it to avoid inifinite loop + // ATTR_SET do not update the attribute, just SET it to avoid infinite loop return Fan.setSpeedPercent(50, Fan.ATTR_SET); } return true; diff --git a/libraries/Matter/src/MatterEndPoint.h b/libraries/Matter/src/MatterEndPoint.h index dd0d91b1ca2..99bff8470d3 100644 --- a/libraries/Matter/src/MatterEndPoint.h +++ b/libraries/Matter/src/MatterEndPoint.h @@ -68,7 +68,7 @@ class MatterEndPoint { return false; } if (attribute::get_val(attribute, attrVal) == ESP_OK) { - log_v("GET_VAL Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + log_v("GET_VAL Success for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); return true; } log_e("GET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); @@ -82,7 +82,7 @@ class MatterEndPoint { return false; } if (attribute::set_val(attribute, attrVal) == ESP_OK) { - log_v("SET_VAL Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + log_v("SET_VAL Success for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); return true; } log_e("SET_VAL FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); @@ -92,7 +92,7 @@ class MatterEndPoint { // update the value of an attribute from its cluster id and attribute it bool updateAttributeVal(uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *attrVal) { if (attribute::update(endpoint_id, cluster_id, attribute_id, attrVal) == ESP_OK) { - log_v("Update Suceess for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); + log_v("Update Success for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); return true; } log_e("Update FAILED! for cluster %d, attribute %d with value %d", cluster_id, attribute_id, attrVal->val.u32); diff --git a/libraries/Matter/src/MatterEndpoints/MatterFan.cpp b/libraries/Matter/src/MatterEndpoints/MatterFan.cpp index 3992be61c09..8db6a317ead 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterFan.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterFan.cpp @@ -96,7 +96,7 @@ bool MatterFan::begin(uint8_t percent, FanMode_t fanMode, FanModeSequence_t fanM endpoint_t *endpoint = fan::create(node::get(), &fan_config, ENDPOINT_FLAG_NONE, (void *)this); if (endpoint == nullptr) { - log_e("Failed to create Generic swtich endpoint"); + log_e("Failed to create Fan endpoint"); return false; } @@ -152,7 +152,7 @@ bool MatterFan::setMode(FanMode_t newMode, bool performUpdate) { return true; } -// this function will change the Fan Speed by callin the user application callback +// this function will change the Fan Speed by calling the user application callback // it is up to the application to decide to turn on, off or change the speed of the fan bool MatterFan::setSpeedPercent(uint8_t newPercent, bool performUpdate) { if (!started) {