MeshTalos-Client/managed_components/espressif__arduino-esp32/libraries/Zigbee/src/ZigbeeHandlers.cpp
2025-12-03 14:20:11 +08:00

475 lines
21 KiB
C++

// Copyright 2025 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.
/* Zigbee Common Functions */
#include "ZigbeeCore.h"
#include "Arduino.h"
#if CONFIG_ZB_ENABLED
#include "esp_ota_ops.h"
#if CONFIG_ZB_DELTA_OTA // Delta OTA, code is prepared for this feature but not enabled by default
#include "esp_delta_ota_ops.h"
#endif
//OTA Upgrade defines and variables
#define OTA_ELEMENT_HEADER_LEN 6 /* OTA element format header size include tag identifier and length field */
/**
* @name Enumeration for the tag identifier denotes the type and format of the data within the element
* @anchor esp_ota_element_tag_id_t
*/
typedef enum esp_ota_element_tag_id_e {
UPGRADE_IMAGE = 0x0000, /*!< Upgrade image */
} esp_ota_element_tag_id_t;
static const esp_partition_t *s_ota_partition = NULL;
static esp_ota_handle_t s_ota_handle = 0;
static bool s_tagid_received = false;
// forward declaration of all implemented handlers
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);
static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_resp_message_t *message);
static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_window_covering_movement_message_t *message);
static esp_err_t zb_ota_upgrade_status_handler(const esp_zb_zcl_ota_upgrade_value_message_t *message);
static esp_err_t zb_ota_upgrade_query_image_resp_handler(const esp_zb_zcl_ota_upgrade_query_image_resp_message_t *message);
// Zigbee action handlers
[[maybe_unused]]
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) {
esp_err_t ret = ESP_OK;
switch (callback_id) {
case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: ret = zb_attribute_set_handler((esp_zb_zcl_set_attr_value_message_t *)message); break;
case ESP_ZB_CORE_REPORT_ATTR_CB_ID: ret = zb_attribute_reporting_handler((esp_zb_zcl_report_attr_message_t *)message); break;
case ESP_ZB_CORE_CMD_READ_ATTR_RESP_CB_ID: ret = zb_cmd_read_attr_resp_handler((esp_zb_zcl_cmd_read_attr_resp_message_t *)message); break;
case ESP_ZB_CORE_CMD_REPORT_CONFIG_RESP_CB_ID: ret = zb_configure_report_resp_handler((esp_zb_zcl_cmd_config_report_resp_message_t *)message); break;
case ESP_ZB_CORE_CMD_IAS_ZONE_ZONE_STATUS_CHANGE_NOT_ID:
ret = zb_cmd_ias_zone_status_change_handler((esp_zb_zcl_ias_zone_status_change_notification_message_t *)message);
break;
case ESP_ZB_CORE_IAS_ZONE_ENROLL_RESPONSE_VALUE_CB_ID:
ret = zb_cmd_ias_zone_enroll_response_handler((esp_zb_zcl_ias_zone_enroll_response_message_t *)message);
break;
case ESP_ZB_CORE_WINDOW_COVERING_MOVEMENT_CB_ID:
ret = zb_window_covering_movement_resp_handler((esp_zb_zcl_window_covering_movement_message_t *)message);
break;
case ESP_ZB_CORE_OTA_UPGRADE_VALUE_CB_ID: ret = zb_ota_upgrade_status_handler((esp_zb_zcl_ota_upgrade_value_message_t *)message); break;
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_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;
}
static esp_err_t zb_attribute_set_handler(const esp_zb_zcl_set_attr_value_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(
"Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, message->attribute.id,
message->attribute.data.size
);
// List through all Zigbee EPs and call the callback function, with the message
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY) {
(*it)->zbIdentify(message); //method zbIdentify implemented in the common EP class
} else {
(*it)->zbAttributeSet(message); //method zbAttributeSet must be implemented in specific EP class
}
}
}
return ESP_OK;
}
static esp_err_t zb_attribute_reporting_handler(const esp_zb_zcl_report_attr_message_t *message) {
if (!message) {
log_e("Empty message");
return ESP_FAIL;
}
if (message->status != ESP_ZB_ZCL_STATUS_SUCCESS) {
log_e("Received message: error status(%d)", message->status);
return ESP_ERR_INVALID_ARG;
}
log_v(
"Received report from address(0x%x) src endpoint(%d) to dst endpoint(%d) cluster(0x%x)", message->src_address.u.short_addr, message->src_endpoint,
message->dst_endpoint, message->cluster
);
// List through all Zigbee EPs and call the callback function, with the message
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->dst_endpoint == (*it)->getEndpoint()) {
(*it)->zbAttributeRead(
message->cluster, &message->attribute, message->src_endpoint, message->src_address
); //method zbAttributeRead must be implemented in specific EP class
}
}
return ESP_OK;
}
static esp_err_t zb_cmd_read_attr_resp_handler(const esp_zb_zcl_cmd_read_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(
"Read 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<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
esp_zb_zcl_read_attr_resp_variable_t *variable = message->variables;
while (variable) {
log_v(
"Read attribute response: status(%d), cluster(0x%x), attribute(0x%x), type(0x%x), value(%d)", variable->status, message->info.cluster,
variable->attribute.id, variable->attribute.data.type, variable->attribute.data.value ? *(uint8_t *)variable->attribute.data.value : 0
);
if (variable->status == ESP_ZB_ZCL_STATUS_SUCCESS) {
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_BASIC) {
(*it)->zbReadBasicCluster(&variable->attribute); //method zbReadBasicCluster implemented in the common EP class
} else if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_TIME) {
(*it)->zbReadTimeCluster(&variable->attribute); //method zbReadTimeCluster implemented in the common EP class
} else {
(*it)->zbAttributeRead(
message->info.cluster, &variable->attribute, message->info.src_endpoint, message->info.src_address
); //method zbAttributeRead must be implemented in specific EP class
}
}
variable = variable->next;
}
}
}
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<ZigbeeEP *>::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");
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;
}
esp_zb_zcl_config_report_resp_variable_t *variable = message->variables;
while (variable) {
log_v(
"Configure report response: status(%d), cluster(0x%x), direction(0x%x), attribute(0x%x)", variable->status, message->info.cluster, variable->direction,
variable->attribute_id
);
variable = variable->next;
}
return ESP_OK;
}
static esp_err_t zb_cmd_ias_zone_status_change_handler(const esp_zb_zcl_ias_zone_status_change_notification_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(
"IAS Zone Status Notification: 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<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
(*it)->zbIASZoneStatusChangeNotification(message);
}
}
return ESP_OK;
}
static esp_err_t zb_cmd_ias_zone_enroll_response_handler(const esp_zb_zcl_ias_zone_enroll_response_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("IAS Zone Enroll Response received");
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
(*it)->zbIASZoneEnrollResponse(message);
}
}
return ESP_OK;
}
static esp_err_t zb_window_covering_movement_resp_handler(const esp_zb_zcl_window_covering_movement_message_t *message) {
if (!message) {
log_e("Empty message");
}
if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) {
log_e("Received message: error status(%d)", message->info.status);
}
log_v(
"Received message: endpoint(%d), cluster(0x%x), command(0x%x), payload(%d)", message->info.dst_endpoint, message->info.cluster, message->command,
message->payload
);
// List through all Zigbee EPs and call the callback function, with the message
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
(*it)->zbWindowCoveringMovementCmd(message); //method zbWindowCoveringMovementCmd must be implemented in specific EP class
}
}
return ESP_OK;
}
static esp_err_t esp_element_ota_data(uint32_t total_size, const void *payload, uint16_t payload_size, void **outbuf, uint16_t *outlen) {
static uint16_t tagid = 0;
void *data_buf = NULL;
uint16_t data_len;
if (!s_tagid_received) {
uint32_t length = 0;
if (!payload || payload_size <= OTA_ELEMENT_HEADER_LEN) {
log_e("Invalid element format");
return ESP_ERR_INVALID_ARG;
}
const uint8_t *payload_ptr = (const uint8_t *)payload;
tagid = *(const uint16_t *)payload_ptr;
length = *(const uint32_t *)(payload_ptr + sizeof(tagid));
if ((length + OTA_ELEMENT_HEADER_LEN) != total_size) {
log_e("Invalid element length [%ld/%ld]", length, total_size);
return ESP_ERR_INVALID_ARG;
}
s_tagid_received = true;
data_buf = (void *)(payload_ptr + OTA_ELEMENT_HEADER_LEN);
data_len = payload_size - OTA_ELEMENT_HEADER_LEN;
} else {
data_buf = (void *)payload;
data_len = payload_size;
}
switch (tagid) {
case UPGRADE_IMAGE:
*outbuf = data_buf;
*outlen = data_len;
break;
default:
log_e("Unsupported element tag identifier %d", tagid);
return ESP_ERR_INVALID_ARG;
break;
}
return ESP_OK;
}
static esp_err_t zb_ota_upgrade_status_handler(const esp_zb_zcl_ota_upgrade_value_message_t *message) {
static uint32_t total_size = 0;
static uint32_t offset = 0;
[[maybe_unused]]
static int64_t start_time = 0;
esp_err_t ret = ESP_OK;
if (message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS) {
switch (message->upgrade_status) {
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_START:
log_i("Zigbee - OTA upgrade start");
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
(*it)->zbOTAState(true); // Notify that OTA is active
}
start_time = esp_timer_get_time();
s_ota_partition = esp_ota_get_next_update_partition(NULL);
assert(s_ota_partition);
#if CONFIG_ZB_DELTA_OTA
ret = esp_delta_ota_begin(s_ota_partition, 0, &s_ota_handle);
#else
ret = esp_ota_begin(s_ota_partition, 0, &s_ota_handle);
#endif
if (ret != ESP_OK) {
log_e("Zigbee - Failed to begin OTA partition, status: %s", esp_err_to_name(ret));
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
(*it)->zbOTAState(false); // Notify that OTA is no longer active
}
return ret;
}
break;
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE:
total_size = message->ota_header.image_size;
offset += message->payload_size;
log_i("Zigbee - OTA Client receives data: progress [%ld/%ld]", offset, total_size);
if (message->payload_size && message->payload) {
uint16_t payload_size = 0;
void *payload = NULL;
ret = esp_element_ota_data(total_size, message->payload, message->payload_size, &payload, &payload_size);
if (ret != ESP_OK) {
log_e("Zigbee - Failed to element OTA data, status: %s", esp_err_to_name(ret));
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
(*it)->zbOTAState(false); // Notify that OTA is no longer active
}
return ret;
}
#if CONFIG_ZB_DELTA_OTA
ret = esp_delta_ota_write(s_ota_handle, payload, payload_size);
#else
ret = esp_ota_write(s_ota_handle, (const void *)payload, payload_size);
#endif
if (ret != ESP_OK) {
log_e("Zigbee - Failed to write OTA data to partition, status: %s", esp_err_to_name(ret));
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
(*it)->zbOTAState(false); // Notify that OTA is no longer active
}
return ret;
}
}
break;
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_APPLY: log_i("Zigbee - OTA upgrade apply"); break;
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_CHECK:
ret = offset == total_size ? ESP_OK : ESP_FAIL;
offset = 0;
total_size = 0;
s_tagid_received = false;
log_i("Zigbee - OTA upgrade check status: %s", esp_err_to_name(ret));
break;
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_FINISH:
log_i("Zigbee - OTA Finish");
log_i(
"Zigbee - OTA Information: version: 0x%lx, manufacturer code: 0x%x, image type: 0x%x, total size: %ld bytes, cost time: %lld ms,",
message->ota_header.file_version, message->ota_header.manufacturer_code, message->ota_header.image_type, message->ota_header.image_size,
(esp_timer_get_time() - start_time) / 1000
);
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
(*it)->zbOTAState(false); // Notify that OTA is no longer active
}
#if CONFIG_ZB_DELTA_OTA
ret = esp_delta_ota_end(s_ota_handle);
#else
ret = esp_ota_end(s_ota_handle);
#endif
if (ret != ESP_OK) {
log_e("Zigbee - Failed to end OTA partition, status: %s", esp_err_to_name(ret));
return ret;
}
ret = esp_ota_set_boot_partition(s_ota_partition);
if (ret != ESP_OK) {
log_e("Zigbee - Failed to set OTA boot partition, status: %s", esp_err_to_name(ret));
return ret;
}
log_w("Zigbee - Prepare to restart system");
esp_restart();
break;
default: log_i("Zigbee - OTA status: %d", message->upgrade_status); break;
}
}
return ret;
}
static esp_err_t zb_ota_upgrade_query_image_resp_handler(const esp_zb_zcl_ota_upgrade_query_image_resp_message_t *message) {
if (message->info.status == ESP_ZB_ZCL_STATUS_SUCCESS) {
log_i("Zigbee - Queried OTA image from address: 0x%04hx, endpoint: %d", message->server_addr.u.short_addr, message->server_endpoint);
log_i("Zigbee - Image version: 0x%lx, manufacturer code: 0x%x, image size: %ld", message->file_version, message->manufacturer_code, message->image_size);
if (message->image_size == 0) {
log_i("Zigbee - Rejecting OTA image upgrade, image size is 0");
return ESP_FAIL;
}
if (message->file_version == 0) {
log_i("Zigbee - Rejecting OTA image upgrade, file version is 0");
return ESP_FAIL;
}
log_i("Zigbee - Approving OTA image upgrade");
} else {
log_i("Zigbee - OTA image upgrade response status: 0x%x", message->info.status);
}
return ESP_OK;
}
static esp_err_t zb_cmd_default_resp_handler(const esp_zb_zcl_cmd_default_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(
"Received default response: from address(0x%x), src_endpoint(%d) to dst_endpoint(%d), cluster(0x%x) with status 0x%x",
message->info.src_address.u.short_addr, message->info.src_endpoint, message->info.dst_endpoint, message->info.cluster, message->status_code
);
// Call global callback if set
Zigbee.callDefaultResponseCallback((zb_cmd_type_t)message->resp_to_cmd, message->status_code, message->info.dst_endpoint, message->info.cluster);
// List through all Zigbee EPs and call the callback function, with the message
for (std::list<ZigbeeEP *>::iterator it = Zigbee.ep_objects.begin(); it != Zigbee.ep_objects.end(); ++it) {
if (message->info.dst_endpoint == (*it)->getEndpoint()) {
(*it)->zbDefaultResponse(message); //method zbDefaultResponse is implemented in the common EP class
}
}
return ESP_OK;
}
#endif // CONFIG_ZB_ENABLED