From 1714833b941624f1790ca68ffa81d3341f950e3f Mon Sep 17 00:00:00 2001 From: Feras Muki Date: Wed, 18 Jan 2023 13:02:28 -0500 Subject: [PATCH] Add commands support for dynamically generated clusters in dynamic-bridge-app (#23840) * Add commands for bridge-generated dynamic clusters * Allow reads and writes to externally stored OnOff cluster attribute * dynamic-bridge-app: implement generic read and write callbacks for externally stored attributes * Update bridge idl tests to include external storage flags * Restyled by clang-format * Document the data buffer padding size used when reading/writing external attributes * Add better error handling for attribute read/write overrides in dynamic-bridge-app * Allow bitmap32 attribute reads for dynamic clusters in dynamic-bridge-app * Adjust indentation in BridgeClustersCpp.jinja * Change the incoming command list for generated clusters to static * Change the incoming command list for generated clusters to const * Rename incomingCommandList to mIncomingCommandList for generated clusters in dynamic-bridge-app * add a test for bridge generated cluster headers * Restyle one line if statements in dynamic-bridge-app main.cpp * dynamic-bridge-app: change buffer read logic to assume byte arrays * Restyled by clang-format * dynamic-bridge-app: move tests to the proper location * dynamic-bridge-app: undo formatting done to matter_idl tests restyled by clang-formatter * dynamic-bridge-app: change read and write functions to use AnonymousTag() instead of Tag() * dynamic-bridge-app: document unclear code and refactor function and variable names for better readability * dynamic-bridge-app: mark OnListWriteBegin and OnListWriteEnd as overrides * dynamic-bridge-app: fix error handling in WriteValueToBuffer * dynamic-bridge-app: simplify the include path in BridgeClustersCpp jinja template and update the relevant tests * dynamic-bridge-app: change CHIP_ERROR_BUFFER_TOO_SMALL errors to CHIP_ERROR_INVALID_ARGUMENT * dynamic-bridge-app: allow read operations on externally stored list attributes * Restyled by whitespace * Restyled by clang-format * dynamic-bridge-app: formatting - add whitespace between function definitions * dynamic-bridge-app: fix typo in GeneratedClusters.h Co-authored-by: Restyled.io --- .../linux/UserInputBackend.cpp | 17 +- .../linux/include/GeneratedClusters.h | 12 + .../linux/include/data-model/Attribute.h | 19 ++ .../linux/include/data-model/DataModel.h | 56 ++++ .../dynamic-bridge-app/linux/include/main.h | 2 + examples/dynamic-bridge-app/linux/main.cpp | 279 +++++++++++++++++- .../generators/bridge/BridgeClustersCpp.jinja | 24 +- .../matter_idl/tests/available_tests.yaml | 5 + .../tests/inputs/cluster_with_commands.matter | 44 +++ .../bridge/DemoClusterServer.h | 6 +- .../bridge/BridgeClustersImpl.h | 26 ++ .../bridge/BridgeGlobalStructs.h | 13 + .../cluster_with_commands/bridge/OnOff.h | 49 +++ .../bridge/DemoClusterServer.h | 6 +- .../several_clusters/bridge/FirstServer.h | 4 +- .../several_clusters/bridge/SecondServer.h | 4 +- .../outputs/several_clusters/bridge/Third.h | 6 +- .../several_clusters/bridge/ThirdServer.h | 6 +- .../simple_attribute/bridge/MyClusterServer.h | 4 +- 19 files changed, 551 insertions(+), 31 deletions(-) create mode 100644 scripts/py_matter_idl/matter_idl/tests/inputs/cluster_with_commands.matter create mode 100644 scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeClustersImpl.h create mode 100644 scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeGlobalStructs.h create mode 100644 scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/OnOff.h diff --git a/examples/dynamic-bridge-app/linux/UserInputBackend.cpp b/examples/dynamic-bridge-app/linux/UserInputBackend.cpp index 9bdf4b8e689710..b609ad9cceffa9 100644 --- a/examples/dynamic-bridge-app/linux/UserInputBackend.cpp +++ b/examples/dynamic-bridge-app/linux/UserInputBackend.cpp @@ -120,7 +120,8 @@ void AddCluster(const std::vector & tokens) auto c = CreateCluster(cluster_name.c_str()); if (c) { - g_pending->AddCluster(std::make_unique(std::move(c))); + g_pending->AddCluster( + std::make_unique(std::move(c), c->GetIncomingCommandList(), c->GetOutgoingCommandList())); } else { @@ -285,20 +286,20 @@ void ParseValue(std::vector * data, uint16_t size, const std::string & { case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: - wr.PutString(chip::TLV::Tag(), str.data(), (uint32_t) str.size()); + wr.PutString(chip::TLV::AnonymousTag(), str.data(), (uint32_t) str.size()); break; case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: - wr.PutBytes(chip::TLV::Tag(), (const uint8_t *) str.data(), (uint32_t) str.size()); + wr.PutBytes(chip::TLV::AnonymousTag(), (const uint8_t *) str.data(), (uint32_t) str.size()); break; case ZCL_STRUCT_ATTRIBUTE_TYPE: // Writing structs not supported yet break; case ZCL_SINGLE_ATTRIBUTE_TYPE: - wr.Put(chip::TLV::Tag(), (float) atof(str.c_str())); + wr.Put(chip::TLV::AnonymousTag(), (float) atof(str.c_str())); break; case ZCL_DOUBLE_ATTRIBUTE_TYPE: - wr.Put(chip::TLV::Tag(), atof(str.c_str())); + wr.Put(chip::TLV::AnonymousTag(), atof(str.c_str())); break; case ZCL_INT8S_ATTRIBUTE_TYPE: case ZCL_INT16S_ATTRIBUTE_TYPE: @@ -308,7 +309,7 @@ void ParseValue(std::vector * data, uint16_t size, const std::string & case ZCL_INT48S_ATTRIBUTE_TYPE: case ZCL_INT56S_ATTRIBUTE_TYPE: case ZCL_INT64S_ATTRIBUTE_TYPE: - wr.Put(chip::TLV::Tag(), (int64_t) strtoll(str.c_str(), nullptr, 10)); + wr.Put(chip::TLV::AnonymousTag(), (int64_t) strtoll(str.c_str(), nullptr, 10)); break; case ZCL_INT8U_ATTRIBUTE_TYPE: @@ -319,12 +320,12 @@ void ParseValue(std::vector * data, uint16_t size, const std::string & case ZCL_INT48U_ATTRIBUTE_TYPE: case ZCL_INT56U_ATTRIBUTE_TYPE: case ZCL_INT64U_ATTRIBUTE_TYPE: - wr.Put(chip::TLV::Tag(), (uint64_t) strtoll(str.c_str(), nullptr, 10)); + wr.Put(chip::TLV::AnonymousTag(), (uint64_t) strtoll(str.c_str(), nullptr, 10)); break; default: // Assume integer - wr.Put(chip::TLV::Tag(), (int64_t) strtoll(str.c_str(), nullptr, 10)); + wr.Put(chip::TLV::AnonymousTag(), (int64_t) strtoll(str.c_str(), nullptr, 10)); break; } wr.Finalize(); diff --git a/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h b/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h index 9e417814ac04a6..02ad1aaeaf7423 100644 --- a/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h +++ b/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h @@ -24,4 +24,16 @@ class GeneratedCluster virtual chip::ClusterId GetClusterId() = 0; // Gets the list of available attributes for this cluster. virtual std::vector GetAttributes() = 0; + + // Returns a list of client to server commands. Can be nullptr or terminated by 0xFFFF_FFFF. + // The returned list mirrors the `acceptedCommandList` field in `EmberAfCluster` + // This function is used to pass a command list when creating a `DynamicCluster` and its underlying `EmberAfCluster`. See + // `AddCluster` in `UserInputBackend`. + virtual const chip::CommandId * GetIncomingCommandList() { return nullptr; } + + // Returns a list of server generated commands (responses to client commands). Can be nullptr or terminated by 0xFFFF_FFFF. + // The returned list mirrors the `generatedCommandList` field in `EmberAfCluster` + // This function is used to pass a command list when creating a `DynamicCluster` and its underlying `EmberAfCluster`. See + // `AddCluster` in `UserInputBackend`. + virtual const chip::CommandId * GetOutgoingCommandList() { return nullptr; } }; diff --git a/examples/dynamic-bridge-app/linux/include/data-model/Attribute.h b/examples/dynamic-bridge-app/linux/include/data-model/Attribute.h index 7a7be2a997036c..8a75f811c4b3bc 100644 --- a/examples/dynamic-bridge-app/linux/include/data-model/Attribute.h +++ b/examples/dynamic-bridge-app/linux/include/data-model/Attribute.h @@ -27,6 +27,8 @@ class AttributeInterface // Read the contents of the attribute. virtual CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & aPath, chip::app::AttributeValueEncoder & aEncoder) = 0; + + virtual CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & aPath, chip::TLV::TLVWriter & writer) = 0; }; // This is the base type for implementing an attribute @@ -52,6 +54,23 @@ struct Attribute : public AttributeInterface return chip::app::DataModel::Encode(aPath, aEncoder, mData); } + template >::value, bool> = true> + CHIP_ERROR ReadValue(const chip::app::ConcreteReadAttributePath & aPath, chip::TLV::TLVWriter & writer, Type & value) + { + return chip::app::DataModel::Encode(aPath, writer, chip::TLV::AnonymousTag(), value); + } + + template >::value, bool> = true> + CHIP_ERROR ReadValue(const chip::app::ConcreteReadAttributePath & aPath, chip::TLV::TLVWriter & writer, Type & value) + { + return chip::app::DataModel::Encode(writer, chip::TLV::AnonymousTag(), value); + } + + CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & aPath, chip::TLV::TLVWriter & writer) override + { + return ReadValue(aPath, writer, mData); + } + void operator=(const Type & value) { mData = value; } const Type & Peek() const { return mData; } diff --git a/examples/dynamic-bridge-app/linux/include/data-model/DataModel.h b/examples/dynamic-bridge-app/linux/include/data-model/DataModel.h index fde7bf07841f43..6f78787830e3f3 100644 --- a/examples/dynamic-bridge-app/linux/include/data-model/DataModel.h +++ b/examples/dynamic-bridge-app/linux/include/data-model/DataModel.h @@ -65,6 +65,7 @@ CHIP_ERROR Encode(const ConcreteReadAttributePath &, AttributeValueEncoder & aEn /* * @brief * Lists that are string-like should be encoded as char/byte spans. + * Encode using a given AttributeValueEncoder. */ template < typename X, @@ -74,11 +75,26 @@ CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, AttributeValueEncoder return aEncoder.Encode(Span>(x.data(), x.size())); } +/* + * @brief + * Lists that are string-like should be encoded as char/byte spans. + * Encode using a given TLVWriter. + */ +template < + typename X, + std::enable_if_t>::value && sizeof(std::decay_t) == sizeof(char), bool> = true> +CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, TLV::TLVWriter & writer, TLV::Tag tag, const X & x) +{ + return writer.Put(tag, Span>(x.data(), x.size())); +} + /* * @brief * * If an item is requested from a list, encode just that single item, or the entire list otherwise. * + * Encodes items using a given AttributeValueEncoder. + * * The object must satisfy the following constraints * size() must return an integer * begin() must return a type conforming to LegacyRandomAccessIterator @@ -113,6 +129,46 @@ CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, AttributeValueEncoder }); } +/* + * @brief + * + * If an item is requested from a list, encode just that single item, or the entire list otherwise. + * + * Encodes items using a given TLVWriter. + * + * The object must satisfy the following constraints + * size() must return an integer + * begin() must return a type conforming to LegacyRandomAccessIterator + * + * This is const X& instead of X&& because it is "more specialized" and so this overload will + * be chosen if possible. + */ +template < + typename X, + std::enable_if_t>::value && (sizeof(std::decay_t) > sizeof(char)), bool> = true> +CHIP_ERROR Encode(const ConcreteReadAttributePath & aPath, TLV::TLVWriter & writer, TLV::Tag tag, const X & x) +{ + if (aPath.mListIndex.HasValue()) + { + uint16_t index = aPath.mListIndex.Value(); + if (index >= x.size()) + return CHIP_ERROR_INVALID_ARGUMENT; + + auto it = x.begin(); + std::advance(it, index); + return Encode(writer, tag, *it); + } + TLV::TLVType type; + + ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Array, type)); + for (auto & item : x) + { + ReturnErrorOnFailure(Encode(writer, tag, item)); + } + ReturnErrorOnFailure(writer.EndContainer(type)); + return CHIP_NO_ERROR; +} + /* * @brief * Set of overloaded encode methods that can be called from AttributeAccessInterface::Read diff --git a/examples/dynamic-bridge-app/linux/include/main.h b/examples/dynamic-bridge-app/linux/include/main.h index 30df5ae0248f9c..d11982d85fbc55 100644 --- a/examples/dynamic-bridge-app/linux/include/main.h +++ b/examples/dynamic-bridge-app/linux/include/main.h @@ -62,3 +62,5 @@ chip::Span GetActionListInfo(chip::EndpointId parentId); chip::Optional LookupClusterByName(const char * name); std::unique_ptr CreateCluster(const char * name); std::unique_ptr CreateCluster(chip::ClusterId id); +EmberAfStatus HandleReadOnOffAttribute(Attribute * attribute, uint8_t * buffer, uint16_t maxReadLength); +EmberAfStatus HandleWriteOnOffAttribute(Attribute * attribute, uint8_t * buffer); diff --git a/examples/dynamic-bridge-app/linux/main.cpp b/examples/dynamic-bridge-app/linux/main.cpp index 1c6ed2300771c7..f71f120e3bdd38 100644 --- a/examples/dynamic-bridge-app/linux/main.cpp +++ b/examples/dynamic-bridge-app/linux/main.cpp @@ -85,6 +85,8 @@ struct CommonAttributeAccessInterface : public chip::app::AttributeAccessInterfa CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & aPath, chip::app::AttributeValueEncoder & aEncoder) override; CHIP_ERROR Write(const chip::app::ConcreteDataAttributePath & aPath, chip::app::AttributeValueDecoder & aDecoder) override; + CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & aPath, TLV::TLVWriter & writer); + void OnListWriteBegin(const chip::app::ConcreteAttributePath & aPath) override; void OnListWriteEnd(const chip::app::ConcreteAttributePath & aPath, bool aWriteWasSuccessful) override; }; @@ -97,7 +99,9 @@ CommonCluster * CommonAttributeAccessInterface::FindCluster(const chip::app::Con for (auto c : dev->clusters()) { if (c->GetClusterId() == path.mClusterId) + { return static_cast(c); + } } } return nullptr; @@ -108,10 +112,14 @@ CHIP_ERROR CommonAttributeAccessInterface::Read(const chip::app::ConcreteReadAtt { CommonCluster * c = FindCluster(aPath); if (!c) - return CHIP_ERROR_NOT_IMPLEMENTED; + { + return CHIP_ERROR_NOT_FOUND; + } AttributeInterface * a = c->FindAttribute(aPath.mAttributeId); if (!a) - return CHIP_ERROR_NOT_IMPLEMENTED; + { + return CHIP_ERROR_NOT_FOUND; + } return a->Read(aPath, aEncoder); } @@ -120,10 +128,27 @@ CHIP_ERROR CommonAttributeAccessInterface::Write(const chip::app::ConcreteDataAt { CommonCluster * c = FindCluster(aPath); if (!c) - return CHIP_ERROR_NOT_IMPLEMENTED; + { + return CHIP_ERROR_NOT_FOUND; + } return c->ForwardWriteToBridge(aPath, aDecoder); } +CHIP_ERROR CommonAttributeAccessInterface::Read(const chip::app::ConcreteReadAttributePath & aPath, chip::TLV::TLVWriter & writer) +{ + CommonCluster * c = FindCluster(aPath); + if (!c) + { + return CHIP_ERROR_NOT_FOUND; + } + AttributeInterface * a = c->FindAttribute(aPath.mAttributeId); + if (!a) + { + return CHIP_ERROR_NOT_FOUND; + } + return a->Read(aPath, writer); +} + void CommonAttributeAccessInterface::OnListWriteBegin(const chip::app::ConcreteAttributePath & aPath) { CommonCluster * c = FindCluster(aPath); @@ -131,7 +156,9 @@ void CommonAttributeAccessInterface::OnListWriteBegin(const chip::app::ConcreteA { AttributeInterface * a = c->FindAttribute(aPath.mAttributeId); if (a) + { a->ListWriteBegin(aPath); + } } } @@ -142,7 +169,9 @@ void CommonAttributeAccessInterface::OnListWriteEnd(const chip::app::ConcreteAtt { AttributeInterface * a = c->FindAttribute(aPath.mAttributeId); if (a) + { a->ListWriteEnd(aPath, aWriteWasSuccessful); + } } } @@ -176,6 +205,134 @@ std::unique_ptr CreateCluster(chip::ClusterId id) return nullptr; } +CHIP_ERROR TLVWriteValue(chip::TLV::TLVWriter & wr, const Span & v) +{ + return wr.PutString(chip::TLV::AnonymousTag(), v); +} + +CHIP_ERROR TLVWriteValue(chip::TLV::TLVWriter & wr, const bool & v) +{ + return wr.PutBoolean(chip::TLV::AnonymousTag(), v); +} + +template +CHIP_ERROR TLVWriteValue(chip::TLV::TLVWriter & wr, const T & v) +{ + return wr.Put(chip::TLV::AnonymousTag(), v); +} + +CHIP_ERROR WriteValueToBuffer(const bool & value, chip::Span buffer) +{ + if (buffer.size() != 1) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + *(buffer.data()) = value ? 1 : 0; + return CHIP_NO_ERROR; +} + +template +CHIP_ERROR WriteValueToBuffer(const T & value, chip::Span buffer) +{ + size_t value_size = sizeof(value); + if (buffer.size() != value_size) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + memcpy(buffer.data(), &value, value_size); + return CHIP_NO_ERROR; +} + +template +CHIP_ERROR WriteValueToBuffer(chip::TLV::TLVReader & reader, chip::Span buffer) +{ + T v; + ReturnErrorOnFailure(chip::app::DataModel::Decode(reader, v)); + return WriteValueToBuffer(v, buffer); +} + +// Describes a conversion direction between: +// - A binary buffer (passed from ember internals) +// - A TLV data buffer (used by TLVWriter and TLVReader) +enum ConversionDirection +{ + BUFFER_TO_TLV, + TLV_TO_BUFFER +}; + +template +CHIP_ERROR PerformTLVBufferConversion(std::vector * tlvData, chip::Span buffer, + ConversionDirection convert_direction) +{ + CHIP_ERROR err; + if (convert_direction == BUFFER_TO_TLV) + { + // buffer.size() is ignored here, because it was called from the external write ember callback, + // which does not provide a buffer size + chip::TLV::TLVWriter wr; + wr.Init(tlvData->data(), tlvData->size()); + T value; + memcpy(&value, buffer.data(), sizeof(T)); + err = TLVWriteValue(wr, value); + wr.Finalize(); + tlvData->resize(wr.GetLengthWritten()); + } + else + { + chip::TLV::TLVReader rd; + rd.Init(tlvData->data(), tlvData->size()); + rd.Next(); + err = WriteValueToBuffer(rd, buffer); + } + return err; +} + +CHIP_ERROR PerformTLVBufferConversionForType(std::vector * tlvData, chip::Span buffer, EmberAfAttributeType type, + ConversionDirection convert_direction) +{ + switch (type) + { + case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: + case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion>(tlvData, buffer, convert_direction); + case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: + case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + case ZCL_STRUCT_ATTRIBUTE_TYPE: + // structs not supported yet + return CHIP_ERROR_NOT_IMPLEMENTED; + case ZCL_SINGLE_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + case ZCL_DOUBLE_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + case ZCL_INT8S_ATTRIBUTE_TYPE: + case ZCL_INT16S_ATTRIBUTE_TYPE: + case ZCL_INT24S_ATTRIBUTE_TYPE: + case ZCL_INT32S_ATTRIBUTE_TYPE: + case ZCL_INT40S_ATTRIBUTE_TYPE: + case ZCL_INT48S_ATTRIBUTE_TYPE: + case ZCL_INT56S_ATTRIBUTE_TYPE: + case ZCL_INT64S_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + case ZCL_INT8U_ATTRIBUTE_TYPE: + case ZCL_INT16U_ATTRIBUTE_TYPE: + case ZCL_INT24U_ATTRIBUTE_TYPE: + case ZCL_INT32U_ATTRIBUTE_TYPE: + case ZCL_INT40U_ATTRIBUTE_TYPE: + case ZCL_INT48U_ATTRIBUTE_TYPE: + case ZCL_INT56U_ATTRIBUTE_TYPE: + case ZCL_INT64U_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + case ZCL_BOOLEAN_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + case ZCL_BITMAP32_ATTRIBUTE_TYPE: + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + default: + // Assume integer + return PerformTLVBufferConversion(tlvData, buffer, convert_direction); + } +} + bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Actions::Commands::InstantAction::DecodableType & commandData) { @@ -184,12 +341,126 @@ bool emberAfActionsClusterInstantActionCallback(app::CommandHandler * commandObj return true; } +EmberAfStatus emberAfExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId, + const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer, + uint16_t maxReadLength) +{ + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + if ((endpointIndex >= CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) || (gDevices[endpointIndex] == nullptr)) + { + ChipLogError(DeviceLayer, "Could not find dynamic endpoint: %d", endpoint); + return EMBER_ZCL_STATUS_UNSUPPORTED_ENDPOINT; + } + + chip::app::AttributeAccessInterface * accessInterface = chip::app::GetAttributeAccessOverride(endpoint, clusterId); + + if (accessInterface == nullptr) + { + ChipLogError(DeviceLayer, "Cluster %d has no attribute access override", clusterId); + return EMBER_ZCL_STATUS_FAILURE; + } + + // adding 64 bytes as padding to include the staging buffer used by + // TLVReader and TLVWriter, which is 17 bytes + std::vector tlvData(attributeMetadata->size + 64); + + // read the attribute and write it to `data` + chip::TLV::TLVWriter writer; + writer.Init(tlvData.data(), tlvData.size()); + + // this cast is safe because all the registered attribute accessors are of type `CommonAttributeAccessInterface`. See `main()` + CHIP_ERROR err = static_cast(accessInterface) + ->Read(chip::app::ConcreteDataAttributePath(endpoint, clusterId, attributeMetadata->attributeId), writer); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "%" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(DeviceLayer, "Attribute access interface failed to read attribute %d, for endpoint %d cluster %d", + attributeMetadata->attributeId, endpoint, clusterId); + return EMBER_ZCL_STATUS_FAILURE; + } + writer.Finalize(); + tlvData.resize(writer.GetLengthWritten()); + + // read from `data` and write to `buffer` + + // maxReadLength here is the maximum number of bytes to read from the attribute value and to write into the buffer. + err = PerformTLVBufferConversionForType(&tlvData, chip::Span(buffer, maxReadLength), attributeMetadata->attributeType, + TLV_TO_BUFFER); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "%" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(DeviceLayer, "Failed to write attribute to buffer. Endpoint %d, Cluster %d, Attribute %d", endpoint, clusterId, + attributeMetadata->attributeId); + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + +EmberAfStatus emberAfExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId, + const EmberAfAttributeMetadata * attributeMetadata, uint8_t * buffer) +{ + uint16_t endpointIndex = emberAfGetDynamicIndexFromEndpoint(endpoint); + + if ((endpointIndex >= CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) || (gDevices[endpointIndex] == nullptr)) + { + ChipLogError(DeviceLayer, "Could not find dynamic endpoint: %d", endpoint); + return EMBER_ZCL_STATUS_FAILURE; + } + + chip::app::AttributeAccessInterface * accessInterface = chip::app::GetAttributeAccessOverride(endpoint, clusterId); + + if (accessInterface == nullptr) + { + ChipLogError(DeviceLayer, "Cluster %d has no attribute access override", clusterId); + return EMBER_ZCL_STATUS_FAILURE; + } + + // adding 64 bytes as padding to include the staging buffer used by + // TLVReader and TLVWriter, which is 17 bytes + std::vector tlvData(attributeMetadata->size + 64); + + // read from `buffer` and write to `data` + + // buffer size will not be used in this code path, so we set it to 0. See `PerformTLVBufferConversion` + CHIP_ERROR err = PerformTLVBufferConversionForType(&tlvData, chip::Span(buffer, 0), attributeMetadata->attributeType, + BUFFER_TO_TLV); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "%" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(DeviceLayer, "Failed to read value from buffer: Endpoint %d, Cluster %d, Attribute %d", endpoint, clusterId, + attributeMetadata->attributeId); + return EMBER_ZCL_STATUS_FAILURE; + } + + // read from `data` and write to attribute + chip::TLV::TLVReader reader; + reader.Init(tlvData.data(), tlvData.size()); + reader.Next(); + chip::app::AttributeValueDecoder decoder(reader, chip::Access::SubjectDescriptor()); + err = + accessInterface->Write(chip::app::ConcreteReadAttributePath(endpoint, clusterId, attributeMetadata->attributeId), decoder); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "%" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(DeviceLayer, + "Attribute access interface failed to write attribute value. Endpoint %d, Cluster %d, Attribute %d", endpoint, + clusterId, attributeMetadata->attributeId); + return EMBER_ZCL_STATUS_FAILURE; + } + + return EMBER_ZCL_STATUS_SUCCESS; +} + Device * FindDeviceEndpoint(chip::EndpointId id) { for (auto dev : gDevices) { if (dev && dev->GetEndpointId() == id) + { return dev; + } } return nullptr; } @@ -261,7 +532,9 @@ Room * FindRoom(const std::string & name) for (auto & room : gRooms) { if (room.GetName() == name) + { return &room; + } } return nullptr; } diff --git a/scripts/py_matter_idl/matter_idl/generators/bridge/BridgeClustersCpp.jinja b/scripts/py_matter_idl/matter_idl/generators/bridge/BridgeClustersCpp.jinja index 62c1f41e77ac26..14a6aca640b0ad 100644 --- a/scripts/py_matter_idl/matter_idl/generators/bridge/BridgeClustersCpp.jinja +++ b/scripts/py_matter_idl/matter_idl/generators/bridge/BridgeClustersCpp.jinja @@ -1,7 +1,7 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct {{cluster.name}}Cluster : public GeneratedCluster @@ -42,7 +42,7 @@ struct {{cluster.name}}Cluster : public GeneratedCluster {{cluster.name}}Cluster() : {%- for attr in cluster.attributes %} - m{{attr.definition.name | capitalcase}}(chip::CharSpan("{{attr.definition.name}}"), {{attr.definition.code}}, {{attr | getMask(cluster, idl)}}, {{attr | getRawSizeAndType(cluster, idl)}}{{attr | getInit(cluster, idl)}}){{"," if not loop.last}} + m{{attr.definition.name | capitalcase}}(chip::CharSpan("{{attr.definition.name}}"), {{attr.definition.code}}, {{attr | getMask(cluster, idl)}} | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), {{attr | getRawSizeAndType(cluster, idl)}}{{attr | getInit(cluster, idl)}}){{"," if not loop.last}} {%- endfor %} { } @@ -62,6 +62,26 @@ struct {{cluster.name}}Cluster : public GeneratedCluster {% for attr in cluster.attributes %} {{"List" if attr.definition.is_list}}Attribute<{{attr | getType(cluster, idl)}}> m{{attr.definition.name | capitalcase}}; {%- endfor %} + +{%- if cluster.commands | length > 0 %} + static const chip::CommandId mIncomingCommandList[]; + const chip::CommandId * GetIncomingCommandList() override + { + return mIncomingCommandList; + } +{%- endif %} +}; + +{%- if cluster.commands | length > 0 %} +#ifndef {{cluster.name | constcase}}_CLUSTER_INCOMING_COMMANDS +#define {{cluster.name | constcase}}_CLUSTER_INCOMING_COMMANDS +const chip::CommandId {{cluster.name}}Cluster::mIncomingCommandList[] = { +{%- for command in cluster.commands %} + {{command.code}}, +{%- endfor %} + chip::kInvalidCommandId }; +#endif +{%- endif %} } diff --git a/scripts/py_matter_idl/matter_idl/tests/available_tests.yaml b/scripts/py_matter_idl/matter_idl/tests/available_tests.yaml index bd5311ab050c48..a4e2c6778b5f95 100644 --- a/scripts/py_matter_idl/matter_idl/tests/available_tests.yaml +++ b/scripts/py_matter_idl/matter_idl/tests/available_tests.yaml @@ -59,6 +59,11 @@ bridge: bridge/ThirdServer.h: outputs/several_clusters/bridge/ThirdServer.h bridge/Third.h: outputs/several_clusters/bridge/Third.h + inputs/cluster_with_commands.matter: + bridge/BridgeClustersImpl.h: outputs/cluster_with_commands/bridge/BridgeClustersImpl.h + bridge/BridgeGlobalStructs.h: outputs/cluster_with_commands/bridge/BridgeGlobalStructs.h + bridge/OnOff.h: outputs/cluster_with_commands/bridge/OnOff.h + cpp-app: inputs/several_clusters.matter: app/PluginApplicationCallbacks.h: outputs/several_clusters/cpp-app/PluginApplicationCallbacks.h diff --git a/scripts/py_matter_idl/matter_idl/tests/inputs/cluster_with_commands.matter b/scripts/py_matter_idl/matter_idl/tests/inputs/cluster_with_commands.matter new file mode 100644 index 00000000000000..c6128be7ffd870 --- /dev/null +++ b/scripts/py_matter_idl/matter_idl/tests/inputs/cluster_with_commands.matter @@ -0,0 +1,44 @@ + +server cluster OnOff = 6 { + enum OnOffDelayedAllOffEffectVariant : ENUM8 { + kFadeToOffIn0p8Seconds = 0; + kNoFade = 1; + k50PercentDimDownIn0p8SecondsThenFadeToOffIn12Seconds = 2; + } + + enum OnOffDyingLightEffectVariant : ENUM8 { + k20PercenterDimUpIn0p5SecondsThenFadeToOffIn1Second = 0; + } + + enum OnOffEffectIdentifier : ENUM8 { + kDelayedAllOff = 0; + kDyingLight = 1; + } + + enum OnOffStartUpOnOff : ENUM8 { + kOff = 0; + kOn = 1; + kTogglePreviousOnOff = 2; + } + + bitmap OnOffControl : BITMAP8 { + kAcceptOnlyWhenOn = 0x1; + } + + bitmap OnOffFeature : BITMAP32 { + kLighting = 0x1; + } + + bitmap SceneFeatures : BITMAP32 { + kSceneNames = 0x1; + } + + readonly attribute boolean onOff = 0; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + command Off(): DefaultSuccess = 0; + command On(): DefaultSuccess = 1; + command Toggle(): DefaultSuccess = 2; +} + diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_struct_attribute/bridge/DemoClusterServer.h b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_struct_attribute/bridge/DemoClusterServer.h index bdf2488396763a..eac0b0aeca0d1c 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_struct_attribute/bridge/DemoClusterServer.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_struct_attribute/bridge/DemoClusterServer.h @@ -1,15 +1,15 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct DemoClusterCluster : public GeneratedCluster { DemoClusterCluster() : - mSingleFailSafe(chip::CharSpan("singleFailSafe"), 5, ATTRIBUTE_MASK_WRITABLE, ZCL_STRUCT_ATTRIBUTE_TYPE, sizeof(ArmFailSafeRequest)), - mArmFailsafes(chip::CharSpan("armFailsafes"), 100, ATTRIBUTE_MASK_WRITABLE, ZCL_ARRAY_ATTRIBUTE_TYPE, sizeof(ArmFailSafeRequest)) + mSingleFailSafe(chip::CharSpan("singleFailSafe"), 5, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_STRUCT_ATTRIBUTE_TYPE, sizeof(ArmFailSafeRequest)), + mArmFailsafes(chip::CharSpan("armFailsafes"), 100, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_ARRAY_ATTRIBUTE_TYPE, sizeof(ArmFailSafeRequest)) { } diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeClustersImpl.h b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeClustersImpl.h new file mode 100644 index 00000000000000..0484909c914739 --- /dev/null +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeClustersImpl.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "bridge/OnOff.h" + +namespace clusters { + +struct ClusterInfo +{ + chip::ClusterId id; + const char *name; + uint16_t size; + GeneratedCluster* (*ctor)(void*); +} static const kKnownClusters[] = { + + { + 6, + "OnOff", + sizeof(OnOffCluster), + [](void *mem) -> GeneratedCluster* { + return new(mem) OnOffCluster(); + }, + }, +}; + +} diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeGlobalStructs.h b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeGlobalStructs.h new file mode 100644 index 00000000000000..3b56a6f868b109 --- /dev/null +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/BridgeGlobalStructs.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#include +#include + +namespace clusters { + + + +} diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/OnOff.h b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/OnOff.h new file mode 100644 index 00000000000000..1e34c2c7953418 --- /dev/null +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/cluster_with_commands/bridge/OnOff.h @@ -0,0 +1,49 @@ +#pragma once + +#include "BridgeGlobalStructs.h" +#include "GeneratedClusters.h" + +namespace clusters { +struct OnOffCluster : public GeneratedCluster +{ + + OnOffCluster() : + mOnOff(chip::CharSpan("onOff"), 0, 0 | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_BOOLEAN_ATTRIBUTE_TYPE, 1), + mFeatureMap(chip::CharSpan("featureMap"), 65532, 0 | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_BITMAP32_ATTRIBUTE_TYPE, 4), + mClusterRevision(chip::CharSpan("clusterRevision"), 65533, 0 | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_INT16U_ATTRIBUTE_TYPE, 2, ZCL_ON_OFF_CLUSTER_REVISION) + { + } + + static constexpr uint32_t kClusterId =6; + chip::ClusterId GetClusterId() override { return kClusterId; } + + std::vector GetAttributes() override + { + return std::vector({ + static_cast(&mOnOff), + static_cast(&mFeatureMap), + static_cast(&mClusterRevision), + }); + } + + + Attribute mOnOff; + Attribute mFeatureMap; + Attribute mClusterRevision; + static const chip::CommandId mIncomingCommandList[]; + const chip::CommandId * GetIncomingCommandList() override + { + return mIncomingCommandList; + } +}; +#ifndef ON_OFF_CLUSTER_INCOMING_COMMANDS +#define ON_OFF_CLUSTER_INCOMING_COMMANDS +const chip::CommandId OnOffCluster::mIncomingCommandList[] = { + 0, + 1, + 2, + chip::kInvalidCommandId +}; +#endif + +} diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/global_struct_attribute/bridge/DemoClusterServer.h b/scripts/py_matter_idl/matter_idl/tests/outputs/global_struct_attribute/bridge/DemoClusterServer.h index 78f7e89c33aaf9..3c6feeb5a3baba 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/global_struct_attribute/bridge/DemoClusterServer.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/global_struct_attribute/bridge/DemoClusterServer.h @@ -1,15 +1,15 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct DemoClusterCluster : public GeneratedCluster { DemoClusterCluster() : - mSingleLabel(chip::CharSpan("singleLabel"), 32, ATTRIBUTE_MASK_WRITABLE, ZCL_STRUCT_ATTRIBUTE_TYPE, sizeof(LabelStruct)), - mSomeLabels(chip::CharSpan("someLabels"), 33, ATTRIBUTE_MASK_WRITABLE, ZCL_ARRAY_ATTRIBUTE_TYPE, sizeof(LabelStruct)) + mSingleLabel(chip::CharSpan("singleLabel"), 32, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_STRUCT_ATTRIBUTE_TYPE, sizeof(LabelStruct)), + mSomeLabels(chip::CharSpan("someLabels"), 33, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_ARRAY_ATTRIBUTE_TYPE, sizeof(LabelStruct)) { } diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/FirstServer.h b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/FirstServer.h index c5d0ede099ddec..2786817c0dc8a5 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/FirstServer.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/FirstServer.h @@ -1,14 +1,14 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct FirstCluster : public GeneratedCluster { FirstCluster() : - mSomeInteger(chip::CharSpan("someInteger"), 1, ATTRIBUTE_MASK_WRITABLE, ZCL_INT16U_ATTRIBUTE_TYPE, 2) + mSomeInteger(chip::CharSpan("someInteger"), 1, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_INT16U_ATTRIBUTE_TYPE, 2) { } diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/SecondServer.h b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/SecondServer.h index e80573cf55b5cc..71905bf252bd60 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/SecondServer.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/SecondServer.h @@ -1,14 +1,14 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct SecondCluster : public GeneratedCluster { SecondCluster() : - mSomeBytes(chip::CharSpan("someBytes"), 123, 0, ZCL_OCTET_STRING_ATTRIBUTE_TYPE, 32) + mSomeBytes(chip::CharSpan("someBytes"), 123, 0 | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_OCTET_STRING_ATTRIBUTE_TYPE, 32) { } diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/Third.h b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/Third.h index 48e8c4537e4799..3b15195c0af9e4 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/Third.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/Third.h @@ -1,15 +1,15 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct ThirdCluster : public GeneratedCluster { ThirdCluster() : - mSomeEnum(chip::CharSpan("someEnum"), 10, ATTRIBUTE_MASK_WRITABLE, ZCL_ENUM8_ATTRIBUTE_TYPE, 1), - mClusterRevision(chip::CharSpan("clusterRevision"), 65533, 0, ZCL_INT16U_ATTRIBUTE_TYPE, 2, ZCL_THIRD_CLUSTER_REVISION) + mSomeEnum(chip::CharSpan("someEnum"), 10, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_ENUM8_ATTRIBUTE_TYPE, 1), + mClusterRevision(chip::CharSpan("clusterRevision"), 65533, 0 | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_INT16U_ATTRIBUTE_TYPE, 2, ZCL_THIRD_CLUSTER_REVISION) { } diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/ThirdServer.h b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/ThirdServer.h index 6f52806e477a89..d2f8bccb12d648 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/ThirdServer.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/several_clusters/bridge/ThirdServer.h @@ -1,15 +1,15 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct ThirdCluster : public GeneratedCluster { ThirdCluster() : - mSomeEnum(chip::CharSpan("someEnum"), 10, ATTRIBUTE_MASK_WRITABLE, ZCL_ENUM8_ATTRIBUTE_TYPE, 1), - mOptions(chip::CharSpan("options"), 20, ATTRIBUTE_MASK_WRITABLE, ZCL_BITMAP8_ATTRIBUTE_TYPE, 1) + mSomeEnum(chip::CharSpan("someEnum"), 10, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_ENUM8_ATTRIBUTE_TYPE, 1), + mOptions(chip::CharSpan("options"), 20, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_BITMAP8_ATTRIBUTE_TYPE, 1) { } diff --git a/scripts/py_matter_idl/matter_idl/tests/outputs/simple_attribute/bridge/MyClusterServer.h b/scripts/py_matter_idl/matter_idl/tests/outputs/simple_attribute/bridge/MyClusterServer.h index 96c233b1a356de..831b39de9cfca4 100644 --- a/scripts/py_matter_idl/matter_idl/tests/outputs/simple_attribute/bridge/MyClusterServer.h +++ b/scripts/py_matter_idl/matter_idl/tests/outputs/simple_attribute/bridge/MyClusterServer.h @@ -1,14 +1,14 @@ #pragma once #include "BridgeGlobalStructs.h" -#include "third_party/connectedhomeip/examples/dynamic-bridge-app/linux/include/GeneratedClusters.h" +#include "GeneratedClusters.h" namespace clusters { struct MyClusterCluster : public GeneratedCluster { MyClusterCluster() : - mClusterAttr(chip::CharSpan("clusterAttr"), 1, ATTRIBUTE_MASK_WRITABLE, ZCL_INT16U_ATTRIBUTE_TYPE, 2) + mClusterAttr(chip::CharSpan("clusterAttr"), 1, ATTRIBUTE_MASK_WRITABLE | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE), ZCL_INT16U_ATTRIBUTE_TYPE, 2) { }