diff --git a/docs/en/zigbee/ep_color_dimmable_light.rst b/docs/en/zigbee/ep_color_dimmable_light.rst index b2fa6d0bf91..46a785424e2 100644 --- a/docs/en/zigbee/ep_color_dimmable_light.rst +++ b/docs/en/zigbee/ep_color_dimmable_light.rst @@ -5,13 +5,17 @@ ZigbeeColorDimmableLight About ----- -The ``ZigbeeColorDimmableLight`` class provides an endpoint for color dimmable lights in Zigbee networks. This endpoint implements the Zigbee Home Automation (HA) standard for color lighting devices, supporting RGB color control, dimming, and scene management. +The ``ZigbeeColorDimmableLight`` class provides an endpoint for color dimmable lights in Zigbee networks. This endpoint implements the Zigbee Home Automation (HA) standard for color lighting devices, supporting multiple color modes (RGB/XY, HSV, and Color Temperature), dimming, and scene management. **Features:** * On/off control -* Brightness level control (0-100%) -* RGB color control -* HSV color support +* Brightness level control (0-255) +* RGB/XY color control +* HSV (Hue/Saturation) color support +* Color temperature (mireds) support +* Configurable color capabilities (enable/disable color modes) +* Separate callbacks for RGB, HSV, and Temperature modes +* Automatic color mode switching * Scene and group support * Automatic state restoration * Integration with common endpoint features (binding, OTA, etc.) @@ -20,6 +24,8 @@ The ``ZigbeeColorDimmableLight`` class provides an endpoint for color dimmable l **Use Cases:** * Smart RGB light bulbs * Color-changing LED strips +* Tunable white light bulbs +* Full-spectrum color temperature lights * Mood lighting systems * Entertainment lighting * Architectural lighting @@ -42,13 +48,66 @@ Creates a new Zigbee color dimmable light endpoint. * ``endpoint`` - Endpoint number (1-254) +Color Capabilities +****************** + +setLightColorCapabilities +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Configures which color modes are supported by the light. Must be called before starting Zigbee. By default, only XY (RGB) mode is enabled. + +.. code-block:: arduino + + bool setLightColorCapabilities(uint16_t capabilities); + +* ``capabilities`` - Bit flags indicating supported color modes (can be combined with bitwise OR): + + * ``ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION`` - Hue/Saturation support + * ``ZIGBEE_COLOR_CAPABILITY_X_Y`` - XY (RGB) support + * ``ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP`` - Color temperature support + * ``ZIGBEE_COLOR_CAPABILITY_ENHANCED_HUE`` - Enhanced hue support + * ``ZIGBEE_COLOR_CAPABILITY_COLOR_LOOP`` - Color loop support + +**Example:** + +.. code-block:: arduino + + // Enable XY and Temperature modes + light.setLightColorCapabilities( + ZIGBEE_COLOR_CAPABILITY_X_Y | ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP + ); + + // Enable all color modes + light.setLightColorCapabilities( + ZIGBEE_COLOR_CAPABILITY_X_Y | + ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION | + ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP + ); + +This function will return ``true`` if successful, ``false`` otherwise. + Callback Functions ****************** +Callback Type Definitions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +For better type safety and readability, typedefs are provided for all callback functions: + +.. code-block:: arduino + + typedef void (*ZigbeeColorLightRgbCallback)(bool state, uint8_t red, uint8_t green, uint8_t blue, uint8_t level); + typedef void (*ZigbeeColorLightHsvCallback)(bool state, uint8_t hue, uint8_t saturation, uint8_t value); + typedef void (*ZigbeeColorLightTempCallback)(bool state, uint8_t level, uint16_t color_temperature); + +These typedefs can be used instead of raw function pointer syntax for better code clarity. + onLightChange ^^^^^^^^^^^^^ -Sets the callback function for light state changes. +.. deprecated:: This method is deprecated and will be removed in a future major version. Use ``onLightChangeRgb()`` instead. + +Sets the legacy callback function for light state changes (RGB mode). .. code-block:: arduino @@ -56,6 +115,69 @@ Sets the callback function for light state changes. * ``callback`` - Function pointer to the light change callback (state, red, green, blue, level) + * ``state`` - Light state (true = on, false = off) + * ``red`` - Red component (0-255) + * ``green`` - Green component (0-255) + * ``blue`` - Blue component (0-255) + * ``level`` - Brightness level (0-255) + +.. note:: + This method is deprecated. Please use ``onLightChangeRgb()`` for RGB/XY mode callbacks. + +onLightChangeRgb +^^^^^^^^^^^^^^^^ + +Sets the callback function for RGB/XY color mode changes. + +.. code-block:: arduino + + void onLightChangeRgb(ZigbeeColorLightRgbCallback callback); + // or using raw function pointer syntax: + void onLightChangeRgb(void (*callback)(bool, uint8_t, uint8_t, uint8_t, uint8_t)); + +* ``callback`` - Function pointer to the RGB light change callback (state, red, green, blue, level) + + * ``state`` - Light state (true = on, false = off) + * ``red`` - Red component (0-255) + * ``green`` - Green component (0-255) + * ``blue`` - Blue component (0-255) + * ``level`` - Brightness level (0-255) + +onLightChangeHsv +^^^^^^^^^^^^^^^^ + +Sets the callback function for HSV (Hue/Saturation) color mode changes. + +.. code-block:: arduino + + void onLightChangeHsv(ZigbeeColorLightHsvCallback callback); + // or using raw function pointer syntax: + void onLightChangeHsv(void (*callback)(bool, uint8_t, uint8_t, uint8_t)); + +* ``callback`` - Function pointer to the HSV light change callback (state, hue, saturation, value) + + * ``state`` - Light state (true = on, false = off) + * ``hue`` - Hue component (0-254) + * ``saturation`` - Saturation component (0-254) + * ``value`` - Value/brightness component (0-255) + +onLightChangeTemp +^^^^^^^^^^^^^^^^^ + +Sets the callback function for color temperature mode changes. + +.. code-block:: arduino + + void onLightChangeTemp(ZigbeeColorLightTempCallback callback); + // or using raw function pointer syntax: + void onLightChangeTemp(void (*callback)(bool, uint8_t, uint16_t)); + +* ``callback`` - Function pointer to the temperature light change callback (state, level, temperature_mireds) + + * ``state`` - Light state (true = on, false = off) + * ``level`` - Brightness level (0-255) + * ``temperature_mireds`` - Color temperature in mireds (inverse of Kelvin) + Control Methods *************** @@ -81,14 +203,14 @@ Sets the light brightness level. bool setLightLevel(uint8_t level); -* ``level`` - Brightness level (0-100, where 0 is off, 100 is full brightness) +* ``level`` - Brightness level (0-255, where 0 is off, 255 is full brightness) This function will return ``true`` if successful, ``false`` otherwise. setLightColor (RGB) ^^^^^^^^^^^^^^^^^^^ -Sets the light color using RGB values. +Sets the light color using RGB values. Requires ``ZIGBEE_COLOR_CAPABILITY_X_Y`` capability to be enabled. .. code-block:: arduino @@ -100,12 +222,12 @@ Sets the light color using RGB values. * ``blue`` - Blue component (0-255) * ``rgb_color`` - RGB color structure -This function will return ``true`` if successful, ``false`` otherwise. +This function will return ``true`` if successful, ``false`` otherwise. Returns ``false`` if XY capability is not enabled. setLightColor (HSV) ^^^^^^^^^^^^^^^^^^^ -Sets the light color using HSV values. +Sets the light color using HSV values. Requires ``ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION`` capability to be enabled. .. code-block:: arduino @@ -113,24 +235,72 @@ Sets the light color using HSV values. * ``hsv_color`` - HSV color structure +This function will return ``true`` if successful, ``false`` otherwise. Returns ``false`` if HSV capability is not enabled. + +setLightColorTemperature +^^^^^^^^^^^^^^^^^^^^^^^^ + +Sets the light color temperature in mireds. Requires ``ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP`` capability to be enabled. + +.. code-block:: arduino + + bool setLightColorTemperature(uint16_t color_temperature); + +* ``color_temperature`` - Color temperature in mireds (inverse of Kelvin: mireds = 1000000 / Kelvin) + +**Example:** + +.. code-block:: arduino + + // Set to 4000K (cool white) + uint16_t mireds = 1000000 / 4000; // = 250 mireds + light.setLightColorTemperature(mireds); + + // Set to 2700K (warm white) + mireds = 1000000 / 2700; // = 370 mireds + light.setLightColorTemperature(mireds); + +This function will return ``true`` if successful, ``false`` otherwise. Returns ``false`` if color temperature capability is not enabled. + +setLightColorTemperatureRange +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sets the minimum and maximum color temperature range supported by the hardware. + +.. code-block:: arduino + + bool setLightColorTemperatureRange(uint16_t min_temp, uint16_t max_temp); + +* ``min_temp`` - Minimum color temperature in mireds +* ``max_temp`` - Maximum color temperature in mireds + +**Example:** + +.. code-block:: arduino + + // Set range for 2000K (warm) to 6500K (cool) + uint16_t min_mireds = 1000000 / 6500; // = 154 mireds + uint16_t max_mireds = 1000000 / 2000; // = 500 mireds + light.setLightColorTemperatureRange(min_mireds, max_mireds); + This function will return ``true`` if successful, ``false`` otherwise. setLight ^^^^^^^^ -Sets all light parameters at once. +Sets all light parameters at once (RGB mode). Requires ``ZIGBEE_COLOR_CAPABILITY_X_Y`` capability to be enabled. .. code-block:: arduino bool setLight(bool state, uint8_t level, uint8_t red, uint8_t green, uint8_t blue); * ``state`` - Light state (true/false) -* ``level`` - Brightness level (0-100) +* ``level`` - Brightness level (0-255) * ``red`` - Red component (0-255) * ``green`` - Green component (0-255) * ``blue`` - Blue component (0-255) -This function will return ``true`` if successful, ``false`` otherwise. +This function will return ``true`` if successful, ``false`` otherwise. Returns ``false`` if XY capability is not enabled. State Retrieval Methods *********************** @@ -155,7 +325,7 @@ Gets the current brightness level. uint8_t getLightLevel(); -This function will return current brightness level (0-100). +This function will return current brightness level (0-255). getLightColor ^^^^^^^^^^^^^ @@ -201,18 +371,117 @@ Gets the current blue component. This function will return current blue component (0-255). +getLightColorTemperature +^^^^^^^^^^^^^^^^^^^^^^^^ + +Gets the current color temperature. + +.. code-block:: arduino + + uint16_t getLightColorTemperature(); + +This function will return current color temperature in mireds. + +getLightColorMode +^^^^^^^^^^^^^^^^^ + +Gets the current active color mode. + +.. code-block:: arduino + + uint8_t getLightColorMode(); + +This function will return current color mode: +* ``ZIGBEE_COLOR_MODE_HUE_SATURATION`` (0x00) - HSV mode +* ``ZIGBEE_COLOR_MODE_CURRENT_X_Y`` (0x01) - XY/RGB mode +* ``ZIGBEE_COLOR_MODE_TEMPERATURE`` (0x02) - Temperature mode + +getLightColorHue +^^^^^^^^^^^^^^^^ + +Gets the current hue value (HSV mode). + +.. code-block:: arduino + + uint8_t getLightColorHue(); + +This function will return current hue value (0-254). + +getLightColorSaturation +^^^^^^^^^^^^^^^^^^^^^^^ + +Gets the current saturation value (HSV mode). + +.. code-block:: arduino + + uint8_t getLightColorSaturation(); + +This function will return current saturation value (0-254). + +getLightColorCapabilities +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Gets the currently configured color capabilities. + +.. code-block:: arduino + + uint16_t getLightColorCapabilities(); + +This function will return the current color capabilities bit flags. + Utility Methods *************** restoreLight ^^^^^^^^^^^^ -Restores the light to its last known state. +Restores the light to its last known state. Uses the appropriate callback based on the current color mode. .. code-block:: arduino void restoreLight(); +Color Modes and Automatic Behavior +********************************** + +The ``ZigbeeColorDimmableLight`` class supports three color modes: + +* **XY/RGB Mode** (``ZIGBEE_COLOR_MODE_CURRENT_X_Y``) - Uses X/Y coordinates for color representation, internally converted to RGB +* **HSV Mode** (``ZIGBEE_COLOR_MODE_HUE_SATURATION``) - Uses Hue and Saturation values directly, without RGB conversion +* **Temperature Mode** (``ZIGBEE_COLOR_MODE_TEMPERATURE``) - Uses color temperature in mireds + +**Automatic Mode Switching:** + +The color mode is automatically updated based on which attributes are set: + +* Setting RGB colors via ``setLight()`` or ``setLightColor()`` (RGB) → switches to XY mode +* Setting HSV colors via ``setLightColor()`` (HSV) → switches to HSV mode +* Setting temperature via ``setLightColorTemperature()`` → switches to Temperature mode +* Receiving Zigbee commands for XY/HSV/TEMP attributes → automatically switches to the corresponding mode + +**Capability Validation:** + +All set methods validate that the required capability is enabled before allowing the operation: + +* RGB/XY methods require ``ZIGBEE_COLOR_CAPABILITY_X_Y`` +* HSV methods require ``ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION`` +* Temperature methods require ``ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP`` + +If a capability is not enabled, the method will return ``false`` and log an error. + +**Callback Selection:** + +The appropriate callback is automatically called based on the current color mode: + +* RGB/XY mode → ``onLightChangeRgb()`` callback +* HSV mode → ``onLightChangeHsv()`` callback +* Temperature mode → ``onLightChangeTemp()`` callback + +When level or state changes occur, the callback for the current color mode is used automatically. + +.. note:: + The legacy ``onLightChange()`` callback is deprecated and will be removed in a future major version. Always use the mode-specific callbacks (``onLightChangeRgb()``, ``onLightChangeHsv()``, or ``onLightChangeTemp()``). + Example ------- diff --git a/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino b/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino index e84720d4863..5549fcca959 100644 --- a/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino +++ b/libraries/Zigbee/examples/Zigbee_Color_Dimmable_Light/Zigbee_Color_Dimmable_Light.ino @@ -13,10 +13,10 @@ // limitations under the License. /** - * @brief This example demonstrates Zigbee Color Dimmable light bulb. + * @brief This example demonstrates Zigbee Color Dimmable light bulb with RGB and Temperature support. * * The example demonstrates how to use Zigbee library to create an end device with - * color dimmable light end point. + * color dimmable light end point supporting both RGB (X/Y) and Color Temperature modes. * The light bulb is a Zigbee end device, which is controlled by a Zigbee coordinator. * * Proper Zigbee mode must be selected in Tools->Zigbee mode @@ -40,6 +40,15 @@ uint8_t button = BOOT_PIN; ZigbeeColorDimmableLight zbColorLight = ZigbeeColorDimmableLight(ZIGBEE_RGB_LIGHT_ENDPOINT); +/********************* Temperature conversion functions **************************/ +uint16_t kelvinToMireds(uint16_t kelvin) { + return 1000000 / kelvin; +} + +uint16_t miredsToKelvin(uint16_t mireds) { + return 1000000 / mireds; +} + /********************* RGB LED functions **************************/ void setRGBLight(bool state, uint8_t red, uint8_t green, uint8_t blue, uint8_t level) { if (!state) { @@ -50,6 +59,20 @@ void setRGBLight(bool state, uint8_t red, uint8_t green, uint8_t blue, uint8_t l rgbLedWrite(led, red * brightness, green * brightness, blue * brightness); } +/********************* Temperature LED functions **************************/ +void setTempLight(bool state, uint8_t level, uint16_t mireds) { + if (!state) { + rgbLedWrite(led, 0, 0, 0); + return; + } + float brightness = (float)level / 255; + // Convert mireds to color temperature (K) and map to white/yellow + uint16_t kelvin = miredsToKelvin(mireds); + uint8_t warm = constrain(map(kelvin, 2000, 6500, 255, 0), 0, 255); + uint8_t cold = constrain(map(kelvin, 2000, 6500, 0, 255), 0, 255); + rgbLedWrite(led, warm * brightness, warm * brightness, cold * brightness); +} + // Create a task on identify call to handle the identify function void identify(uint16_t time) { static uint8_t blink = 1; @@ -73,8 +96,13 @@ void setup() { // Init button for factory reset pinMode(button, INPUT_PULLUP); - // Set callback function for light change - zbColorLight.onLightChange(setRGBLight); + // Enable both XY (RGB) and Temperature color capabilities + uint16_t capabilities = ZIGBEE_COLOR_CAPABILITY_X_Y | ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP; + zbColorLight.setLightColorCapabilities(capabilities); + + // Set callback functions for RGB and Temperature modes + zbColorLight.onLightChangeRgb(setRGBLight); + zbColorLight.onLightChangeTemp(setTempLight); // Optional: Set callback function for device identify zbColorLight.onIdentify(identify); @@ -82,6 +110,9 @@ void setup() { // Optional: Set Zigbee device name and model zbColorLight.setManufacturerAndModel("Espressif", "ZBColorLightBulb"); + // Set min/max temperature range (High Kelvin -> Low Mireds: Min and Max is switched) + zbColorLight.setLightColorTemperatureRange(kelvinToMireds(6500), kelvinToMireds(2000)); + // Add endpoint to Zigbee Core Serial.println("Adding ZigbeeLight endpoint to Zigbee Core"); Zigbee.addEndpoint(&zbColorLight); diff --git a/libraries/Zigbee/keywords.txt b/libraries/Zigbee/keywords.txt index c940d85d4d1..9d981dcc39c 100644 --- a/libraries/Zigbee/keywords.txt +++ b/libraries/Zigbee/keywords.txt @@ -49,6 +49,7 @@ ZigbeeWindowCoveringType KEYWORD1 ZigbeeFanMode KEYWORD1 ZigbeeFanModeSequence KEYWORD1 zb_cmd_type_t KEYWORD1 +ZigbeeColorMode KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -109,12 +110,23 @@ setLight KEYWORD2 setLightState KEYWORD2 setLightLevel KEYWORD2 setLightColor KEYWORD2 +setLightColorTemperature KEYWORD2 +setLightColorCapabilities KEYWORD2 +setLightColorTemperatureRange KEYWORD2 getLightState KEYWORD2 getLightLevel KEYWORD2 getLightRed KEYWORD2 getLightGreen KEYWORD2 getLightBlue KEYWORD2 +getLightColorTemperature KEYWORD2 +getLightColorMode KEYWORD2 +getLightColorHue KEYWORD2 +getLightColorSaturation KEYWORD2 +getLightColorCapabilities KEYWORD2 onLightChange KEYWORD2 +onLightChangeRgb KEYWORD2 +onLightChangeHsv KEYWORD2 +onLightChangeTemp KEYWORD2 onLightColorChangeWithSource KEYWORD2 onLightLevelChange KEYWORD2 onLightLevelChangeWithSource KEYWORD2 @@ -325,3 +337,13 @@ ZB_MULTISTATE_APPLICATION_TYPE_11_INDEX LITERAL1 ZB_MULTISTATE_APPLICATION_TYPE_11_NUM_STATES LITERAL1 ZB_MULTISTATE_APPLICATION_TYPE_11_STATE_NAMES LITERAL1 ZB_MULTISTATE_APPLICATION_TYPE_OTHER_INDEX LITERAL1 + +#ZigbeeColorDimmableLight +ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION LITERAL1 +ZIGBEE_COLOR_CAPABILITY_ENHANCED_HUE LITERAL1 +ZIGBEE_COLOR_CAPABILITY_COLOR_LOOP LITERAL1 +ZIGBEE_COLOR_CAPABILITY_X_Y LITERAL1 +ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP LITERAL1 +ZIGBEE_COLOR_MODE_HUE_SATURATION LITERAL1 +ZIGBEE_COLOR_MODE_CURRENT_X_Y LITERAL1 +ZIGBEE_COLOR_MODE_TEMPERATURE LITERAL1 diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp index 42ce9552a62..a31cf174c4e 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp @@ -18,7 +18,6 @@ ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP(endpoint) { _device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID; - _on_light_change = nullptr; esp_zb_color_dimmable_light_cfg_t light_cfg = ZIGBEE_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG(); _cluster_list = esp_zb_color_dimmable_light_clusters_create(&light_cfg); @@ -27,9 +26,19 @@ ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP( uint8_t hue = 0; uint8_t saturation = 0; + // Add support for Color Temperature and Hue Saturation attributes + uint16_t color_temperature = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_DEF_VALUE; + uint16_t min_temp = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MIN_MIREDS_DEFAULT_VALUE; + uint16_t max_temp = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MAX_MIREDS_DEFAULT_VALUE; + esp_zb_attribute_list_t *color_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID, &hue); esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID, &saturation); + esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ID, &color_temperature); + esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MIN_MIREDS_ID, &min_temp); + esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MAX_MIREDS_ID, &max_temp); + uint8_t color_mode = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_MODE_DEFAULT_VALUE; + esp_zb_color_control_cluster_add_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_MODE_ID, &color_mode); _ep_config = { .endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID, .app_device_version = 0 @@ -39,6 +48,15 @@ ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP( _current_state = false; _current_level = 255; _current_color = {255, 255, 255}; + _current_hsv = {0, 0, 255}; + _current_color_temperature = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_DEF_VALUE; + _current_color_mode = ZIGBEE_COLOR_MODE_CURRENT_X_Y; //default XY color mode + _color_capabilities = ZIGBEE_COLOR_CAPABILITY_X_Y; //default XY color supported only + + // Initialize callbacks to nullptr + _on_light_change_rgb = nullptr; + _on_light_change_hsv = nullptr; + _on_light_change_temp = nullptr; } uint16_t ZigbeeColorDimmableLight::getCurrentColorX() { @@ -63,12 +81,19 @@ uint8_t ZigbeeColorDimmableLight::getCurrentColorHue() { } uint8_t ZigbeeColorDimmableLight::getCurrentColorSaturation() { - return (*(uint16_t *)esp_zb_zcl_get_attribute( + return (*(uint8_t *)esp_zb_zcl_get_attribute( _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID ) ->data_p); } +uint16_t ZigbeeColorDimmableLight::getCurrentColorTemperature() { + return (*(uint16_t *)esp_zb_zcl_get_attribute( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ID + ) + ->data_p); +} + //set attribute method -> method overridden in child class void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) { //check the data and call right method @@ -76,7 +101,7 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { if (_current_state != *(bool *)message->attribute.data.value) { _current_state = *(bool *)message->attribute.data.value; - lightChanged(); + lightChangedByMode(); } return; } else { @@ -86,37 +111,117 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me if (message->attribute.id == ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { if (_current_level != *(uint8_t *)message->attribute.data.value) { _current_level = *(uint8_t *)message->attribute.data.value; - lightChanged(); + // Update HSV value if in HSV mode + if (_current_color_mode == ZIGBEE_COLOR_MODE_HUE_SATURATION) { + _current_hsv.v = _current_level; + } + lightChangedByMode(); } return; } else { log_w("Received message ignored. Attribute ID: %d not supported for Level Control", message->attribute.id); } } else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL) { - if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_MODE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_8BIT_ENUM) { + uint8_t new_color_mode = (*(uint8_t *)message->attribute.data.value); + if (new_color_mode > ZIGBEE_COLOR_MODE_TEMPERATURE) { + log_w("Invalid color mode received: %d", new_color_mode); + return; + } + + // Validate that the requested color mode is supported by capabilities + if (!isColorModeSupported(new_color_mode)) { + log_w("Color mode %d not supported by current capabilities: 0x%04x", new_color_mode, _color_capabilities); + return; + } + + _current_color_mode = new_color_mode; + log_v("Color mode changed to: %d", _current_color_mode); + // Don't call setLightColorMode() here - the attribute was already set externally + // Just update our internal state + return; + } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_X_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + // Validate XY capability + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_X_Y)) { + log_w("XY color capability not enabled, but XY attribute received. Current capabilities: 0x%04x", _color_capabilities); + return; + } uint16_t light_color_x = (*(uint16_t *)message->attribute.data.value); uint16_t light_color_y = getCurrentColorY(); - //calculate RGB from XY and call setColor() + // Update color mode to XY if not already + if (_current_color_mode != ZIGBEE_COLOR_MODE_CURRENT_X_Y) { + setLightColorMode(ZIGBEE_COLOR_MODE_CURRENT_X_Y); + } + //calculate RGB from XY and call RGB callback _current_color = espXYToRgbColor(255, light_color_x, light_color_y, false); - lightChanged(); + lightChangedRgb(); return; } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + // Validate XY capability + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_X_Y)) { + log_w("XY color capability not enabled, but XY attribute received. Current capabilities: 0x%04x", _color_capabilities); + return; + } uint16_t light_color_x = getCurrentColorX(); uint16_t light_color_y = (*(uint16_t *)message->attribute.data.value); - //calculate RGB from XY and call setColor() + // Update color mode to XY if not already + if (_current_color_mode != ZIGBEE_COLOR_MODE_CURRENT_X_Y) { + setLightColorMode(ZIGBEE_COLOR_MODE_CURRENT_X_Y); + } + //calculate RGB from XY and call RGB callback _current_color = espXYToRgbColor(255, light_color_x, light_color_y, false); - lightChanged(); + lightChangedRgb(); return; } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { + // Validate Hue/Saturation capability + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION)) { + log_w("Hue/Saturation color capability not enabled, but Hue attribute received. Current capabilities: 0x%04x", _color_capabilities); + return; + } uint8_t light_color_hue = (*(uint8_t *)message->attribute.data.value); - _current_color = espHsvToRgbColor(light_color_hue, getCurrentColorSaturation(), 255); - lightChanged(); + // Update color mode to HS if not already + if (_current_color_mode != ZIGBEE_COLOR_MODE_HUE_SATURATION) { + setLightColorMode(ZIGBEE_COLOR_MODE_HUE_SATURATION); + } + // Store HSV values and call HSV callback (don't convert to RGB) + _current_hsv.h = light_color_hue; + _current_hsv.s = getCurrentColorSaturation(); + _current_hsv.v = _current_level; // Use level as value + lightChangedHsv(); return; } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) { + // Validate Hue/Saturation capability + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION)) { + log_w("Hue/Saturation color capability not enabled, but Saturation attribute received. Current capabilities: 0x%04x", _color_capabilities); + return; + } uint8_t light_color_saturation = (*(uint8_t *)message->attribute.data.value); - _current_color = espHsvToRgbColor(getCurrentColorHue(), light_color_saturation, 255); - lightChanged(); + // Update color mode to HS if not already + if (_current_color_mode != ZIGBEE_COLOR_MODE_HUE_SATURATION) { + setLightColorMode(ZIGBEE_COLOR_MODE_HUE_SATURATION); + } + // Store HSV values and call HSV callback (don't convert to RGB) + _current_hsv.h = getCurrentColorHue(); + _current_hsv.s = light_color_saturation; + _current_hsv.v = _current_level; // Use level as value + lightChangedHsv(); + return; + } else if (message->attribute.id == ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) { + // Validate Color Temperature capability + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP)) { + log_w("Color temperature capability not enabled, but Temperature attribute received. Current capabilities: 0x%04x", _color_capabilities); + return; + } + uint16_t light_color_temp = (*(uint16_t *)message->attribute.data.value); + // Update color mode to TEMP if not already + if (_current_color_mode != ZIGBEE_COLOR_MODE_TEMPERATURE) { + setLightColorMode(ZIGBEE_COLOR_MODE_TEMPERATURE); + } + if (_current_color_temperature != light_color_temp) { + _current_color_temperature = light_color_temp; + lightChangedTemp(); + } return; } else { log_w("Received message ignored. Attribute ID: %d not supported for Color Control", message->attribute.id); @@ -126,24 +231,58 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me } } -void ZigbeeColorDimmableLight::lightChanged() { - if (_on_light_change) { - _on_light_change(_current_state, _current_color.r, _current_color.g, _current_color.b, _current_level); +void ZigbeeColorDimmableLight::lightChangedRgb() { + if (_on_light_change_rgb) { + _on_light_change_rgb(_current_state, _current_color.r, _current_color.g, _current_color.b, _current_level); + } +} + +void ZigbeeColorDimmableLight::lightChangedHsv() { + if (_on_light_change_hsv) { + _on_light_change_hsv(_current_state, _current_hsv.h, _current_hsv.s, _current_hsv.v); + } +} + +void ZigbeeColorDimmableLight::lightChangedTemp() { + if (_on_light_change_temp) { + _on_light_change_temp(_current_state, _current_level, _current_color_temperature); + } +} + +void ZigbeeColorDimmableLight::lightChangedByMode() { + // Call the appropriate callback based on current color mode + switch (_current_color_mode) { + case ZIGBEE_COLOR_MODE_CURRENT_X_Y: lightChangedRgb(); break; + case ZIGBEE_COLOR_MODE_HUE_SATURATION: lightChangedHsv(); break; + case ZIGBEE_COLOR_MODE_TEMPERATURE: lightChangedTemp(); break; + default: log_e("Unknown color mode: %d", _current_color_mode); break; } } bool ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, uint8_t green, uint8_t blue) { + // Check if XY color capability is enabled + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_X_Y)) { + log_e("XY color capability not enabled. Current capabilities: 0x%04x", _color_capabilities); + return false; + } + if (!setLightColorMode(ZIGBEE_COLOR_MODE_CURRENT_X_Y)) { + log_e("Failed to set light color mode: %d", ZIGBEE_COLOR_MODE_CURRENT_X_Y); + return false; + } esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS; - //Update all attributes + // Update all attributes _current_state = state; _current_level = level; _current_color = {red, green, blue}; - lightChanged(); + lightChangedRgb(); espXyColor_t xy_color = espRgbColorToXYColor(_current_color); espHsvColor_t hsv_color = espRgbColorToHsvColor(_current_color); - uint8_t hue = std::min((uint8_t)hsv_color.h, (uint8_t)254); // Clamp to 0-254 - uint8_t saturation = std::min((uint8_t)hsv_color.s, (uint8_t)254); // Clamp to 0-254 + // Clamp hue and saturation to valid Zigbee range (0-254, where 254 = 0xFE is max per ZCL spec) + uint8_t hue = std::min(std::max((uint8_t)hsv_color.h, (uint8_t)0), (uint8_t)254); + uint8_t saturation = std::min(std::max((uint8_t)hsv_color.s, (uint8_t)0), (uint8_t)254); + // Update HSV state + _current_hsv = hsv_color; log_v("Updating light state: %d, level: %d, color: %d, %d, %d", state, level, red, green, blue); /* Update light clusters */ @@ -180,7 +319,7 @@ bool ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, log_e("Failed to set light y color: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); goto unlock_and_return; } - //set hue + //set hue (for compatibility) ret = esp_zb_zcl_set_attribute_val( _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID, &hue, false ); @@ -188,7 +327,7 @@ bool ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, log_e("Failed to set light hue: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); goto unlock_and_return; } - //set saturation + //set saturation (for compatibility) ret = esp_zb_zcl_set_attribute_val( _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID, &saturation, false ); @@ -202,11 +341,52 @@ bool ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, } bool ZigbeeColorDimmableLight::setLightState(bool state) { - return setLight(state, _current_level, _current_color.r, _current_color.g, _current_color.b); + if (_current_state == state) { + return true; // No change needed + } + + _current_state = state; + esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS; + esp_zb_lock_acquire(portMAX_DELAY); + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_ON_OFF, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID, &_current_state, false + ); + esp_zb_lock_release(); + + if (ret == ESP_ZB_ZCL_STATUS_SUCCESS) { + lightChangedByMode(); // Call appropriate callback based on current color mode + } else { + log_e("Failed to set light state: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + } + + return ret == ESP_ZB_ZCL_STATUS_SUCCESS; } bool ZigbeeColorDimmableLight::setLightLevel(uint8_t level) { - return setLight(_current_state, level, _current_color.r, _current_color.g, _current_color.b); + if (_current_level == level) { + return true; // No change needed + } + + _current_level = level; + // Update HSV value if in HSV mode + if (_current_color_mode == ZIGBEE_COLOR_MODE_HUE_SATURATION) { + _current_hsv.v = level; + } + + esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS; + esp_zb_lock_acquire(portMAX_DELAY); + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID, &_current_level, false + ); + esp_zb_lock_release(); + + if (ret == ESP_ZB_ZCL_STATUS_SUCCESS) { + lightChangedByMode(); // Call appropriate callback based on current color mode + } else { + log_e("Failed to set light level: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + } + + return ret == ESP_ZB_ZCL_STATUS_SUCCESS; } bool ZigbeeColorDimmableLight::setLightColor(uint8_t red, uint8_t green, uint8_t blue) { @@ -218,8 +398,172 @@ bool ZigbeeColorDimmableLight::setLightColor(espRgbColor_t rgb_color) { } bool ZigbeeColorDimmableLight::setLightColor(espHsvColor_t hsv_color) { - espRgbColor_t rgb_color = espHsvColorToRgbColor(hsv_color); - return setLight(_current_state, _current_level, rgb_color.r, rgb_color.g, rgb_color.b); + // Check if Hue/Saturation color capability is enabled + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION)) { + log_e("Hue/Saturation color capability not enabled. Current capabilities: 0x%04x", _color_capabilities); + return false; + } + + esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS; + if (!setLightColorMode(ZIGBEE_COLOR_MODE_HUE_SATURATION)) { + log_e("Failed to set light color mode: %d", ZIGBEE_COLOR_MODE_HUE_SATURATION); + return false; + } + // Update HSV state and level from value component + _current_hsv = hsv_color; + _current_level = hsv_color.v; // Use HSV value component to update brightness level + lightChangedHsv(); + + // Clamp hue and saturation to valid Zigbee range (0-254, where 254 = 0xFE is max per ZCL spec) + uint8_t hue = std::clamp((uint8_t)hsv_color.h, (uint8_t)0, (uint8_t)254); + uint8_t saturation = std::clamp((uint8_t)hsv_color.s, (uint8_t)0, (uint8_t)254); + + log_v("Updating light HSV: H=%d, S=%d, V=%d (level=%d)", hue, saturation, hsv_color.v, _current_level); + /* Update light clusters */ + esp_zb_lock_acquire(portMAX_DELAY); + //set level (brightness from HSV value component) + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_LEVEL_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_LEVEL_CONTROL_CURRENT_LEVEL_ID, &_current_level, false + ); + if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Failed to set light level: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + goto unlock_and_return; + } + //set hue + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_HUE_ID, &hue, false + ); + if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Failed to set light hue: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + goto unlock_and_return; + } + //set saturation + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_SATURATION_ID, &saturation, false + ); + if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Failed to set light saturation: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + goto unlock_and_return; + } +unlock_and_return: + esp_zb_lock_release(); + return ret == ESP_ZB_ZCL_STATUS_SUCCESS; +} + +bool ZigbeeColorDimmableLight::setLightColorTemperature(uint16_t color_temperature) { + // Check if Color Temperature capability is enabled + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP)) { + log_e("Color temperature capability not enabled. Current capabilities: 0x%04x", _color_capabilities); + return false; + } + if (!setLightColorMode(ZIGBEE_COLOR_MODE_TEMPERATURE)) { + log_e("Failed to set light color mode: %d", ZIGBEE_COLOR_MODE_TEMPERATURE); + return false; + } + + _current_color_temperature = color_temperature; + lightChangedTemp(); + + log_v("Updating light color temperature: %d", color_temperature); + + esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS; + /* Update light clusters */ + esp_zb_lock_acquire(portMAX_DELAY); + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMPERATURE_ID, + &_current_color_temperature, false + ); + if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Failed to set light color temperature: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + goto unlock_and_return; + } +unlock_and_return: + esp_zb_lock_release(); + return ret == ESP_ZB_ZCL_STATUS_SUCCESS; +} + +bool ZigbeeColorDimmableLight::isColorModeSupported(uint8_t color_mode) { + switch (color_mode) { + case ZIGBEE_COLOR_MODE_CURRENT_X_Y: return (_color_capabilities & ZIGBEE_COLOR_CAPABILITY_X_Y) != 0; + case ZIGBEE_COLOR_MODE_HUE_SATURATION: return (_color_capabilities & ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION) != 0; + case ZIGBEE_COLOR_MODE_TEMPERATURE: return (_color_capabilities & ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP) != 0; + default: return false; + } +} + +bool ZigbeeColorDimmableLight::setLightColorMode(uint8_t color_mode) { + if (color_mode > ZIGBEE_COLOR_MODE_TEMPERATURE) { + log_e("Invalid color mode: %d", color_mode); + return false; + } + + // Check if the requested color mode is supported by capabilities + if (!isColorModeSupported(color_mode)) { + log_e("Color mode %d not supported by current capabilities: 0x%04x", color_mode, _color_capabilities); + return false; + } + + esp_zb_zcl_status_t ret = ESP_ZB_ZCL_STATUS_SUCCESS; + log_v("Setting color mode: %d", color_mode); + esp_zb_lock_acquire(portMAX_DELAY); + ret = esp_zb_zcl_set_attribute_val( + _endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_MODE_ID, &color_mode, false + ); + esp_zb_lock_release(); + if (ret != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Failed to set light color mode: 0x%x: %s", ret, esp_zb_zcl_status_to_name(ret)); + return false; + } + _current_color_mode = color_mode; + return true; +} + +bool ZigbeeColorDimmableLight::setLightColorCapabilities(uint16_t capabilities) { + esp_zb_attribute_list_t *color_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + if (!color_cluster) { + log_e("Color control cluster not found"); + return false; + } + + // Validate capabilities (max value is 0x001f per ZCL spec) + if (capabilities > 0x001f) { + log_e("Invalid color capabilities value: 0x%04x (max: 0x001f)", capabilities); + return false; + } + + _color_capabilities = capabilities; + + esp_err_t ret = esp_zb_cluster_update_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_CAPABILITIES_ID, &_color_capabilities); + if (ret != ESP_OK) { + log_e("Failed to set color capabilities: 0x%x: %s", ret, esp_err_to_name(ret)); + return false; + } + + log_v("Color capabilities set to: 0x%04x", _color_capabilities); + return true; +} + +bool ZigbeeColorDimmableLight::setLightColorTemperatureRange(uint16_t min_temp, uint16_t max_temp) { + esp_zb_attribute_list_t *color_cluster = esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + if (!color_cluster) { + log_e("Color control cluster not found"); + return false; + } + if (!(_color_capabilities & ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP)) { + log_e("Color temperature capability not enabled. Current capabilities: 0x%04x", _color_capabilities); + return false; + } + esp_err_t ret = esp_zb_cluster_update_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MIN_MIREDS_ID, &min_temp); + if (ret != ESP_OK) { + log_e("Failed to set min value: 0x%x: %s", ret, esp_err_to_name(ret)); + return false; + } + ret = esp_zb_cluster_update_attr(color_cluster, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_COLOR_TEMP_PHYSICAL_MAX_MIREDS_ID, &max_temp); + if (ret != ESP_OK) { + log_e("Failed to set max value: 0x%x: %s", ret, esp_err_to_name(ret)); + return false; + } + return true; } #endif // CONFIG_ZB_ENABLED diff --git a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h index 5f47002d196..92fbb913f2c 100644 --- a/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h +++ b/libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h @@ -64,16 +64,51 @@ }, \ } +// Color capabilities bit flags (matching ZCL spec) - can be combined with bitwise OR +static constexpr uint16_t ZIGBEE_COLOR_CAPABILITY_HUE_SATURATION = (1 << 0); // Bit 0: Hue/saturation supported +static constexpr uint16_t ZIGBEE_COLOR_CAPABILITY_ENHANCED_HUE = (1 << 1); // Bit 1: Enhanced hue supported +static constexpr uint16_t ZIGBEE_COLOR_CAPABILITY_COLOR_LOOP = (1 << 2); // Bit 2: Color loop supported +static constexpr uint16_t ZIGBEE_COLOR_CAPABILITY_X_Y = (1 << 3); // Bit 3: X/Y supported +static constexpr uint16_t ZIGBEE_COLOR_CAPABILITY_COLOR_TEMP = (1 << 4); // Bit 4: Color temperature supported + +// Color mode enum values (matching ZCL spec) +enum ZigbeeColorMode { + ZIGBEE_COLOR_MODE_HUE_SATURATION = 0x00, // CurrentHue and CurrentSaturation + ZIGBEE_COLOR_MODE_CURRENT_X_Y = 0x01, // CurrentX and CurrentY // codespell:ignore currenty + ZIGBEE_COLOR_MODE_TEMPERATURE = 0x02, // ColorTemperature +}; + +// Callback function type definitions for better readability and type safety +// RGB callback: (state, red, green, blue, level) +typedef void (*ZigbeeColorLightRgbCallback)(bool state, uint8_t red, uint8_t green, uint8_t blue, uint8_t level); +// HSV callback: (state, hue, saturation, value) - value represents brightness (0-255) +typedef void (*ZigbeeColorLightHsvCallback)(bool state, uint8_t hue, uint8_t saturation, uint8_t value); +// Temperature callback: (state, level, color_temperature_in_mireds) +typedef void (*ZigbeeColorLightTempCallback)(bool state, uint8_t level, uint16_t color_temperature); + class ZigbeeColorDimmableLight : public ZigbeeEP { public: ZigbeeColorDimmableLight(uint8_t endpoint); ~ZigbeeColorDimmableLight() {} - void onLightChange(void (*callback)(bool, uint8_t, uint8_t, uint8_t, uint8_t)) { - _on_light_change = callback; + // Must be called before starting Zigbee, by default XY are selected as color mode + bool setLightColorCapabilities(uint16_t capabilities); + + [[deprecated("Use onLightChangeRgb() instead. This will be removed in a future major version.")]] + void onLightChange(ZigbeeColorLightRgbCallback callback) { + _on_light_change_rgb = callback; + } + void onLightChangeRgb(ZigbeeColorLightRgbCallback callback) { + _on_light_change_rgb = callback; + } + void onLightChangeHsv(ZigbeeColorLightHsvCallback callback) { + _on_light_change_hsv = callback; + } + void onLightChangeTemp(ZigbeeColorLightTempCallback callback) { + _on_light_change_temp = callback; } void restoreLight() { - lightChanged(); + lightChangedByMode(); } bool setLightState(bool state); @@ -81,7 +116,9 @@ class ZigbeeColorDimmableLight : public ZigbeeEP { bool setLightColor(uint8_t red, uint8_t green, uint8_t blue); bool setLightColor(espRgbColor_t rgb_color); bool setLightColor(espHsvColor_t hsv_color); + bool setLightColorTemperature(uint16_t color_temperature); bool setLight(bool state, uint8_t level, uint8_t red, uint8_t green, uint8_t blue); + bool setLightColorTemperatureRange(uint16_t min_temp, uint16_t max_temp); bool getLightState() { return _current_state; @@ -101,22 +138,53 @@ class ZigbeeColorDimmableLight : public ZigbeeEP { uint8_t getLightBlue() { return _current_color.b; } + uint16_t getLightColorTemperature() { + return _current_color_temperature; + } + uint8_t getLightColorMode() { + return _current_color_mode; + } + uint8_t getLightColorHue() { + return _current_hsv.h; + } + uint8_t getLightColorSaturation() { + return _current_hsv.s; + } + uint16_t getLightColorCapabilities() { + return _color_capabilities; + } private: void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override; + bool setLightColorMode(uint8_t color_mode); + bool isColorModeSupported(uint8_t color_mode); uint16_t getCurrentColorX(); uint16_t getCurrentColorY(); uint8_t getCurrentColorHue(); uint8_t getCurrentColorSaturation(); + uint16_t getCurrentColorTemperature(); + + void lightChangedRgb(); + void lightChangedHsv(); + void lightChangedTemp(); + void lightChangedByMode(); - void lightChanged(); - //callback function to be called on light change (State, R, G, B, Level) - void (*_on_light_change)(bool, uint8_t, uint8_t, uint8_t, uint8_t); + //callback function to be called on light change for RGB (State, R, G, B, Level) + ZigbeeColorLightRgbCallback _on_light_change_rgb; + //callback function to be called on light change for HSV (State, H, S, V, Level) + ZigbeeColorLightHsvCallback _on_light_change_hsv; + //callback function to be called on light change for TEMP (State, Level, Temperature) + ZigbeeColorLightTempCallback _on_light_change_temp; bool _current_state; uint8_t _current_level; espRgbColor_t _current_color; + espHsvColor_t _current_hsv; + uint16_t _current_color_temperature; + uint8_t _current_color_mode; + + uint16_t _color_capabilities; }; #endif // CONFIG_ZB_ENABLED