diff --git a/examples/all-clusters-app/esp32/README.md b/examples/all-clusters-app/esp32/README.md index bdc52346378150..6c6d60fbc340b9 100644 --- a/examples/all-clusters-app/esp32/README.md +++ b/examples/all-clusters-app/esp32/README.md @@ -5,16 +5,16 @@ control. --- -- [CHIP ESP32 All Clusters Example](#chip-esp32-all-clusters-example) - - [Supported Devices](#supported-devices) - - [Building the Example Application](#building-the-example-application) - - [Commissioning and cluster control](#commissioning-and-cluster-control) - - [Setting up Python Controller](#setting-up-python-controller) - - [Commissioning over BLE](#commissioning-over-ble) - - [Cluster control](#cluster-control) - - [Flashing app using script](#flashing-app-using-script) - - [Note](#note) - - [Using the RPC console](#using-the-rpc-console) +- [CHIP ESP32 All Clusters Example](#chip-esp32-all-clusters-example) + - [Supported Devices](#supported-devices) + - [Building the Example Application](#building-the-example-application) + - [Commissioning and cluster control](#commissioning-and-cluster-control) + - [Setting up Python Controller](#setting-up-python-controller) + - [Commissioning over BLE](#commissioning-over-ble) + - [Cluster control](#cluster-control) + - [Flashing app using script](#flashing-app-using-script) + - [Note](#note) + - [Using the RPC console](#using-the-rpc-console) --- diff --git a/src/app/AttributeAccessInterface.h b/src/app/AttributeAccessInterface.h index 6690d812a02da0..afa7bf72e8829f 100644 --- a/src/app/AttributeAccessInterface.h +++ b/src/app/AttributeAccessInterface.h @@ -126,6 +126,27 @@ class AttributeAccessInterface */ virtual CHIP_ERROR Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) = 0; + /** + * Callback for writing attributes. + * + * @param [in] aPath indicates which exact data is being write. + * @param [in] aTLVReader A pointer to a TLVReader, which should point to the beginning + * of this AttributeDataElement to write. + * @param [out] aDataWrite whether we actually tried to write data. If + * this function returns success and aDataWrite is + * false, the AttributeAccessInterface did not try + * to write any data. In this case, normal attribute + * access will happen for the write. This may involve + * writing to the attribute store or external attribute + * callbacks. + */ + virtual CHIP_ERROR Write(const ConcreteAttributePath & aPath, TLV::TLVReader & aReader, bool * aDataWrite) + { + *aDataWrite = false; + + return CHIP_NO_ERROR; + } + /** * Mechanism for keeping track of a chain of AttributeAccessInterfaces. */ diff --git a/src/app/clusters/test-cluster-server/test-cluster-server.cpp b/src/app/clusters/test-cluster-server/test-cluster-server.cpp index 689969833e948d..1f961d7fc813a2 100644 --- a/src/app/clusters/test-cluster-server/test-cluster-server.cpp +++ b/src/app/clusters/test-cluster-server/test-cluster-server.cpp @@ -44,6 +44,8 @@ using namespace chip::app::Clusters::TestCluster::Attributes; constexpr const char * kErrorStr = "Test Cluster: List Octet cluster (0x%02x) Error setting '%s' attribute: 0x%02x"; #endif // CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ +constexpr uint8_t kAttributeCount = 4; + namespace { class TestAttrAccess : public AttributeAccessInterface @@ -53,15 +55,24 @@ class TestAttrAccess : public AttributeAccessInterface TestAttrAccess() : AttributeAccessInterface(Optional::Missing(), TestCluster::Id) {} CHIP_ERROR Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + CHIP_ERROR Write(const ConcreteAttributePath & aPath, TLV::TLVReader & aReader, bool * aDataWrite) override; private: CHIP_ERROR ReadListInt8uAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WriteListInt8uAttribute(TLV::TLVReader & aReader); CHIP_ERROR ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WriteListOctetStringAttribute(TLV::TLVReader & aReader); CHIP_ERROR ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WriteListStructOctetStringAttribute(TLV::TLVReader & aReader); CHIP_ERROR ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder); + CHIP_ERROR WrtieListNullablesAndOptionalsStructAttribute(TLV::TLVReader & aReader); }; TestAttrAccess gAttrAccess; +uint8_t listUint8Data[kAttributeCount]; +char listOctetStringData[kAttributeCount][6]; +char listOperationalCert[kAttributeCount][6]; +Structs::TestListStructOctet::Type listStructOctetStringData[kAttributeCount]; CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) { @@ -87,6 +98,33 @@ CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & aPath, AttributeVa return CHIP_NO_ERROR; } +CHIP_ERROR TestAttrAccess::Write(const ConcreteAttributePath & aPath, TLV::TLVReader & aReader, bool * aDataWrite) +{ + *aDataWrite = true; + + switch (aPath.mAttributeId) + { + case ListInt8u::Id: { + return WriteListInt8uAttribute(aReader); + } + case ListOctetString::Id: { + return WriteListOctetStringAttribute(aReader); + } + case ListStructOctetString::Id: { + return WriteListStructOctetStringAttribute(aReader); + } + case ListNullablesAndOptionalsStruct::Id: { + return WrtieListNullablesAndOptionalsStructAttribute(aReader); + } + default: { + *aDataWrite = false; + break; + } + } + + return CHIP_NO_ERROR; +} + #if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ EmberAfStatus writeAttribute(EndpointId endpoint, AttributeId attributeId, uint8_t * buffer, int32_t index = -1) { @@ -113,14 +151,13 @@ EmberAfStatus writeTestListInt8uAttribute(EndpointId endpoint) EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; AttributeId attributeId = ZCL_LIST_ATTRIBUTE_ID; - uint16_t attributeCount = 4; - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeCount; index++) { status = writeAttribute(endpoint, attributeId, (uint8_t *) &index, index); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); } - status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount); + status = writeAttribute(endpoint, attributeId, (uint8_t *) &kAttributeCount); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); return status; } @@ -129,26 +166,46 @@ EmberAfStatus writeTestListInt8uAttribute(EndpointId endpoint) CHIP_ERROR TestAttrAccess::ReadListInt8uAttribute(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR { - constexpr uint16_t attributeCount = 4; - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeCount; index++) { - ReturnErrorOnFailure(encoder.Encode(index)); + ReturnErrorOnFailure(encoder.Encode(listUint8Data[index])); } return CHIP_NO_ERROR; }); } +CHIP_ERROR TestAttrAccess::WriteListInt8uAttribute(TLV::TLVReader & aReader) +{ + DataModel::DecodableList list; + + ReturnErrorOnFailure(DataModel::Decode(aReader, list)); + + uint8_t index = 0; + auto iter = list.begin(); + while (iter.Next()) + { + auto & entry = iter.GetValue(); + listUint8Data[index++] = entry; + } + + if (iter.GetStatus() != CHIP_NO_ERROR) + { + return CHIP_ERROR_INVALID_DATA_LIST; + } + + return CHIP_NO_ERROR; +} + #if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint) { EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; AttributeId attributeId = ZCL_LIST_OCTET_STRING_ATTRIBUTE_ID; - uint16_t attributeCount = 4; - char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; - ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); + char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; + ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeCount; index++) { sprintf(data + strlen(data) - 1, "%d", index); @@ -156,7 +213,7 @@ EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint) VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); } - status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount); + status = writeAttribute(endpoint, attributeId, (uint8_t *) &kAttributeCount); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); return status; } @@ -165,30 +222,49 @@ EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint) CHIP_ERROR TestAttrAccess::ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR { - constexpr uint16_t attributeCount = 4; - char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; - - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeCount; index++) { - snprintf(data + strlen(data) - 1, 2, "%d", index); - ByteSpan span(Uint8::from_char(data), strlen(data)); + ByteSpan span(Uint8::from_char(listOctetStringData[index]), strlen(listOctetStringData[index])); ReturnErrorOnFailure(encoder.Encode(span)); } return CHIP_NO_ERROR; }); } +CHIP_ERROR TestAttrAccess::WriteListOctetStringAttribute(TLV::TLVReader & aReader) +{ + DataModel::DecodableList list; + + ReturnErrorOnFailure(DataModel::Decode(aReader, list)); + + uint8_t index = 0; + auto iter = list.begin(); + while (iter.Next()) + { + const ByteSpan & entry = iter.GetValue(); + + Platform::CopyString(listOctetStringData[index], sizeof(listOctetStringData[index]), entry); + index++; + } + + if (iter.GetStatus() != CHIP_NO_ERROR) + { + return CHIP_ERROR_INVALID_DATA_LIST; + } + + return CHIP_NO_ERROR; +} + #if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint) { EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; AttributeId attributeId = ZCL_LIST_STRUCT_OCTET_STRING_ATTRIBUTE_ID; - uint16_t attributeCount = 4; - char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; - ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); + char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; + ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data)); - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeCount; index++) { sprintf(data + strlen(data) - 1, "%d", index); @@ -200,7 +276,7 @@ EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint) VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); } - status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount); + status = writeAttribute(endpoint, attributeId, (uint8_t *) &kAttributeCount); VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status); return status; } @@ -209,23 +285,45 @@ EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint) CHIP_ERROR TestAttrAccess::ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR { - constexpr uint16_t attributeCount = 4; - char data[6] = { 'T', 'e', 's', 't', 'N', '\0' }; - - for (uint8_t index = 0; index < attributeCount; index++) + for (uint8_t index = 0; index < kAttributeCount; index++) { - snprintf(data + strlen(data) - 1, 2, "%d", index); - ByteSpan span(Uint8::from_char(data), strlen(data)); - Structs::TestListStructOctet::Type structOctet; - structOctet.fabricIndex = index; - structOctet.operationalCert = span; + structOctet.fabricIndex = listStructOctetStringData[index].fabricIndex; + structOctet.operationalCert = listStructOctetStringData[index].operationalCert; ReturnErrorOnFailure(encoder.Encode(structOctet)); } + return CHIP_NO_ERROR; }); } +CHIP_ERROR TestAttrAccess::WriteListStructOctetStringAttribute(TLV::TLVReader & aReader) +{ + DataModel::DecodableList list; + + ReturnErrorOnFailure(DataModel::Decode(aReader, list)); + + uint8_t index = 0; + auto iter = list.begin(); + while (iter.Next()) + { + const Structs::TestListStructOctet::Type & entry = iter.GetValue(); + + Platform::CopyString(listOperationalCert[index], sizeof(listOperationalCert[index]), entry.operationalCert); + listStructOctetStringData[index].fabricIndex = entry.fabricIndex; + listStructOctetStringData[index].operationalCert = + ByteSpan(Uint8::from_char(listOperationalCert[index]), strlen(listOperationalCert[index])); + index++; + } + + if (iter.GetStatus() != CHIP_NO_ERROR) + { + return CHIP_ERROR_INVALID_DATA_LIST; + } + + return CHIP_NO_ERROR; +} + CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR { @@ -237,6 +335,11 @@ CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(Attribut }); } +CHIP_ERROR TestAttrAccess::WrtieListNullablesAndOptionalsStructAttribute(TLV::TLVReader & aReader) +{ + // TODO Add yaml test case for NullablesAndOptionalsStruct list + return CHIP_NO_ERROR; +} } // namespace bool emberAfTestClusterClusterTestCallback(app::CommandHandler *, const app::ConcreteCommandPath & commandPath, diff --git a/src/app/tests/suites/TestCluster.yaml b/src/app/tests/suites/TestCluster.yaml index cc14d5e8f7ec3e..8e58b8ce14dff0 100644 --- a/src/app/tests/suites/TestCluster.yaml +++ b/src/app/tests/suites/TestCluster.yaml @@ -720,36 +720,6 @@ tests: arguments: value: "" - # Tests for List attribute - - - label: "Read attribute LIST" - command: "readAttribute" - attribute: "list_int8u" - response: - value: [1, 2, 3, 4] - - # Tests for List Octet String attribute - - - label: "Read attribute LIST_OCTET_STRING" - command: "readAttribute" - attribute: "list_octet_string" - response: - value: ["Test0", "Test1", "Test2", "Test3"] - - # Tests for List Struct Octet String attribute - - - label: "Read attribute LIST_STRUCT_OCTET_STRING" - command: "readAttribute" - attribute: "list_struct_octet_string" - response: - value: - [ - { fabricIndex: 0, operationalCert: "Test0" }, - { fabricIndex: 1, operationalCert: "Test1" }, - { fabricIndex: 2, operationalCert: "Test2" }, - { fabricIndex: 3, operationalCert: "Test3" }, - ] - # Tests for Epoch Microseconds - label: "Read attribute EPOCH_US Default Value" diff --git a/src/app/tests/suites/TestClusterComplexTypes.yaml b/src/app/tests/suites/TestClusterComplexTypes.yaml index 8241a66258ba69..244b56dae38330 100644 --- a/src/app/tests/suites/TestClusterComplexTypes.yaml +++ b/src/app/tests/suites/TestClusterComplexTypes.yaml @@ -439,6 +439,55 @@ tests: - name: "value" value: false + - label: + "Write attribute LIST With List of INT8U and none of them is set to 0" + command: "writeAttribute" + attribute: "list_int8u" + arguments: + value: [1, 2, 3, 4] + + - label: "Read attribute LIST With List of INT8U" + command: "readAttribute" + attribute: "list_int8u" + response: + value: [1, 2, 3, 4] + + - label: "Write attribute LIST With List of OCTET_STRING" + command: "writeAttribute" + attribute: "list_octet_string" + arguments: + value: ["Test0", "Test1", "Test2", "Test3"] + + - label: "Read attribute LIST With List of OCTET_STRING" + command: "readAttribute" + attribute: "list_octet_string" + response: + value: ["Test0", "Test1", "Test2", "Test3"] + + - label: "Write attribute LIST With List of LIST_STRUCT_OCTET_STRING" + command: "writeAttribute" + attribute: "list_struct_octet_string" + arguments: + value: + [ + { fabricIndex: 0, operationalCert: "Test0" }, + { fabricIndex: 1, operationalCert: "Test1" }, + { fabricIndex: 2, operationalCert: "Test2" }, + { fabricIndex: 3, operationalCert: "Test3" }, + ] + + - label: "Read attribute LIST With List of LIST_STRUCT_OCTET_STRING" + command: "readAttribute" + attribute: "list_struct_octet_string" + response: + value: + [ + { fabricIndex: 0, operationalCert: "Test0" }, + { fabricIndex: 1, operationalCert: "Test1" }, + { fabricIndex: 2, operationalCert: "Test2" }, + { fabricIndex: 3, operationalCert: "Test3" }, + ] + # Tests for Nullables and Optionals - label: "Send Test Command with optional arg set." diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 58aa77a11c96fb..cf563f6ed12fe2 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -477,6 +477,22 @@ CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & a attributePathParams.mClusterId = aClusterInfo.mClusterId; attributePathParams.mFieldId = aClusterInfo.mFieldId; + // TODO: Refactor WriteSingleClusterData and all dependent functions to take ConcreteAttributePath instead of ClusterInfo + // as the input argument. + AttributeAccessInterface * attrOverride = findAttributeAccessOverride(aClusterInfo.mEndpointId, aClusterInfo.mClusterId); + if (attrOverride != nullptr) + { + bool dataWrite; + ConcreteAttributePath path(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mFieldId); + + ReturnErrorOnFailure(attrOverride->Write(path, aReader, &dataWrite)); + + if (dataWrite) + { + return apWriteHandler->AddStatus(attributePathParams, Protocols::InteractionModel::Status::Success); + } + } + auto imCode = WriteSingleClusterDataInternal(aClusterInfo, aReader, apWriteHandler); return apWriteHandler->AddStatus(attributePathParams, imCode); }