From a87107fb152eb5f17ff9188b53f36b84460a119a Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Tue, 25 Nov 2025 18:55:15 +0530 Subject: [PATCH] components/esp_matter: extend set val for internally managed writable attributes - re-implemented the set_val to set them using the TLV buffer for an attribute using DataModelProvider::WriteAttribute() API. - renamed older set_val() to set_val_internal() and made it private. - changed the set_val's occurances with set_val_internal inside the component. Since our sdk should not be worrying about getting data from the internally managed attributes, its safe to use the set_val_internal(). - updated release notes --- RELEASE_NOTES.md | 15 ++ .../data_model/esp_matter_attribute_utils.cpp | 5 +- .../data_model/esp_matter_attribute_utils.h | 1 + .../data_model/esp_matter_data_model.cpp | 142 +++++++++++++++--- .../data_model/esp_matter_data_model.h | 30 +++- .../data_model/esp_matter_feature.cpp | 12 +- .../private/esp_matter_data_model_priv.h | 11 ++ .../esp_matter_ember_stubs.cpp | 2 +- 8 files changed, 185 insertions(+), 33 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7fba49205..fd6099385 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,18 @@ +# 27-Nov-2025 +### Feature: attribute::get_val() and attribute::set_val() APIs + +- `attribute::get_val()` + - API now supports getting the value of internally managed attributes of primitive data types. + - Complex types are not supported yet, eg: (Array, Struct, List, etc.) + +- `attribute::set_val()` + - API now supports setting the value of internally managed attributes which are writable and of data type primitive. + - Attributes which are not writable but managed internally are not supported yet + - Complex types are not supported yet, eg: (Array, Struct, List, etc.) + +- For unsupported attributes, the APIs will return `ESP_ERR_NOT_SUPPORTED`, + please use the cluster-specific setter APIs. + ## 21-Nov-2025 ### API Changes: Attribute Creation Function Names diff --git a/components/esp_matter/data_model/esp_matter_attribute_utils.cpp b/components/esp_matter/data_model/esp_matter_attribute_utils.cpp index 9e5fb3a15..219ffd11e 100644 --- a/components/esp_matter/data_model/esp_matter_attribute_utils.cpp +++ b/components/esp_matter/data_model/esp_matter_attribute_utils.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -641,7 +642,7 @@ esp_err_t update(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_i /* Here, the val_print function gets called on attribute write.*/ attribute::val_print(endpoint_id, cluster_id, attribute_id, val, false); - esp_err_t err = attribute::set_val(attr, val); + esp_err_t err = attribute::set_val_internal(attr, val); if (err == ESP_OK) { data_model::provider::get_instance().Temporary_ReportAttributeChanged( chip::app::AttributePathParams(endpoint_id, cluster_id, attribute_id)); @@ -667,7 +668,7 @@ esp_err_t report(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_i /* Here, the val_print function gets called on attribute write.*/ attribute::val_print(endpoint_id, cluster_id, attribute_id, val, false); - esp_err_t err = attribute::set_val(attr, val, false); + esp_err_t err = attribute::set_val_internal(attr, val, false); if (err == ESP_OK) { /* Report attribute */ MatterReportingAttributeChangeCallback(endpoint_id, cluster_id, attribute_id); diff --git a/components/esp_matter/data_model/esp_matter_attribute_utils.h b/components/esp_matter/data_model/esp_matter_attribute_utils.h index 0a7647bb4..684f51ba6 100644 --- a/components/esp_matter/data_model/esp_matter_attribute_utils.h +++ b/components/esp_matter/data_model/esp_matter_attribute_utils.h @@ -412,6 +412,7 @@ esp_err_t update(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_i * This API reports the attribute value. * After this API is called, the application doesn't gets the attribute update callback with `PRE_UPDATE` or `POST_UPDATE`, the * attribute is updated in the database. + * This also marks the attribute as dirty, so that it can be reported in the next subscription report. * * @param[in] endpoint_id Endpoint ID of the attribute. * @param[in] cluster_id Cluster ID of the attribute. diff --git a/components/esp_matter/data_model/esp_matter_data_model.cpp b/components/esp_matter/data_model/esp_matter_data_model.cpp index 6f10cfc2a..0e018520f 100644 --- a/components/esp_matter/data_model/esp_matter_data_model.cpp +++ b/components/esp_matter/data_model/esp_matter_data_model.cpp @@ -147,6 +147,7 @@ inline bool is_wildcard_endpoint_id(uint16_t endpoint_id) // We allocate this large scoped buffer to read the attribute as the TLV data // This is enough to store the TLV encoded buffer for a primitive type of data const uint32_t k_max_tlv_size_to_read_attribute_value = 512; +const uint32_t k_max_tlv_size_to_write_attribute_value = 512; } // namespace @@ -544,7 +545,7 @@ attribute_t *create(cluster_t *cluster, uint32_t attribute_id, uint16_t flags, e } } if (!attribute_updated) { - set_val((attribute_t *)attribute, &val); + set_val_internal((attribute_t *)attribute, &val, false); } } @@ -638,13 +639,14 @@ static void deferred_attribute_write(chip::System::Layer *layer, void *attribute {current_attribute->attribute_val_type, current_attribute->attribute_val}); } -esp_err_t set_val(attribute_t *attribute, esp_matter_attr_val_t *val, bool call_callbacks) +esp_err_t set_val_internal(attribute_t *attribute, esp_matter_attr_val_t *val, bool call_callbacks) { - VerifyOrReturnError(attribute && val, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Attribute and val cannot be NULL")); + VerifyOrReturnError(attribute && val, ESP_ERR_INVALID_ARG); _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"); + VerifyOrReturnError(current_attribute->attribute_val_type == val->type, ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Different value type : Expected Type : %u Attempted Type: %u", current_attribute->attribute_val_type, val->type)); @@ -817,7 +819,7 @@ static esp_err_t get_val_from_tlv_data(const uint8_t *tlv_data, uint32_t tlv_len } // Helper function to find the cluster_id and attribute_id for internally managed attributes -static esp_err_t find_cluster_and_endpoint_id_for_internally_managed_attribute(_attribute_base_t *attribute_base, +static esp_err_t find_cluster_and_endpoint_id_for_internally_managed_attribute(const _attribute_base_t *attribute_base, uint16_t &out_endpoint_id, uint32_t &out_cluster_id) { @@ -883,31 +885,32 @@ esp_err_t get_val(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_ return get_val_from_tlv_data(scoped_buf.Get(), writer.GetLengthWritten(), val); } +static esp_err_t get_path_from_attribute_handle(const _attribute_t *attribute, uint16_t &endpoint_id, + uint32_t &cluster_id, uint32_t &attribute_id) +{ + attribute_id = attribute->attribute_id; + if (attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY) { + // for connectedhomeip managed attributes, we need to find the cluster and endpoint id + return find_cluster_and_endpoint_id_for_internally_managed_attribute(attribute, endpoint_id, cluster_id); + } + + // in case of esp-matter managed attributes, we can directly use the endpoint and cluster id + endpoint_id = attribute->endpoint_id; + cluster_id = attribute->cluster_id; + return ESP_OK; +} + esp_err_t get_val(attribute_t *attribute, esp_matter_attr_val_t *val) { VerifyOrReturnError(attribute && val, ESP_ERR_INVALID_ARG); _attribute_t *current_attribute = (_attribute_t *)attribute; - uint32_t attribute_id = current_attribute->attribute_id; - uint32_t cluster_id = chip::kInvalidClusterId; uint16_t endpoint_id = chip::kInvalidEndpointId; + uint32_t cluster_id = chip::kInvalidClusterId; + uint32_t attribute_id = chip::kInvalidAttributeId; - if (current_attribute->flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY) { - // for internally managed attributes, we need to find the cluster and endpoint id - esp_err_t err = find_cluster_and_endpoint_id_for_internally_managed_attribute(current_attribute, - endpoint_id, cluster_id); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to get cluster and ep id for attribute, err: %d", err); - return err; - } - - cluster_id = cluster_id; - attribute_id = attribute_id; - } else { - // in case of externally managed attributes, we can directly use the endpoint and cluster id - endpoint_id = current_attribute->endpoint_id; - cluster_id = current_attribute->cluster_id; - } + esp_err_t err = get_path_from_attribute_handle(current_attribute, endpoint_id, cluster_id, attribute_id); + VerifyOrReturnError(err == ESP_OK, err); return get_val(endpoint_id, cluster_id, attribute_id, val); } @@ -936,6 +939,101 @@ esp_matter_val_type_t get_val_type(uint16_t endpoint_id, uint32_t cluster_id, ui return get_val_type(get(endpoint_id, cluster_id, attribute_id)); } +// Helper function: Set value via WriteAttribute with kInternal flag (for SCI/AAI writable attributes) +static esp_err_t set_val_via_write_attribute(uint16_t endpoint_id, uint32_t cluster_id, + uint32_t attribute_id, esp_matter_attr_val_t *val) +{ + chip::Platform::ScopedMemoryBuffer tlv_buffer; + tlv_buffer.Calloc(k_max_tlv_size_to_write_attribute_value); + VerifyOrReturnError(!tlv_buffer.IsNull(), ESP_ERR_NO_MEM); + + chip::TLV::TLVWriter writer; + writer.Init(tlv_buffer.Get(), k_max_tlv_size_to_write_attribute_value); + + // Encoding is within a structure: + // - BEGIN_STRUCT + // - 1: + // - END_STRUCT + chip::TLV::TLVType outerContainer; + VerifyOrReturnError(writer.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, outerContainer) == CHIP_NO_ERROR, ESP_FAIL); + data_model::attribute_data_encode_buffer encode_buffer(*val); + VerifyOrReturnError(encode_buffer.Encode(writer, chip::TLV::ContextTag(1)) == CHIP_NO_ERROR, ESP_FAIL); + VerifyOrReturnError(writer.EndContainer(outerContainer) == CHIP_NO_ERROR, ESP_FAIL); + VerifyOrReturnError(writer.Finalize() == CHIP_NO_ERROR, ESP_FAIL); + + // position the reader inside the buffer, on the encoded value + chip::TLV::TLVReader reader; + reader.Init(tlv_buffer.Get(), writer.GetLengthWritten()); + VerifyOrReturnError(reader.Next() == CHIP_NO_ERROR, ESP_FAIL); + VerifyOrReturnError(reader.EnterContainer(outerContainer) == CHIP_NO_ERROR, ESP_FAIL); + VerifyOrReturnError(reader.Next() == CHIP_NO_ERROR, ESP_FAIL); + + // create write request and call WriteAttribute through the provider + chip::app::DataModel::WriteAttributeRequest request; + request.path = chip::app::ConcreteDataAttributePath(endpoint_id, cluster_id, attribute_id); + request.operationFlags.Set(chip::app::DataModel::OperationFlags::kInternal); + + chip::Access::SubjectDescriptor subjectDescriptor; + chip::app::AttributeValueDecoder decoder(reader, subjectDescriptor); + + // we need to ensure locks are in place to avoid assertion errors + esp_matter::lock::chip_stack_lock(portMAX_DELAY); + + chip::app::DataModel::ActionReturnStatus status = + esp_matter::data_model::provider::get_instance().WriteAttribute(request, decoder); + + esp_matter::lock::chip_stack_unlock(); + + if (status.IsError()) { + chip::app::DataModel::ActionReturnStatus::StringStorage storage; + ESP_LOGE(TAG, "WriteAttribute failed with status: %s", status.c_str(storage)); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t set_val(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, esp_matter_attr_val_t *val, bool call_callbacks) +{ + VerifyOrReturnError(val && val->type != ESP_MATTER_VAL_TYPE_INVALID, ESP_ERR_INVALID_ARG); + VerifyOrReturnError(val->type != ESP_MATTER_VAL_TYPE_ARRAY, ESP_ERR_NOT_SUPPORTED); + + attribute_t *attr = get(endpoint_id, cluster_id, attribute_id); + VerifyOrReturnError(attr, ESP_ERR_NOT_FOUND); + VerifyOrReturnError(get_val_type(attr) == val->type, ESP_ERR_INVALID_ARG); + + uint16_t flags = get_flags(attr); + + if (!(flags & ATTRIBUTE_FLAG_MANAGED_INTERNALLY)) { + // this updates the value of attribute in the esp-matter storage + return attribute::set_val_internal(attr, val, call_callbacks); + } + + // we can use DataModelProvider::WriteAttribute API for writable attributes + if (flags & ATTRIBUTE_FLAG_WRITABLE) { + return set_val_via_write_attribute(endpoint_id, cluster_id, attribute_id, val); + } + + // TODO: If not writable, we could use the cluster-specific setter API to update the value + // with the code-driven effort, we can get the cluster object and call the setter API + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t set_val(attribute_t *attribute, esp_matter_attr_val_t *val, bool call_callbacks) +{ + VerifyOrReturnError(attribute && val, ESP_ERR_INVALID_ARG); + _attribute_t *current_attribute = (_attribute_t *)attribute; + + uint16_t endpoint_id = chip::kInvalidEndpointId; + uint32_t cluster_id = chip::kInvalidClusterId; + uint32_t attribute_id = chip::kInvalidAttributeId; + + esp_err_t err = get_path_from_attribute_handle(current_attribute, endpoint_id, cluster_id, attribute_id); + VerifyOrReturnError(err == ESP_OK, err); + + return set_val(endpoint_id, cluster_id, attribute_id, val, call_callbacks); +} + 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")); diff --git a/components/esp_matter/data_model/esp_matter_data_model.h b/components/esp_matter/data_model/esp_matter_data_model.h index 1dd161d2c..32cfc3d5c 100644 --- a/components/esp_matter/data_model/esp_matter_data_model.h +++ b/components/esp_matter/data_model/esp_matter_data_model.h @@ -884,11 +884,37 @@ attribute_t *get_next(attribute_t *attribute); */ uint32_t get_id(attribute_t *attribute); -/** Set attribute val +/** Set attribute value * - * Set/Update the value of the attribute (has `ATTRIBUTE_FLAG_EXTERNAL_STORAGE` flag) in the database. + * This API is a unified API that works across all storage types. + * It supports writing attributes whose value storage is managed by esp matter data model + * as well as writable attributes that are not managed by esp matter data model. + * Right now this only works for primitive types and strings. + * + * TODO: support writing complex types (arrays, structs, lists) + * TODO: support remaining attributes by using cluster-specific setter APIs + * + * This API uses the DataModelProvider::WriteAttribute API to write the value of the attribute, + * tries to write value to the supported storages. + * + * @note: For unsupported cases, this API will return ESP_ERR_NOT_SUPPORTED, please use + * cluster-specific setter APIs for complex types (arrays, structs, lists) and remaining attributes. * * @note: Once `esp_matter::start()` is done, `attribute::update()` should be used to update the attribute value. + * + * @param[in] endpoint_id Endpoint id. + * @param[in] cluster_id Cluster id. + * @param[in] attribute_id Attribute id. + * @param[in] val Pointer to `esp_matter_attr_val_t` containing the new value. + * @param[in] call_callbacks Whether to call attribute change pre/post callbacks. + * + * @return ESP_OK on success. + * @return error in case of failure. + */ +esp_err_t set_val(uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id, + esp_matter_attr_val_t *val, bool call_callbacks = true); + +/** Set attribute val, similar to set_val but with attribute handle * * @param[in] attribute Attribute handle. * @param[in] val Pointer to `esp_matter_attr_val_t`. Use appropriate elements as per the value type. diff --git a/components/esp_matter/data_model/esp_matter_feature.cpp b/components/esp_matter/data_model/esp_matter_feature.cpp index e237a5d3b..b7bbe912f 100644 --- a/components/esp_matter/data_model/esp_matter_feature.cpp +++ b/components/esp_matter/data_model/esp_matter_feature.cpp @@ -42,8 +42,8 @@ static esp_err_t update_feature_map(cluster_t *cluster, uint32_t value) VerifyOrReturnError(attribute::get_val_internal(attribute, &val) == ESP_OK, ESP_FAIL); val.val.u32 |= value; /* Here we can't call attribute::update() since the chip stack would not have started yet, since we are - still creating the data model. So, we are directly using attribute::set_val(). */ - return attribute::set_val(attribute, &val); + still creating the data model. So, we are directly using attribute::set_val_internal(). */ + return attribute::set_val_internal(attribute, &val, false); } static uint32_t get_feature_map_value(cluster_t *cluster) @@ -608,8 +608,8 @@ static esp_err_t update_color_capability(cluster_t *cluster, uint16_t value) VerifyOrReturnError(esp_matter::attribute::get_val_internal(attribute, &val) == ESP_OK, ESP_FAIL); val.val.u16 |= value; /* Here we can't call attribute::update() since the chip stack would not have started yet, since we are - still creating the data model. So, we are directly using attribute::set_val(). */ - return esp_matter::attribute::set_val(attribute, &val); + still creating the data model. So, we are directly using attribute::set_val_internal(). */ + return esp_matter::attribute::set_val_internal(attribute, &val, false); } namespace hue_saturation { @@ -827,7 +827,7 @@ esp_err_t add(cluster_t *cluster, config_t *config) esp_matter_attr_val_t val = esp_matter_invalid(NULL); VerifyOrReturnError(esp_matter::attribute::get_val_internal(attribute, &val) == ESP_OK, ESP_FAIL); val.val.u8 = val.val.u8 | set_third_bit; - return esp_matter::attribute::set_val(attribute, &val); + return esp_matter::attribute::set_val_internal(attribute, &val, false); } ESP_LOGE(TAG, "Cluster shall support Lift feature"); @@ -917,7 +917,7 @@ esp_err_t add(cluster_t *cluster, config_t *config) esp_matter_attr_val_t val = esp_matter_invalid(NULL); VerifyOrReturnError(esp_matter::attribute::get_val_internal(attribute, &val) == ESP_OK, ESP_FAIL); val.val.u8 = val.val.u8 | set_fourth_bit; - return esp_matter::attribute::set_val(attribute, &val); + return esp_matter::attribute::set_val_internal(attribute, &val, false); } ESP_LOGE(TAG, "Cluster shall support Tilt feature"); diff --git a/components/esp_matter/data_model/private/esp_matter_data_model_priv.h b/components/esp_matter/data_model/private/esp_matter_data_model_priv.h index e50c6cb9e..0abf39379 100644 --- a/components/esp_matter/data_model/private/esp_matter_data_model_priv.h +++ b/components/esp_matter/data_model/private/esp_matter_data_model_priv.h @@ -52,6 +52,17 @@ namespace attribute { */ esp_err_t get_val_internal(attribute_t *attribute, esp_matter_attr_val_t *val); +/** Set the attribute value in the esp-matter storage + * + * @param[in] attribute Attribute handle. + * @param[in] val Pointer to `esp_matter_attr_val_t`. Use appropriate elements as per the value type. + * @param[in] call_callbacks Whether to call attribute change pre/post callbacks. + * + * @return ESP_OK on success. + * @return error in case of failure. + */ +esp_err_t set_val_internal(attribute_t *attribute, esp_matter_attr_val_t *val, bool call_callbacks = true); + } // namespace attribute } // namespace esp_matter diff --git a/components/esp_matter/data_model_provider/esp_matter_ember_stubs.cpp b/components/esp_matter/data_model_provider/esp_matter_ember_stubs.cpp index a00233b90..b0abf29ce 100644 --- a/components/esp_matter/data_model_provider/esp_matter_ember_stubs.cpp +++ b/components/esp_matter/data_model_provider/esp_matter_ember_stubs.cpp @@ -1048,7 +1048,7 @@ Status emberAfWriteAttribute(const chip::app::ConcreteAttributePath &path, const if (status != Status::Success) { return status; } - esp_err_t err = esp_matter::attribute::set_val(attribute, &val); + esp_err_t err = esp_matter::attribute::set_val_internal(attribute, &val); if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) { status = Status::Failure; }