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
This commit is contained in:
Shubham Patil
2025-11-25 18:55:15 +05:30
parent e322e230df
commit a87107fb15
8 changed files with 185 additions and 33 deletions
+15
View File
@@ -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
@@ -24,6 +24,7 @@
#include <esp_matter_console.h>
#include <esp_matter_core.h>
#include <esp_matter_mem.h>
#include <esp_matter_data_model_priv.h>
#include <string.h>
#include <app/util/attribute-storage.h>
@@ -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);
@@ -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.
@@ -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<uint8_t> 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: <encoded value>
// - 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"));
@@ -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.
@@ -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");
@@ -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
@@ -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;
}