Skip to content

Commit 3f65036

Browse files
committed
Merge branch 'attribute_update_cb' into 'main'
components/esp_matter: Add attribute update callback in case of attribute managed by SCI and AAI. See merge request app-frameworks/esp-matter!1371
2 parents be6172f + 651c1c6 commit 3f65036

File tree

3 files changed

+89
-11
lines changed

3 files changed

+89
-11
lines changed

components/esp_matter/data_model/esp_matter_data_model.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ esp_err_t set_callback(callback_t callback)
293293
return ESP_OK;
294294
}
295295

296-
static esp_err_t execute_callback(callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id,
297-
uint32_t attribute_id, esp_matter_attr_val_t *val)
296+
esp_err_t execute_callback(callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id,
297+
uint32_t attribute_id, esp_matter_attr_val_t *val)
298298
{
299299
if (attribute_callback) {
300300
#ifdef CONFIG_ESP_MATTER_ENABLE_DATA_MODEL

components/esp_matter/data_model/private/esp_matter_data_model_priv.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ namespace attribute {
6161
*/
6262
esp_err_t get_val_internal(attribute_t *attribute, esp_matter_attr_val_t *val);
6363

64+
/** Execute the attribute update callback
65+
*
66+
* This function executes the attribute update callback set via set_callback().
67+
* This is used internally to notify the application about attribute changes.
68+
*
69+
* @param[in] type Callback type (PRE_UPDATE or POST_UPDATE).
70+
* @param[in] endpoint_id Endpoint ID of the attribute.
71+
* @param[in] cluster_id Cluster ID of the attribute.
72+
* @param[in] attribute_id Attribute ID of the attribute.
73+
* @param[in] val Pointer to the attribute value.
74+
*
75+
* @return ESP_OK on success.
76+
* @return error in case of failure.
77+
*/
78+
esp_err_t execute_callback(callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id,
79+
uint32_t attribute_id, esp_matter_attr_val_t *val);
80+
6481
/** Set the attribute value in the esp-matter storage
6582
*
6683
* @param[in] attribute Attribute handle.

components/esp_matter/data_model_provider/esp_matter_data_model_provider.cpp

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ using namespace chip::app;
4040

4141
constexpr char TAG[] = "DataModelProvider";
4242

43+
namespace chip {
44+
namespace app {
45+
46+
/**
47+
* Accessor class for AttributeValueDecoder to get the TLV reader.
48+
* This leverages the existing friend declaration in the SDK's AttributeValueDecoder.
49+
*/
50+
class TestOnlyAttributeValueDecoderAccessor
51+
{
52+
public:
53+
TestOnlyAttributeValueDecoderAccessor(AttributeValueDecoder & decoder) : mDecoder(decoder) {}
54+
55+
const TLV::TLVReader & GetReader() { return mDecoder.mReader; }
56+
57+
private:
58+
AttributeValueDecoder & mDecoder;
59+
};
60+
61+
} // namespace app
62+
} // namespace chip
63+
4364
namespace {
4465
/// Attempts to read via an attribute access interface (AAI)
4566
///
@@ -299,14 +320,51 @@ ActionReturnStatus provider::ReadAttribute(const ReadAttributeRequest &request,
299320

300321
ActionReturnStatus provider::WriteAttribute(const WriteAttributeRequest &request, AttributeValueDecoder &decoder)
301322
{
323+
attribute_t *attribute = attribute::get(request.path.mEndpointId, request.path.mClusterId,
324+
request.path.mAttributeId);
325+
326+
// Decode the new value once - copy TLV reader to leave original decoder intact for mRegistry/AAI
327+
esp_matter_attr_val_t new_val = esp_matter_invalid(nullptr);
328+
329+
VerifyOrReturnValue(attribute, Protocols::InteractionModel::Status::UnsupportedAttribute);
330+
331+
esp_matter_val_type_t current_val_type = attribute::get_val_type(attribute);
332+
333+
TestOnlyAttributeValueDecoderAccessor accessor(decoder);
334+
TLV::TLVReader reader_copy;
335+
reader_copy.Init(accessor.GetReader());
336+
AttributeValueDecoder decoder_copy(reader_copy, decoder.GetSubjectDescriptor());
337+
attribute_data_decode_buffer data_buffer(current_val_type);
338+
if (decoder_copy.Decode(data_buffer) == CHIP_NO_ERROR) {
339+
new_val = data_buffer.get_attr_val();
340+
// PRE_UPDATE callback
341+
if (attribute::execute_callback(attribute::PRE_UPDATE, request.path.mEndpointId,
342+
request.path.mClusterId, request.path.mAttributeId,
343+
&new_val) != ESP_OK) {
344+
return Protocols::InteractionModel::Status::Failure;
345+
}
346+
}
347+
348+
// Helper to execute POST_UPDATE callback
349+
auto execute_post_update = [&]() {
350+
351+
attribute::execute_callback(attribute::POST_UPDATE, request.path.mEndpointId,
352+
request.path.mClusterId, request.path.mAttributeId, &new_val);
353+
};
354+
355+
// mRegistry handles its own storage
302356
if (auto *cluster = mRegistry.Get(request.path); cluster != nullptr) {
303-
return cluster->WriteAttribute(request, decoder);
357+
ActionReturnStatus ret = cluster->WriteAttribute(request, decoder);
358+
if (ret.IsSuccess()) {
359+
execute_post_update();
360+
}
361+
return ret;
304362
}
363+
305364
Status status = CheckDataModelPath(request.path);
306365
VerifyOrReturnValue(status == Protocols::InteractionModel::Status::Success,
307366
CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status));
308367
cluster_t *cluster = cluster::get(request.path.mEndpointId, request.path.mClusterId);
309-
attribute_t *attribute = attribute::get(cluster, request.path.mAttributeId);
310368
if (request.path.mDataVersion.HasValue()) {
311369
chip::DataVersion data_version;
312370
VerifyOrReturnValue(cluster::get_data_version(cluster, data_version) == ESP_OK,
@@ -315,6 +373,7 @@ ActionReturnStatus provider::WriteAttribute(const WriteAttributeRequest &request
315373
Protocols::InteractionModel::Status::DataVersionMismatch);
316374
}
317375

376+
// AAI handles its own storage
318377
AttributeAccessInterface *aai =
319378
AttributeAccessInterfaceRegistry::Instance().Get(request.path.mEndpointId, request.path.mClusterId);
320379
std::optional<CHIP_ERROR> aai_result = TryWriteViaAccessInterface(request.path, aai, decoder);
@@ -323,21 +382,23 @@ ActionReturnStatus provider::WriteAttribute(const WriteAttributeRequest &request
323382
cluster::increase_data_version(cluster);
324383
AttributePathParams path(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId);
325384
mContext->dataModelChangeListener.MarkDirty(path);
385+
execute_post_update();
326386
}
327387
return *aai_result;
328388
}
329389

330-
esp_matter_attr_val_t val = esp_matter_invalid(nullptr);
331-
VerifyOrReturnValue(attribute::get_val_internal(attribute, &val) == ESP_OK, Protocols::InteractionModel::Status::Failure);
332-
attribute_data_decode_buffer data_buffer(val.type);
333-
ReturnErrorOnFailure(decoder.Decode(data_buffer));
334-
esp_err_t err =
335-
attribute::update(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId, &data_buffer.get_attr_val());
390+
// Use set_val_internal with call_callbacks=false since we already called PRE_UPDATE
391+
esp_err_t err = attribute::set_val_internal(attribute, &new_val, false);
336392
if (err == ESP_ERR_NO_MEM) {
337393
return Protocols::InteractionModel::Status::ResourceExhausted;
338-
} else if (err != ESP_OK) {
394+
} else if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) {
339395
return Protocols::InteractionModel::Status::Failure;
340396
}
397+
// Increase data version and mark dirty
398+
cluster::increase_data_version(cluster);
399+
AttributePathParams path(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId);
400+
mContext->dataModelChangeListener.MarkDirty(path);
401+
execute_post_update();
341402
return Protocols::InteractionModel::Status::Success;
342403
}
343404

0 commit comments

Comments
 (0)