diff --git a/examples/common/pigweed/rpc_services/Attributes.h b/examples/common/pigweed/rpc_services/Attributes.h index 99e348e3a205ba..261831ba9b6223 100644 --- a/examples/common/pigweed/rpc_services/Attributes.h +++ b/examples/common/pigweed/rpc_services/Attributes.h @@ -221,7 +221,7 @@ class Attributes : public pw_rpc::nanopb::Attributes::Service request.operationFlags.Set(app::DataModel::OperationFlags::kInternal); request.subjectDescriptor = &subjectDescriptor; - std::optional info = provider->GetClusterInfo(path); + std::optional info = provider->GetServerClusterInfo(path); if (!info.has_value()) { return ::pw::Status::NotFound(); diff --git a/src/app/AttributePathExpandIterator.cpp b/src/app/AttributePathExpandIterator.cpp index 96aa2772912139..419067db9ddf23 100644 --- a/src/app/AttributePathExpandIterator.cpp +++ b/src/app/AttributePathExpandIterator.cpp @@ -115,13 +115,13 @@ std::optional AttributePathExpandIterator::NextClusterId() { if (mpAttributePath->mValue.HasWildcardClusterId()) { - ClusterEntry entry = mDataModelProvider->FirstCluster(mOutputPath.mEndpointId); + ClusterEntry entry = mDataModelProvider->FirstServerCluster(mOutputPath.mEndpointId); return entry.IsValid() ? std::make_optional(entry.path.mClusterId) : std::nullopt; } // only return a cluster if it is valid const ConcreteClusterPath clusterPath(mOutputPath.mEndpointId, mpAttributePath->mValue.mClusterId); - if (!mDataModelProvider->GetClusterInfo(clusterPath).has_value()) + if (!mDataModelProvider->GetServerClusterInfo(clusterPath).has_value()) { return std::nullopt; } @@ -131,7 +131,7 @@ std::optional AttributePathExpandIterator::NextClusterId() VerifyOrReturnValue(mpAttributePath->mValue.HasWildcardClusterId(), std::nullopt); - ClusterEntry entry = mDataModelProvider->NextCluster(mOutputPath); + ClusterEntry entry = mDataModelProvider->NextServerCluster(mOutputPath); return entry.IsValid() ? std::make_optional(entry.path.mClusterId) : std::nullopt; } @@ -141,8 +141,8 @@ std::optional AttributePathExpandIterator::NextEndpointId() { if (mpAttributePath->mValue.HasWildcardEndpointId()) { - EndpointId id = mDataModelProvider->FirstEndpoint(); - return (id != kInvalidEndpointId) ? std::make_optional(id) : std::nullopt; + EndpointEntry ep = mDataModelProvider->FirstEndpoint(); + return (ep.id != kInvalidEndpointId) ? std::make_optional(ep.id) : std::nullopt; } return mpAttributePath->mValue.mEndpointId; @@ -150,8 +150,8 @@ std::optional AttributePathExpandIterator::NextEndpointId() VerifyOrReturnValue(mpAttributePath->mValue.HasWildcardEndpointId(), std::nullopt); - EndpointId id = mDataModelProvider->NextEndpoint(mOutputPath.mEndpointId); - return (id != kInvalidEndpointId) ? std::make_optional(id) : std::nullopt; + EndpointEntry ep = mDataModelProvider->NextEndpoint(mOutputPath.mEndpointId); + return (ep.id != kInvalidEndpointId) ? std::make_optional(ep.id) : std::nullopt; } void AttributePathExpandIterator::ResetCurrentCluster() diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 56acd096611565..78967c6d53c680 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -89,14 +89,14 @@ bool MayHaveAccessibleEventPathForEndpoint(DataModel::Provider * aProvider, Endp aSubjectDescriptor); } - DataModel::ClusterEntry clusterEntry = aProvider->FirstCluster(aEventPath.mEndpointId); + DataModel::ClusterEntry clusterEntry = aProvider->FirstServerCluster(aEventPath.mEndpointId); while (clusterEntry.IsValid()) { if (MayHaveAccessibleEventPathForEndpointAndCluster(clusterEntry.path, aEventPath, aSubjectDescriptor)) { return true; } - clusterEntry = aProvider->NextCluster(clusterEntry.path); + clusterEntry = aProvider->NextServerCluster(clusterEntry.path); } return false; @@ -112,10 +112,9 @@ bool MayHaveAccessibleEventPath(DataModel::Provider * aProvider, const EventPath return MayHaveAccessibleEventPathForEndpoint(aProvider, aEventPath.mEndpointId, aEventPath, subjectDescriptor); } - for (EndpointId endpointId = aProvider->FirstEndpoint(); endpointId != kInvalidEndpointId; - endpointId = aProvider->NextEndpoint(endpointId)) + for (DataModel::EndpointEntry ep = aProvider->FirstEndpoint(); ep.IsValid(); ep = aProvider->NextEndpoint(ep.id)) { - if (MayHaveAccessibleEventPathForEndpoint(aProvider, endpointId, aEventPath, subjectDescriptor)) + if (MayHaveAccessibleEventPathForEndpoint(aProvider, ep.id, aEventPath, subjectDescriptor)) { return true; } @@ -1793,17 +1792,16 @@ Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistenc // We failed, figure out why ... // - if (provider->GetClusterInfo(aCommandPath).has_value()) + if (provider->GetServerClusterInfo(aCommandPath).has_value()) { return Protocols::InteractionModel::Status::UnsupportedCommand; // cluster exists, so command is invalid } // At this point either cluster or endpoint does not exist. If we find the endpoint, then the cluster // is invalid - for (EndpointId endpoint = provider->FirstEndpoint(); endpoint != kInvalidEndpointId; - endpoint = provider->NextEndpoint(endpoint)) + for (DataModel::EndpointEntry ep = provider->FirstEndpoint(); ep.IsValid(); ep = provider->NextEndpoint(ep.id)) { - if (endpoint == aCommandPath.mEndpointId) + if (ep.id == aCommandPath.mEndpointId) { // endpoint exists, so cluster is invalid return Protocols::InteractionModel::Status::UnsupportedCluster; diff --git a/src/app/clusters/descriptor/descriptor.cpp b/src/app/clusters/descriptor/descriptor.cpp index a8e50387646d5d..409240e66a8b9e 100644 --- a/src/app/clusters/descriptor/descriptor.cpp +++ b/src/app/clusters/descriptor/descriptor.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -73,19 +75,13 @@ CHIP_ERROR DescriptorAttrAccess::ReadFeatureMap(EndpointId endpoint, AttributeVa CHIP_ERROR DescriptorAttrAccess::ReadTagListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([&endpoint](const auto & encoder) -> CHIP_ERROR { - Clusters::Descriptor::Structs::SemanticTagStruct::Type tag; - size_t index = 0; - CHIP_ERROR err = CHIP_NO_ERROR; - while ((err = GetSemanticTagForEndpointAtIndex(endpoint, index, tag)) == CHIP_NO_ERROR) + auto tag = InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetFirstSemanticTag(endpoint); + while (tag.has_value()) { - ReturnErrorOnFailure(encoder.Encode(tag)); - index++; + ReturnErrorOnFailure(encoder.Encode(tag.value())); + tag = InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetNextSemanticTag(endpoint, tag.value()); } - if (err == CHIP_ERROR_NOT_FOUND) - { - return CHIP_NO_ERROR; - } - return err; + return CHIP_NO_ERROR; }); } @@ -93,67 +89,63 @@ CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, Attribu { CHIP_ERROR err = CHIP_NO_ERROR; + auto endpointInfo = InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetEndpointInfo(endpoint); if (endpoint == 0x00) { err = aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR { - for (uint16_t index = 0; index < emberAfEndpointCount(); index++) + auto endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstEndpoint(); + while (endpointEntry.IsValid()) { - if (emberAfEndpointIndexIsEnabled(index)) + if (endpointEntry.id != 0) { - EndpointId endpointId = emberAfEndpointFromIndex(index); - if (endpointId == 0) - continue; - - ReturnErrorOnFailure(encoder.Encode(endpointId)); + ReturnErrorOnFailure(encoder.Encode(endpointEntry.id)); } + endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextEndpoint(endpointEntry.id); } - return CHIP_NO_ERROR; }); } - else if (IsFlatCompositionForEndpoint(endpoint)) + else if (endpointInfo.has_value() && + endpointInfo->compositionPattern == DataModel::EndpointCompositionPattern::kFullFamilyPattern) { err = aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR { - for (uint16_t index = 0; index < emberAfEndpointCount(); index++) + auto endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstEndpoint(); + while (endpointEntry.IsValid()) { - if (!emberAfEndpointIndexIsEnabled(index)) - continue; - - uint16_t childIndex = index; - while (childIndex != chip::kInvalidListIndex) + EndpointId parentEndpointId = endpointEntry.info.parentId; + while (parentEndpointId != chip::kInvalidEndpointId) { - EndpointId parentEndpointId = emberAfParentEndpointFromIndex(childIndex); - if (parentEndpointId == chip::kInvalidEndpointId) - break; - if (parentEndpointId == endpoint) { - ReturnErrorOnFailure(encoder.Encode(emberAfEndpointFromIndex(index))); + ReturnErrorOnFailure(encoder.Encode(endpointEntry.id)); break; } - - childIndex = emberAfIndexFromEndpoint(parentEndpointId); + auto parentEndpointInfo = + InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetEndpointInfo(parentEndpointId); + if (!parentEndpointInfo.has_value()) + { + break; + } + parentEndpointId = parentEndpointInfo->parentId; } + endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextEndpoint(endpointEntry.id); } return CHIP_NO_ERROR; }); } - else if (IsTreeCompositionForEndpoint(endpoint)) + else if (endpointInfo.has_value() && endpointInfo->compositionPattern == DataModel::EndpointCompositionPattern::kTreePattern) { err = aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR { - for (uint16_t index = 0; index < emberAfEndpointCount(); index++) + auto endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstEndpoint(); + while (endpointEntry.IsValid()) { - if (!emberAfEndpointIndexIsEnabled(index)) - continue; - - EndpointId parentEndpointId = emberAfParentEndpointFromIndex(index); - if (parentEndpointId == endpoint) + if (endpointEntry.info.parentId == endpoint) { - ReturnErrorOnFailure(encoder.Encode(emberAfEndpointFromIndex(index))); + ReturnErrorOnFailure(encoder.Encode(endpointEntry.id)); } + endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextEndpoint(endpointEntry.id); } - return CHIP_NO_ERROR; }); } @@ -165,16 +157,15 @@ CHIP_ERROR DescriptorAttrAccess::ReadDeviceAttribute(EndpointId endpoint, Attrib { CHIP_ERROR err = aEncoder.EncodeList([&endpoint](const auto & encoder) -> CHIP_ERROR { Descriptor::Structs::DeviceTypeStruct::Type deviceStruct; - CHIP_ERROR err2; - auto deviceTypeList = emberAfDeviceTypeListFromEndpoint(endpoint, err2); - ReturnErrorOnFailure(err2); + auto deviceType = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstDeviceType(endpoint); - for (auto & deviceType : deviceTypeList) + while (deviceType.has_value()) { - deviceStruct.deviceType = deviceType.deviceId; - deviceStruct.revision = deviceType.deviceVersion; + deviceStruct.deviceType = deviceType->deviceTypeId; + deviceStruct.revision = deviceType->deviceTypeRevision; ReturnErrorOnFailure(encoder.Encode(deviceStruct)); + deviceType = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextDeviceType(endpoint, *deviceType); } return CHIP_NO_ERROR; @@ -186,12 +177,24 @@ CHIP_ERROR DescriptorAttrAccess::ReadDeviceAttribute(EndpointId endpoint, Attrib CHIP_ERROR DescriptorAttrAccess::ReadClientServerAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder, bool server) { CHIP_ERROR err = aEncoder.EncodeList([&endpoint, server](const auto & encoder) -> CHIP_ERROR { - uint8_t clusterCount = emberAfClusterCount(endpoint, server); - - for (uint8_t clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) + if (server) { - const EmberAfCluster * cluster = emberAfGetNthCluster(endpoint, clusterIndex, server); - ReturnErrorOnFailure(encoder.Encode(cluster->clusterId)); + auto clusterEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstServerCluster(endpoint); + while (clusterEntry.IsValid()) + { + ReturnErrorOnFailure(encoder.Encode(clusterEntry.path.mClusterId)); + clusterEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextServerCluster(clusterEntry.path); + } + } + else + { + ConcreteClusterPath clusterPath = + InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstClientCluster(endpoint); + while (clusterPath.HasValidIds()) + { + ReturnErrorOnFailure(encoder.Encode(clusterPath.mClusterId)); + clusterPath = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextClientCluster(clusterPath); + } } return CHIP_NO_ERROR; diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp index b0815da48e0c08..4eae3de1e247db 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp @@ -128,10 +128,8 @@ std::variant LoadClusterInfo(const ConcreteC } DataModel::ClusterInfo info(*versionPtr); - // TODO: set entry flags: // info->flags.Set(ClusterQualityFlags::kDiagnosticsData) - return info; } @@ -194,6 +192,23 @@ DataModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const Emb return DataModel::ClusterEntry::kInvalid; } +ClusterId FirstClientClusterId(const EmberAfEndpointType * endpoint, unsigned start_index, unsigned & found_index) +{ + for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) + { + const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; + if (!cluster.IsClient()) + { + continue; + } + + found_index = cluster_idx; + return cluster.clusterId; + } + + return kInvalidClusterId; +} + /// Load the attribute information into the specified destination /// /// `info` is assumed to be default-constructed/clear (i.e. this sets flags, but does not reset them). @@ -255,8 +270,8 @@ DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & ot { DataModel::DeviceTypeEntry entry; - entry.deviceTypeId = other.deviceId; - entry.deviceTypeVersion = other.deviceVersion; + entry.deviceTypeId = other.deviceId; + entry.deviceTypeRevision = other.deviceVersion; return entry; } @@ -265,7 +280,7 @@ DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & ot // so you must do `a == b` and the `b == a` will not work. bool operator==(const DataModel::DeviceTypeEntry & a, const EmberAfDeviceType & b) { - return (a.deviceTypeId == b.deviceId) && (a.deviceTypeVersion == b.deviceVersion); + return (a.deviceTypeId == b.deviceId) && (a.deviceTypeRevision == b.deviceVersion); } /// Find the `index` where one of the following holds: @@ -300,6 +315,96 @@ unsigned FindNextDeviceTypeIndex(Span types, const Data const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); +std::optional GetEndpointInfoAtIndex(uint16_t endpointIndex) +{ + VerifyOrReturnValue(emberAfEndpointIndexIsEnabled(endpointIndex), std::nullopt); + EndpointId parent = emberAfParentEndpointFromIndex(endpointIndex); + if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kFullFamily) + { + return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kFullFamilyPattern); + } + if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kTree) + { + return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kTreePattern); + } + return std::nullopt; +} + +DataModel::EndpointEntry FirstEndpointEntry(unsigned start_index, uint16_t & found_index) +{ + // find the first enabled index after the start index + const uint16_t lastEndpointIndex = emberAfEndpointCount(); + for (uint16_t endpoint_idx = static_cast(start_index); endpoint_idx < lastEndpointIndex; endpoint_idx++) + { + if (emberAfEndpointIndexIsEnabled(endpoint_idx)) + { + found_index = endpoint_idx; + DataModel::EndpointEntry endpointEntry = DataModel::EndpointEntry::kInvalid; + endpointEntry.id = emberAfEndpointFromIndex(endpoint_idx); + auto endpointInfo = GetEndpointInfoAtIndex(endpoint_idx); + // The endpoint info should have value as this endpoint should be valid at this time + VerifyOrDie(endpointInfo.has_value()); + endpointEntry.info = endpointInfo.value(); + return endpointEntry; + } + } + + // No enabled endpoint found. Give up + return DataModel::EndpointEntry::kInvalid; +} + +bool operator==(const DataModel::Provider::SemanticTag & tagA, const DataModel::Provider::SemanticTag & tagB) +{ + // Label is an optional and nullable value of CharSpan. Optional and Nullable have overload for ==, + // But `==` is deleted for CharSpan. Here we only check whether the string is the same. + if (tagA.label.HasValue() != tagB.label.HasValue()) + { + return false; + } + if (tagA.label.HasValue()) + { + if (tagA.label.Value().IsNull() != tagB.label.Value().IsNull()) + { + return false; + } + if (!tagA.label.Value().IsNull()) + { + if (!tagA.label.Value().Value().data_equal(tagB.label.Value().Value())) + { + return false; + } + } + } + return (tagA.tag == tagB.tag) && (tagA.mfgCode == tagB.mfgCode) && (tagA.namespaceID == tagB.namespaceID); +} + +std::optional FindNextSemanticTagIndex(EndpointId endpoint, const DataModel::Provider::SemanticTag & previous, + unsigned hintWherePreviousMayBe) +{ + DataModel::Provider::SemanticTag hintTag; + // Check whether the hint is the previous tag + if (GetSemanticTagForEndpointAtIndex(endpoint, hintWherePreviousMayBe, hintTag) == CHIP_NO_ERROR) + { + if (previous == hintTag) + { + return std::make_optional(hintWherePreviousMayBe + 1); + } + } + // If the hint is not the previous tag, iterate over all the tags to find the index for the previous tag + unsigned index = 0; + // Ensure that the next index is in the range + while (GetSemanticTagForEndpointAtIndex(endpoint, index + 1, hintTag) == CHIP_NO_ERROR && + GetSemanticTagForEndpointAtIndex(endpoint, index, hintTag) == CHIP_NO_ERROR) + { + if (previous == hintTag) + { + return std::make_optional(index + 1); + } + index++; + } + return std::nullopt; +} + } // namespace std::optional CodegenDataModelProvider::EmberCommandListIterator::First(const CommandId * list) @@ -397,21 +502,19 @@ bool CodegenDataModelProvider::EndpointExists(EndpointId endpoint) return (emberAfIndexFromEndpoint(endpoint) != kEmberInvalidEndpointIndex); } -EndpointId CodegenDataModelProvider::FirstEndpoint() +std::optional CodegenDataModelProvider::GetEndpointInfo(EndpointId endpoint) { - // find the first enabled index - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) + std::optional endpoint_idx = TryFindEndpointIndex(endpoint); + if (endpoint_idx.has_value()) { - if (emberAfEndpointIndexIsEnabled(endpoint_idx)) - { - mEndpointIterationHint = endpoint_idx; - return emberAfEndpointFromIndex(endpoint_idx); - } + return GetEndpointInfoAtIndex(static_cast(*endpoint_idx)); } + return std::nullopt; +} - // No enabled endpoint found. Give up - return kInvalidEndpointId; +DataModel::EndpointEntry CodegenDataModelProvider::FirstEndpoint() +{ + return FirstEndpointEntry(0, mEndpointIterationHint); } std::optional CodegenDataModelProvider::TryFindEndpointIndex(EndpointId id) const @@ -434,51 +537,41 @@ std::optional CodegenDataModelProvider::TryFindEndpointIndex(EndpointI return std::make_optional(idx); } -EndpointId CodegenDataModelProvider::NextEndpoint(EndpointId before) +DataModel::EndpointEntry CodegenDataModelProvider::NextEndpoint(EndpointId before) { - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - std::optional before_idx = TryFindEndpointIndex(before); if (!before_idx.has_value()) { - return kInvalidEndpointId; + return DataModel::EndpointEntry::kInvalid; } - - // find the first enabled index - for (uint16_t endpoint_idx = static_cast(*before_idx + 1); endpoint_idx < lastEndpointIndex; endpoint_idx++) - { - if (emberAfEndpointIndexIsEnabled(endpoint_idx)) - { - mEndpointIterationHint = endpoint_idx; - return emberAfEndpointFromIndex(endpoint_idx); - } - } - - // No enabled enpoint after "before" was found, give up - return kInvalidEndpointId; + return FirstEndpointEntry(*before_idx + 1, mEndpointIterationHint); } -DataModel::ClusterEntry CodegenDataModelProvider::FirstCluster(EndpointId endpointId) +DataModel::ClusterEntry CodegenDataModelProvider::FirstServerCluster(EndpointId endpointId) { const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid); VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid); VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid); - return FirstServerClusterEntry(endpointId, endpoint, 0, mClusterIterationHint); + return FirstServerClusterEntry(endpointId, endpoint, 0, mServerClusterIterationHint); } -std::optional CodegenDataModelProvider::TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, - ClusterId id) const +std::optional CodegenDataModelProvider::TryFindClusterIndex(const EmberAfEndpointType * endpoint, ClusterId id, + ClusterSide side) const { const unsigned clusterCount = endpoint->clusterCount; + unsigned hint = side == ClusterSide::kServer ? mServerClusterIterationHint : mClientClusterIterationHint; - if (mClusterIterationHint < clusterCount) + if (hint < clusterCount) { - const EmberAfCluster & cluster = endpoint->cluster[mClusterIterationHint]; - if (cluster.IsServer() && (cluster.clusterId == id)) + const EmberAfCluster & cluster = endpoint->cluster[hint]; + if (((side == ClusterSide::kServer) && cluster.IsServer()) || ((side == ClusterSide::kClient) && cluster.IsClient())) { - return std::make_optional(mClusterIterationHint); + if (cluster.clusterId == id) + { + return std::make_optional(hint); + } } } @@ -488,7 +581,11 @@ std::optional CodegenDataModelProvider::TryFindServerClusterIndex(cons for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++) { const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (cluster.IsServer() && (cluster.clusterId == id)) + if (((side == ClusterSide::kServer) && !cluster.IsServer()) || ((side == ClusterSide::kClient) && !cluster.IsClient())) + { + continue; + } + if (cluster.clusterId == id) { return std::make_optional(cluster_idx); } @@ -497,7 +594,7 @@ std::optional CodegenDataModelProvider::TryFindServerClusterIndex(cons return std::nullopt; } -DataModel::ClusterEntry CodegenDataModelProvider::NextCluster(const ConcreteClusterPath & before) +DataModel::ClusterEntry CodegenDataModelProvider::NextServerCluster(const ConcreteClusterPath & before) { // TODO: This search still seems slow (ember will loop). Should use index hints as long // as ember API supports it @@ -507,16 +604,16 @@ DataModel::ClusterEntry CodegenDataModelProvider::NextCluster(const ConcreteClus VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid); VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid); - std::optional cluster_idx = TryFindServerClusterIndex(endpoint, before.mClusterId); + std::optional cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kServer); if (!cluster_idx.has_value()) { return DataModel::ClusterEntry::kInvalid; } - return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mClusterIterationHint); + return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mServerClusterIterationHint); } -std::optional CodegenDataModelProvider::GetClusterInfo(const ConcreteClusterPath & path) +std::optional CodegenDataModelProvider::GetServerClusterInfo(const ConcreteClusterPath & path) { const EmberAfCluster * cluster = FindServerCluster(path); @@ -537,6 +634,35 @@ std::optional CodegenDataModelProvider::GetClusterInfo(c return std::make_optional(std::get(info)); } +ConcreteClusterPath CodegenDataModelProvider::FirstClientCluster(EndpointId endpointId) +{ + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); + VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId)); + VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(endpointId, kInvalidClusterId)); + VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId)); + + return ConcreteClusterPath(endpointId, FirstClientClusterId(endpoint, 0, mClientClusterIterationHint)); +} + +ConcreteClusterPath CodegenDataModelProvider::NextClientCluster(const ConcreteClusterPath & before) +{ + // TODO: This search still seems slow (ember will loop). Should use index hints as long + // as ember API supports it + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); + + VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId)); + VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId)); + VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId)); + + std::optional cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kClient); + if (!cluster_idx.has_value()) + { + return ConcreteClusterPath(before.mEndpointId, kInvalidClusterId); + } + + return ConcreteClusterPath(before.mEndpointId, FirstClientClusterId(endpoint, *cluster_idx + 1, mClientClusterIterationHint)); +} + DataModel::AttributeEntry CodegenDataModelProvider::FirstAttribute(const ConcreteClusterPath & path) { const EmberAfCluster * cluster = FindServerCluster(path); @@ -772,5 +898,29 @@ std::optional CodegenDataModelProvider::NextDeviceTy return DeviceTypeEntryFromEmber(deviceTypes[idx]); } +std::optional CodegenDataModelProvider::GetFirstSemanticTag(EndpointId endpoint) +{ + Clusters::Descriptor::Structs::SemanticTagStruct::Type tag; + // we start at the beginning + mSemanticTagIterationHint = 0; + if (GetSemanticTagForEndpointAtIndex(endpoint, 0, tag) == CHIP_NO_ERROR) + { + return std::make_optional(tag); + } + return std::nullopt; +} + +std::optional CodegenDataModelProvider::GetNextSemanticTag(EndpointId endpoint, + const SemanticTag & previous) +{ + Clusters::Descriptor::Structs::SemanticTagStruct::Type tag; + std::optional idx = FindNextSemanticTagIndex(endpoint, previous, mSemanticTagIterationHint); + if (idx.has_value() && GetSemanticTagForEndpointAtIndex(endpoint, *idx, tag) == CHIP_NO_ERROR) + { + return std::make_optional(tag); + } + return std::nullopt; +} + } // namespace app } // namespace chip diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider.h b/src/app/codegen-data-model-provider/CodegenDataModelProvider.h index 59b91747a0ef07..35193a492f819b 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider.h +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider.h @@ -149,17 +149,24 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider chip::TLV::TLVReader & input_arguments, CommandHandler * handler) override; /// attribute tree iteration - EndpointId FirstEndpoint() override; - EndpointId NextEndpoint(EndpointId before) override; + DataModel::EndpointEntry FirstEndpoint() override; + DataModel::EndpointEntry NextEndpoint(EndpointId before) override; + std::optional GetEndpointInfo(EndpointId endpoint) override; bool EndpointExists(EndpointId endpoint) override; std::optional FirstDeviceType(EndpointId endpoint) override; std::optional NextDeviceType(EndpointId endpoint, const DataModel::DeviceTypeEntry & previous) override; - DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override; - DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; - std::optional GetClusterInfo(const ConcreteClusterPath & path) override; + std::optional GetFirstSemanticTag(EndpointId endpoint) override; + std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override; + + DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override; + DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override; + std::optional GetServerClusterInfo(const ConcreteClusterPath & path) override; + + ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override; + ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override; DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; @@ -175,10 +182,12 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider private: // Iteration is often done in a tight loop going through all values. // To avoid N^2 iterations, cache a hint of where something is positioned - uint16_t mEndpointIterationHint = 0; - unsigned mClusterIterationHint = 0; - unsigned mAttributeIterationHint = 0; - unsigned mDeviceTypeIterationHint = 0; + uint16_t mEndpointIterationHint = 0; + unsigned mServerClusterIterationHint = 0; + unsigned mClientClusterIterationHint = 0; + unsigned mAttributeIterationHint = 0; + unsigned mDeviceTypeIterationHint = 0; + unsigned mSemanticTagIterationHint = 0; EmberCommandListIterator mAcceptedCommandsIterator; EmberCommandListIterator mGeneratedCommandsIterator; @@ -191,6 +200,13 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider ClusterReference(const ConcreteClusterPath p, const EmberAfCluster * c) : path(p), cluster(c) {} }; + + enum class ClusterSide : uint8_t + { + kServer, + kClient, + }; + std::optional mPreviouslyFoundCluster; unsigned mEmberMetadataStructureGeneration = 0; @@ -203,7 +219,8 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId id) const; /// Find the index of the given cluster id - std::optional TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id) const; + std::optional TryFindClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id, + ClusterSide clusterSide) const; /// Find the index of the given endpoint id std::optional TryFindEndpointIndex(chip::EndpointId id) const; diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp index de2f8886476707..7dcfe7feccff4d 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp @@ -190,7 +190,7 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const Dat if (request.path.mDataVersion.HasValue()) { - std::optional clusterInfo = GetClusterInfo(request.path); + std::optional clusterInfo = GetServerClusterInfo(request.path); if (!clusterInfo.has_value()) { ChipLogError(DataManagement, "Unable to get cluster info for Endpoint 0x%x, Cluster " ChipLogFormatMEI, diff --git a/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp index c9b2a1b99a375c..50d8ec67df3deb 100644 --- a/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp @@ -96,6 +96,17 @@ constexpr uint8_t kDeviceTypeId2Version = 11; constexpr DeviceTypeId kDeviceTypeId3 = 3; constexpr uint8_t kDeviceTypeId3Version = 33; +constexpr uint8_t kNamespaceID1 = 123; +constexpr uint8_t kTag1 = 10; +constexpr char kLabel1[] = "Label1"; + +constexpr uint8_t kNamespaceID2 = 254; +constexpr uint8_t kTag2 = 22; +constexpr char kLabel2[] = "Label2"; + +constexpr uint8_t kNamespaceID3 = 3; +constexpr uint8_t kTag3 = 32; + static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); @@ -280,10 +291,16 @@ const MockNodeConfig gTestNodeConfig({ MockClusterConfig(MockClusterId(2), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), }), + MockClusterConfig(MockClusterId(3), {}, {}, {}, {}, BitMask().Set(MockClusterSide::kClient)), + MockClusterConfig(MockClusterId(4), {}, {}, {}, {}, BitMask().Set(MockClusterSide::kClient)), }, { { kDeviceTypeId1, kDeviceTypeId1Version}, { kDeviceTypeId2, kDeviceTypeId2Version}, { kDeviceTypeId3, kDeviceTypeId3Version}, + },{ + { MakeNullable(VendorId::TestVendor1), kNamespaceID1, kTag1, MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel1)))}, + { Nullable(), kNamespaceID2, kTag2, MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel2)))}, + { MakeNullable(VendorId::TestVendor3), kNamespaceID3, kTag3, NullOptional}, }), MockEndpointConfig(kMockEndpoint2, { MockClusterConfig(MockClusterId(1), { @@ -308,11 +325,14 @@ const MockNodeConfig gTestNodeConfig({ }, /* attributes */ {}, /* events */ {11}, /* acceptedCommands */ - {4, 6} /* generatedCommands */ + {4, 6}, /* generatedCommands */ + BitMask().Set(MockClusterSide::kClient).Set(MockClusterSide::kServer) ), + MockClusterConfig(MockClusterId(4), {}, {}, {}, {}, MockClusterSide::kClient), }, { { kDeviceTypeId2, kDeviceTypeId2Version}, - }), + }, {}, + EndpointComposition::kTree), MockEndpointConfig(kMockEndpoint3, { MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), @@ -875,43 +895,109 @@ TEST(TestCodegenModelViaMocks, IterateOverEndpoints) CodegenDataModelProviderWithContext model; // This iteration relies on the hard-coding that occurs when mock_ember is used - EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + EndpointEntry ep = model.FirstEndpoint(); + EXPECT_EQ(ep.id, kMockEndpoint1); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + ep = model.NextEndpoint(kMockEndpoint1); + EXPECT_EQ(ep.id, kMockEndpoint2); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTreePattern); + ep = model.NextEndpoint(kMockEndpoint2); + EXPECT_EQ(ep.id, kMockEndpoint3); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + ep = model.NextEndpoint(kMockEndpoint3); + EXPECT_EQ(ep.id, kInvalidEndpointId); /// Some out of order requests should work as well - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); - EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); + ep = model.NextEndpoint(kMockEndpoint2); + EXPECT_EQ(ep.id, kMockEndpoint3); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + ep = model.NextEndpoint(kMockEndpoint2); + EXPECT_EQ(ep.id, kMockEndpoint3); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + ep = model.NextEndpoint(kMockEndpoint1); + EXPECT_EQ(ep.id, kMockEndpoint2); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTreePattern); + ep = model.NextEndpoint(kMockEndpoint1); + EXPECT_EQ(ep.id, kMockEndpoint2); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTreePattern); + ep = model.NextEndpoint(kMockEndpoint2); + EXPECT_EQ(ep.id, kMockEndpoint3); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + ep = model.NextEndpoint(kMockEndpoint1); + EXPECT_EQ(ep.id, kMockEndpoint2); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTreePattern); + ep = model.NextEndpoint(kMockEndpoint3); + EXPECT_EQ(ep.id, kInvalidEndpointId); + ep = model.NextEndpoint(kMockEndpoint3); + EXPECT_EQ(ep.id, kInvalidEndpointId); + ep = model.FirstEndpoint(); + EXPECT_EQ(ep.id, kMockEndpoint1); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + ep = model.FirstEndpoint(); + EXPECT_EQ(ep.id, kMockEndpoint1); + EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); + EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamilyPattern); + + // invalid endpoiunts + ep = model.NextEndpoint(kInvalidEndpointId); + EXPECT_EQ(ep.id, kInvalidEndpointId); + ep = model.NextEndpoint(987u); + EXPECT_EQ(ep.id, kInvalidEndpointId); +} + +TEST(TestCodegenModelViaMocks, GetEndpointInfo) +{ + UseMockNodeConfig config(gTestNodeConfig); + CodegenDataModelProviderWithContext model; + + std::optional info = model.GetEndpointInfo(kMockEndpoint1); + ASSERT_TRUE(info.has_value()); + EXPECT_EQ(info->parentId, kInvalidEndpointId); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(info->compositionPattern, // NOLINT(bugprone-unchecked-optional-access) + EndpointCompositionPattern::kFullFamilyPattern); + info = model.GetEndpointInfo(kMockEndpoint2); + ASSERT_TRUE(info.has_value()); + EXPECT_EQ(info->parentId, kInvalidEndpointId); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(info->compositionPattern, // NOLINT(bugprone-unchecked-optional-access) + EndpointCompositionPattern::kTreePattern); + info = model.GetEndpointInfo(kMockEndpoint3); + ASSERT_TRUE(info.has_value()); + EXPECT_EQ(info->parentId, kInvalidEndpointId); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(info->compositionPattern, // NOLINT(bugprone-unchecked-optional-access) + EndpointCompositionPattern::kFullFamilyPattern); // invalid endpoiunts - EXPECT_EQ(model.NextEndpoint(kInvalidEndpointId), kInvalidEndpointId); - EXPECT_EQ(model.NextEndpoint(987u), kInvalidEndpointId); + info = model.GetEndpointInfo(kInvalidEndpointId); + EXPECT_FALSE(info.has_value()); + info = model.GetEndpointInfo(987u); + EXPECT_FALSE(info.has_value()); } -TEST(TestCodegenModelViaMocks, IterateOverClusters) +TEST(TestCodegenModelViaMocks, IterateOverServerClusters) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; chip::Test::ResetVersion(); - EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); - EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); - EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds()); - EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds()); + EXPECT_FALSE(model.FirstServerCluster(kEndpointIdThatIsMissing).path.HasValidIds()); + EXPECT_FALSE(model.FirstServerCluster(kInvalidEndpointId).path.HasValidIds()); + EXPECT_FALSE(model.NextServerCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds()); + EXPECT_FALSE(model.NextServerCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); + EXPECT_FALSE(model.NextServerCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds()); // mock endpoint 1 has 2 mock clusters: 1 and 2 - ClusterEntry entry = model.FirstCluster(kMockEndpoint1); + ClusterEntry entry = model.FirstServerCluster(kMockEndpoint1); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); @@ -920,31 +1006,31 @@ TEST(TestCodegenModelViaMocks, IterateOverClusters) chip::Test::BumpVersion(); - entry = model.NextCluster(entry.path); + entry = model.NextServerCluster(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.info.dataVersion, 1u); EXPECT_EQ(entry.info.flags.Raw(), 0u); - entry = model.NextCluster(entry.path); + entry = model.NextServerCluster(entry.path); EXPECT_FALSE(entry.path.HasValidIds()); // mock endpoint 3 has 4 mock clusters: 1 through 4 - entry = model.FirstCluster(kMockEndpoint3); + entry = model.FirstServerCluster(kMockEndpoint3); for (uint16_t clusterId = 1; clusterId <= 4; clusterId++) { ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint3); EXPECT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); - entry = model.NextCluster(entry.path); + entry = model.NextServerCluster(entry.path); } EXPECT_FALSE(entry.path.HasValidIds()); // repeat calls should work for (int i = 0; i < 10; i++) { - entry = model.FirstCluster(kMockEndpoint1); + entry = model.FirstServerCluster(kMockEndpoint1); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); @@ -952,14 +1038,14 @@ TEST(TestCodegenModelViaMocks, IterateOverClusters) for (int i = 0; i < 10; i++) { - ClusterEntry nextEntry = model.NextCluster(entry.path); + ClusterEntry nextEntry = model.NextServerCluster(entry.path); ASSERT_TRUE(nextEntry.path.HasValidIds()); EXPECT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); } } -TEST(TestCodegenModelViaMocks, GetClusterInfo) +TEST(TestCodegenModelViaMocks, GetServerClusterInfo) { UseMockNodeConfig config(gTestNodeConfig); @@ -967,24 +1053,78 @@ TEST(TestCodegenModelViaMocks, GetClusterInfo) chip::Test::ResetVersion(); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value()); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value()); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value()); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value()); + ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value()); + ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value()); + ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value()); + ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value()); // now get the value - std::optional info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + std::optional info = model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); ASSERT_TRUE(info.has_value()); EXPECT_EQ(info->dataVersion, 0u); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) chip::Test::BumpVersion(); - info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + info = model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); ASSERT_TRUE(info.has_value()); EXPECT_EQ(info->dataVersion, 1u); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) } +TEST(TestCodegenModelViaMocks, IterateOverClientClusters) +{ + UseMockNodeConfig config(gTestNodeConfig); + CodegenDataModelProviderWithContext model; + + EXPECT_FALSE(model.FirstClientCluster(kEndpointIdThatIsMissing).HasValidIds()); + EXPECT_FALSE(model.FirstClientCluster(kInvalidEndpointId).HasValidIds()); + EXPECT_FALSE(model.NextClientCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).HasValidIds()); + EXPECT_FALSE(model.NextClientCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds()); + EXPECT_FALSE(model.NextClientCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).HasValidIds()); + + // mock endpoint 1 has 2 mock client clusters: 3 and 4 + ConcreteClusterPath path = model.FirstClientCluster(kMockEndpoint1); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(path.mClusterId, MockClusterId(3)); + + path = model.NextClientCluster(path); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(path.mClusterId, MockClusterId(4)); + + path = model.NextClientCluster(path); + EXPECT_FALSE(path.HasValidIds()); + + // mock endpoint 2 has 1 mock client clusters: 3(has server side at the same time) and 4 + path = model.FirstClientCluster(kMockEndpoint2); + for (uint16_t clusterId = 3; clusterId <= 4; clusterId++) + { + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(path.mClusterId, MockClusterId(clusterId)); + path = model.NextClientCluster(path); + } + EXPECT_FALSE(path.HasValidIds()); + + // repeat calls should work + for (int i = 0; i < 10; i++) + { + path = model.FirstClientCluster(kMockEndpoint1); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(path.mClusterId, MockClusterId(3)); + } + + for (int i = 0; i < 10; i++) + { + ConcreteClusterPath nextPath = model.NextClientCluster(path); + ASSERT_TRUE(nextPath.HasValidIds()); + EXPECT_EQ(nextPath.mEndpointId, kMockEndpoint1); + EXPECT_EQ(nextPath.mClusterId, MockClusterId(4)); + } +} + TEST(TestCodegenModelViaMocks, IterateOverAttributes) { UseMockNodeConfig config(gTestNodeConfig); @@ -2569,15 +2709,15 @@ TEST(TestCodegenModelViaMocks, DeviceTypeIteration) // Mock endpoint 1 has 3 device types std::optional entry = model.FirstDeviceType(kMockEndpoint1); ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version })); + std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version })); + std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version })); + std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_FALSE(entry.has_value()); @@ -2585,19 +2725,19 @@ TEST(TestCodegenModelViaMocks, DeviceTypeIteration) // Mock endpoint 2 has 1 device types entry = model.FirstDeviceType(kMockEndpoint2); ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version })); + std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint2, *entry); ASSERT_FALSE(entry.has_value()); // out of order query works - entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }); + entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }); entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version })); + std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version })); // invalid query fails - entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version }); + entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version }); entry = model.NextDeviceType(kMockEndpoint2, *entry); ASSERT_FALSE(entry.has_value()); @@ -2605,3 +2745,60 @@ TEST(TestCodegenModelViaMocks, DeviceTypeIteration) entry = model.FirstDeviceType(kMockEndpoint3); ASSERT_FALSE(entry.has_value()); } + +TEST(TestCodegenModelViaMocks, SemanticTagIteration) +{ + UseMockNodeConfig config(gTestNodeConfig); + CodegenDataModelProviderWithContext model; + + // Mock endpoint 1 has 3 semantic tags + std::optional tag = model.GetFirstSemanticTag(kMockEndpoint1); + ASSERT_TRUE(tag.has_value()); + EXPECT_EQ(tag->mfgCode, MakeNullable(VendorId::TestVendor1)); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->namespaceID, kNamespaceID1); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->tag, kTag1); // NOLINT(bugprone-unchecked-optional-access) + ASSERT_TRUE(tag->label.HasValue() && (!tag->label.Value().IsNull())); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE( + tag->label.Value().Value().data_equal(CharSpan::fromCharString(kLabel1))); // NOLINT(bugprone-unchecked-optional-access) + tag = model.GetNextSemanticTag(kMockEndpoint1, *tag); // NOLINT(bugprone-unchecked-optional-access) + ASSERT_TRUE(tag.has_value()); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE(tag->mfgCode.IsNull()); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->namespaceID, kNamespaceID2); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->tag, kTag2); // NOLINT(bugprone-unchecked-optional-access) + ASSERT_TRUE(tag->label.HasValue() && (!tag->label.Value().IsNull())); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE( + tag->label.Value().Value().data_equal(CharSpan::fromCharString(kLabel2))); // NOLINT(bugprone-unchecked-optional-access) + tag = model.GetNextSemanticTag(kMockEndpoint1, *tag); // NOLINT(bugprone-unchecked-optional-access) + ASSERT_TRUE(tag.has_value()); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->mfgCode, MakeNullable(VendorId::TestVendor3)); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->namespaceID, kNamespaceID3); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->tag, kTag3); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_FALSE(tag->label.HasValue()); // NOLINT(bugprone-unchecked-optional-access) + tag = model.GetNextSemanticTag(kMockEndpoint1, *tag); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_FALSE(tag.has_value()); + + // out of order query works + DataModel::Provider::SemanticTag existTag = { + .mfgCode = MakeNullable(VendorId::TestVendor1), + .namespaceID = kNamespaceID1, + .tag = kTag1, + .label = MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel1))), + }; + tag = model.GetNextSemanticTag(kMockEndpoint1, existTag); + ASSERT_TRUE(tag.has_value()); + EXPECT_TRUE(tag->mfgCode.IsNull()); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->namespaceID, kNamespaceID2); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(tag->tag, kTag2); // NOLINT(bugprone-unchecked-optional-access) + ASSERT_TRUE(tag->label.HasValue() && (!tag->label.Value().IsNull())); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_TRUE( + tag->label.Value().Value().data_equal(CharSpan::fromCharString(kLabel2))); // NOLINT(bugprone-unchecked-optional-access) + + // invalid query fails + existTag.tag = kTag2; + tag = model.GetNextSemanticTag(kMockEndpoint1, existTag); + ASSERT_FALSE(tag.has_value()); + + // empty endpoint works + tag = model.GetFirstSemanticTag(kMockEndpoint2); + ASSERT_FALSE(tag.has_value()); +} diff --git a/src/app/data-model-provider/MetadataTypes.cpp b/src/app/data-model-provider/MetadataTypes.cpp index ed3e2667ddc9d7..2729698c49b1ed 100644 --- a/src/app/data-model-provider/MetadataTypes.cpp +++ b/src/app/data-model-provider/MetadataTypes.cpp @@ -30,12 +30,14 @@ const ClusterEntry ClusterEntry::kInvalid{ .info = ClusterInfo(0 /* version */), // version of invalid cluster entry does not matter }; +const EndpointEntry EndpointEntry::kInvalid{ .id = kInvalidEndpointId, .info = EndpointInfo(kInvalidEndpointId) }; + // A default implementation if just first/next exist bool ProviderMetadataTree::EndpointExists(EndpointId endpoint) { - for (EndpointId id = FirstEndpoint(); id != kInvalidEndpointId; id = NextEndpoint(id)) + for (EndpointEntry ep = FirstEndpoint(); ep.IsValid(); ep = NextEndpoint(ep.id)) { - if (id == endpoint) + if (ep.id == endpoint) { return true; } diff --git a/src/app/data-model-provider/MetadataTypes.h b/src/app/data-model-provider/MetadataTypes.h index c1d173c985ee7a..ab9005412e5261 100644 --- a/src/app/data-model-provider/MetadataTypes.h +++ b/src/app/data-model-provider/MetadataTypes.h @@ -20,16 +20,46 @@ #include #include +#include #include #include #include +#include #include #include +#include namespace chip { namespace app { namespace DataModel { +enum class EndpointCompositionPattern : uint8_t +{ + kTreePattern = 0x1, + kFullFamilyPattern = 0x2, +}; + +struct EndpointInfo +{ + // kInvalidEndpointId if there is no explicit parent endpoint (which means the parent is endpoint 0, + // for endpoints other than endpoint 0). + EndpointId parentId; + EndpointCompositionPattern compositionPattern; + + explicit EndpointInfo(EndpointId parent) : parentId(parent), compositionPattern(EndpointCompositionPattern::kFullFamilyPattern) + {} + explicit EndpointInfo(EndpointId parent, EndpointCompositionPattern pattern) : parentId(parent), compositionPattern(pattern) {} +}; + +struct EndpointEntry +{ + EndpointId id; + EndpointInfo info; + + bool IsValid() const { return id != kInvalidEndpointId; } + static const EndpointEntry kInvalid; +}; + enum class ClusterQualityFlags : uint32_t { kDiagnosticsData = 0x0001, // `K` quality, may be filtered out in subscriptions @@ -111,11 +141,11 @@ struct CommandEntry struct DeviceTypeEntry { DeviceTypeId deviceTypeId; - uint8_t deviceTypeVersion; + uint8_t deviceTypeRevision; bool operator==(const DeviceTypeEntry & other) const { - return (deviceTypeId == other.deviceTypeId) && (deviceTypeVersion == other.deviceTypeVersion); + return (deviceTypeId == other.deviceTypeId) && (deviceTypeRevision == other.deviceTypeRevision); } }; @@ -140,18 +170,31 @@ class ProviderMetadataTree public: virtual ~ProviderMetadataTree() = default; - virtual EndpointId FirstEndpoint() = 0; - virtual EndpointId NextEndpoint(EndpointId before) = 0; + // This iteration will list all the endpoints in the data model + virtual EndpointEntry FirstEndpoint() = 0; + virtual EndpointEntry NextEndpoint(EndpointId before) = 0; + virtual std::optional GetEndpointInfo(EndpointId id) = 0; virtual bool EndpointExists(EndpointId id); // This iteration describes device types registered on an endpoint virtual std::optional FirstDeviceType(EndpointId endpoint) = 0; virtual std::optional NextDeviceType(EndpointId endpoint, const DeviceTypeEntry & previous) = 0; - // This iteration will list all clusters on a given endpoint - virtual ClusterEntry FirstCluster(EndpointId endpoint) = 0; - virtual ClusterEntry NextCluster(const ConcreteClusterPath & before) = 0; - virtual std::optional GetClusterInfo(const ConcreteClusterPath & path) = 0; + // This iteration describes semantic tags registered on an endpoint + using SemanticTag = Clusters::Descriptor::Structs::SemanticTagStruct::Type; + virtual std::optional GetFirstSemanticTag(EndpointId endpoint) = 0; + virtual std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) = 0; + + // This iteration will list all server clusters on a given endpoint + virtual ClusterEntry FirstServerCluster(EndpointId endpoint) = 0; + virtual ClusterEntry NextServerCluster(const ConcreteClusterPath & before) = 0; + virtual std::optional GetServerClusterInfo(const ConcreteClusterPath & path) = 0; + + // This iteration will list all client clusters on a given endpoint + // As the client cluster is only a client without any attributes/commands, + // these functions only return the cluster path. + virtual ConcreteClusterPath FirstClientCluster(EndpointId endpoint) = 0; + virtual ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) = 0; // Attribute iteration and accessors provide cluster-level access over // attributes diff --git a/src/app/dynamic_server/DynamicDispatcher.cpp b/src/app/dynamic_server/DynamicDispatcher.cpp index ba4525e2fc7add..634055a9851a6d 100644 --- a/src/app/dynamic_server/DynamicDispatcher.cpp +++ b/src/app/dynamic_server/DynamicDispatcher.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -274,6 +275,28 @@ Protocols::InteractionModel::Status emAfReadOrWriteAttribute(const EmberAfAttrib return Protocols::InteractionModel::Status::UnsupportedAttribute; } +namespace chip { +namespace app { + +EndpointComposition GetCompositionForEndpointIndex(uint16_t endpointIndex) +{ + return EndpointComposition::kFullFamily; +} + +} // namespace app +} // namespace chip + +EndpointId emberAfParentEndpointFromIndex(uint16_t index) +{ + return kInvalidEndpointId; +} + +CHIP_ERROR GetSemanticTagForEndpointAtIndex(EndpointId endpoint, size_t index, + Clusters::Descriptor::Structs::SemanticTagStruct::Type & tag) +{ + return CHIP_ERROR_NOT_FOUND; +} + void emberAfAttributeChanged(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId, AttributesChangedListener * listener) { diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 63d71bfc92db7a..e2691e519ac13d 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -51,7 +51,7 @@ using Protocols::InteractionModel::Status; Status EventPathValid(DataModel::Provider * model, const ConcreteEventPath & eventPath) { - if (!model->GetClusterInfo(eventPath).has_value()) + if (!model->GetServerClusterInfo(eventPath).has_value()) { return model->EndpointExists(eventPath.mEndpointId) ? Status::UnsupportedCluster : Status::UnsupportedEndpoint; } @@ -148,7 +148,7 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode readRequest.path = path; DataVersion version = 0; - if (std::optional clusterInfo = dataModel->GetClusterInfo(path); clusterInfo.has_value()) + if (std::optional clusterInfo = dataModel->GetServerClusterInfo(path); clusterInfo.has_value()) { version = clusterInfo->dataVersion; } @@ -208,7 +208,7 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode bool IsClusterDataVersionEqualTo(DataModel::Provider * dataModel, const ConcreteClusterPath & path, DataVersion dataVersion) { - std::optional info = dataModel->GetClusterInfo(path); + std::optional info = dataModel->GetServerClusterInfo(path); if (!info.has_value()) { return false; diff --git a/src/app/tests/test-interaction-model-api.cpp b/src/app/tests/test-interaction-model-api.cpp index d263e251d2da7d..867d4c895ebf55 100644 --- a/src/app/tests/test-interaction-model-api.cpp +++ b/src/app/tests/test-interaction-model-api.cpp @@ -149,16 +149,21 @@ std::optional TestImCustomDataModel::Invoke(const InvokeRequ return std::make_optional(CHIP_ERROR_NOT_IMPLEMENTED); } -EndpointId TestImCustomDataModel::FirstEndpoint() +DataModel::EndpointEntry TestImCustomDataModel::FirstEndpoint() { return CodegenDataModelProviderInstance()->FirstEndpoint(); } -EndpointId TestImCustomDataModel::NextEndpoint(EndpointId before) +DataModel::EndpointEntry TestImCustomDataModel::NextEndpoint(EndpointId before) { return CodegenDataModelProviderInstance()->NextEndpoint(before); } +std::optional TestImCustomDataModel::GetEndpointInfo(EndpointId endpoint) +{ + return CodegenDataModelProviderInstance()->GetEndpointInfo(endpoint); +} + std::optional TestImCustomDataModel::FirstDeviceType(EndpointId endpoint) { return std::nullopt; @@ -170,19 +175,40 @@ std::optional TestImCustomDataModel::NextDeviceType( return std::nullopt; } -ClusterEntry TestImCustomDataModel::FirstCluster(EndpointId endpoint) +std::optional TestImCustomDataModel::GetFirstSemanticTag(EndpointId endpoint) +{ + return std::nullopt; +} + +std::optional TestImCustomDataModel::GetNextSemanticTag(EndpointId endpoint, + const SemanticTag & previous) +{ + return std::nullopt; +} + +ClusterEntry TestImCustomDataModel::FirstServerCluster(EndpointId endpoint) +{ + return CodegenDataModelProviderInstance()->FirstServerCluster(endpoint); +} + +ClusterEntry TestImCustomDataModel::NextServerCluster(const ConcreteClusterPath & before) +{ + return CodegenDataModelProviderInstance()->NextServerCluster(before); +} + +std::optional TestImCustomDataModel::GetServerClusterInfo(const ConcreteClusterPath & path) { - return CodegenDataModelProviderInstance()->FirstCluster(endpoint); + return CodegenDataModelProviderInstance()->GetServerClusterInfo(path); } -ClusterEntry TestImCustomDataModel::NextCluster(const ConcreteClusterPath & before) +ConcreteClusterPath TestImCustomDataModel::FirstClientCluster(EndpointId endpoint) { - return CodegenDataModelProviderInstance()->NextCluster(before); + return CodegenDataModelProviderInstance()->FirstClientCluster(endpoint); } -std::optional TestImCustomDataModel::GetClusterInfo(const ConcreteClusterPath & path) +ConcreteClusterPath TestImCustomDataModel::NextClientCluster(const ConcreteClusterPath & before) { - return CodegenDataModelProviderInstance()->GetClusterInfo(path); + return CodegenDataModelProviderInstance()->NextClientCluster(before); } AttributeEntry TestImCustomDataModel::FirstAttribute(const ConcreteClusterPath & cluster) diff --git a/src/app/tests/test-interaction-model-api.h b/src/app/tests/test-interaction-model-api.h index 0e0fd2c0e5b661..d382ccd8090a0a 100644 --- a/src/app/tests/test-interaction-model-api.h +++ b/src/app/tests/test-interaction-model-api.h @@ -110,14 +110,19 @@ class TestImCustomDataModel : public DataModel::Provider std::optional Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) override; - EndpointId FirstEndpoint() override; - EndpointId NextEndpoint(EndpointId before) override; + DataModel::EndpointEntry FirstEndpoint() override; + DataModel::EndpointEntry NextEndpoint(EndpointId before) override; + std::optional GetEndpointInfo(EndpointId endpoint) override; std::optional FirstDeviceType(EndpointId endpoint) override; std::optional NextDeviceType(EndpointId endpoint, const DataModel::DeviceTypeEntry & previous) override; - DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override; - DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; - std::optional GetClusterInfo(const ConcreteClusterPath & path) override; + std::optional GetFirstSemanticTag(EndpointId endpoint) override; + std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override; + DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override; + DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override; + std::optional GetServerClusterInfo(const ConcreteClusterPath & path) override; + ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override; + ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override; DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; diff --git a/src/app/util/af-types.h b/src/app/util/af-types.h index 04275a2515c23c..e3559069ed9df3 100644 --- a/src/app/util/af-types.h +++ b/src/app/util/af-types.h @@ -120,6 +120,8 @@ struct EmberAfCluster uint16_t eventCount; bool IsServer() const { return (mask & CLUSTER_MASK_SERVER) != 0; } + + bool IsClient() const { return (mask & CLUSTER_MASK_CLIENT) != 0; } }; /** diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index 4d9cc2368f6900..3df2ba7c6676aa 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -1415,6 +1415,20 @@ bool IsTreeCompositionForEndpoint(EndpointId endpoint) return emAfEndpoints[index].bitmask.Has(EmberAfEndpointOptions::isTreeComposition); } +EndpointComposition GetCompositionForEndpointIndex(uint16_t endpointIndex) +{ + VerifyOrReturnValue(endpointIndex < ArraySize(emAfEndpoints), EndpointComposition::kInvalid); + if (emAfEndpoints[endpointIndex].bitmask.Has(EmberAfEndpointOptions::isFlatComposition)) + { + return EndpointComposition::kFullFamily; + } + if (emAfEndpoints[endpointIndex].bitmask.Has(EmberAfEndpointOptions::isTreeComposition)) + { + return EndpointComposition::kTree; + } + return EndpointComposition::kInvalid; +} + } // namespace app } // namespace chip diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index 711f6a7cff768d..6930db7a965ea2 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -378,5 +378,17 @@ bool IsFlatCompositionForEndpoint(EndpointId endpoint); */ bool IsTreeCompositionForEndpoint(EndpointId endpoint); +enum class EndpointComposition : uint8_t +{ + kFullFamily, + kTree, + kInvalid, +}; + +/** + * @brief Returns the composition for a given endpoint index + */ +EndpointComposition GetCompositionForEndpointIndex(uint16_t index); + } // namespace app } // namespace chip diff --git a/src/app/util/mock/MockNodeConfig.cpp b/src/app/util/mock/MockNodeConfig.cpp index c6572ed0aef075..bb919318e3cbb7 100644 --- a/src/app/util/mock/MockNodeConfig.cpp +++ b/src/app/util/mock/MockNodeConfig.cpp @@ -115,7 +115,7 @@ const T * findById(const std::vector & vector, decltype(std::declval().id) MockClusterConfig::MockClusterConfig(ClusterId aId, std::initializer_list aAttributes, std::initializer_list aEvents, std::initializer_list aAcceptedCommands, - std::initializer_list aGeneratedCommands) : + std::initializer_list aGeneratedCommands, BitMask side) : id(aId), attributes(aAttributes), events(aEvents), mEmberCluster{}, mAcceptedCommands(aAcceptedCommands), mGeneratedCommands(aGeneratedCommands) @@ -127,9 +127,17 @@ MockClusterConfig::MockClusterConfig(ClusterId aId, std::initializer_list(attributes.size()); - mEmberCluster.mask = CLUSTER_MASK_SERVER; mEmberCluster.eventCount = static_cast(mEmberEventList.size()); mEmberCluster.eventList = mEmberEventList.data(); @@ -177,9 +185,11 @@ const MockAttributeConfig * MockClusterConfig::attributeById(AttributeId attribu } MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list aClusters, - std::initializer_list aDeviceTypes) : + std::initializer_list aDeviceTypes, + std::initializer_list aTags, + app::EndpointComposition aComposition) : id(aId), - clusters(aClusters), mDeviceTypes(aDeviceTypes), mEmberEndpoint{} + composition(aComposition), clusters(aClusters), mDeviceTypes(aDeviceTypes), mSemanticTags(aTags), mEmberEndpoint{} { VerifyOrDie(aClusters.size() < UINT8_MAX); @@ -193,8 +203,8 @@ MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list #include +#include #include #include @@ -74,11 +75,18 @@ struct MockEventConfig const EventId id; }; +enum class MockClusterSide : uint8_t +{ + kServer = 0x1, + kClient = 0x2, +}; + struct MockClusterConfig { MockClusterConfig(ClusterId aId, std::initializer_list aAttributes = {}, std::initializer_list aEvents = {}, std::initializer_list aAcceptedCommands = {}, - std::initializer_list aGeneratedCommands = {}); + std::initializer_list aGeneratedCommands = {}, + BitMask side = BitMask().Set(MockClusterSide::kServer)); // Cluster-config is self-referential: mEmberCluster.attributes references mAttributeMetaData.data() MockClusterConfig(const MockClusterConfig & other); @@ -102,7 +110,9 @@ struct MockClusterConfig struct MockEndpointConfig { MockEndpointConfig(EndpointId aId, std::initializer_list aClusters = {}, - std::initializer_list aDeviceTypes = {}); + std::initializer_list aDeviceTypes = {}, + std::initializer_list aTags = {}, + app::EndpointComposition composition = app::EndpointComposition::kFullFamily); // Endpoint-config is self-referential: mEmberEndpoint.clusters references mEmberClusters.data() MockEndpointConfig(const MockEndpointConfig & other); @@ -115,12 +125,19 @@ struct MockEndpointConfig return Span(mDeviceTypes.data(), mDeviceTypes.size()); } + Span semanticTags() const + { + return Span(mSemanticTags.data(), mSemanticTags.size()); + } + const EndpointId id; + const app::EndpointComposition composition; const std::vector clusters; private: std::vector mEmberClusters; std::vector mDeviceTypes; + std::vector mSemanticTags; EmberAfEndpointType mEmberEndpoint; }; diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 57f3d1e5216264..f41ba4de69fb3a 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -237,6 +237,39 @@ chip::EndpointId emberAfEndpointFromIndex(uint16_t index) return config.endpoints[index].id; } +namespace chip { +namespace app { + +EndpointComposition GetCompositionForEndpointIndex(uint16_t endpointIndex) +{ + return GetMockNodeConfig().endpoints[endpointIndex].composition; +} + +} // namespace app +} // namespace chip + +EndpointId emberAfParentEndpointFromIndex(uint16_t index) +{ + return kInvalidEndpointId; +} + +CHIP_ERROR GetSemanticTagForEndpointAtIndex(EndpointId endpoint, size_t index, + Clusters::Descriptor::Structs::SemanticTagStruct::Type & tag) +{ + auto ep = GetMockNodeConfig().endpointById(endpoint); + + if (ep) + { + auto semanticTags = ep->semanticTags(); + if (index < semanticTags.size()) + { + tag = semanticTags[index]; + return CHIP_NO_ERROR; + } + } + return CHIP_ERROR_NOT_FOUND; +} + chip::Optional emberAfGetNthClusterId(chip::EndpointId endpointId, uint8_t n, bool server) { VerifyOrReturnValue(server, NullOptional); // only server clusters supported diff --git a/src/controller/tests/data_model/DataModelFixtures.cpp b/src/controller/tests/data_model/DataModelFixtures.cpp index 6f42c38a7789e4..7ef9975bdfddcd 100644 --- a/src/controller/tests/data_model/DataModelFixtures.cpp +++ b/src/controller/tests/data_model/DataModelFixtures.cpp @@ -474,16 +474,21 @@ std::optional CustomDataModel::Invoke(const InvokeRequest & return std::nullopt; // handler status is set by the dispatch } -EndpointId CustomDataModel::FirstEndpoint() +DataModel::EndpointEntry CustomDataModel::FirstEndpoint() { return CodegenDataModelProviderInstance()->FirstEndpoint(); } -EndpointId CustomDataModel::NextEndpoint(EndpointId before) +DataModel::EndpointEntry CustomDataModel::NextEndpoint(EndpointId before) { return CodegenDataModelProviderInstance()->NextEndpoint(before); } +std::optional CustomDataModel::GetEndpointInfo(EndpointId endpoint) +{ + return CodegenDataModelProviderInstance()->GetEndpointInfo(endpoint); +} + std::optional CustomDataModel::FirstDeviceType(EndpointId endpoint) { return std::nullopt; @@ -495,19 +500,40 @@ std::optional CustomDataModel::NextDeviceType(Endpoi return std::nullopt; } -ClusterEntry CustomDataModel::FirstCluster(EndpointId endpoint) +std::optional CustomDataModel::GetFirstSemanticTag(EndpointId endpoint) +{ + return std::nullopt; +} + +std::optional CustomDataModel::GetNextSemanticTag(EndpointId endpoint, + const SemanticTag & previous) +{ + return std::nullopt; +} + +ClusterEntry CustomDataModel::FirstServerCluster(EndpointId endpoint) +{ + return CodegenDataModelProviderInstance()->FirstServerCluster(endpoint); +} + +ClusterEntry CustomDataModel::NextServerCluster(const ConcreteClusterPath & before) +{ + return CodegenDataModelProviderInstance()->NextServerCluster(before); +} + +std::optional CustomDataModel::GetServerClusterInfo(const ConcreteClusterPath & path) { - return CodegenDataModelProviderInstance()->FirstCluster(endpoint); + return CodegenDataModelProviderInstance()->GetServerClusterInfo(path); } -ClusterEntry CustomDataModel::NextCluster(const ConcreteClusterPath & before) +ConcreteClusterPath CustomDataModel::FirstClientCluster(EndpointId endpoint) { - return CodegenDataModelProviderInstance()->NextCluster(before); + return CodegenDataModelProviderInstance()->FirstClientCluster(endpoint); } -std::optional CustomDataModel::GetClusterInfo(const ConcreteClusterPath & path) +ConcreteClusterPath CustomDataModel::NextClientCluster(const ConcreteClusterPath & before) { - return CodegenDataModelProviderInstance()->GetClusterInfo(path); + return CodegenDataModelProviderInstance()->NextClientCluster(before); } AttributeEntry CustomDataModel::FirstAttribute(const ConcreteClusterPath & cluster) diff --git a/src/controller/tests/data_model/DataModelFixtures.h b/src/controller/tests/data_model/DataModelFixtures.h index e9b32aead650ee..c8136751c87ea1 100644 --- a/src/controller/tests/data_model/DataModelFixtures.h +++ b/src/controller/tests/data_model/DataModelFixtures.h @@ -122,14 +122,19 @@ class CustomDataModel : public DataModel::Provider std::optional Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) override; - EndpointId FirstEndpoint() override; - EndpointId NextEndpoint(EndpointId before) override; + DataModel::EndpointEntry FirstEndpoint() override; + DataModel::EndpointEntry NextEndpoint(EndpointId before) override; + std::optional GetEndpointInfo(EndpointId endpoint) override; std::optional FirstDeviceType(EndpointId endpoint) override; std::optional NextDeviceType(EndpointId endpoint, const DataModel::DeviceTypeEntry & previous) override; - DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override; - DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; - std::optional GetClusterInfo(const ConcreteClusterPath & path) override; + std::optional GetFirstSemanticTag(EndpointId endpoint) override; + std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override; + DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override; + DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override; + std::optional GetServerClusterInfo(const ConcreteClusterPath & path) override; + ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override; + ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override; DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; std::optional GetAttributeInfo(const ConcreteAttributePath & path) override;