From 111e35d8ae7c6e66957eeee6a7b097a8dd61bbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:47:13 +0200 Subject: [PATCH 1/4] feat(zigbee): Add cmd_write_attr_resp handler --- libraries/Zigbee/src/ZigbeeEP.h | 1 + libraries/Zigbee/src/ZigbeeHandlers.cpp | 33 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index ce1d2befa26..2df9bf5c3be 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -149,6 +149,7 @@ class ZigbeeEP { // list of all handlers function calls, to be override by EPs implementation virtual void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {}; virtual void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute, uint8_t src_endpoint, esp_zb_zcl_addr_t src_address) {}; + virtual void zbWriteAttributeResponse(uint16_t cluster_id, uint16_t attribute_id, esp_zb_zcl_status_t status, uint8_t src_endpoint, esp_zb_zcl_addr_t src_address) {}; virtual void zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented virtual void zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message); virtual void zbWindowCoveringMovementCmd(const esp_zb_zcl_window_covering_movement_message_t *message) {}; diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index e54790cc392..5df6a873672 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -42,6 +42,7 @@ static bool s_tagid_received = false; static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_message_t *message); static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message); static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message); +static esp_err_t zb_cmd_write_attr_resp_handler(const esp_zb_zcl_cmd_write_attr_resp_message_t *message); static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message); static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_message_t *message); static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_message_t *message); @@ -73,6 +74,7 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, ret = zb_ota_upgrade_query_image_resp_handler((esp_zb_zcl_ota_upgrade_query_image_resp_message_t *)message); break; case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: ret = zb_cmd_default_resp_handler((esp_zb_zcl_cmd_default_resp_message_t *)message); break; + case ESP_ZB_CORE_CMD_WRITE_ATTR_RESP_CB_ID: ret = zb_cmd_write_attr_resp_handler((esp_zb_zcl_cmd_write_attr_resp_message_t *)message); break; default: log_w("Receive unhandled Zigbee action(0x%x) callback", callback_id); break; } return ret; @@ -170,6 +172,37 @@ static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_re return ESP_OK; } +static esp_err_t zb_cmd_write_attr_resp_handler(const esp_zb_zcl_cmd_write_attr_resp_message_t *message) { + if (!message) { + log_e("Empty message"); + return ESP_FAIL; + } + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + log_e("Received message: error status(%d)", message->info.status); + return ESP_ERR_INVALID_ARG; + } + log_v( + "Write attribute response: from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->info.src_address.u.short_addr, + message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster + ); + for (std::list::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) { + if (message->info.dst_endpoint == (*it)->getEndpoint()) { + esp_zb_zcl_write_attr_resp_variable_t *variable = message->variables; + while (variable) { + log_v( + "Write attribute response: status(%d), cluster(0x%x), attribute(0x%x)", variable->status, message->info.cluster, variable->attribute_id + ); + if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) { + (*it)->zbWriteAttributeResponse(message->info.cluster, variable->attribute_id, variable->status, message->info.src_endpoint, message->info.src_address); + } + variable = variable->next; + } + } + } + return ESP_OK; +} + + static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message) { if (!message) { log_e("Empty message"); From bdac2b9cf329cb3407b3e99b20fa7e5f749c584e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:48:04 +0200 Subject: [PATCH 2/4] feat(zigbee): Add option to allow binding multiple endpoints with single device --- libraries/Zigbee/src/ZigbeeCore.cpp | 11 ++++++++--- libraries/Zigbee/src/ZigbeeCore.h | 8 ++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libraries/Zigbee/src/ZigbeeCore.cpp b/libraries/Zigbee/src/ZigbeeCore.cpp index 2e48a9d2461..a0dd0163bbe 100644 --- a/libraries/Zigbee/src/ZigbeeCore.cpp +++ b/libraries/Zigbee/src/ZigbeeCore.cpp @@ -46,6 +46,7 @@ ZigbeeCore::ZigbeeCore() { _scan_duration = 3; // default scan duration _rx_on_when_idle = true; _debug = false; + _allow_multi_endpoint_binding = false; _global_default_response_cb = nullptr; // Initialize global callback to nullptr if (!lock) { lock = xSemaphoreCreateBinary(); @@ -392,7 +393,9 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint()); (*it)->findEndpoint(&cmd_req); log_d("Endpoint %d is searching for device", (*it)->getEndpoint()); - break; // Only one endpoint per device + if(!Zigbee.allowMultiEndpointBinding()) { // If multi endpoint binding is not allowed, break the loop to keep backwards compatibility + break; + } } } } @@ -422,11 +425,13 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { break; } } - log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint()); if (!found) { + log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint()); (*it)->findEndpoint(&cmd_req); log_d("Endpoint %d is searching for device", (*it)->getEndpoint()); - break; // Only one endpoint per device + if(!Zigbee.allowMultiEndpointBinding()) { // If multi endpoint binding is not allowed, break the loop to keep backwards compatibility + break; + } } } } diff --git a/libraries/Zigbee/src/ZigbeeCore.h b/libraries/Zigbee/src/ZigbeeCore.h index ae92e21f3a4..fdfc3f298f5 100644 --- a/libraries/Zigbee/src/ZigbeeCore.h +++ b/libraries/Zigbee/src/ZigbeeCore.h @@ -117,6 +117,7 @@ class ZigbeeCore { zigbee_scan_result_t *_scan_result; SemaphoreHandle_t lock; bool _debug; + bool _allow_multi_endpoint_binding; // Global default response callback void (*_global_default_response_cb)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster); @@ -196,6 +197,13 @@ class ZigbeeCore { return _debug; } + void allowMultiEndpointBinding(bool allow) { + _allow_multi_endpoint_binding = allow; + } + bool allowMultiEndpointBinding() { + return _allow_multi_endpoint_binding; + } + // Set global default response callback void onGlobalDefaultResponse(void (*callback)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster)) { _global_default_response_cb = callback; From 3b2f39a26cb2c3b880959a72c8d4e8f05f61a701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= <90197375+P-R-O-C-H-Y@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:59:52 +0200 Subject: [PATCH 3/4] feat(zigbee): Add new method to keywords.txt --- libraries/Zigbee/keywords.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/Zigbee/keywords.txt b/libraries/Zigbee/keywords.txt index e67b0ec8578..67cb47ee319 100644 --- a/libraries/Zigbee/keywords.txt +++ b/libraries/Zigbee/keywords.txt @@ -76,6 +76,7 @@ scanComplete KEYWORD2 getScanResult KEYWORD2 scanDelete KEYWORD2 factoryReset KEYWORD2 +allowMultiEndpointBinding KEYWORD2 # Common ZigbeeEP setEpConfig KEYWORD2 From 93c5e9ff7fd109193baeae994d3f2e33d17f13b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:39:08 +0000 Subject: [PATCH 4/4] ci(pre-commit): Apply automatic fixes --- libraries/Zigbee/src/ZigbeeCore.cpp | 4 ++-- libraries/Zigbee/src/ZigbeeCore.h | 2 +- libraries/Zigbee/src/ZigbeeEP.h | 3 ++- libraries/Zigbee/src/ZigbeeHandlers.cpp | 13 ++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/Zigbee/src/ZigbeeCore.cpp b/libraries/Zigbee/src/ZigbeeCore.cpp index a0dd0163bbe..9706d732752 100644 --- a/libraries/Zigbee/src/ZigbeeCore.cpp +++ b/libraries/Zigbee/src/ZigbeeCore.cpp @@ -393,7 +393,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint()); (*it)->findEndpoint(&cmd_req); log_d("Endpoint %d is searching for device", (*it)->getEndpoint()); - if(!Zigbee.allowMultiEndpointBinding()) { // If multi endpoint binding is not allowed, break the loop to keep backwards compatibility + if (!Zigbee.allowMultiEndpointBinding()) { // If multi endpoint binding is not allowed, break the loop to keep backwards compatibility break; } } @@ -429,7 +429,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { log_d("Device not bound to endpoint %d and it is free to bound!", (*it)->getEndpoint()); (*it)->findEndpoint(&cmd_req); log_d("Endpoint %d is searching for device", (*it)->getEndpoint()); - if(!Zigbee.allowMultiEndpointBinding()) { // If multi endpoint binding is not allowed, break the loop to keep backwards compatibility + if (!Zigbee.allowMultiEndpointBinding()) { // If multi endpoint binding is not allowed, break the loop to keep backwards compatibility break; } } diff --git a/libraries/Zigbee/src/ZigbeeCore.h b/libraries/Zigbee/src/ZigbeeCore.h index fdfc3f298f5..6295832fa03 100644 --- a/libraries/Zigbee/src/ZigbeeCore.h +++ b/libraries/Zigbee/src/ZigbeeCore.h @@ -203,7 +203,7 @@ class ZigbeeCore { bool allowMultiEndpointBinding() { return _allow_multi_endpoint_binding; } - + // Set global default response callback void onGlobalDefaultResponse(void (*callback)(zb_cmd_type_t resp_to_cmd, esp_zb_zcl_status_t status, uint8_t endpoint, uint16_t cluster)) { _global_default_response_cb = callback; diff --git a/libraries/Zigbee/src/ZigbeeEP.h b/libraries/Zigbee/src/ZigbeeEP.h index 2df9bf5c3be..a873205808f 100644 --- a/libraries/Zigbee/src/ZigbeeEP.h +++ b/libraries/Zigbee/src/ZigbeeEP.h @@ -149,7 +149,8 @@ class ZigbeeEP { // list of all handlers function calls, to be override by EPs implementation virtual void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {}; virtual void zbAttributeRead(uint16_t cluster_id, const esp_zb_zcl_attribute_t *attribute, uint8_t src_endpoint, esp_zb_zcl_addr_t src_address) {}; - virtual void zbWriteAttributeResponse(uint16_t cluster_id, uint16_t attribute_id, esp_zb_zcl_status_t status, uint8_t src_endpoint, esp_zb_zcl_addr_t src_address) {}; + virtual void + zbWriteAttributeResponse(uint16_t cluster_id, uint16_t attribute_id, esp_zb_zcl_status_t status, uint8_t src_endpoint, esp_zb_zcl_addr_t src_address) {}; virtual void zbReadBasicCluster(const esp_zb_zcl_attribute_t *attribute); //already implemented virtual void zbIdentify(const esp_zb_zcl_set_attr_value_message_t *message); virtual void zbWindowCoveringMovementCmd(const esp_zb_zcl_window_covering_movement_message_t *message) {}; diff --git a/libraries/Zigbee/src/ZigbeeHandlers.cpp b/libraries/Zigbee/src/ZigbeeHandlers.cpp index 5df6a873672..29c4e670f66 100644 --- a/libraries/Zigbee/src/ZigbeeHandlers.cpp +++ b/libraries/Zigbee/src/ZigbeeHandlers.cpp @@ -73,9 +73,9 @@ static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, case ESP_ZB_CORE_OTA_UPGRADE_QUERY_IMAGE_RESP_CB_ID: ret = zb_ota_upgrade_query_image_resp_handler((esp_zb_zcl_ota_upgrade_query_image_resp_message_t *)message); break; - case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: ret = zb_cmd_default_resp_handler((esp_zb_zcl_cmd_default_resp_message_t *)message); break; + case ESP_ZB_CORE_CMD_DEFAULT_RESP_CB_ID: ret = zb_cmd_default_resp_handler((esp_zb_zcl_cmd_default_resp_message_t *)message); break; case ESP_ZB_CORE_CMD_WRITE_ATTR_RESP_CB_ID: ret = zb_cmd_write_attr_resp_handler((esp_zb_zcl_cmd_write_attr_resp_message_t *)message); break; - default: log_w("Receive unhandled Zigbee action(0x%x) callback", callback_id); break; + default: log_w("Receive unhandled Zigbee action(0x%x) callback", callback_id); break; } return ret; } @@ -189,11 +189,11 @@ static esp_err_t zb_cmd_write_attr_resp_handler(const esp_zb_zcl_cmd_write_attr_ if (message->info.dst_endpoint == (*it)->getEndpoint()) { esp_zb_zcl_write_attr_resp_variable_t *variable = message->variables; while (variable) { - log_v( - "Write attribute response: status(%d), cluster(0x%x), attribute(0x%x)", variable->status, message->info.cluster, variable->attribute_id - ); + log_v("Write attribute response: status(%d), cluster(0x%x), attribute(0x%x)", variable->status, message->info.cluster, variable->attribute_id); if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) { - (*it)->zbWriteAttributeResponse(message->info.cluster, variable->attribute_id, variable->status, message->info.src_endpoint, message->info.src_address); + (*it)->zbWriteAttributeResponse( + message->info.cluster, variable->attribute_id, variable->status, message->info.src_endpoint, message->info.src_address + ); } variable = variable->next; } @@ -202,7 +202,6 @@ static esp_err_t zb_cmd_write_attr_resp_handler(const esp_zb_zcl_cmd_write_attr_ return ESP_OK; } - static esp_err_t zb_configure_report_resp_handler(const esp_zb_zcl_cmd_config_report_resp_message_t *message) { if (!message) { log_e("Empty message");