Merge branch 'backport/v1.5/cluster-pre-attr-change-cb' into 'release/v1.5'

[v1.5]: esp_matter/data_model_provider: Add support to call ClusterPreAttributeChangeCallback from esp-matter data model provider.

See merge request app-frameworks/esp-matter!1476
This commit is contained in:
Shu Chen
2026-03-26 04:03:53 +00:00
3 changed files with 290 additions and 15 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
[codespell]
ignore-regex = _
ignore-words-list = ot, bootup, requestor, pase, lits
ignore-words-list = ot, bootup, requestor, pase, lits, kNo
@@ -13,12 +13,12 @@
// limitations under the License.
#include <esp_log.h>
#include <esp_matter_attr_data_buffer.h>
#include <esp_matter_attribute_utils.h>
#include <esp_matter_cluster.h>
#include <esp_matter_data_model.h>
#include <esp_matter_data_model_priv.h>
#include <esp_matter_data_model_provider.h>
#include <esp_matter_attr_data_buffer.h>
#include <access/Privilege.h>
#include <app-common/zap-generated/cluster-objects.h>
@@ -27,6 +27,8 @@
#include <app/CommandHandlerInterface.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/util/attribute-storage.h>
#include <app/util/attribute-table.h>
#include <clusters/shared/GlobalIds.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
@@ -34,6 +36,8 @@
#include <zap-generated/access.h>
#include <app/persistence/AttributePersistenceProviderInstance.h>
#include <app/persistence/DefaultAttributePersistenceProvider.h>
#include <cstring>
#include <vector>
using namespace chip;
using namespace chip::app;
@@ -41,6 +45,206 @@ using namespace chip::app;
constexpr char TAG[] = "DataModelProvider";
namespace {
chip::Protocols::InteractionModel::Status BuildEmberBufferFromAttrVal(const esp_matter_attr_val_t &val,
uint8_t *dataPtr, uint16_t bufferSize)
{
switch (val.type) {
case ESP_MATTER_VAL_TYPE_BOOLEAN:
case ESP_MATTER_VAL_TYPE_NULLABLE_BOOLEAN: {
if (bufferSize < sizeof(bool) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<bool>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(*(uint8_t *)(&(val.val.b)))) {
Traits::SetNull(*(uint8_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.b, *dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_FLOAT:
case ESP_MATTER_VAL_TYPE_NULLABLE_FLOAT: {
if (bufferSize < sizeof(float) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<float>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.f)) {
Traits::SetNull(*(float *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.f, *(float *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_OCTET_STRING:
case ESP_MATTER_VAL_TYPE_CHAR_STRING: {
if (bufferSize < val.val.a.t || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
uint8_t len = val.val.a.s;
size_t header_size = sizeof(len);
if (bufferSize < header_size) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
memcpy(dataPtr, &len, header_size);
// UINT8_MAX is reserved for null value
if (len < UINT8_MAX) {
size_t max_payload = bufferSize - header_size;
size_t copy_len = (static_cast<size_t>(len) <= max_payload) ? static_cast<size_t>(len) : max_payload;
memcpy(dataPtr + header_size, val.val.a.b, copy_len);
}
break;
}
case ESP_MATTER_VAL_TYPE_LONG_OCTET_STRING:
case ESP_MATTER_VAL_TYPE_LONG_CHAR_STRING: {
if (bufferSize < val.val.a.t || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
size_t header_size = sizeof(val.val.a.s);
if (bufferSize < header_size) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
memcpy(dataPtr, &val.val.a.s, header_size);
// UINT16_MAX is reserved for null value
if (val.val.a.s < UINT16_MAX) {
size_t max_payload = bufferSize - header_size;
size_t copy_len = (static_cast<size_t>(val.val.a.s) <= max_payload)
? static_cast<size_t>(val.val.a.s)
: max_payload;
memcpy(dataPtr + header_size, val.val.a.b, copy_len);
}
break;
}
case ESP_MATTER_VAL_TYPE_INT8:
case ESP_MATTER_VAL_TYPE_NULLABLE_INT8: {
if (bufferSize < sizeof(int8_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<int8_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.i8)) {
Traits::SetNull(*(int8_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.i8, *(int8_t *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_BITMAP8:
case ESP_MATTER_VAL_TYPE_NULLABLE_BITMAP8:
case ESP_MATTER_VAL_TYPE_ENUM8:
case ESP_MATTER_VAL_TYPE_NULLABLE_ENUM8:
case ESP_MATTER_VAL_TYPE_UINT8:
case ESP_MATTER_VAL_TYPE_NULLABLE_UINT8: {
if (bufferSize < sizeof(uint8_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<uint8_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.u8)) {
Traits::SetNull(*dataPtr);
} else {
Traits::WorkingToStorage(val.val.u8, *dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_INT16:
case ESP_MATTER_VAL_TYPE_NULLABLE_INT16: {
if (bufferSize < sizeof(int16_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<int16_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.i16)) {
Traits::SetNull(*(int16_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.i16, *(int16_t *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_BITMAP16:
case ESP_MATTER_VAL_TYPE_NULLABLE_BITMAP16:
case ESP_MATTER_VAL_TYPE_ENUM16:
case ESP_MATTER_VAL_TYPE_NULLABLE_ENUM16:
case ESP_MATTER_VAL_TYPE_UINT16:
case ESP_MATTER_VAL_TYPE_NULLABLE_UINT16: {
if (bufferSize < sizeof(uint16_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<uint16_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.u16)) {
Traits::SetNull(*(uint16_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.u16, *(uint16_t *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_INT32:
case ESP_MATTER_VAL_TYPE_NULLABLE_INT32: {
if (bufferSize < sizeof(int32_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<int32_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.i32)) {
Traits::SetNull(*(int32_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.i32, *(int32_t *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_BITMAP32:
case ESP_MATTER_VAL_TYPE_NULLABLE_BITMAP32:
case ESP_MATTER_VAL_TYPE_UINT32:
case ESP_MATTER_VAL_TYPE_NULLABLE_UINT32: {
if (bufferSize < sizeof(uint32_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<uint32_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.u32)) {
Traits::SetNull(*(uint32_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.u32, *(uint32_t *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_INT64:
case ESP_MATTER_VAL_TYPE_NULLABLE_INT64: {
if (bufferSize < sizeof(int64_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<int64_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.i64)) {
Traits::SetNull(*(int64_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.i64, *(int64_t *)dataPtr);
}
break;
}
case ESP_MATTER_VAL_TYPE_UINT64:
case ESP_MATTER_VAL_TYPE_NULLABLE_UINT64: {
if (bufferSize < sizeof(uint64_t) || !dataPtr) {
return chip::Protocols::InteractionModel::Status::ResourceExhausted;
}
using Traits = chip::app::NumericAttributeTraits<uint64_t>;
if ((val.type & ESP_MATTER_VAL_NULLABLE_BASE) && Traits::IsNullValue(val.val.u64)) {
Traits::SetNull(*(uint64_t *)dataPtr);
} else {
Traits::WorkingToStorage(val.val.u64, *(uint64_t *)dataPtr);
}
break;
}
default:
return chip::Protocols::InteractionModel::Status::InvalidDataType;
}
return chip::Protocols::InteractionModel::Status::Success;
}
/// Attempts to read via an attribute access interface (AAI)
///
/// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success).
@@ -305,17 +509,32 @@ ActionReturnStatus provider::WriteAttribute(const WriteAttributeRequest &request
return *aai_result;
}
const EmberAfAttributeMetadata *metadata =
emberAfLocateAttributeMetadata(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId);
VerifyOrReturnValue(metadata != nullptr, Protocols::InteractionModel::Status::Failure);
esp_matter_attr_val_t val = esp_matter_invalid(nullptr);
VerifyOrReturnValue(attribute::get_val_internal(attribute, &val) == ESP_OK, Protocols::InteractionModel::Status::Failure);
attribute_data_decode_buffer data_buffer(val.type);
ReturnErrorOnFailure(decoder.Decode(data_buffer));
esp_err_t err =
attribute::update(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId, &data_buffer.get_attr_val());
if (err == ESP_ERR_NO_MEM) {
return Protocols::InteractionModel::Status::ResourceExhausted;
} else if (err != ESP_OK) {
return Protocols::InteractionModel::Status::Failure;
VerifyOrReturnValue(attribute::get_val_internal(attribute, &val) == ESP_OK,
Protocols::InteractionModel::Status::Failure);
attribute_data_decode_buffer decoded_buffer(val.type);
ReturnErrorOnFailure(decoder.Decode(decoded_buffer));
std::vector<uint8_t> data_buff(metadata->size);
Status buffer_status =
BuildEmberBufferFromAttrVal(decoded_buffer.get_attr_val(), data_buff.data(),
static_cast<uint16_t>(data_buff.size()));
if (buffer_status != Protocols::InteractionModel::Status::Success) {
return buffer_status;
}
EmberAfWriteDataInput data_input(data_buff.data(), metadata->attributeType);
data_input.SetChangeListener(&mContext->dataModelChangeListener);
status = emberAfWriteAttribute(request.path, data_input);
if (status != Protocols::InteractionModel::Status::Success) {
return status;
}
// Increase data version and mark dirty
cluster::increase_data_version(cluster);
return Protocols::InteractionModel::Status::Success;
}
@@ -1019,6 +1019,33 @@ chip::Protocols::InteractionModel::Status emberAfReadAttribute(chip::EndpointId
return get_raw_data_buffer_from_attr_val(val, dataPtr, readLength);
}
chip::Protocols::InteractionModel::Status __attribute__((weak))
MatterPreAttributeChangeCallback(const chip::app::ConcreteAttributePath &attributePath, uint8_t type, uint16_t size,
uint8_t * value)
{
return chip::Protocols::InteractionModel::Status::Success;
}
void __attribute__((weak))
MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath &attributePath, uint8_t type, uint16_t size,
uint8_t * value)
{}
// This function is used to call the per-cluster pre-attribute changed callback
Status emAfClusterPreAttributeChangedCallback(const chip::app::ConcreteAttributePath &attributePath, esp_matter::cluster_t *cluster, EmberAfAttributeType attributeType,
uint16_t size, uint8_t * value)
{
Status status = Status::Success;
esp_matter::cluster::function_pre_attribute_change_t f =
(esp_matter::cluster::function_pre_attribute_change_t)esp_matter::cluster::get_function(
cluster, esp_matter::CLUSTER_FLAG_PRE_ATTRIBUTE_CHANGED_FUNCTION);
if (f != nullptr) {
status = f(attributePath, attributeType, size, value);
}
return status;
}
Status emberAfWriteAttribute(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId,
uint8_t *value, EmberAfAttributeType dataType)
{
@@ -1041,13 +1068,38 @@ Status emberAfWriteAttribute(const chip::app::ConcreteAttributePath &path, const
if (!attribute) {
return chip::Protocols::InteractionModel::Status::UnsupportedAttribute;
}
esp_matter_attr_val_t val = esp_matter_invalid(nullptr);
Status status = get_attr_val_from_raw_data_buffer(input.dataPtr, input.dataType, val,
esp_matter::attribute::get_flags(attribute) &
esp_matter::ATTRIBUTE_FLAG_NULLABLE);
const chip::app::ConcreteAttributePath attributePath(path.mEndpointId, path.mClusterId, path.mAttributeId);
const EmberAfAttributeMetadata *metadata = emberAfLocateAttributeMetadata(path.mEndpointId, path.mClusterId, path.mAttributeId);
if (!metadata) {
return chip::Protocols::InteractionModel::Status::UnsupportedAttribute;
}
// Pre write attribute callback for all attribute changes, regardless of cluster.
Status status = MatterPreAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
if (status != Status::Success) {
return status;
}
// Pre-write attribute callback specific to the cluster that the attribute lives in.
status = emAfClusterPreAttributeChangedCallback(attributePath, cluster, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
// Ignore the following write operation and return success
if (status == Status::WriteIgnored) {
return Status::Success;
}
if (status != Status::Success) {
return status;
}
esp_matter_attr_val_t val = esp_matter_invalid(nullptr);
status = get_attr_val_from_raw_data_buffer(input.dataPtr, input.dataType, val,
esp_matter::attribute::get_flags(attribute) &
esp_matter::ATTRIBUTE_FLAG_NULLABLE);
if (status != Status::Success) {
return status;
}
esp_err_t err = esp_matter::attribute::set_val_internal(attribute, &val);
if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) {
status = Status::Failure;
@@ -1064,6 +1116,10 @@ Status emberAfWriteAttribute(const chip::app::ConcreteAttributePath &path, const
}
}
}
// Post write attribute callback for all attributes changes, regardless of cluster.
MatterPostAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
return status;
}