diff --git a/CMakeLists.txt b/CMakeLists.txt index ba41d9ae962..9ccfecc2dac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,8 @@ set(ARDUINO_LIBRARY_OpenThread_SRCS set(ARDUINO_LIBRARY_Matter_SRCS libraries/Matter/src/MatterEndpoints/MatterOnOffLight.cpp libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp + libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp + libraries/Matter/src/MatterUtil/ColorFormat.cpp libraries/Matter/src/Matter.cpp) set(ARDUINO_LIBRARY_PPP_SRCS diff --git a/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino b/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino index 5fd3152dacf..cac511926aa 100644 --- a/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino +++ b/libraries/Matter/examples/MatterDimmableLight/MatterDimmableLight.ino @@ -23,8 +23,8 @@ MatterDimmableLight DimmableLight; // it will keep last OnOff & Brightness state stored, using Preferences Preferences matterPref; -const char *onOffPrefKey = "OnOffState"; -const char *brightnessPrefKey = "BrightnessState"; +const char *onOffPrefKey = "OnOff"; +const char *brightnessPrefKey = "Brightness"; // set your board RGB LED pin here #ifdef RGB_BUILTIN diff --git a/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino b/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino index 8f4276643e2..0bdd0eb19b7 100644 --- a/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino +++ b/libraries/Matter/examples/MatterOnOffLight/MatterOnOffLight.ino @@ -23,7 +23,7 @@ MatterOnOffLight OnOffLight; // it will keep last OnOff state stored, using Preferences Preferences matterPref; -const char *onOffPrefKey = "OnOffState"; +const char *onOffPrefKey = "OnOff"; // set your board LED pin here #ifdef LED_BUILTIN diff --git a/libraries/Matter/examples/Matter_CW_WW_Light/Matter_CW_WW_Light.ino b/libraries/Matter/examples/Matter_CW_WW_Light/Matter_CW_WW_Light.ino new file mode 100644 index 00000000000..39392d90225 --- /dev/null +++ b/libraries/Matter/examples/Matter_CW_WW_Light/Matter_CW_WW_Light.ino @@ -0,0 +1,196 @@ +// 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 +// Color Temperature CW/WW Light Endpoint +MatterColorTemperatureLight CW_WW_Light; + +// it will keep last OnOff & Brightness state stored, using Preferences +Preferences matterPref; +const char *onOffPrefKey = "OnOff"; +const char *brightnessPrefKey = "Brightness"; +const char *temperaturePrefKey = "Temperature"; + +// 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 Color Temperature Light +bool setLightState(bool state, uint8_t brightness, uint16_t temperature_Mireds) { + + if (state) { +#ifdef RGB_BUILTIN + CtColor_t ct = {temperature_Mireds}; + RgbColor_t rgb_ct = CTToRgb(ct); + // simple intensity correction + float brightnessPercent = (float)brightness / MatterColorTemperatureLight::MAX_BRIGHTNESS; + rgb_ct.r = brightnessPercent * rgb_ct.r; + rgb_ct.g = brightnessPercent * rgb_ct.g; + rgb_ct.b = brightnessPercent * rgb_ct.b; + // set the RGB LED + rgbLedWrite(ledPin, rgb_ct.r, rgb_ct.g, rgb_ct.b); +#else + // No Color RGB LED, just use the brightness to control the LED + analogWrite(ledPin, brightness); +#endif + } else { + digitalWrite(ledPin, LOW); + } + // store last Brightness and OnOff state for when the Light is restarted / power goes off + matterPref.putUChar(brightnessPrefKey, brightness); + matterPref.putBool(onOffPrefKey, state); + matterPref.putUShort(temperaturePrefKey, temperature_Mireds); + // 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 + matterPref.begin("MatterPrefs", false); + // default OnOff state is ON if not stored before + bool lastOnOffState = matterPref.getBool(onOffPrefKey, true); + // default brightness ~= 6% (15/255) + uint8_t lastBrightness = matterPref.getUChar(brightnessPrefKey, 15); + // default temperature ~= 454 Mireds (Warm White) + uint16_t lastTemperature = matterPref.getUShort(temperaturePrefKey, MatterColorTemperatureLight::WARM_WHITE_COLOR_TEMPERATURE); + CW_WW_Light.begin(lastOnOffState, lastBrightness, lastTemperature); + // set the callback function to handle the Light state change + CW_WW_Light.onChange(setLightState); + + // lambda functions are used to set the attribute change callbacks + CW_WW_Light.onChangeOnOff([](bool state) { + Serial.printf("Light OnOff changed to %s\r\n", state ? "ON" : "OFF"); + return true; + }); + CW_WW_Light.onChangeBrightness([](uint8_t level) { + Serial.printf("Light Brightness changed to %d\r\n", level); + return true; + }); + CW_WW_Light.onChangeColorTemperature([](uint16_t temperature) { + Serial.printf("Light Color Temperature changed to %d\r\n", temperature); + 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 | Color Temperature: %d mireds \r\n", CW_WW_Light ? "ON" : "OFF", CW_WW_Light.getBrightness(), + CW_WW_Light.getColorTemperature() + ); + // configure the Light based on initial on-off state and brightness + CW_WW_Light.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 | Color Temperature: %d mireds \r\n", CW_WW_Light ? "ON" : "OFF", CW_WW_Light.getBrightness(), + CW_WW_Light.getColorTemperature() + ); + // configure the Light based on initial on-off state and brightness + CW_WW_Light.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!"); + CW_WW_Light.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."); + CW_WW_Light = false; // turn the light off + Matter.decommission(); + } + } +} diff --git a/libraries/Matter/examples/Matter_CW_WW_Light/ci.json b/libraries/Matter/examples/Matter_CW_WW_Light/ci.json new file mode 100644 index 00000000000..556a8a9ee6b --- /dev/null +++ b/libraries/Matter/examples/Matter_CW_WW_Light/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 98abce410b1..bcc99253e72 100644 --- a/libraries/Matter/keywords.txt +++ b/libraries/Matter/keywords.txt @@ -10,7 +10,12 @@ Matter KEYWORD1 ArduinoMatter KEYWORD1 MatterOnOffLight KEYWORD1 MatterDimmableLight KEYWORD1 +MatterColorTemperatureLight KEYWORD1 MatterEndPoint KEYWORD1 +CtColor_t KEYWORD1 +XyColor_t KEYWORD1 +HsvColor_t KEYWORD1 +RgbColor_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -30,12 +35,28 @@ setOnOff KEYWORD2 getOnOff KEYWORD2 setBrightness KEYWORD2 getBrightness KEYWORD2 +setColorTemperature KEYWORD2 +getColorTemperature KEYWORD2 toggle KEYWORD2 updateAccessory KEYWORD2 onChange KEYWORD2 onChangeOnOff KEYWORD2 onChangeBrightness KEYWORD2 +onChangeColorTemperature KEYWORD2 +XYToRgb KEYWORD2 +HsvToRgb KEYWORD2 +CTToRgb KEYWORD2 +RgbToHsv KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### + +MAX_BRIGHTNESS LITERAL1 +MAX_COLOR_TEMPERATURE LITERAL1 +MIN_COLOR_TEMPERATURE LITERAL1 +COOL_WHITE_COLOR_TEMPERATURE LITERAL1 +DAYLIGHT_WHITE_COLOR_TEMPERATURE LITERAL1 +WHITE_COLOR_TEMPERATURE LITERAL1 +SOFT_WHITE_COLOR_TEMPERATURE LITERAL1 +WARM_WHITE_COLOR_TEMPERATURE LITERAL1 diff --git a/libraries/Matter/src/Matter.h b/libraries/Matter/src/Matter.h index f88b7788016..e9d8b715388 100644 --- a/libraries/Matter/src/Matter.h +++ b/libraries/Matter/src/Matter.h @@ -18,8 +18,10 @@ #include #include +#include #include #include +#include using namespace esp_matter; @@ -47,6 +49,7 @@ class ArduinoMatter { // list of Matter EndPoints Friend Classes friend class MatterOnOffLight; friend class MatterDimmableLight; + friend class MatterColorTemperatureLight; protected: static void _init(); diff --git a/libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp b/libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp new file mode 100644 index 00000000000..7bbcb83dcfe --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.cpp @@ -0,0 +1,245 @@ +// 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 MatterColorTemperatureLight::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 CW_WW Light device has not begun."); + return false; + } + + log_d("CW_WW 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("CW_WW Light 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, colorTemperatureLevel); + } + if (ret == true) { + onOffState = val->val.b; + } + } + break; + case LevelControl::Id: + if (attribute_id == LevelControl::Attributes::CurrentLevel::Id) { + log_d("CW_WW Light Brightness changed to %d", val->val.u8); + if (_onChangeBrightnessCB != NULL) { + ret &= _onChangeBrightnessCB(val->val.u8); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(onOffState, val->val.u8, colorTemperatureLevel); + } + if (ret == true) { + brightnessLevel = val->val.u8; + } + } + break; + case ColorControl::Id: + if (attribute_id == ColorControl::Attributes::ColorTemperatureMireds::Id) { + log_d("CW_WW Light Temperature changed to %d", val->val.u16); + if (_onChangeTemperatureCB != NULL) { + ret &= _onChangeTemperatureCB(val->val.u16); + } + if (_onChangeCB != NULL) { + ret &= _onChangeCB(onOffState, brightnessLevel, val->val.u16); + } + if (ret == true) { + colorTemperatureLevel = val->val.u16; + } + } + break; + } + } + return ret; +} + +MatterColorTemperatureLight::MatterColorTemperatureLight() {} + +MatterColorTemperatureLight::~MatterColorTemperatureLight() { + end(); +} + +bool MatterColorTemperatureLight::begin(bool initialState, uint8_t brightness, uint16_t ColorTemperature) { + ArduinoMatter::_init(); + color_temperature_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; + + light_config.color_control.color_mode = (uint8_t)ColorControl::ColorMode::kColorTemperature; + light_config.color_control.enhanced_color_mode = (uint8_t)ColorControl::ColorMode::kColorTemperature; + light_config.color_control.color_temperature.color_temperature_mireds = ColorTemperature; + light_config.color_control.color_temperature.startup_color_temperature_mireds = nullptr; + colorTemperatureLevel = ColorTemperature; + + // endpoint handles can be used to add/modify clusters. + endpoint_t *endpoint = color_temperature_light::create(node::get(), &light_config, ENDPOINT_FLAG_NONE, (void *)this); + if (endpoint == nullptr) { + log_e("Failed to create CW_WW light endpoint"); + return false; + } + + setEndPointId(endpoint::get_id(endpoint)); + log_i("CW_WW Light created with endpoint_id %d", getEndPointId()); + + /* Mark deferred persistence for some attributes that might be changed rapidly */ + cluster_t *level_control_cluster = cluster::get(endpoint, LevelControl::Id); + attribute_t *current_level_attribute = attribute::get(level_control_cluster, LevelControl::Attributes::CurrentLevel::Id); + attribute::set_deferred_persistence(current_level_attribute); + + cluster_t *color_control_cluster = cluster::get(endpoint, ColorControl::Id); + attribute_t *color_temp_attribute = attribute::get(color_control_cluster, ColorControl::Attributes::ColorTemperatureMireds::Id); + attribute::set_deferred_persistence(color_temp_attribute); + + started = true; + return true; +} + +void MatterColorTemperatureLight::end() { + started = false; +} + +bool MatterColorTemperatureLight::setOnOff(bool newState) { + if (!started) { + log_e("Matter CW_WW 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 MatterColorTemperatureLight::updateAccessory() { + if (_onChangeCB != NULL) { + _onChangeCB(onOffState, brightnessLevel, colorTemperatureLevel); + } +} + +bool MatterColorTemperatureLight::getOnOff() { + return onOffState; +} + +bool MatterColorTemperatureLight::toggle() { + return setOnOff(!onOffState); +} + +bool MatterColorTemperatureLight::setBrightness(uint8_t newBrightness) { + if (!started) { + log_w("Matter CW_WW 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 MatterColorTemperatureLight::getBrightness() { + return brightnessLevel; +} + +bool MatterColorTemperatureLight::setColorTemperature(uint16_t newTemperature) { + if (!started) { + log_w("Matter CW_WW Light device has not begun."); + return false; + } + + // avoid processing the a "no-change" + if (colorTemperatureLevel == newTemperature) { + return true; + } + + colorTemperatureLevel = newTemperature; + + endpoint_t *endpoint = endpoint::get(node::get(), endpoint_id); + cluster_t *cluster = cluster::get(endpoint, ColorControl::Id); + attribute_t *attribute = attribute::get(cluster, ColorControl::Attributes::ColorTemperatureMireds::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + + if (val.val.u16 != colorTemperatureLevel) { + val.val.u16 = colorTemperatureLevel; + attribute::update(endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id, &val); + } + return true; +} + +uint16_t MatterColorTemperatureLight::getColorTemperature() { + return colorTemperatureLevel; +} + +MatterColorTemperatureLight::operator bool() { + return getOnOff(); +} + +void MatterColorTemperatureLight::operator=(bool newState) { + setOnOff(newState); +} +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.h b/libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.h new file mode 100644 index 00000000000..a37f362f475 --- /dev/null +++ b/libraries/Matter/src/MatterEndpoints/MatterColorTemperatureLight.h @@ -0,0 +1,94 @@ +// 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 MatterColorTemperatureLight : public MatterEndPoint { +public: + static const uint8_t MAX_BRIGHTNESS = 255; + static const uint16_t MAX_COLOR_TEMPERATURE = 500; + static const uint16_t MIN_COLOR_TEMPERATURE = 100; + // main color temperature values + static const uint16_t COOL_WHITE_COLOR_TEMPERATURE = 142; + static const uint16_t DAYLIGHT_WHITE_COLOR_TEMPERATURE = 181; + static const uint16_t WHITE_COLOR_TEMPERATURE = 250; + static const uint16_t SOFT_WHITE_COLOR_TEMPERATURE = 370; + static const uint16_t WARM_WHITE_COLOR_TEMPERATURE = 454; + + MatterColorTemperatureLight(); + ~MatterColorTemperatureLight(); + // default initial state is off, brightness is 64 (25%) and temperature is 370 (Soft White) + virtual bool begin(bool initialState = false, uint8_t brightness = 64, uint16_t colorTemperature = 370); + // 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 + + bool setColorTemperature(uint16_t newTemperature); // returns true if successful + uint16_t getColorTemperature(); // returns current temperature + + // 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 Callbqck for whenever the Light temperature value is changed by the Matter Controller + using EndPointTemperatureCB = std::function; + void onChangeColorTemperature(EndPointTemperatureCB onChangeCB) { + _onChangeTemperatureCB = 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) + uint16_t colorTemperatureLevel = 0; // default initial color temperature is 0, but it can be changed by begin(bool, uint8_t, uint16_t) + EndPointOnOffCB _onChangeOnOffCB = NULL; + EndPointBrightnessCB _onChangeBrightnessCB = NULL; + EndPointTemperatureCB _onChangeTemperatureCB = NULL; + EndPointCB _onChangeCB = NULL; +}; +#endif /* CONFIG_ESP_MATTER_ENABLE_DATA_MODEL */ diff --git a/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp index 7907ae3a90a..c3991e0c0f3 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp +++ b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.cpp @@ -94,6 +94,12 @@ bool MatterDimmableLight::begin(bool initialState, uint8_t brightness) { setEndPointId(endpoint::get_id(endpoint)); log_i("Dimmable Light created with endpoint_id %d", getEndPointId()); + + /* Mark deferred persistence for some attributes that might be changed rapidly */ + cluster_t *level_control_cluster = cluster::get(endpoint, LevelControl::Id); + attribute_t *current_level_attribute = attribute::get(level_control_cluster, LevelControl::Attributes::CurrentLevel::Id); + attribute::set_deferred_persistence(current_level_attribute); + started = true; return true; } diff --git a/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h index fbfccde6105..aacce883277 100644 --- a/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h +++ b/libraries/Matter/src/MatterEndpoints/MatterDimmableLight.h @@ -25,8 +25,8 @@ class MatterDimmableLight : public MatterEndPoint { MatterDimmableLight(); ~MatterDimmableLight(); - // default initial state is off and brightness is 0 - virtual bool begin(bool initialState = false, uint8_t brightness = 0); + // default initial state is off and brightness is 64 (25%) + virtual bool begin(bool initialState = false, uint8_t brightness = 64); // this will just stop processing Light Matter events void end(); diff --git a/libraries/Matter/src/MatterUtil/ColorFormat.cpp b/libraries/Matter/src/MatterUtil/ColorFormat.cpp new file mode 100644 index 00000000000..41d845dcdb9 --- /dev/null +++ b/libraries/Matter/src/MatterUtil/ColorFormat.cpp @@ -0,0 +1,203 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ColorFormat.h" + +#include + +// define a clamp macro to substitute the std::clamp macro which is available from C++17 onwards +#define clamp(a, min, max) ((a) < (min) ? (min) : ((a) > (max) ? (max) : (a))) + +RgbColor_t HsvToRgb(HsvColor_t hsv) { + RgbColor_t rgb; + + uint16_t i = hsv.h / 60; + uint16_t rgb_max = hsv.v; + uint16_t rgb_min = (uint16_t)(rgb_max * (100 - hsv.s)) / 100; + uint16_t diff = hsv.h % 60; + uint16_t rgb_adj = (uint16_t)((rgb_max - rgb_min) * diff) / 60; + + switch (i) { + case 0: + rgb.r = (uint8_t)rgb_max; + rgb.g = (uint8_t)(rgb_min + rgb_adj); + rgb.b = (uint8_t)rgb_min; + break; + case 1: + rgb.r = (uint8_t)(rgb_max - rgb_adj); + rgb.g = (uint8_t)rgb_max; + rgb.b = (uint8_t)rgb_min; + break; + case 2: + rgb.r = (uint8_t)rgb_min; + rgb.g = (uint8_t)rgb_max; + rgb.b = (uint8_t)(rgb_min + rgb_adj); + break; + case 3: + rgb.r = (uint8_t)rgb_min; + rgb.g = (uint8_t)(rgb_max - rgb_adj); + rgb.b = (uint8_t)rgb_max; + break; + case 4: + rgb.r = (uint8_t)(rgb_min + rgb_adj); + rgb.g = (uint8_t)rgb_min; + rgb.b = (uint8_t)rgb_max; + break; + default: + rgb.r = (uint8_t)rgb_max; + rgb.g = (uint8_t)rgb_min; + rgb.b = (uint8_t)(rgb_max - rgb_adj); + break; + } + + return rgb; +} + +HsvColor_t RgbToHsv(RgbColor_t rgb) { + HsvColor_t hsv; + + uint16_t rgb_max = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b); + uint16_t rgb_min = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b); + uint16_t diff = rgb_max - rgb_min; + + if (diff == 0) { + hsv.h = 0; + } else if (rgb_max == rgb.r) { + hsv.h = (uint8_t)(60 * ((rgb.g - rgb.b) * 100) / diff); + } else if (rgb_max == rgb.g) { + hsv.h = (uint8_t)(60 * (((rgb.b - rgb.r) * 100) / diff + 2 * 100)); + } else { + hsv.h = (uint8_t)(60 * (((rgb.r - rgb.g) * 100) / diff + 4 * 100)); + } + + if (rgb_max == 0) { + hsv.s = 0; + } else { + hsv.s = (uint8_t)((diff * 100) / rgb_max); + } + + hsv.v = (uint8_t)rgb_max; + if (hsv.h < 0) { + hsv.h += 360; + } + + return hsv; +} + +RgbColor_t XYToRgb(uint8_t Level, uint16_t current_X, uint16_t current_Y) { + // convert xyY color space to RGB + + // https://www.easyrgb.com/en/math.php + // https://en.wikipedia.org/wiki/SRGB + // refer https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space + + // The current_X/current_Y attribute contains the current value of the normalized chromaticity value of x/y. + // The value of x/y shall be related to the current_X/current_Y attribute by the relationship + // x = current_X/65536 + // y = current_Y/65536 + // z = 1-x-y + + RgbColor_t rgb; + + float x, y, z; + float X, Y, Z; + float r, g, b; + + x = ((float)current_X) / 65535.0f; + y = ((float)current_Y) / 65535.0f; + + z = 1.0f - x - y; + + // Calculate XYZ values + + // Y - given brightness in 0 - 1 range + Y = ((float)Level) / 254.0f; + X = (Y / y) * x; + Z = (Y / y) * z; + + // X, Y and Z input refer to a D65/2° standard illuminant. + // sR, sG and sB (standard RGB) output range = 0 ÷ 255 + // convert XYZ to RGB - CIE XYZ to sRGB + X = X / 100.0f; + Y = Y / 100.0f; + Z = Z / 100.0f; + + r = (X * 3.2406f) - (Y * 1.5372f) - (Z * 0.4986f); + g = -(X * 0.9689f) + (Y * 1.8758f) + (Z * 0.0415f); + b = (X * 0.0557f) - (Y * 0.2040f) + (Z * 1.0570f); + + // apply gamma 2.2 correction + r = (r <= 0.0031308f ? 12.92f * r : (1.055f) * pow(r, (1.0f / 2.4f)) - 0.055f); + g = (g <= 0.0031308f ? 12.92f * g : (1.055f) * pow(g, (1.0f / 2.4f)) - 0.055f); + b = (b <= 0.0031308f ? 12.92f * b : (1.055f) * pow(b, (1.0f / 2.4f)) - 0.055f); + + // Round off + r = clamp(r, 0, 1); + g = clamp(g, 0, 1); + b = clamp(b, 0, 1); + + // these rgb values are in the range of 0 to 1, convert to limit of HW specific LED + rgb.r = (uint8_t)(r * 255); + rgb.g = (uint8_t)(g * 255); + rgb.b = (uint8_t)(b * 255); + + return rgb; +} + +RgbColor_t CTToRgb(CtColor_t ct) { + RgbColor_t rgb = {0, 0, 0}; + float r, g, b; + + if (ct.ctMireds == 0) { + return rgb; + } + // Algorithm credits to Tanner Helland: https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html + + // Convert Mireds to centiKelvins. k = 1,000,000/mired + float ctCentiKelvin = 10000 / ct.ctMireds; + + // Red + if (ctCentiKelvin <= 66) { + r = 255; + } else { + r = 329.698727446f * pow(ctCentiKelvin - 60, -0.1332047592f); + } + + // Green + if (ctCentiKelvin <= 66) { + g = 99.4708025861f * log(ctCentiKelvin) - 161.1195681661f; + } else { + g = 288.1221695283f * pow(ctCentiKelvin - 60, -0.0755148492f); + } + + // Blue + if (ctCentiKelvin >= 66) { + b = 255; + } else { + if (ctCentiKelvin <= 19) { + b = 0; + } else { + b = 138.5177312231 * log(ctCentiKelvin - 10) - 305.0447927307; + } + } + rgb.r = (uint8_t)clamp(r, 0, 255); + rgb.g = (uint8_t)clamp(g, 0, 255); + rgb.b = (uint8_t)clamp(b, 0, 255); + + return rgb; +} diff --git a/libraries/Matter/src/MatterUtil/ColorFormat.h b/libraries/Matter/src/MatterUtil/ColorFormat.h new file mode 100644 index 00000000000..254a51c7144 --- /dev/null +++ b/libraries/Matter/src/MatterUtil/ColorFormat.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +struct RgbColor_t { + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct HsvColor_t { + int16_t h; + uint8_t s; + uint8_t v; +}; + +struct XyColor_t { + uint16_t x; + uint16_t y; +}; + +struct CtColor_t { + uint16_t ctMireds; +}; + +RgbColor_t XYToRgb(uint8_t Level, uint16_t current_X, uint16_t current_Y); +RgbColor_t HsvToRgb(HsvColor_t hsv); +RgbColor_t CTToRgb(CtColor_t ct); +HsvColor_t RgbToHsv(RgbColor_t rgb);