diff --git a/.codespellrc b/.codespellrc index e348e72da..479a2602a 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] ignore-regex = _ -ignore-words-list = ot, bootup, requestor, pase, lits +ignore-words-list = ot, bootup, requestor, pase, lits, kNo diff --git a/components/esp_matter/data_model_provider/esp_matter_data_model_provider.cpp b/components/esp_matter/data_model_provider/esp_matter_data_model_provider.cpp index 4dd3a8d5b..9d0bac85b 100644 --- a/components/esp_matter/data_model_provider/esp_matter_data_model_provider.cpp +++ b/components/esp_matter/data_model_provider/esp_matter_data_model_provider.cpp @@ -13,12 +13,12 @@ // limitations under the License. #include -#include #include #include #include #include #include +#include #include #include @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -34,6 +36,8 @@ #include #include #include +#include +#include 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; + 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; + 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(len) <= max_payload) ? static_cast(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(val.val.a.s) <= max_payload) + ? static_cast(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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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 data_buff(metadata->size); + Status buffer_status = + BuildEmberBufferFromAttrVal(decoded_buffer.get_attr_val(), data_buff.data(), + static_cast(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; } 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 b0abf29ce..90e5b852f 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 @@ -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; }