// 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. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "app/server/Server.h" #include "credentials/GroupDataProviderImpl.h" #define ESP_MATTER_MAX_DEVICE_TYPE_COUNT CONFIG_ESP_MATTER_MAX_DEVICE_TYPE_COUNT #define MAX_GROUPS_PER_FABRIC_PER_ENDPOINT CONFIG_MAX_GROUPS_PER_FABRIC_PER_ENDPOINT static const char *TAG = "data_model"; using chip::CommandId; using chip::DataVersion; using chip::EventId; using chip::kInvalidAttributeId; using chip::kInvalidCommandId; using chip::kInvalidClusterId; using chip::kInvalidEndpointId; namespace esp_matter { struct _attribute_base_t { uint16_t flags; // This struct is for attributes managed internally. uint16_t index; uint32_t attribute_id; struct _attribute_base_t *next; }; struct _attribute_t : public _attribute_base_t { uint32_t cluster_id; // This struct is for attributes not managed internally. esp_matter_attr_val_t val; attribute::callback_t override_callback; uint16_t endpoint_id; }; typedef struct _command { uint32_t command_id; uint16_t flags; command::callback_t callback; command::callback_t user_callback; struct _command *next; } _command_t; typedef struct _event { uint32_t event_id; struct _event *next; } _event_t; typedef struct _cluster { uint8_t index; uint16_t endpoint_id; cluster::plugin_server_init_callback_t plugin_server_init_callback; cluster::delegate_init_callback_t delegate_init_callback; void * delegate_pointer; cluster::add_bounds_callback_t add_bounds_callback; _attribute_base_t *attribute_list; /* If attribute is managed internally, the actual pointer type is _internal_attribute_t. When operating attribute_list, do check the flags first! */ EmberAfAttributeMetadata *matter_attributes; _command_t *command_list; _event_t *event_list; struct _cluster *next; } _cluster_t; typedef struct _endpoint { uint16_t endpoint_id; uint8_t device_type_count; uint8_t cluster_count; uint8_t device_type_versions[ESP_MATTER_MAX_DEVICE_TYPE_COUNT]; uint32_t device_type_ids[ESP_MATTER_MAX_DEVICE_TYPE_COUNT]; uint16_t flags; uint16_t parent_endpoint_id; _cluster_t *cluster_list; EmberAfEndpointType *endpoint_type; chip::DataVersion *data_versions_ptr; EmberAfDeviceType *device_types_ptr; void *priv_data; Identify *identify; struct _endpoint *next; } _endpoint_t; typedef struct _node { _endpoint_t *endpoint_list; uint16_t min_unused_endpoint_id; } _node_t; namespace node { static _node_t *node = NULL; // If Matter server or ESP-Matter data model is not enabled. we will never use minimum unused endpoint id. esp_err_t store_min_unused_endpoint_id() { VerifyOrReturnError((node && esp_matter::is_started()), ESP_ERR_INVALID_STATE, ESP_LOGE(TAG, "Node does not exist or esp_matter does not start")); nvs_handle_t handle; esp_err_t err = nvs_open_from_partition(ESP_MATTER_NVS_PART_NAME, ESP_MATTER_KVS_NAMESPACE, NVS_READWRITE, &handle); VerifyOrReturnError(err == ESP_OK, err, ESP_LOGE(TAG, "Failed to open the node nvs_namespace")); err = nvs_set_u16(handle, "min_uu_ep_id", node->min_unused_endpoint_id); nvs_commit(handle); nvs_close(handle); return err; } esp_err_t read_min_unused_endpoint_id() { VerifyOrReturnError((node && esp_matter::is_started()), ESP_ERR_INVALID_STATE, ESP_LOGE(TAG, "Node does not exist or esp_matter does not start")); nvs_handle_t handle; esp_err_t err = nvs_open_from_partition(ESP_MATTER_NVS_PART_NAME, ESP_MATTER_KVS_NAMESPACE, NVS_READONLY, &handle); if (err == ESP_OK) { err = nvs_get_u16(handle, "min_uu_ep_id", &node->min_unused_endpoint_id); nvs_close(handle); } if (err == ESP_ERR_NVS_NOT_FOUND) { ESP_LOGI(TAG, "Cannot find minimum unused endpoint_id, try to find in the previous namespace"); // Try to read the minimum unused endpoint_id from the previous node namespace. err = nvs_open_from_partition(ESP_MATTER_NVS_PART_NAME, "node", NVS_READONLY, &handle); VerifyOrReturnError(err == ESP_OK, err, ESP_LOGI(TAG, "Failed to open node namespace")); err = nvs_get_u16(handle, "min_uu_ep_id", &node->min_unused_endpoint_id); nvs_close(handle); if (err == ESP_OK) { // If the minimum unused endpoint_id is got, we will erase it from the previous namespace // and store it to the new namespace. if (nvs_open_from_partition(ESP_MATTER_NVS_PART_NAME, "node", NVS_READWRITE, &handle) == ESP_OK) { if (nvs_erase_key(handle, "min_uu_ep_id") != ESP_OK) { ESP_LOGE(TAG, "Failed to erase minimum unused endpoint_id"); } else { nvs_commit(handle); } nvs_close(handle); } return store_min_unused_endpoint_id(); } } else if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to get minimum unused endpoint_id in the %s nvs_namespace", ESP_MATTER_KVS_NAMESPACE); } return err; } } /* node */ namespace command { command_entry_t *get_cluster_accepted_command_list(uint32_t cluster_id); size_t get_cluster_accepted_command_count(uint32_t cluster_id); command_entry_t *get_cluster_generated_command_list(uint32_t cluster_id); size_t get_cluster_generated_command_count(uint32_t cluster_id); } /* command */ namespace attribute { static EmberAfAttributeMetadata *get_external_attribute_metadata(_attribute_t * attribute) { if (NULL == attribute || (attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY)) { return NULL; } _cluster_t *cluster = (_cluster_t *)cluster::get(attribute->endpoint_id, attribute->cluster_id); if (NULL == cluster) { return NULL; } return &cluster->matter_attributes[attribute->index]; } static esp_err_t free_default_value(attribute_t *attribute) { VerifyOrReturnError(attribute, ESP_FAIL, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; EmberAfAttributeMetadata *matter_attribute = get_external_attribute_metadata(current_attribute); if (!matter_attribute) { ESP_LOGE(TAG, "Attribute Metadata is not found"); return ESP_ERR_NOT_FOUND; } /* Free value if data is more than 2 bytes or if it is min max attribute */ if (current_attribute->flags & ATTRIBUTE_FLAG_MIN_MAX) { if (matter_attribute->size > 2) { esp_matter_mem_free((void *)matter_attribute->defaultValue.ptrToMinMaxValue->defaultValue.ptrToDefaultValue); esp_matter_mem_free((void *)matter_attribute->defaultValue.ptrToMinMaxValue->minValue.ptrToDefaultValue); esp_matter_mem_free((void *)matter_attribute->defaultValue.ptrToMinMaxValue->maxValue.ptrToDefaultValue); } esp_matter_mem_free((void *)matter_attribute->defaultValue.ptrToMinMaxValue); } else if (matter_attribute->size > 2) { esp_matter_mem_free((void *)matter_attribute->defaultValue.ptrToDefaultValue); } return ESP_OK; } static EmberAfDefaultAttributeValue get_default_value_from_data(esp_matter_attr_val_t *val, EmberAfAttributeType attribute_type, uint16_t attribute_size) { EmberAfDefaultAttributeValue default_value = (uint16_t)0; uint8_t *value = (uint8_t *)esp_matter_mem_calloc(1, attribute_size); VerifyOrReturnValue(value, default_value, ESP_LOGE(TAG, "Could not allocate value buffer for default value")); get_data_from_attr_val(val, &attribute_type, &attribute_size, value); if (attribute_size > 2) { /* Directly set the pointer */ default_value = value; } else { /* This data is 2 bytes or less. This should be represented as uint16. Copy the bytes appropriately for 0 or 1 or 2 bytes to be converted to uint16. Then free the allocated buffer. */ uint16_t int_value = 0; if (attribute_size == 2) { memcpy(&int_value, value, attribute_size); } else if (attribute_size == 1) { int_value = (uint16_t)*value; } default_value = int_value; esp_matter_mem_free(value); } return default_value; } static esp_err_t set_default_value_from_current_val(attribute_t *attribute, esp_matter_attr_val_t *min, esp_matter_attr_val_t *max) { VerifyOrReturnError(attribute, ESP_FAIL, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; EmberAfAttributeMetadata *matter_attribute = get_external_attribute_metadata(current_attribute); if (!matter_attribute) { ESP_LOGE(TAG, "Attribute Metadata is not found"); return ESP_ERR_NOT_FOUND; } esp_matter_attr_val_t *val = ¤t_attribute->val; /* Get size */ EmberAfAttributeType attribute_type = 0; uint16_t attribute_size = 0; get_data_from_attr_val(val, &attribute_type, &attribute_size, NULL); /* Get and set value */ if (current_attribute->flags & ATTRIBUTE_FLAG_MIN_MAX) { EmberAfAttributeMinMaxValue *temp_value = (EmberAfAttributeMinMaxValue *)esp_matter_mem_calloc(1, sizeof(EmberAfAttributeMinMaxValue)); VerifyOrReturnError(temp_value, ESP_FAIL, ESP_LOGE(TAG, "Could not allocate ptrToMinMaxValue for default value")); temp_value->defaultValue = get_default_value_from_data(val, attribute_type, attribute_size); temp_value->minValue = get_default_value_from_data(min, attribute_type, attribute_size); temp_value->maxValue = get_default_value_from_data(max, attribute_type, attribute_size); matter_attribute->defaultValue.ptrToMinMaxValue = temp_value; } else if (attribute_size > 2) { EmberAfDefaultAttributeValue temp_value = get_default_value_from_data(val, attribute_type, attribute_size); matter_attribute->defaultValue.ptrToDefaultValue = temp_value.ptrToDefaultValue; } else { EmberAfDefaultAttributeValue temp_value = get_default_value_from_data(val, attribute_type, attribute_size); matter_attribute->defaultValue.defaultValue = temp_value.defaultValue; } return ESP_OK; } } /* attribute */ namespace endpoint { static int get_next_index() { uint16_t endpoint_id = 0; for (int index = 0; index < MAX_ENDPOINT_COUNT; index++) { endpoint_id = emberAfEndpointFromIndex(index); if (endpoint_id == kInvalidEndpointId) { return index; } } return 0xFFFF; } // global instance so that we can reset it when needed. // We may need to reset it when new endpoint is added or existing endpoint is removed. // This is specifically for bridged device. static chip::Credentials::GroupDataProviderImpl *s_group_data_provider = nullptr; static uint16_t s_groups_server_cluster_count = 0; static void resize_group_data_provider() { // don't do anything if the count is the same uint16_t groups_server_cluster_count = node::get_server_cluster_endpoint_count(chip::app::Clusters::Groups::Id); if (s_groups_server_cluster_count == groups_server_cluster_count) { return; } s_groups_server_cluster_count = groups_server_cluster_count; uint16_t max_groups_per_fabric = s_groups_server_cluster_count * MAX_GROUPS_PER_FABRIC_PER_ENDPOINT; auto group_data_provider = new (std::nothrow) chip::Credentials::GroupDataProviderImpl(max_groups_per_fabric, CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC); if (!group_data_provider) { ESP_LOGE(TAG, "Failed to allocate memory for group data provider"); return; } group_data_provider->SetStorageDelegate(&chip::Server::GetInstance().GetPersistentStorage()); group_data_provider->SetSessionKeystore(chip::Server::GetInstance().GetSessionKeystore()); // As we are re-using the persistent storage instance from the Server class instance, // which has all the data from the previous endpoints, so no harm in re-sizing. group_data_provider->Init(); // delete the old one if it exists if (s_group_data_provider) { delete s_group_data_provider; } s_group_data_provider = group_data_provider; chip::Credentials::SetGroupDataProvider(s_group_data_provider); } static esp_err_t disable(endpoint_t *endpoint) { /* Take lock if not already taken */ lock::status_t lock_status = lock::chip_stack_lock(portMAX_DELAY); VerifyOrReturnError(lock_status != lock::FAILED, ESP_FAIL, ESP_LOGE(TAG, "Could not get task context")); /* Remove endpoint */ _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; int endpoint_index = emberAfGetDynamicIndexFromEndpoint(current_endpoint->endpoint_id); if (endpoint_index == 0xFFFF) { ESP_LOGE(TAG, "Could not find endpoint index"); if (lock_status == lock::SUCCESS) { lock::chip_stack_unlock(); } return ESP_FAIL; } emberAfClearDynamicEndpoint(endpoint_index); if (lock_status == lock::SUCCESS) { lock::chip_stack_unlock(); } /* Delete identify */ if (current_endpoint->identify) { chip::Platform::Delete(current_endpoint->identify); current_endpoint->identify = NULL; } // resize the group data provider to match the new endpoint count resize_group_data_provider(); return ESP_OK; } esp_err_t enable(endpoint_t *endpoint) { VerifyOrReturnError(endpoint, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; /* Device types */ EmberAfDeviceType *device_types_ptr = (EmberAfDeviceType *)esp_matter_mem_calloc(current_endpoint->device_type_count, sizeof(EmberAfDeviceType)); if (!device_types_ptr) { ESP_LOGE(TAG, "Couldn't allocate device_types"); /* goto cleanup is not used here to avoid 'crosses initialization' of device_types below */ return ESP_ERR_NO_MEM; } for (size_t i = 0; i < current_endpoint->device_type_count; ++i) { device_types_ptr[i].deviceTypeId = current_endpoint->device_type_ids[i]; device_types_ptr[i].deviceTypeRevision = current_endpoint->device_type_versions[i]; } chip::Span device_types(device_types_ptr, current_endpoint->device_type_count); current_endpoint->device_types_ptr = device_types_ptr; /* Clusters */ _cluster_t *cluster = current_endpoint->cluster_list; int cluster_count = SinglyLinkedList<_cluster_t>::count(cluster); int cluster_index = 0; DataVersion *data_versions_ptr = (DataVersion *)esp_matter_mem_calloc(1, cluster_count * sizeof(DataVersion)); if (!data_versions_ptr) { ESP_LOGE(TAG, "Couldn't allocate data_versions"); esp_matter_mem_free(device_types_ptr); current_endpoint->device_types_ptr = NULL; /* goto cleanup is not used here to avoid 'crosses initialization' of data_versions below */ return ESP_ERR_NO_MEM; } chip::Span data_versions(data_versions_ptr, cluster_count); current_endpoint->data_versions_ptr = data_versions_ptr; /* Variables */ /* This is needed to avoid 'crosses initialization' errors because of goto */ esp_err_t err = ESP_OK; lock::status_t lock_status = lock::FAILED; CHIP_ERROR status = CHIP_NO_ERROR; CommandId *accepted_command_ids = NULL; CommandId *generated_command_ids = NULL; _command_t *command = NULL; command_entry_t *command_list = NULL; uint32_t cluster_id = kInvalidClusterId; int command_count = 0; int command_index = 0; int command_flag = COMMAND_FLAG_NONE; EventId *event_ids = NULL; _event_t *event = NULL; int event_count = 0; int event_index = 0; int endpoint_index = 0; while (cluster) { /* Attributes */ /* Handled in attribute::create() */ /* Commands */ command = NULL; command_count = 0; command_index = 0; command_flag = COMMAND_FLAG_NONE; accepted_command_ids = NULL; generated_command_ids = NULL; cluster_id = cluster::get_id((cluster_t*)cluster); /* Init identify if exists and not initialized */ if (cluster_id == chip::app::Clusters::Identify::Id && current_endpoint->identify == NULL) { _attribute_t *identify_type_attr = (_attribute_t *)attribute::get( current_endpoint->endpoint_id, cluster_id, chip::app::Clusters::Identify::Attributes::IdentifyType::Id); if (identify_type_attr) { if (identification::init(current_endpoint->endpoint_id, identify_type_attr->val.val.u8) != ESP_OK) { ESP_LOGE(TAG, "Failed to init identification"); err = ESP_FAIL; break; } } else { ESP_LOGE(TAG, "Can't get IdentifyType attribute in Identify cluster"); err = ESP_ERR_INVALID_STATE; break; } } /* Client Generated Commands */ command_flag = COMMAND_FLAG_ACCEPTED; command = cluster->command_list; command_count = SinglyLinkedList<_command_t>::count_with_flag(command, command_flag); command_count += command::get_cluster_accepted_command_count(cluster_id); if (command_count > 0) { command_index = 0; accepted_command_ids = (CommandId *)esp_matter_mem_calloc(1, (command_count + 1) * sizeof(CommandId)); if (!accepted_command_ids) { ESP_LOGE(TAG, "Couldn't allocate accepted_command_ids"); err = ESP_ERR_NO_MEM; break; } while (command) { if (command->flags & command_flag) { accepted_command_ids[command_index] = command->command_id; command_index++; } command = command->next; } command_list = command::get_cluster_accepted_command_list(cluster_id); for(size_t index = 0; command_index < command_count && command_list; index++) { accepted_command_ids[command_index] = command_list[index].command_id; command_index++; } accepted_command_ids[command_index] = kInvalidCommandId; } /* Server Generated Commands */ command_flag = COMMAND_FLAG_GENERATED; command = cluster->command_list; command_count = SinglyLinkedList<_command_t>::count_with_flag(command, command_flag); command_count += command::get_cluster_generated_command_count(cluster_id); if (command_count > 0) { command_index = 0; generated_command_ids = (CommandId *)esp_matter_mem_calloc(1, (command_count + 1) * sizeof(CommandId)); if (!generated_command_ids) { ESP_LOGE(TAG, "Couldn't allocate generated_command_ids"); err = ESP_ERR_NO_MEM; break; } while (command) { if (command->flags & command_flag) { generated_command_ids[command_index] = command->command_id; command_index++; } command = command->next; } command_list = command::get_cluster_generated_command_list(cluster_id); for(size_t index = 0; command_index < command_count && command_list; index++) { generated_command_ids[command_index] = command_list[index].command_id; command_index++; } generated_command_ids[command_index] = kInvalidCommandId; } /* Event */ event = cluster->event_list; event_count = SinglyLinkedList<_event_t>::count(event); if (event_count > 0) { event_index = 0; event_ids = (EventId *)esp_matter_mem_calloc(1, (event_count + 1) * sizeof(EventId)); if (!event_ids) { ESP_LOGE(TAG, "Couldn't allocate event_ids"); err = ESP_ERR_NO_MEM; break; } while (event) { event_ids[event_index] = event->event_id; event_index++; event = event->next; } event_ids[event_index] = chip::kInvalidEventId; } /* Fill up the cluster */ EmberAfCluster *matter_clusters = (EmberAfCluster *)(¤t_endpoint->endpoint_type->cluster[cluster_index]); matter_clusters->attributes = cluster->matter_attributes; matter_clusters->acceptedCommandList = accepted_command_ids; matter_clusters->generatedCommandList = generated_command_ids; matter_clusters->eventList = event_ids; matter_clusters->eventCount = event_count; /* Get next cluster */ current_endpoint->endpoint_type->endpointSize += matter_clusters->clusterSize; cluster = cluster->next; cluster_index++; /* This is to avoid double free in case of errors */ accepted_command_ids = NULL; generated_command_ids = NULL; event_ids = NULL; } if (err != ESP_OK) { goto cleanup; } current_endpoint->endpoint_type->clusterCount = cluster_count; /* Take lock if not already taken */ lock_status = lock::chip_stack_lock(portMAX_DELAY); if (lock_status == lock::FAILED) { ESP_LOGE(TAG, "Could not get task context"); goto cleanup; } /* Add Endpoint */ endpoint_index = endpoint::get_next_index(); status = emberAfSetDynamicEndpoint(endpoint_index, current_endpoint->endpoint_id, current_endpoint->endpoint_type, data_versions, device_types, current_endpoint->parent_endpoint_id); if (status != CHIP_NO_ERROR) { ESP_LOGE(TAG, "Error adding dynamic endpoint %" PRIu16 ": %" CHIP_ERROR_FORMAT, current_endpoint->endpoint_id, status.Format()); err = ESP_FAIL; if (lock_status == lock::SUCCESS) { lock::chip_stack_unlock(); } goto cleanup; } if (lock_status == lock::SUCCESS) { lock::chip_stack_unlock(); } ESP_LOGI(TAG, "Dynamic endpoint %" PRIu16 " added", current_endpoint->endpoint_id); return err; cleanup: esp_matter_mem_free(generated_command_ids); esp_matter_mem_free(accepted_command_ids); esp_matter_mem_free(event_ids); if (current_endpoint->endpoint_type->cluster) { for (int cluster_index = 0; cluster_index < cluster_count; cluster_index++) { /* Free attributes */ esp_matter_mem_free((void *)current_endpoint->endpoint_type->cluster[cluster_index].attributes); /* Free commands */ esp_matter_mem_free((void *)current_endpoint->endpoint_type->cluster[cluster_index].acceptedCommandList); esp_matter_mem_free((void *)current_endpoint->endpoint_type->cluster[cluster_index].generatedCommandList); /* Free events */ esp_matter_mem_free((void *)current_endpoint->endpoint_type->cluster[cluster_index].eventList); } } esp_matter_mem_free(data_versions_ptr); current_endpoint->data_versions_ptr = NULL; esp_matter_mem_free(device_types_ptr); current_endpoint->device_types_ptr = NULL; return err; } esp_err_t enable_all() { node_t *node = node::get(); /* Not returning error, since the node will not be initialized for application using the data model from zap */ VerifyOrReturnError(node, ESP_OK); endpoint_t *endpoint = get_first(node); while (endpoint) { enable(endpoint); endpoint = get_next(endpoint); } return ESP_OK; } } /* endpoint */ namespace attribute { attribute_t *create(cluster_t *cluster, uint32_t attribute_id, uint16_t flags, esp_matter_attr_val_t val, uint16_t max_val_size) { /* Find */ VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; attribute_t *existing_attribute = get(cluster, attribute_id); if (existing_attribute) { ESP_LOGW(TAG, "Attribute 0x%08" PRIX32 " on cluster 0x%08" PRIX32 " already exists. Not creating again.", attribute_id, cluster::get_id(cluster)); return existing_attribute; } endpoint_t *endpoint = endpoint::get(current_cluster->endpoint_id); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; /* Matter attributes */ EmberAfCluster *matter_clusters = (EmberAfCluster *)(¤t_endpoint->endpoint_type->cluster[current_cluster->index]); matter_clusters->attributeCount++; int attribute_count = matter_clusters->attributeCount; if (current_cluster->matter_attributes) { current_cluster->matter_attributes = (EmberAfAttributeMetadata *)esp_matter_mem_realloc(current_cluster->matter_attributes, attribute_count * sizeof(EmberAfAttributeMetadata)); } else { current_cluster->matter_attributes = (EmberAfAttributeMetadata *)esp_matter_mem_calloc(1, attribute_count * sizeof(EmberAfAttributeMetadata)); } if (!current_cluster->matter_attributes) { ESP_LOGE(TAG, "Couldn't allocate matter_attributes"); return NULL; } /* Set */ EmberAfAttributeMetadata *matter_attribute = ¤t_cluster->matter_attributes[attribute_count - 1]; matter_attribute->attributeId = attribute_id; /* esp-matter uses uint16_t as the flags for the extras, EmberAfAttributeMetadata uses uint8_t as the mask. The conversion from uint16 to uint8 is as expected for that the extra flags are only used in esp-matter. */ matter_attribute->mask = static_cast(flags); if (!(flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY)) { matter_attribute->mask |= ATTRIBUTE_FLAG_EXTERNAL_STORAGE; } matter_attribute->attributeType = 0; matter_attribute->size = 0; _attribute_t *attribute = NULL; if (!(flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY)) { /* Allocate */ attribute = (_attribute_t *)esp_matter_mem_calloc(1, sizeof(_attribute_t)); if (!attribute) { ESP_LOGE(TAG, "Couldn't allocate _attribute_t"); return NULL; } attribute->index = attribute_count - 1; attribute->attribute_id = attribute_id; attribute->cluster_id = matter_clusters->clusterId; attribute->endpoint_id = current_cluster->endpoint_id; attribute->flags = flags; attribute->flags |= ATTRIBUTE_FLAG_EXTERNAL_STORAGE; // After reboot, string and array are treated as Invalid. So need to store val.type and size of attribute value. attribute->val.type = val.type; if (val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING || val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING || val.type == ESP_MATTER_VAL_TYPE_OCTET_STRING || val.type == ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING || val.type == ESP_MATTER_VAL_TYPE_ARRAY) { attribute->val.val.a.s = val.val.a.s; attribute->val.val.a.n = val.val.a.n; attribute->val.val.a.t = val.val.a.t; } bool attribute_updated = false; if (flags & ATTRIBUTE_FLAG_NONVOLATILE) { // Lets directly read into attribute->val so that we don't have to set the attribute value again. esp_err_t err = get_val_from_nvs(attribute->endpoint_id, attribute->cluster_id, attribute_id, attribute->val); if (err == ESP_OK) { attribute_updated = true; } } if (!attribute_updated) { set_val((attribute_t *)attribute, &val); } set_default_value_from_current_val((attribute_t *)attribute, NULL, NULL); attribute::get_data_from_attr_val(&attribute->val, &matter_attribute->attributeType, &matter_attribute->size, NULL); if (attribute->val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING || attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING) { uint16_t size_for_storing_str_len = attribute->val.val.a.t - attribute->val.val.a.s; matter_attribute->size = max_val_size + size_for_storing_str_len; } matter_clusters->clusterSize += matter_attribute->size; } else { attribute = (_attribute_t *)esp_matter_mem_calloc(1, sizeof(_attribute_base_t)); attribute->attribute_id = attribute_id; attribute->index = attribute_count - 1; attribute->flags = flags; } /* Add */ SinglyLinkedList<_attribute_base_t>::append(¤t_cluster->attribute_list, attribute); return (attribute_t *)attribute; } static esp_err_t destroy(attribute_t *attribute) { VerifyOrReturnError(attribute, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; if (current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY) { // For attribute managed internally, free as the _attribute_base_t pointer. esp_matter_mem_free((_attribute_base_t *)attribute); return ESP_OK; } /* Default value needs to be deleted first since it uses the current val. */ free_default_value(attribute); /* Delete val here, if required */ if (current_attribute->val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_OCTET_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_ARRAY) { /* Free buf */ esp_matter_mem_free(current_attribute->val.val.a.b); } /* Erase the persistent data */ if (attribute::get_flags(attribute) & ATTRIBUTE_FLAG_NONVOLATILE) { erase_val_in_nvs(current_attribute->endpoint_id, current_attribute->cluster_id, current_attribute->attribute_id); } /* Free */ esp_matter_mem_free(current_attribute); return ESP_OK; } attribute_t *get(cluster_t *cluster, uint32_t attribute_id) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; _attribute_base_t *current_attribute = current_cluster->attribute_list; while (current_attribute) { if (current_attribute->attribute_id == attribute_id) { break; } current_attribute = current_attribute->next; } return (attribute_t *)current_attribute; } attribute_t *get(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id) { cluster_t *cluster = cluster::get(endpoint_id, cluster_id); return get(cluster, attribute_id); } attribute_t *get_first(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return (attribute_t *)current_cluster->attribute_list; } attribute_t *get_next(attribute_t *attribute) { VerifyOrReturnValue(attribute, NULL, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; return (attribute_t *)current_attribute->next; } uint32_t get_id(attribute_t *attribute) { VerifyOrReturnValue(attribute, kInvalidAttributeId, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; return current_attribute->attribute_id; } constexpr uint16_t k_deferred_attribute_persistence_time_ms = CONFIG_ESP_MATTER_DEFERRED_ATTR_PERSISTENCE_TIME_MS; static void deferred_attribute_write(chip::System::Layer *layer, void *attribute_ptr) { _attribute_t *current_attribute = (_attribute_t *)attribute_ptr; ESP_LOGI(TAG, "Store the deferred attribute 0x%" PRIx32 " of cluster 0x%" PRIX32 " on endpoint 0x%" PRIx16, current_attribute->attribute_id, current_attribute->cluster_id, current_attribute->endpoint_id); store_val_in_nvs(current_attribute->endpoint_id, current_attribute->cluster_id, current_attribute->attribute_id, current_attribute->val); } esp_err_t set_val(attribute_t *attribute, esp_matter_attr_val_t *val) { VerifyOrReturnError(attribute, ESP_FAIL, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; ESP_RETURN_ON_FALSE(!(current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY), ESP_ERR_NOT_SUPPORTED, TAG, "Attribute is not managed by esp matter data model"); if (val->type == ESP_MATTER_VAL_TYPE_CHAR_STRING || val->type == ESP_MATTER_VAL_TYPE_OCTET_STRING || val->type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING || val->type == ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING || val->type == ESP_MATTER_VAL_TYPE_ARRAY) { /* Free old buf */ esp_matter_mem_free(current_attribute->val.val.a.b); current_attribute->val.val.a.b = NULL; if (val->val.a.s > 0) { /* Alloc new buf */ uint8_t *new_buf = (uint8_t *)esp_matter_mem_calloc(1, val->val.a.s); VerifyOrReturnError(new_buf, ESP_ERR_NO_MEM, ESP_LOGE(TAG, "Could not allocate new buffer")); /* Copy to new buf and assign */ memcpy(new_buf, val->val.a.b, val->val.a.s); current_attribute->val.val.a.b = new_buf; current_attribute->val.val.a.s = val->val.a.s; current_attribute->val.val.a.n = val->val.a.n; current_attribute->val.val.a.t = val->val.a.t; } else { ESP_LOGD(TAG, "Set val called with string with size 0"); } } else { memcpy((void *)¤t_attribute->val, (void *)val, sizeof(esp_matter_attr_val_t)); } if (current_attribute->flags & ATTRIBUTE_FLAG_NONVOLATILE) { if (current_attribute->flags & ATTRIBUTE_FLAG_DEFERRED) { if (!chip::DeviceLayer::SystemLayer().IsTimerActive(deferred_attribute_write, current_attribute)) { auto & system_layer = chip::DeviceLayer::SystemLayer(); system_layer.StartTimer(chip::System::Clock::Milliseconds16(k_deferred_attribute_persistence_time_ms), deferred_attribute_write, current_attribute); } } else { store_val_in_nvs(current_attribute->endpoint_id, current_attribute->cluster_id, current_attribute->attribute_id, current_attribute->val); } } return ESP_OK; } esp_err_t get_val(attribute_t *attribute, esp_matter_attr_val_t *val) { VerifyOrReturnError(attribute, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; ESP_RETURN_ON_FALSE(!(current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY), ESP_ERR_NOT_SUPPORTED, TAG, "Attribute is not managed by esp matter data model"); memcpy((void *)val, (void *)¤t_attribute->val, sizeof(esp_matter_attr_val_t)); return ESP_OK; } esp_err_t add_bounds(attribute_t *attribute, esp_matter_attr_val_t min, esp_matter_attr_val_t max) { VerifyOrReturnError(attribute, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; ESP_RETURN_ON_FALSE(!(current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY), ESP_ERR_NOT_SUPPORTED, TAG, "Attribute is not managed by esp matter data model"); /* Check if bounds can be set */ if (current_attribute->val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_OCTET_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_ARRAY) { ESP_LOGE(TAG, "Bounds cannot be set for string/array type attributes"); return ESP_ERR_INVALID_ARG; } VerifyOrReturnError(((current_attribute->val.type == min.type) && (current_attribute->val.type == max.type)), ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Cannot set bounds because of val type mismatch: expected: %d, min: %d, max: %d", current_attribute->val.type, min.type, max.type)); EmberAfAttributeMetadata *matter_attribute= get_external_attribute_metadata(current_attribute); if (!matter_attribute) { ESP_LOGE(TAG, "Attribute Metadata is not found"); return ESP_ERR_NOT_FOUND; } matter_attribute->mask |= ATTRIBUTE_FLAG_MIN_MAX; current_attribute->flags |= ATTRIBUTE_FLAG_MIN_MAX; /* Set the default value again after setting the bounds and the flag */ set_default_value_from_current_val(attribute, &min, &max); return ESP_OK; } esp_err_t get_bounds(attribute_t *attribute, esp_matter_attr_bounds_t *bounds) { if (!attribute || !bounds) { ESP_LOGE(TAG, "Attribute or bounds cannot be NULL"); return ESP_ERR_INVALID_ARG; } _attribute_t *current_attribute = (_attribute_t *)attribute; EmberAfAttributeMetadata *matter_attribute= get_external_attribute_metadata(current_attribute); if (!matter_attribute) { ESP_LOGE(TAG, "Attribute Metadata is not found"); return ESP_ERR_NOT_FOUND; } if (!(matter_attribute->mask & ATTRIBUTE_FLAG_MIN_MAX)) { ESP_LOGW(TAG, "Endpoint 0x%04" PRIX16 "'s Cluster 0x%08" PRIX32 "'s Attribute 0x%08" PRIX32 " has not set bounds", current_attribute->endpoint_id, current_attribute->cluster_id, matter_attribute->attributeId); return ESP_ERR_INVALID_ARG; } if (matter_attribute->size > 2) { get_attr_val_from_data(&bounds->min, matter_attribute->attributeType, matter_attribute->size, (uint8_t *)matter_attribute->defaultValue.ptrToMinMaxValue->minValue.ptrToDefaultValue, matter_attribute); get_attr_val_from_data(&bounds->max, matter_attribute->attributeType, matter_attribute->size, (uint8_t *)matter_attribute->defaultValue.ptrToMinMaxValue->maxValue.ptrToDefaultValue, matter_attribute); } else { uint8_t value[2]; uint16_t min_value = matter_attribute->defaultValue.ptrToMinMaxValue->minValue.defaultValue; memcpy(value, &min_value, matter_attribute->size); get_attr_val_from_data(&bounds->min, matter_attribute->attributeType, matter_attribute->size, value, matter_attribute); uint16_t max_value = matter_attribute->defaultValue.ptrToMinMaxValue->maxValue.defaultValue; memcpy(value, &max_value, matter_attribute->size); get_attr_val_from_data(&bounds->max, matter_attribute->attributeType, matter_attribute->size, value, matter_attribute); } return ESP_OK; } uint16_t get_flags(attribute_t *attribute) { VerifyOrReturnValue(attribute, 0, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; return current_attribute->flags; } esp_err_t set_override_callback(attribute_t *attribute, callback_t callback) { VerifyOrReturnError(attribute, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; ESP_RETURN_ON_FALSE(!(current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY), ESP_ERR_NOT_SUPPORTED, TAG, "Attribute is not managed by esp matter data model"); cluster_t *cluster = cluster::get(current_attribute->endpoint_id, current_attribute->cluster_id); if (current_attribute->val.type == ESP_MATTER_VAL_TYPE_ARRAY || current_attribute->val.type == ESP_MATTER_VAL_TYPE_OCTET_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_CHAR_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING || current_attribute->val.type == ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING) { // The override callback might allocate memory and we have no way to free the memory // TODO: Add memory-safe override callback for these attribute types ESP_LOGE(TAG, "Cannot set override callback for attribute 0x%" PRIX32 " on cluster 0x%" PRIX32, current_attribute->attribute_id, cluster::get_id(cluster)); return ESP_ERR_NOT_SUPPORTED; } current_attribute->override_callback = callback; current_attribute->flags |= ATTRIBUTE_FLAG_OVERRIDE; return ESP_OK; } callback_t get_override_callback(attribute_t *attribute) { VerifyOrReturnValue(attribute, NULL, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; VerifyOrReturnValue(!(current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY), NULL, ESP_LOGE(TAG, "Attribute is not managed by esp matter data model")); return current_attribute->override_callback; } esp_err_t set_deferred_persistence(attribute_t *attribute) { VerifyOrReturnError(attribute, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Attribute cannot be NULL")); _attribute_t *current_attribute = (_attribute_t *)attribute; ESP_RETURN_ON_FALSE(!(current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY), ESP_ERR_NOT_SUPPORTED, TAG, "Attribute is not managed by esp matter data model"); if (!(current_attribute->flags & ATTRIBUTE_FLAG_NONVOLATILE)) { ESP_LOGE(TAG, "Attribute should be non-volatile to set a deferred persistence time"); return ESP_ERR_INVALID_ARG; } current_attribute->flags |= ATTRIBUTE_FLAG_DEFERRED; return ESP_OK; } } /* attribute */ namespace command { command_t *create(cluster_t *cluster, uint32_t command_id, uint8_t flags, callback_t callback) { /* Find */ VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; command_t *existing_command = get(cluster, command_id, flags); if (existing_command) { ESP_LOGW(TAG, "Command 0x%08" PRIX32 " on cluster 0x%08" PRIX32 " already exists. Not creating again.", command_id, cluster::get_id(cluster)); return existing_command; } /* Allocate */ _command_t *command = (_command_t *)esp_matter_mem_calloc(1, sizeof(_command_t)); VerifyOrReturnValue(command, NULL, ESP_LOGE(TAG, "Couldn't allocate _command_t")); /* Set */ command->command_id = command_id; command->flags = flags; command->callback = callback; command->user_callback = NULL; /* Add */ SinglyLinkedList<_command_t>::append(¤t_cluster->command_list, command); return (command_t *)command; } command_t *get(cluster_t *cluster, uint32_t command_id, uint16_t flags) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; _command_t *current_command = (_command_t *)current_cluster->command_list; while (current_command) { if ((current_command->command_id == command_id) && (current_command->flags & flags)) { break; } current_command = current_command->next; } return (command_t *)current_command; } command_t *get_first(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return (command_t *)current_cluster->command_list; } command_t *get_next(command_t *command) { VerifyOrReturnValue(command, NULL, ESP_LOGE(TAG, "Command cannot be NULL")); _command_t *current_command = (_command_t *)command; return (command_t *)current_command->next; } uint32_t get_id(command_t *command) { VerifyOrReturnValue(command, kInvalidCommandId, ESP_LOGE(TAG, "Command cannot be NULL")); _command_t *current_command = (_command_t *)command; return current_command->command_id; } callback_t get_callback(command_t *command) { VerifyOrReturnValue(command, NULL, ESP_LOGE(TAG, "Command cannot be NULL")); _command_t *current_command = (_command_t *)command; return current_command->callback; } callback_t get_user_callback(command_t *command) { VerifyOrReturnValue(command, NULL, ESP_LOGE(TAG, "Command cannot be NULL")); _command_t *current_command = (_command_t *)command; return current_command->user_callback; } void set_user_callback(command_t *command, callback_t user_callback) { if (!command) { ESP_LOGE(TAG, "Command cannot be NULL"); } _command_t *current_command = (_command_t *)command; current_command->user_callback = user_callback; } uint16_t get_flags(command_t *command) { VerifyOrReturnValue(command, 0, ESP_LOGE(TAG, "Command cannot be NULL")); _command_t *current_command = (_command_t *)command; return current_command->flags; } } /* command */ namespace event { event_t *create(cluster_t *cluster, uint32_t event_id) { /* Find */ VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; event_t *existing_event = get(cluster, event_id); if (existing_event) { ESP_LOGW(TAG, "Event 0x%08" PRIX32 " on cluster 0x%08" PRIX32 " already exists. Not creating again.", event_id, cluster::get_id(cluster)); return existing_event; } /* Allocate */ _event_t *event = (_event_t *)esp_matter_mem_calloc(1, sizeof(_event_t)); VerifyOrReturnValue(event, NULL, ESP_LOGE(TAG, "Couldn't allocate _event_t")); /* Set */ event->event_id = event_id; /* Add */ SinglyLinkedList<_event_t>::append(¤t_cluster->event_list, event); return (event_t *)event; } event_t *get(cluster_t *cluster, uint32_t event_id) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; _event_t *current_event = (_event_t *)current_cluster->event_list; while (current_event) { if (current_event->event_id == event_id) { break; } current_event = current_event->next; } return (event_t *)current_event; } event_t *get_first(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return (event_t *)current_cluster->event_list; } event_t *get_next(event_t *event) { VerifyOrReturnValue(event, NULL, ESP_LOGE(TAG, "Event cannot be NULL")); _event_t *current_event = (_event_t *)event; return (event_t *)current_event->next; } uint32_t get_id(event_t *event) { VerifyOrReturnValue(event, chip::kInvalidEventId, ESP_LOGE(TAG, "Event cannot be NULL")); _event_t *current_event = (_event_t *)event; return current_event->event_id; } } /* event */ namespace cluster { cluster_t *create(endpoint_t *endpoint, uint32_t cluster_id, uint8_t flags) { /* Find */ VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Endpoint cannot be NULL")); VerifyOrReturnValue(((flags & CLUSTER_FLAG_SERVER) || (flags & CLUSTER_FLAG_CLIENT)), NULL, ESP_LOGE(TAG, "Server or client cluster flag not set")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; cluster_t *existing_cluster = get(endpoint, cluster_id); if (existing_cluster) { /* If a server already exists, do not create it again */ _cluster_t *_existing_cluster = (_cluster_t *)existing_cluster; // The member EmberAfCluster * in EmberAfEndpointType is const, do a non-const cast to change the mask. EmberAfClusterMask *cluster_flags = (EmberAfClusterMask *)¤t_endpoint->endpoint_type->cluster[_existing_cluster->index].mask; if ((*cluster_flags & CLUSTER_FLAG_SERVER) && (flags & CLUSTER_FLAG_SERVER)) { ESP_LOGW(TAG, "Server Cluster 0x%08" PRIX32 " on endpoint 0x%04" PRIx16 " already exists. Not creating again.", cluster_id, current_endpoint->endpoint_id); return existing_cluster; } /* If a client already exists, do not create it again */ if ((*cluster_flags & CLUSTER_FLAG_CLIENT) && (flags & CLUSTER_FLAG_CLIENT)) { ESP_LOGW(TAG, "Client Cluster 0x%08" PRIX32 " on endpoint 0x%04" PRIx16 " already exists. Not creating again.", cluster_id, current_endpoint->endpoint_id); return existing_cluster; } /* The cluster already exists, but is of a different type. Just update the 'Set' part from below. */ ESP_LOGI(TAG, "Cluster 0x%08" PRIX32 " on endpoint 0x%04" PRIx16 " already exists. Updating values.", cluster_id, current_endpoint->endpoint_id); *cluster_flags |= flags; return existing_cluster; } /* Allocate */ _cluster_t *cluster = (_cluster_t *)esp_matter_mem_calloc(1, sizeof(_cluster_t)); if (!cluster) { ESP_LOGE(TAG, "Couldn't allocate _cluster_t"); return NULL; } /* Matter clusters */ EmberAfCluster *matter_clusters = (EmberAfCluster *)current_endpoint->endpoint_type->cluster; current_endpoint->cluster_count++; cluster->index = current_endpoint->cluster_count - 1; if (matter_clusters) { matter_clusters = (EmberAfCluster *)esp_matter_mem_realloc(matter_clusters, current_endpoint->cluster_count * sizeof(EmberAfCluster)); } else { matter_clusters = (EmberAfCluster *)esp_matter_mem_calloc(1, current_endpoint->cluster_count * sizeof(EmberAfCluster)); } if (!matter_clusters) { ESP_LOGE(TAG, "Couldn't allocate EmberAfCluster"); return NULL; } current_endpoint->endpoint_type->cluster = matter_clusters; /* Set */ EmberAfCluster *matter_cluster = (EmberAfCluster *)¤t_endpoint->endpoint_type->cluster[cluster->index]; cluster->endpoint_id = current_endpoint->endpoint_id; matter_cluster->clusterId = cluster_id; matter_cluster->mask = flags; matter_cluster->clusterSize = 0; matter_cluster->attributeCount = 0; matter_cluster->functions = NULL; /* Add */ SinglyLinkedList<_cluster_t>::append(¤t_endpoint->cluster_list, cluster); return (cluster_t *)cluster; } esp_err_t destroy(cluster_t *cluster) { VerifyOrReturnError(cluster, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; /* Parse and delete all commands */ SinglyLinkedList<_command_t>::delete_list(¤t_cluster->command_list); /* Parse and delete all attributes */ _attribute_base_t *attribute = current_cluster->attribute_list; while (attribute) { _attribute_base_t *next_attribute = attribute->next; attribute::destroy((attribute_t *)attribute); attribute = next_attribute; } /* Parse and delete all events */ SinglyLinkedList<_event_t>::delete_list(¤t_cluster->event_list); /* Free matter_attributes if allocated */ if (current_cluster->matter_attributes) { esp_matter_mem_free(current_cluster->matter_attributes); current_cluster->matter_attributes = NULL; } /* Free */ esp_matter_mem_free(current_cluster); return ESP_OK; } cluster_t *get(endpoint_t *endpoint, uint32_t cluster_id) { VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; _cluster_t *current_cluster = (_cluster_t *)current_endpoint->cluster_list; uint8_t cluster_index = 0; for (cluster_index = 0; cluster_index < current_endpoint->cluster_count; cluster_index++) { if (current_endpoint->endpoint_type->cluster[cluster_index].clusterId == cluster_id) { break; } } if (cluster_index == current_endpoint->cluster_count) { ESP_LOGD(TAG, "Cluster not found"); return NULL; } while (current_cluster) { if (current_cluster->index == cluster_index) { break; } current_cluster = current_cluster->next; } return (cluster_t *)current_cluster; } cluster_t *get(uint16_t endpoint_id, uint32_t cluster_id) { endpoint_t *endpoint = endpoint::get(endpoint_id); return get(endpoint, cluster_id); } cluster_t *get_first(endpoint_t *endpoint) { VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; return (cluster_t *)current_endpoint->cluster_list; } cluster_t *get_next(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return (cluster_t *)current_cluster->next; } uint32_t get_id(cluster_t *cluster) { VerifyOrReturnValue(cluster, kInvalidClusterId, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; endpoint_t *endpoint = endpoint::get(current_cluster->endpoint_id); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; return current_endpoint->endpoint_type->cluster[current_cluster->index].clusterId; } void *get_delegate_impl(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return current_cluster->delegate_pointer; } esp_err_t set_plugin_server_init_callback(cluster_t *cluster, plugin_server_init_callback_t callback) { VerifyOrReturnError(cluster, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; current_cluster->plugin_server_init_callback = callback; return ESP_OK; } plugin_server_init_callback_t get_plugin_server_init_callback(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return current_cluster->plugin_server_init_callback; } esp_err_t set_delegate_and_init_callback(cluster_t *cluster, delegate_init_callback_t callback, void *delegate) { VerifyOrReturnError(cluster, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; current_cluster->delegate_init_callback = callback; current_cluster->delegate_pointer = delegate; return ESP_OK; } delegate_init_callback_t get_delegate_init_callback(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL.")); _cluster_t *current_cluster = (_cluster_t *)cluster; return current_cluster->delegate_init_callback; } esp_err_t set_add_bounds_callback(cluster_t *cluster, add_bounds_callback_t callback) { VerifyOrReturnError(cluster, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; current_cluster->add_bounds_callback = callback; return ESP_OK; } add_bounds_callback_t get_add_bounds_callback(cluster_t *cluster) { VerifyOrReturnValue(cluster, NULL, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; return current_cluster->add_bounds_callback; } esp_err_t add_function_list(cluster_t *cluster, const function_generic_t *function_list, int function_flags) { VerifyOrReturnError(cluster, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Cluster cannot be NULL")); _cluster_t *current_cluster = (_cluster_t *)cluster; endpoint_t *endpoint = endpoint::get(current_cluster->endpoint_id); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; EmberAfCluster *matter_clusters = (EmberAfCluster *)current_endpoint->endpoint_type->cluster; matter_clusters[current_cluster->index].mask |= function_flags; matter_clusters[current_cluster->index].functions = (const EmberAfGenericClusterFunction *)function_list; return ESP_OK; } } /* cluster */ namespace endpoint { endpoint_t *create(node_t *node, uint8_t flags, void *priv_data) { /* Find */ VerifyOrReturnValue(node, NULL, ESP_LOGE(TAG, "Node cannot be NULL")); _node_t *current_node = (_node_t *)node; VerifyOrReturnValue(get_count(node) < CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT, NULL, ESP_LOGE(TAG, "Dynamic endpoint count cannot be greater than CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT:%u", CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT)); /* Allocate */ _endpoint_t *endpoint = (_endpoint_t *)esp_matter_mem_calloc(1, sizeof(_endpoint_t)); VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Couldn't allocate _endpoint_t")); endpoint->endpoint_type = (EmberAfEndpointType *)esp_matter_mem_calloc(1, sizeof(EmberAfEndpointType)); if (!endpoint->endpoint_type) { ESP_LOGE(TAG, "Couldn't allocate EmberAfEndpointType"); esp_matter_mem_free(endpoint); return NULL; } /* Set */ endpoint->endpoint_id = current_node->min_unused_endpoint_id++; endpoint->device_type_count = 0; endpoint->parent_endpoint_id = chip::kInvalidEndpointId; endpoint->flags = flags; endpoint->priv_data = priv_data; /* Store */ if (esp_matter::is_started()) { node::store_min_unused_endpoint_id(); } /* Add */ SinglyLinkedList<_endpoint_t>::append(¤t_node->endpoint_list, endpoint); return (endpoint_t *)endpoint; } endpoint_t *resume(node_t *node, uint8_t flags, uint16_t endpoint_id, void *priv_data) { /* Find */ VerifyOrReturnValue(node, NULL, ESP_LOGE(TAG, "Node cannot be NULL")); _node_t *current_node = (_node_t *)node; _endpoint_t *previous_endpoint = NULL; _endpoint_t *current_endpoint = current_node->endpoint_list; while (current_endpoint) { VerifyOrReturnValue(current_endpoint->endpoint_id != endpoint_id, NULL, ESP_LOGE(TAG, "Could not resume an endpoint that has been added to the node")); previous_endpoint = current_endpoint; current_endpoint = current_endpoint->next; } /* Check */ VerifyOrReturnError(endpoint_id < current_node->min_unused_endpoint_id, NULL, ESP_LOGE(TAG, "The endpoint_id of the resumed endpoint should have been used")); /* Allocate */ _endpoint_t *endpoint = (_endpoint_t *)esp_matter_mem_calloc(1, sizeof(_endpoint_t)); VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Couldn't allocate _endpoint_t")); endpoint->endpoint_type = (EmberAfEndpointType *)esp_matter_mem_calloc(1, sizeof(EmberAfEndpointType)); if (!endpoint->endpoint_type) { ESP_LOGE(TAG, "Couldn't allocate EmberAfEndpointType"); esp_matter_mem_free(endpoint); return NULL; } /* Set */ endpoint->endpoint_id = endpoint_id; endpoint->device_type_count = 0; endpoint->flags = flags; endpoint->priv_data = priv_data; /* Add */ if (previous_endpoint == NULL) { current_node->endpoint_list = endpoint; } else { previous_endpoint->next = endpoint; } return (endpoint_t *)endpoint; } esp_err_t destroy(node_t *node, endpoint_t *endpoint) { VerifyOrReturnError((node && endpoint), ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Node or endpoint cannot be NULL")); _node_t *current_node = (_node_t *)node; _endpoint_t *_endpoint = (_endpoint_t *)endpoint; VerifyOrReturnError((_endpoint->flags & ENDPOINT_FLAG_DESTROYABLE), ESP_FAIL, ESP_LOGE(TAG, "This endpoint cannot be deleted since the ENDPOINT_FLAG_DESTROYABLE is not set")); /* Disable */ disable(endpoint); /* Find current endpoint */ _endpoint_t *current_endpoint = current_node->endpoint_list; _endpoint_t *previous_endpoint = NULL; while (current_endpoint) { if (current_endpoint == _endpoint) { break; } previous_endpoint = current_endpoint; current_endpoint = current_endpoint->next; } VerifyOrReturnError(current_endpoint != NULL, ESP_FAIL, ESP_LOGE(TAG, "Could not find the endpoint to delete")); /* Parse and delete all clusters */ _cluster_t *cluster = current_endpoint->cluster_list; while (cluster) { _cluster_t *next_cluster = cluster->next; cluster::destroy((cluster_t *)cluster); cluster = next_cluster; /* Move cluster_list to find the remain cluster */ current_endpoint->cluster_list = cluster; } /* Free endpoint_type after freeing cluster_list */ { /* Free all clusters in endpoint_type */ EmberAfEndpointType *endpoint_type = current_endpoint->endpoint_type; VerifyOrReturnError(endpoint_type, ESP_ERR_INVALID_STATE, ESP_LOGE(TAG, "endpoint %" PRIu16 "'s endpoint_type is NULL", current_endpoint->endpoint_id)); int cluster_count = endpoint_type->clusterCount; for (int cluster_index = 0; cluster_index < cluster_count; cluster_index++) { /* Free commands */ if (endpoint_type->cluster[cluster_index].acceptedCommandList) { esp_matter_mem_free((void *)endpoint_type->cluster[cluster_index].acceptedCommandList); } if (endpoint_type->cluster[cluster_index].generatedCommandList) { esp_matter_mem_free((void *)endpoint_type->cluster[cluster_index].generatedCommandList); } /* Free events */ if (endpoint_type->cluster[cluster_index].eventList) { esp_matter_mem_free((void *)endpoint_type->cluster[cluster_index].eventList); } } esp_matter_mem_free((void *)endpoint_type->cluster); /* Free data versions */ if (current_endpoint->data_versions_ptr) { esp_matter_mem_free(current_endpoint->data_versions_ptr); current_endpoint->data_versions_ptr = NULL; } /* Free device types */ if (current_endpoint->device_types_ptr) { esp_matter_mem_free(current_endpoint->device_types_ptr); current_endpoint->device_types_ptr = NULL; } /* Free endpoint type */ esp_matter_mem_free(endpoint_type); current_endpoint->endpoint_type = NULL; } /* Remove from list */ if (previous_endpoint == NULL) { current_node->endpoint_list = current_endpoint->next; } else { previous_endpoint->next = current_endpoint->next; } /* Free */ if (current_endpoint->identify != NULL) { chip::Platform::Delete(current_endpoint->identify); current_endpoint->identify = NULL; } esp_matter_mem_free(current_endpoint); // resize the group data provider to match the new endpoint count resize_group_data_provider(); return ESP_OK; } endpoint_t *get(node_t *node, uint16_t endpoint_id) { VerifyOrReturnValue(node, NULL, ESP_LOGE(TAG, "Node cannot be NULL")); _node_t *current_node = (_node_t *)node; _endpoint_t *current_endpoint = (_endpoint_t *)current_node->endpoint_list; while (current_endpoint) { if (current_endpoint->endpoint_id == endpoint_id) { break; } current_endpoint = current_endpoint->next; } return (endpoint_t *)current_endpoint; } endpoint_t *get(uint16_t endpoint_id) { node_t *node = node::get(); return get(node, endpoint_id); } endpoint_t *get_first(node_t *node) { VerifyOrReturnValue(node, NULL, ESP_LOGE(TAG, "Node cannot be NULL")); _node_t *current_node = (_node_t *)node; return (endpoint_t *)current_node->endpoint_list; } endpoint_t *get_next(endpoint_t *endpoint) { VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; return (endpoint_t *)current_endpoint->next; } uint16_t get_count(node_t *node) { VerifyOrReturnValue(node, 0, ESP_LOGE(TAG, "Node cannot be NULL")); uint16_t count = 0; endpoint_t *endpoint = get_first(node); while (endpoint) { count++; endpoint = get_next(endpoint); } return count; } uint16_t get_id(endpoint_t *endpoint) { VerifyOrReturnValue(endpoint, kInvalidEndpointId, ESP_LOGE(TAG, "Endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; return current_endpoint->endpoint_id; } esp_err_t add_device_type(endpoint_t *endpoint, uint32_t device_type_id, uint8_t device_type_version) { VerifyOrReturnError(endpoint, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; VerifyOrReturnError(current_endpoint->device_type_count < ESP_MATTER_MAX_DEVICE_TYPE_COUNT, ESP_FAIL, ESP_LOGE(TAG, "Could not add a new device-type:%" PRIu32 " to the endpoint", device_type_id)); current_endpoint->device_type_ids[current_endpoint->device_type_count] = device_type_id; current_endpoint->device_type_versions[current_endpoint->device_type_count] = device_type_version; current_endpoint->device_type_count++; return ESP_OK; } uint32_t *get_device_type_ids(endpoint_t *endpoint, uint8_t *device_type_count_ptr) { VerifyOrReturnValue((endpoint && device_type_count_ptr), NULL, ESP_LOGE(TAG, "Endpoint and device type count pointer cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; *device_type_count_ptr = current_endpoint->device_type_count; return current_endpoint->device_type_ids; } uint8_t *get_device_type_versions(endpoint_t *endpoint, uint8_t *device_type_count_ptr) { VerifyOrReturnValue((endpoint && device_type_count_ptr), NULL, ESP_LOGE(TAG, "Endpoint and device type count pointer cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; *device_type_count_ptr = current_endpoint->device_type_count; return current_endpoint->device_type_versions; } esp_err_t set_parent_endpoint(endpoint_t *endpoint, endpoint_t *parent_endpoint) { VerifyOrReturnError((endpoint && parent_endpoint), ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Endpoint or parent_endpoint cannot be NULL")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; _endpoint_t *current_parent_endpoint = (_endpoint_t *)parent_endpoint; current_endpoint->parent_endpoint_id = current_parent_endpoint->endpoint_id; return ESP_OK; } void *get_priv_data(uint16_t endpoint_id) { endpoint_t *endpoint = get(endpoint_id); VerifyOrReturnValue(endpoint, NULL, ESP_LOGE(TAG, "Endpoint not found")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; return current_endpoint->priv_data; } esp_err_t set_priv_data(uint16_t endpoint_id, void *priv_data) { endpoint_t *endpoint = get(endpoint_id); VerifyOrReturnError(endpoint, ESP_ERR_NOT_FOUND, ESP_LOGE(TAG, "Endpoint not found")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; current_endpoint->priv_data = priv_data; return ESP_OK; } esp_err_t set_identify(uint16_t endpoint_id, void *identify) { endpoint_t *endpoint = get(endpoint_id); VerifyOrReturnError(endpoint, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Endpoint not found")); _endpoint_t *current_endpoint = (_endpoint_t *)endpoint; current_endpoint->identify = (Identify *)identify; return ESP_OK; } } /* endpoint */ namespace node { node_t *create_raw() { VerifyOrReturnValue(!node, (node_t*) node, ESP_LOGE(TAG, "Node already exists")); node = (_node_t *)esp_matter_mem_calloc(1, sizeof(_node_t)); VerifyOrReturnValue(node, NULL, ESP_LOGE(TAG, "Couldn't allocate _node_t")); return (node_t *)node; } esp_err_t destroy_raw() { VerifyOrReturnError(node, ESP_ERR_INVALID_STATE, ESP_LOGE(TAG, "NULL node cannot be destroyed")); _node_t *current_node = (_node_t *)node; esp_matter_mem_free(current_node); node = NULL; return ESP_OK; } node_t *get() { return (node_t *)node; } esp_err_t destroy() { esp_err_t err = ESP_OK; node_t *current_node = get(); VerifyOrReturnError(current_node, ESP_ERR_INVALID_STATE, ESP_LOGE(TAG, "Node cannot be NULL")); attribute::set_callback(nullptr); identification::set_callback(nullptr); endpoint_t *current_endpoint = endpoint::get_first(current_node); endpoint_t *next_endpoint = nullptr; while (current_endpoint != nullptr) { next_endpoint = endpoint::get_next(current_endpoint); // Endpoints should have destroyable flag set to true before destroying ((_endpoint_t *)current_endpoint)->flags |= ENDPOINT_FLAG_DESTROYABLE; err = endpoint::destroy((node_t *)current_node, current_endpoint); VerifyOrDo(err == ESP_OK, ESP_LOGE(TAG, "Failed to destroy endpoint")); current_endpoint = next_endpoint; } return destroy_raw(); } // Treat 0xFFFF'FFFF as wildcard cluster static inline bool is_wildcard_cluster_id(uint32_t cluster_id) { return cluster_id == chip::kInvalidClusterId; } // Treat 0xFFFF as wildcard endpoint static inline bool is_wildcard_endpoint_id(uint16_t endpoint_id) { return endpoint_id == chip::kInvalidEndpointId; } /** * @brief Get the number of clusters that match the given flags * * @param endpoint_id: The endpoint ID to check, 0xFFFF is treated as wildcard endpoint id * @param cluster_id: The cluster ID to check, 0xFFFF is treated as wildcard cluster id * @param cluster_flags: The flags to check * @return The number of clusters that match the given flags */ static uint32_t get_cluster_count(uint32_t endpoint_id, uint32_t cluster_id, uint8_t cluster_flags) { uint32_t count = 0; node_t *node = get(); VerifyOrReturnValue(node, count, ESP_LOGE(TAG, "Node cannot be NULL")); // lambda to check if cluster matches flags and return 1 if it does, 0 otherwise auto check_cluster_flags = [cluster_flags](const endpoint_t *endpoint, const cluster_t *cluster) -> uint32_t { if (cluster && endpoint) { const _endpoint_t *_endpoint = (_endpoint_t *)endpoint; const _cluster_t *_cluster = (_cluster_t *)cluster; EmberAfClusterMask flags = _endpoint->endpoint_type->cluster[_cluster->index].mask; return (flags & cluster_flags) ? 1 : 0; } return 0; }; // lambda to count all matching clusters for an endpoint auto get_count_on_all_clusters = [&check_cluster_flags](endpoint_t *endpoint) -> uint32_t { uint32_t result = 0; if (!endpoint) return result; cluster_t *cluster = cluster::get_first(endpoint); while (cluster) { result += check_cluster_flags(endpoint, cluster); cluster = cluster::get_next(cluster); } return result; }; // lambda to find and count a specific cluster auto get_count_on_specific_cluster = [&check_cluster_flags](const endpoint_t *endpoint, uint32_t cluster_id) -> uint32_t { if (!endpoint) return 0; cluster_t *cluster = cluster::get((endpoint_t *)endpoint, cluster_id); return check_cluster_flags(endpoint, cluster); }; // Case 1: Wildcard endpoint if (is_wildcard_endpoint_id(endpoint_id)) { endpoint_t *endpoint = endpoint::get_first(node); while (endpoint) { // Case 1.1: Wildcard cluster - count all clusters with matching flags if (is_wildcard_cluster_id(cluster_id)) { count += get_count_on_all_clusters(endpoint); } // Case 1.2: Specific cluster - count if it exists and has matching flags else { count += get_count_on_specific_cluster(endpoint, cluster_id); } endpoint = endpoint::get_next(endpoint); } } // Case 2: Specific endpoint else { endpoint_t *endpoint = endpoint::get(endpoint_id); if (!endpoint) { return count; } // Case 2.1: Wildcard cluster - count all clusters with matching flags if (is_wildcard_cluster_id(cluster_id)) { count += get_count_on_all_clusters(endpoint); } // Case 2.2: Specific cluster - count if it exists and has matching flags else { count += get_count_on_specific_cluster(endpoint, cluster_id); } } return count; } uint32_t get_server_cluster_endpoint_count(uint32_t cluster_id) { return get_cluster_count(chip::kInvalidEndpointId, cluster_id, CLUSTER_FLAG_SERVER); } uint32_t get_client_cluster_endpoint_count(uint32_t cluster_id) { return get_cluster_count(chip::kInvalidEndpointId, cluster_id, CLUSTER_FLAG_CLIENT); } } /* node */ } /* esp_matter */