diff --git a/CMakeLists.txt b/CMakeLists.txt index 591b0b31568..1fd6274f9bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,7 +169,8 @@ set(ARDUINO_LIBRARY_OpenThread_SRCS libraries/OpenThread/src/OThreadCLI_Util.cpp) set(ARDUINO_LIBRARY_Matter_SRCS - libraries/Matter/src/MatterOnOffLight.cpp + libraries/Matter/src/MatterEndpoints/MatterOnOffLight.cpp + libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp libraries/Matter/src/Matter.cpp) set(ARDUINO_LIBRARY_PPP_SRCS diff --git a/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino b/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino index 48ec0355092..9024479c881 100644 --- a/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino +++ b/libraries/Matter/examples/MatterCommissionTest/MatterCommissionTest.ino @@ -1,10 +1,23 @@ +// 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 // On/Off Light Endpoint -#include MatterOnOffLight OnOffLight; // WiFi is manually set and started diff --git a/libraries/Matter/examples/MatterComposedLights/MatterComposedLights.ino b/libraries/Matter/examples/MatterComposedLights/MatterComposedLights.ino index 5d4acb557f5..85fcd9e8973 100644 --- a/libraries/Matter/examples/MatterComposedLights/MatterComposedLights.ino +++ b/libraries/Matter/examples/MatterComposedLights/MatterComposedLights.ino @@ -1,10 +1,23 @@ +// 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 // There will be 3 On/Off Light Endpoints in the same Node -#include MatterOnOffLight OnOffLight1; MatterOnOffLight OnOffLight2; MatterOnOffLight OnOffLight3; @@ -56,9 +69,9 @@ void setup() { OnOffLight1.begin(); OnOffLight2.begin(); OnOffLight3.begin(); - OnOffLight1.onChangeOnOff(setLightOnOff1); - OnOffLight2.onChangeOnOff(setLightOnOff2); - OnOffLight3.onChangeOnOff(setLightOnOff3); + OnOffLight1.onChange(setLightOnOff1); + OnOffLight2.onChange(setLightOnOff2); + OnOffLight3.onChange(setLightOnOff3); // Matter beginning - Last step, after all EndPoints are initialized Matter.begin(); diff --git a/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino b/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino new file mode 100644 index 00000000000..ded0118b7d1 --- /dev/null +++ b/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino @@ -0,0 +1,170 @@ +// 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 +// Dimmable Light Endpoint +MatterDimmableLight DimmableLight; + +// it will keep last OnOff & Brightness state stored, using Preferences +Preferences lastStatePref; + +// set your board RGB LED pin here +#ifdef RGB_BUILTIN +const uint8_t ledPin = RGB_BUILTIN; +#else +const uint8_t ledPin = 2; // Set your pin here if your board has not defined LED_BUILTIN +#warning "Do not forget to set the RGB LED pin" +#endif + +// set your board USER BUTTON pin here +const uint8_t buttonPin = 0; // Set your pin here. Using BOOT Button. C6/C3 use GPIO9. + +// WiFi is manually set and started +const char *ssid = "your-ssid"; // Change this to your WiFi SSID +const char *password = "your-password"; // Change this to your WiFi password + +// Set the RGB LED Light based on the current state of the Dimmable Light +bool setLightState(bool state, uint8_t brightness) { + if (state) { +#ifdef RGB_BUILTIN + rgbLedWrite(ledPin, brightness, brightness, brightness); +#else + analogWrite(ledPin, brightness); +#endif + } else { + digitalWrite(ledPin, LOW); + } + // store last Brightness and OnOff state for when the Light is restarted / power goes off + lastStatePref.putUChar("lastBrightness", brightness); + lastStatePref.putBool("lastOnOffState", state); + // This callback must return the success state to Matter core + return true; +} + +void setup() { + // Initialize the USER BUTTON (Boot button) GPIO that will act as a toggle switch + pinMode(buttonPin, INPUT_PULLUP); + // Initialize the LED (light) GPIO and Matter End Point + pinMode(ledPin, 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); + + // Initialize Matter EndPoint + lastStatePref.begin("matterLight", false); + // default OnOff state is ON if not stored before + bool lastOnOffState = lastStatePref.getBool("lastOnOffState", true); + // default brightness ~= 6% (15/255) + uint8_t lastBrightness = lastStatePref.getUChar("lastBrightness", 15); + DimmableLight.begin(lastOnOffState, lastBrightness); + // set the callback function to handle the Light state change + DimmableLight.onChange(setLightState); + + // lambda functions are used to set the attribute change callbacks + DimmableLight.onChangeOnOff([](bool state) { + Serial.printf("Light OnOff changed to %s\r\n", state ? "ON" : "OFF"); + return true; + }); + DimmableLight.onChangeBrightness([](uint8_t level) { + Serial.printf("Light Brightness changed to %d\r\n", level); + 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."); + Serial.printf("Initial state: %s | brightness: %d\r\n", DimmableLight ? "ON" : "OFF", DimmableLight.getBrightness()); + // configure the Light based on initial on-off state and brightness + DimmableLight.updateAccessory(); + } +} +// 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 light + +void loop() { + // Check Matter Light 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 Light 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 | brightness: %d\r\n", DimmableLight ? "ON" : "OFF", DimmableLight.getBrightness()); + // configure the Light based on initial on-off state and brightness + DimmableLight.updateAccessory(); + Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); + } + + // A button is also used to control the light + // 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 Light toggle switch 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 + // Toggle button is released - toggle the light + Serial.println("User button released. Toggling Light!"); + DimmableLight.toggle(); // Matter Controller also can see the change + + // Factory reset is triggered if the button is pressed longer than 10 seconds + if (time_diff > decommissioningTimeout) { + Serial.println("Decommissioning the Light Matter Accessory. It shall be commissioned again."); + DimmableLight = false; // turn the light off + Matter.decommission(); + } + } +} diff --git a/libraries/Matter/examples/MatterDimmableLight/ci.json b/libraries/Matter/examples/MatterDimmableLight/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/MatterDimmableLight/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/examples/MatterOnOffLight/MatterOnOffLight.ino b/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino index 751bcb3d99e..675e9e989f2 100644 --- a/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino +++ b/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino @@ -1,3 +1,17 @@ +// 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 @@ -5,7 +19,6 @@ // List of Matter Endpoints for this Node // On/Off Light Endpoint -#include MatterOnOffLight OnOffLight; // it will keep last OnOff state stored, using Preferences @@ -72,7 +85,7 @@ void setup() { lastStatePref.begin("matterLight", false); bool lastOnOffState = lastStatePref.getBool("lastOnOffState", true); OnOffLight.begin(lastOnOffState); - OnOffLight.onChangeOnOff(setLightOnOff); + OnOffLight.onChange(setLightOnOff); // Matter beginning - Last step, after all EndPoints are initialized Matter.begin(); @@ -80,7 +93,7 @@ void setup() { if (Matter.isDeviceCommissioned()) { Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); Serial.printf("Initial state: %s\r\n", OnOffLight.getOnOff() ? "ON" : "OFF"); - setLightOnOff(OnOffLight.getOnOff()); // configure the Light based on initial state + OnOffLight.updateAccessory(); // configure the Light based on initial state } } // Button control @@ -107,7 +120,7 @@ void loop() { } } Serial.printf("Initial state: %s\r\n", OnOffLight.getOnOff() ? "ON" : "OFF"); - setLightOnOff(OnOffLight.getOnOff()); // configure the Light based on initial state + OnOffLight.updateAccessory(); // configure the Light based on initial state Serial.println("Matter Node is commissioned and connected to Wi-Fi. Ready for use."); } diff --git a/libraries/Matter/keywords.txt b/libraries/Matter/keywords.txt index 7ff3e90f3b4..98abce410b1 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -7,7 +7,9 @@ ####################################### Matter KEYWORD1 +ArduinoMatter KEYWORD1 MatterOnOffLight KEYWORD1 +MatterDimmableLight KEYWORD1 MatterEndPoint KEYWORD1 ####################################### @@ -16,7 +18,6 @@ MatterEndPoint KEYWORD1 begin KEYWORD2 end KEYWORD2 -start KEYWORD2 getManualPairingCode KEYWORD2 getOnboardingQRCodeUrl KEYWORD2 isDeviceCommissioned KEYWORD2 @@ -27,8 +28,13 @@ decommission KEYWORD2 attributeChangeCB KEYWORD2 setOnOff KEYWORD2 getOnOff KEYWORD2 +setBrightness KEYWORD2 +getBrightness KEYWORD2 toggle KEYWORD2 +updateAccessory KEYWORD2 +onChange KEYWORD2 onChangeOnOff KEYWORD2 +onChangeBrightness KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/libraries/Matter/src/Matter.cpp b/libraries/Matter/src/Matter.cpp index 49504babac0..857438cce03 100644 --- a/libraries/Matter/src/Matter.cpp +++ b/libraries/Matter/src/Matter.cpp @@ -1,3 +1,17 @@ +// 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 @@ -27,25 +41,26 @@ esp_err_t matter_light_attribute_update( static esp_err_t app_attribute_update_cb( attribute::callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val, void *priv_data ) { + log_d("Attribute update callback: type: %u, endpoint: %u, cluster: %u, attribute: %u, val: %u", type, endpoint_id, cluster_id, attribute_id, val->val.u32); esp_err_t err = ESP_OK; MatterEndPoint *ep = (MatterEndPoint *)priv_data; // endpoint pointer to base class switch (type) { case PRE_UPDATE: // Callback before updating the value in the database - log_i("Attribute update callback: PRE_UPDATE"); + log_v("Attribute update callback: PRE_UPDATE"); if (ep != NULL) { err = ep->attributeChangeCB(endpoint_id, cluster_id, attribute_id, val) ? ESP_OK : ESP_FAIL; } break; case POST_UPDATE: // Callback after updating the value in the database - log_i("Attribute update callback: POST_UPDATE"); + log_v("Attribute update callback: POST_UPDATE"); break; case READ: // Callback for reading the attribute value. This is used when the `ATTRIBUTE_FLAG_OVERRIDE` is set. - log_i("Attribute update callback: READ"); + log_v("Attribute update callback: READ"); break; case WRITE: // Callback for writing the attribute value. This is used when the `ATTRIBUTE_FLAG_OVERRIDE` is set. - log_i("Attribute update callback: WRITE"); + log_v("Attribute update callback: WRITE"); break; - default: log_i("Attribute update callback: Unknown type %d", type); + default: log_v("Attribute update callback: Unknown type %d", type); } return err; } @@ -114,7 +129,7 @@ void ArduinoMatter::_init() { void ArduinoMatter::begin() { if (!_matter_has_started) { - log_w("No Matter endpoint has been created. Please create an endpoint first."); + log_e("No Matter endpoint has been created. Please create an endpoint first."); return; } diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index a1ce0f2f644..f88b7788016 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -1,9 +1,25 @@ +// 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 +#include using namespace esp_matter; @@ -30,6 +46,7 @@ class ArduinoMatter { // list of Matter EndPoints Friend Classes friend class MatterOnOffLight; + friend class MatterDimmableLight; protected: static void _init(); diff --git a/libraries/Matter/src/MatterEndPoint.h b/libraries/Matter/src/MatterEndPoint.h index 2be5bf5bb5d..02577957e8e 100644 --- a/libraries/Matter/src/MatterEndPoint.h +++ b/libraries/Matter/src/MatterEndPoint.h @@ -1,3 +1,17 @@ +// 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 @@ -14,7 +28,7 @@ class MatterEndPoint { void setEndPointId(uint16_t ep) { endpoint_id = ep; } - + // 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; protected: diff --git a/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp new file mode 100644 index 00000000000..7907ae3a90a --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp @@ -0,0 +1,184 @@ +// 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 MatterDimmableLight::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 DimmableLight device has not begun."); + return false; + } + + log_d("Dimmable Attr update callback: endpoint: %u, cluster: %u, attribute: %u, val: %u", endpoint_id, cluster_id, attribute_id, val->val.u32); + + if (endpoint_id == getEndPointId()) { + switch (cluster_id) { + case OnOff::Id: + if (attribute_id == OnOff::Attributes::OnOff::Id) { + log_d("DimmableLight On/Off State changed to %d", val->val.b); + if (_onChangeOnOffCB != NULL) { + ret &= _onChangeOnOffCB(val->val.b); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(val->val.b, brightnessLevel); + } + if (ret == true) { + onOffState = val->val.b; + } + } + break; + case LevelControl::Id: + if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) { + log_d("DimmableLight Brightness changed to %d", val->val.u8); + if (_onChangeBrightnessCB != NULL) { + ret &= _onChangeBrightnessCB(val->val.u8); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(onOffState, val->val.u8); + } + if (ret == true) { + brightnessLevel = val->val.u8; + } + } + break; + } + } + return ret; +} + +MatterDimmableLight::MatterDimmableLight() {} + +MatterDimmableLight::~MatterDimmableLight() { + end(); +} + +bool MatterDimmableLight::begin(bool initialState, uint8_t brightness) { + ArduinoMatter::_init(); + dimmable_light::config_t light_config; + + light_config.on_off.on_off = initialState; + light_config.on_off.lighting.start_up_on_off = nullptr; + onOffState = initialState; + + light_config.level_control.current_level = brightness; + light_config.level_control.lighting.start_up_current_level = nullptr; + brightnessLevel = brightness; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = dimmable_light::create(node::get(), &light_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create dimmable light endpoint"); + return false; + } + + setEndPointId(endpoint::get_id(endpoint)); + log_i("Dimmable Light created with endpoint_id %d", getEndPointId()); + started = true; + return true; +} + +void MatterDimmableLight::end() { + started = false; +} + +bool MatterDimmableLight::setOnOff(bool newState) { + if (!started) { + log_e("Matter Dimmable Light device has not begun."); + return false; + } + + // avoid processing the a "no-change" + if (onOffState == newState) { + return true; + } + + onOffState = newState; + + endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id); + cluster_t *cluster = cluster::get(endpoint, OnOff::Id); + attribute_t *attribute = attribute::get(cluster, OnOff::Attributes::OnOff::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + + if (val.val.b != onOffState) { + val.val.b = onOffState; + attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val); + } + return true; +} + +void MatterDimmableLight::updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(onOffState, brightnessLevel); + } +} + +bool MatterDimmableLight::getOnOff() { + return onOffState; +} + +bool MatterDimmableLight::toggle() { + return setOnOff(!onOffState); +} + +bool MatterDimmableLight::setBrightness(uint8_t newBrightness) { + if (!started) { + log_w("Matter Dimmable Light device has not begun."); + return false; + } + + // avoid processing the a "no-change" + if (brightnessLevel == newBrightness) { + return true; + } + + brightnessLevel = newBrightness; + + endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id); + cluster_t *cluster = cluster::get(endpoint, LevelControl::Id); + attribute_t *attribute = attribute::get(cluster, LevelControl::Attributes::CurrentLevel::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + + if (val.val.u8 != brightnessLevel) { + val.val.u8 = brightnessLevel; + attribute::update(endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val); + } + return true; +} + +uint8_t MatterDimmableLight::getBrightness() { + return brightnessLevel; +} + +MatterDimmableLight::operator bool() { + return getOnOff(); +} + +void MatterDimmableLight::operator=(bool newState) { + setOnOff(newState); +} +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h new file mode 100644 index 00000000000..fbfccde6105 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h @@ -0,0 +1,73 @@ +// 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 MatterDimmableLight : public MatterEndPoint { +public: + static const uint8_t MAX_BRIGHTNESS = 255; + + MatterDimmableLight(); + ~MatterDimmableLight(); + // default initial state is off and brightness is 0 + virtual bool begin(bool initialState = false, uint8_t brightness = 0); + // this will just stop processing Light Matter events + void end(); + + bool setOnOff(bool newState); // returns true if successful + bool getOnOff(); // returns current light state + bool toggle(); // returns true if successful + + bool setBrightness(uint8_t newBrightness); // returns true if successful + uint8_t getBrightness(); // returns current brightness + + // used to update the state of the light using the current Matter Light internal state + // It is necessary to set a user callback function using onChange() to handle the physical light state + void updateAccessory(); + + operator bool(); // returns current on/off light state + void operator=(bool state); // turns light 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 Light On/Off state is changed by the Matter Controller + using EndPointOnOffCB = std::function; + void onChangeOnOff(EndPointOnOffCB onChangeCB) { + _onChangeOnOffCB = onChangeCB; + } + // User Callback for whenever the Light brightness value [0..255] is changed by the Matter Controller + using EndPointBrightnessCB = std::function; + void onChangeBrightness(EndPointBrightnessCB onChangeCB) { + _onChangeBrightnessCB = 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; + bool onOffState = false; // default initial state is off, but it can be changed by begin(bool) + uint8_t brightnessLevel = 0; // default initial brightness is 0, but it can be changed by begin(bool, uint8_t) + EndPointOnOffCB _onChangeOnOffCB = NULL; + EndPointBrightnessCB _onChangeBrightnessCB = NULL; + EndPointCB _onChangeCB = NULL; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterOnOffLight.cpp b/libraries/Matter/src/MatterEndpoints/MatterOnOffLight.cpp similarity index 60% rename from libraries/Matter/src/MatterOnOffLight.cpp rename to libraries/Matter/src/MatterEndpoints/MatterOnOffLight.cpp index 7e8926ffdef..3f71ff1eb76 100644 --- a/libraries/Matter/src/MatterOnOffLight.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterOnOffLight.cpp @@ -1,9 +1,23 @@ +// 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 +#include using namespace esp_matter; using namespace esp_matter::endpoint; @@ -12,19 +26,24 @@ using namespace chip::app::Clusters; bool MatterOnOffLight::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_w("Matter On-Off Light device has not begun."); + log_e("Matter On-Off Light 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("OnOffLight 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); - log_d("OnOffLight state changed to %d", val->val.b); - if (ret == true) { - state = val->val.b; - } + ret &= _onChangeCB(val->val.b); + } + if (ret == true) { + onOffState = val->val.b; } } } @@ -41,9 +60,10 @@ MatterOnOffLight::~MatterOnOffLight() { bool MatterOnOffLight::begin(bool initialState) { ArduinoMatter::_init(); on_off_light::config_t light_config; + light_config.on_off.on_off = initialState; - state = initialState; light_config.on_off.lighting.start_up_on_off = nullptr; + onOffState = initialState; // endpoint handles can be used to add/modify clusters. endpoint_t *endpoint = on_off_light::create(node::get(), &light_config, ENDPOINT_FLAG_NONE, (void *)this); @@ -62,18 +82,24 @@ void MatterOnOffLight::end() { started = false; } +void MatterOnOffLight::updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(onOffState); + } +} + bool MatterOnOffLight::setOnOff(bool newState) { if (!started) { - log_w("Matter On-Off Light device has not begun."); + log_e("Matter On-Off Light device has not begun."); return false; } // avoid processing the a "no-change" - if (state == newState) { + if (onOffState == newState) { return true; } - state = newState; + onOffState = newState; endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id); cluster_t *cluster = cluster::get(endpoint, OnOff::Id); @@ -82,19 +108,19 @@ bool MatterOnOffLight::setOnOff(bool newState) { esp_matter_attr_val_t val = esp_matter_invalid(NULL); attribute::get_val(attribute, &val); - if (val.val.b != state) { - val.val.b = state; + if (val.val.b != onOffState) { + val.val.b = onOffState; attribute::update(endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val); } return true; } bool MatterOnOffLight::getOnOff() { - return state; + return onOffState; } bool MatterOnOffLight::toggle() { - return setOnOff(!state); + return setOnOff(!onOffState); } MatterOnOffLight::operator bool() { diff --git a/libraries/Matter/src/MatterOnOffLight.h b/libraries/Matter/src/MatterEndpoints/MatterOnOffLight.h similarity index 55% rename from libraries/Matter/src/MatterOnOffLight.h rename to libraries/Matter/src/MatterEndpoints/MatterOnOffLight.h index 39220652e21..6d140a9948e 100644 --- a/libraries/Matter/src/MatterOnOffLight.h +++ b/libraries/Matter/src/MatterEndpoints/MatterOnOffLight.h @@ -1,3 +1,17 @@ +// 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 @@ -16,19 +30,27 @@ class MatterOnOffLight : public MatterEndPoint { bool getOnOff(); // returns current light state bool toggle(); // returns true if successful + // used to update the state of the light using the current Matter Light internal state + // It is necessary to set a user callback function using onChange() to handle the physical light state + void updateAccessory(); + operator bool(); // returns current light state void operator=(bool state); // turns light 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 Light state is changed by the Matter Controller using EndPointCB = std::function; - void onChangeOnOff(EndPointCB onChangeCB) { + void onChange(EndPointCB onChangeCB) { _onChangeCB = onChangeCB; } + void onChangeOnOff(EndPointCB onChangeCB) { + _onChangeOnOffCB = onChangeCB; + } protected: bool started = false; - bool state = false; // default initial state is off, but it can be changed by begin(bool) + 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 */