Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(zigbee): Support HSV color commands for RGB light endpoint #10959

Merged
merged 6 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions libraries/Zigbee/src/ZigbeeEP.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,13 @@
#if SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED

#include <Arduino.h>
#include <ColorFormat.h>

/* Useful defines */
#define ZB_CMD_TIMEOUT 10000 // 10 seconds
#define OTA_UPGRADE_QUERY_INTERVAL (1 * 60) // 1 hour = 60 minutes

#define ZB_ARRAY_LENTH(arr) (sizeof(arr) / sizeof(arr[0]))
#define XYZ_TO_RGB(X, Y, Z, r, g, b) \
{ \
r = (float)(3.240479 * (X) - 1.537150 * (Y) - 0.498535 * (Z)); \
g = (float)(-0.969256 * (X) + 1.875992 * (Y) + 0.041556 * (Z)); \
b = (float)(0.055648 * (X) - 0.204043 * (Y) + 1.057311 * (Z)); \
if (r > 1) { \
r = 1; \
} \
if (g > 1) { \
g = 1; \
} \
if (b > 1) { \
b = 1; \
} \
}

#define RGB_TO_XYZ(r, g, b, X, Y, Z) \
{ \
Expand Down
116 changes: 61 additions & 55 deletions libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@
ZigbeeColorDimmableLight::ZigbeeColorDimmableLight(uint8_t endpoint) : ZigbeeEP(endpoint) {
_device_id = ESP_ZB_HA_COLOR_DIMMABLE_LIGHT_DEVICE_ID;

esp_zb_color_dimmable_light_cfg_t light_cfg = ESP_ZB_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG();
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);

//Add support for hue and saturation
uint8_t hue = 0;
uint8_t saturation = 0;

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);

_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
};

//set default values
_current_state = false;
_current_level = 255;
_current_red = 255;
_current_green = 255;
_current_blue = 255;
_current_color = {255, 255, 255};
}

uint16_t ZigbeeColorDimmableLight::getCurrentColorX() {
Expand All @@ -32,37 +39,18 @@ uint16_t ZigbeeColorDimmableLight::getCurrentColorY() {
->data_p);
}

void ZigbeeColorDimmableLight::calculateRGB(uint16_t x, uint16_t y, uint8_t &red, uint8_t &green, uint8_t &blue) {
float r, g, b, color_x, color_y;
color_x = (float)x / 65535;
color_y = (float)y / 65535;

float color_X = color_x / color_y;
float color_Z = (1 - color_x - color_y) / color_y;

XYZ_TO_RGB(color_X, 1, color_Z, r, g, b);

red = (uint8_t)(r * (float)255);
green = (uint8_t)(g * (float)255);
blue = (uint8_t)(b * (float)255);
uint8_t ZigbeeColorDimmableLight::getCurrentColorHue() {
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_HUE_ID
)
->data_p);
}

void ZigbeeColorDimmableLight::calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y) {
// Convert RGB to XYZ
float r = (float)red / 255.0f;
float g = (float)green / 255.0f;
float b = (float)blue / 255.0f;

float X, Y, Z;
RGB_TO_XYZ(r, g, b, X, Y, Z);

// Convert XYZ to xy chromaticity coordinates
float color_x = X / (X + Y + Z);
float color_y = Y / (X + Y + Z);

// Convert normalized xy to 16-bit values
x = (uint16_t)(color_x * 65535.0f);
y = (uint16_t)(color_y * 65535.0f);
uint8_t ZigbeeColorDimmableLight::getCurrentColorSaturation() {
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_CURRENT_SATURATION_ID
)
->data_p);
}

//set attribute method -> method overridden in child class
Expand Down Expand Up @@ -94,23 +82,25 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me
uint16_t light_color_x = (*(uint16_t *)message->attribute.data.value);
uint16_t light_color_y = getCurrentColorY();
//calculate RGB from XY and call setColor()
uint8_t red, green, blue;
calculateRGB(light_color_x, light_color_y, red, green, blue);
_current_blue = blue;
_current_green = green;
_current_red = red;
_current_color = espXYToRgbColor(255, light_color_x, light_color_y); //TODO: Check if level is correct
lightChanged();
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) {
uint16_t light_color_x = getCurrentColorX();
uint16_t light_color_y = (*(uint16_t *)message->attribute.data.value);
//calculate RGB from XY and call setColor()
uint8_t red, green, blue;
calculateRGB(light_color_x, light_color_y, red, green, blue);
_current_blue = blue;
_current_green = green;
_current_red = red;
_current_color = espXYToRgbColor(255, light_color_x, light_color_y); //TODO: Check if level is correct
lightChanged();
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) {
uint8_t light_color_hue = (*(uint8_t *)message->attribute.data.value);
_current_color = espHsvToRgbColor(light_color_hue, getCurrentColorSaturation(), 255);
lightChanged();
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) {
uint8_t light_color_saturation = (*(uint8_t *)message->attribute.data.value);
_current_color = espHsvToRgbColor(getCurrentColorHue(), light_color_saturation, 255);
lightChanged();
return;
} else {
Expand All @@ -123,20 +113,21 @@ void ZigbeeColorDimmableLight::zbAttributeSet(const esp_zb_zcl_set_attr_value_me

void ZigbeeColorDimmableLight::lightChanged() {
if (_on_light_change) {
_on_light_change(_current_state, _current_red, _current_green, _current_blue, _current_level);
_on_light_change(_current_state, _current_color.r, _current_color.g, _current_color.b, _current_level);
}
}

void ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red, uint8_t green, uint8_t blue) {
//Update all attributes
_current_state = state;
_current_level = level;
_current_red = red;
_current_green = green;
_current_blue = blue;
_current_color = {red, green, blue};
lightChanged();

log_v("Updating on/off light state to %d", state);
espXyColor_t xy_color = espRgbColorToXYColor(_current_color);
espHsvColor_t hsv_color = espRgbColorToHsvColor(_current_color);

log_v("Updating light state: %d, level: %d, color: %d, %d, %d", state, level, red, green, blue);
/* Update light clusters */
esp_zb_lock_acquire(portMAX_DELAY);
//set on/off state
Expand All @@ -147,28 +138,43 @@ void ZigbeeColorDimmableLight::setLight(bool state, uint8_t level, uint8_t red,
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
);
//set color
uint16_t color_x, color_y;
calculateXY(red, green, blue, color_x, color_y);
//set xy color
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_X_ID, &xy_color.x, false
);
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_X_ID, &color_x, false
_endpoint, ESP_ZB_ZCL_CLUSTER_ID_COLOR_CONTROL, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_COLOR_CONTROL_CURRENT_Y_ID, &xy_color.y, false
);
//set hsv color
uint8_t hue = (uint8_t)hsv_color.h;
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_Y_ID, &color_y, false
_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
);
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, &hsv_color.s, false
);
esp_zb_lock_release();
}

void ZigbeeColorDimmableLight::setLightState(bool state) {
setLight(state, _current_level, _current_red, _current_green, _current_blue);
setLight(state, _current_level, _current_color.r, _current_color.g, _current_color.b);
}

void ZigbeeColorDimmableLight::setLightLevel(uint8_t level) {
setLight(_current_state, level, _current_red, _current_green, _current_blue);
setLight(_current_state, level, _current_color.r, _current_color.g, _current_color.b);
}

void ZigbeeColorDimmableLight::setLightColor(uint8_t red, uint8_t green, uint8_t blue) {
setLight(_current_state, _current_level, red, green, blue);
}

void ZigbeeColorDimmableLight::setLightColor(espRgbColor_t rgb_color) {
setLight(_current_state, _current_level, rgb_color.r, rgb_color.g, rgb_color.b);
}

void ZigbeeColorDimmableLight::setLightColor(espHsvColor_t hsv_color) {
espRgbColor_t rgb_color = espHsvColorToRgbColor(hsv_color);
setLight(_current_state, _current_level, rgb_color.r, rgb_color.g, rgb_color.b);
}

#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
60 changes: 52 additions & 8 deletions libraries/Zigbee/src/ep/ZigbeeColorDimmableLight.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,47 @@
#include "ZigbeeEP.h"
#include "ha/esp_zigbee_ha_standard.h"

#define ZIGBEE_DEFAULT_COLOR_DIMMABLE_LIGHT_CONFIG() \
{ \
.basic_cfg = \
{ \
.zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE, \
.power_source = ESP_ZB_ZCL_BASIC_POWER_SOURCE_DEFAULT_VALUE, \
}, \
.identify_cfg = \
{ \
.identify_time = ESP_ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE, \
}, \
.groups_cfg = \
{ \
.groups_name_support_id = ESP_ZB_ZCL_GROUPS_NAME_SUPPORT_DEFAULT_VALUE, \
}, \
.scenes_cfg = \
{ \
.scenes_count = ESP_ZB_ZCL_SCENES_SCENE_COUNT_DEFAULT_VALUE, \
.current_scene = ESP_ZB_ZCL_SCENES_CURRENT_SCENE_DEFAULT_VALUE, \
.current_group = ESP_ZB_ZCL_SCENES_CURRENT_GROUP_DEFAULT_VALUE, \
.scene_valid = ESP_ZB_ZCL_SCENES_SCENE_VALID_DEFAULT_VALUE, \
.name_support = ESP_ZB_ZCL_SCENES_NAME_SUPPORT_DEFAULT_VALUE, \
}, \
.on_off_cfg = \
{ \
.on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE, \
}, \
.level_cfg = \
{ \
.current_level = ESP_ZB_ZCL_LEVEL_CONTROL_CURRENT_LEVEL_DEFAULT_VALUE, \
}, \
.color_cfg = { \
.current_x = ESP_ZB_ZCL_COLOR_CONTROL_CURRENT_X_DEF_VALUE, \
.current_y = ESP_ZB_ZCL_COLOR_CONTROL_CURRENT_Y_DEF_VALUE, \
.color_mode = ESP_ZB_ZCL_COLOR_CONTROL_COLOR_MODE_DEFAULT_VALUE, \
.options = ESP_ZB_ZCL_COLOR_CONTROL_OPTIONS_DEFAULT_VALUE, \
.enhanced_color_mode = ESP_ZB_ZCL_COLOR_CONTROL_ENHANCED_COLOR_MODE_DEFAULT_VALUE, \
.color_capabilities = 0x0009, \
}, \
}

class ZigbeeColorDimmableLight : public ZigbeeEP {
public:
ZigbeeColorDimmableLight(uint8_t endpoint);
Expand All @@ -24,6 +65,8 @@ class ZigbeeColorDimmableLight : public ZigbeeEP {
void setLightState(bool state);
void setLightLevel(uint8_t level);
void setLightColor(uint8_t red, uint8_t green, uint8_t blue);
void setLightColor(espRgbColor_t rgb_color);
void setLightColor(espHsvColor_t hsv_color);
void setLight(bool state, uint8_t level, uint8_t red, uint8_t green, uint8_t blue);

bool getLightState() {
Expand All @@ -32,33 +75,34 @@ class ZigbeeColorDimmableLight : public ZigbeeEP {
uint8_t getLightLevel() {
return _current_level;
}
espRgbColor_t getLightColor() {
return _current_color;
}
uint8_t getLightRed() {
return _current_red;
return _current_color.r;
}
uint8_t getLightGreen() {
return _current_green;
return _current_color.g;
}
uint8_t getLightBlue() {
return _current_blue;
return _current_color.b;
}

private:
void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;
void calculateRGB(uint16_t x, uint16_t y, uint8_t &red, uint8_t &green, uint8_t &blue);
void calculateXY(uint8_t red, uint8_t green, uint8_t blue, uint16_t &x, uint16_t &y);

uint16_t getCurrentColorX();
uint16_t getCurrentColorY();
uint8_t getCurrentColorHue();
uint8_t getCurrentColorSaturation();

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);

bool _current_state;
uint8_t _current_level;
uint16_t _current_red;
uint16_t _current_green;
uint16_t _current_blue;
espRgbColor_t _current_color;
};

#endif //SOC_IEEE802154_SUPPORTED && CONFIG_ZB_ENABLED
Loading
Loading