Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to report endpoint device types in the DataModel::Provider interface #35861

Merged
merged 12 commits into from
Oct 3, 2024
74 changes: 74 additions & 0 deletions src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ std::optional<DataModel::CommandEntry> EnumeratorCommandFinder::FindCommandEntry
return (*id == kInvalidCommandId) ? DataModel::CommandEntry::kInvalid : CommandEntryFrom(path, *id);
}

DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & other)
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
{
DataModel::DeviceTypeEntry entry;

entry.deviceTypeId = other.deviceId;
entry.deviceTypeVersion = other.deviceVersion;

return entry;
}

bool IsSameDeviceTypeEntry(const DataModel::DeviceTypeEntry & a, const EmberAfDeviceType & b)
{
return (a.deviceTypeId == b.deviceId) && (a.deviceTypeVersion == b.deviceVersion);
}

const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);

} // namespace
Expand Down Expand Up @@ -726,6 +741,65 @@ ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const Concret
return ConcreteCommandPath(before.mEndpointId, before.mClusterId, *commandId);
}

std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint)
{
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
if (!endpoint_index.has_value())
{
return std::nullopt;
}

CHIP_ERROR err = CHIP_NO_ERROR;
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
andy31415 marked this conversation as resolved.
Show resolved Hide resolved

if (deviceTypes.empty())
{
return std::nullopt;
}

// we start at the beginning
mDeviceTypeIterationHint = 0;
return DeviceTypeEntryFromEmber(deviceTypes[0]);
}

std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous)
{
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
if (!endpoint_index.has_value())
{
return std::nullopt;
}

CHIP_ERROR err = CHIP_NO_ERROR;
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);

unsigned idx = 0;
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
if ((mDeviceTypeIterationHint < deviceTypes.size()) && IsSameDeviceTypeEntry(previous, deviceTypes[mDeviceTypeIterationHint]))
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
{
idx = mDeviceTypeIterationHint + 1; // target the NEXT device
}
else
{
while (idx < deviceTypes.size())
{
idx++;
if (IsSameDeviceTypeEntry(previous, deviceTypes[idx - 1]))
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
{
break;
}
}
}

if (idx >= deviceTypes.size())
{
return std::nullopt;
}

mDeviceTypeIterationHint = idx;
return DeviceTypeEntryFromEmber(deviceTypes[idx]);
}

bool CodegenDataModelProvider::EventPathIncludesAccessibleConcretePath(const EventPathParams & path,
const Access::SubjectDescriptor & descriptor)
{
Expand Down
11 changes: 8 additions & 3 deletions src/app/codegen-data-model-provider/CodegenDataModelProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider
EndpointId FirstEndpoint() override;
EndpointId NextEndpoint(EndpointId before) override;

std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous) override;

DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override;
DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override;
std::optional<DataModel::ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) override;
Expand All @@ -115,9 +119,10 @@ 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;
uint16_t mEndpointIterationHint = 0;
unsigned mClusterIterationHint = 0;
unsigned mAttributeIterationHint = 0;
unsigned mDeviceTypeIterationHint = 0;
EmberCommandListIterator mAcceptedCommandsIterator;
EmberCommandListIterator mGeneratedCommandsIterator;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include <lib/support/Span.h>
#include <protocols/interaction_model/StatusCode.h>

#include <optional>
#include <vector>

using namespace chip;
Expand All @@ -86,6 +87,15 @@ constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1;

constexpr AttributeId kReadOnlyAttributeId = 0x5001;

constexpr DeviceTypeId kDeviceTypeId1 = 123;
constexpr uint8_t kDeviceTypeId1Version = 10;

constexpr DeviceTypeId kDeviceTypeId2 = 1122;
constexpr uint8_t kDeviceTypeId2Version = 11;

constexpr DeviceTypeId kDeviceTypeId3 = 3;
constexpr uint8_t kDeviceTypeId3Version = 33;

static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint1);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint2);
Expand Down Expand Up @@ -270,6 +280,10 @@ const MockNodeConfig gTestNodeConfig({
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
}, {
{ kDeviceTypeId1, kDeviceTypeId1Version},
{ kDeviceTypeId2, kDeviceTypeId2Version},
{ kDeviceTypeId3, kDeviceTypeId3Version},
}),
MockEndpointConfig(kMockEndpoint2, {
MockClusterConfig(MockClusterId(1), {
Expand All @@ -296,6 +310,8 @@ const MockNodeConfig gTestNodeConfig({
{11}, /* acceptedCommands */
{4, 6} /* generatedCommands */
),
}, {
{ kDeviceTypeId2, kDeviceTypeId2Version},
}),
MockEndpointConfig(kMockEndpoint3, {
MockClusterConfig(MockClusterId(1), {
Expand Down Expand Up @@ -2580,3 +2596,48 @@ TEST(TestCodegenModelViaMocks, EmberWriteInvalidDataType)
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
}

TEST(TestCodegenModelViaMocks, DeviceTypeIteration)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;

// Mock endpoint 1 has 3 device types
std::optional<DeviceTypeEntry> entry = model.FirstDeviceType(kMockEndpoint1);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = 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 }));
// 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 }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_FALSE(entry.has_value());

// Mock endpoint 2 has 1 device types
entry = model.FirstDeviceType(kMockEndpoint2);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = 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 = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));

// invalid query fails
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version });
entry = model.NextDeviceType(kMockEndpoint2, *entry);
ASSERT_FALSE(entry.has_value());

// empty endpoint works
entry = model.FirstDeviceType(kMockEndpoint3);
ASSERT_FALSE(entry.has_value());
}
17 changes: 17 additions & 0 deletions src/app/data-model-provider/MetadataTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ struct CommandEntry
static const CommandEntry kInvalid;
};

/// Represents a device type that resides on an endpoint
struct DeviceTypeEntry
{
DeviceTypeId deviceTypeId;
uint8_t deviceTypeVersion;

bool operator==(const DeviceTypeEntry & other) const
{
return (deviceTypeId == other.deviceTypeId) && (deviceTypeVersion == other.deviceTypeVersion);
}
};

/// Provides metadata information for a data model
///
/// The data model can be viewed as a tree of endpoint/cluster/(attribute+commands+events)
Expand All @@ -129,6 +141,11 @@ class ProviderMetadataTree
virtual EndpointId FirstEndpoint() = 0;
virtual EndpointId NextEndpoint(EndpointId before) = 0;

// This iteration describes device types registered on an endpoint
virtual std::optional<DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) = 0;
virtual std::optional<DeviceTypeEntry> 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<ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) = 0;
Expand Down
11 changes: 11 additions & 0 deletions src/app/tests/test-interaction-model-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ EndpointId TestImCustomDataModel::NextEndpoint(EndpointId before)
return CodegenDataModelProviderInstance()->NextEndpoint(before);
}

std::optional<DataModel::DeviceTypeEntry> TestImCustomDataModel::FirstDeviceType(EndpointId endpoint)
{
return std::nullopt;
}

std::optional<DataModel::DeviceTypeEntry> TestImCustomDataModel::NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous)
{
return std::nullopt;
}

ClusterEntry TestImCustomDataModel::FirstCluster(EndpointId endpoint)
{
return CodegenDataModelProviderInstance()->FirstCluster(endpoint);
Expand Down
3 changes: 3 additions & 0 deletions src/app/tests/test-interaction-model-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ class TestImCustomDataModel : public DataModel::Provider

EndpointId FirstEndpoint() override;
EndpointId NextEndpoint(EndpointId before) override;
std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous) override;
DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override;
DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override;
std::optional<DataModel::ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) override;
Expand Down
8 changes: 5 additions & 3 deletions src/app/util/attribute-storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,13 +1007,15 @@ uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpoint)

Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(EndpointId endpoint, CHIP_ERROR & err)
{
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
Span<const EmberAfDeviceType> ret;
return emberAfDeviceTypeListFromEndpointIndex(emberAfIndexFromEndpoint(endpoint), err);
}

chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err)
{
if (endpointIndex == 0xFFFF)
{
err = CHIP_ERROR_INVALID_ARGUMENT;
return ret;
return Span<const EmberAfDeviceType>();
}

err = CHIP_NO_ERROR;
Expand Down
1 change: 1 addition & 0 deletions src/app/util/attribute-storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ const EmberAfCluster * emberAfGetNthCluster(chip::EndpointId endpoint, uint8_t n
// Retrieve the device type list associated with a specific endpoint.
//
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(chip::EndpointId endpoint, CHIP_ERROR & err);
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err);

//
// Override the device type list current associated with an endpoint with a user-provided list. The buffers backing
Expand Down
11 changes: 8 additions & 3 deletions src/app/util/mock/MockNodeConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
* limitations under the License.
*/

#include "app/util/af-types.h"
#include <app/util/mock/MockNodeConfig.h>

#include <app/util/att-storage.h>
#include <app/util/attribute-storage.h>
#include <initializer_list>
#include <lib/support/CodeUtils.h>

#include <utility>
Expand Down Expand Up @@ -174,8 +176,10 @@ const MockAttributeConfig * MockClusterConfig::attributeById(AttributeId attribu
return findById(attributes, attributeId, outIndex);
}

MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters) :
id(aId), clusters(aClusters), mEmberEndpoint{}
MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters,
std::initializer_list<EmberAfDeviceType> aDeviceTypes) :
id(aId),
clusters(aClusters), mDeviceTypes(aDeviceTypes), mEmberEndpoint{}
{
VerifyOrDie(aClusters.size() < UINT8_MAX);

Expand All @@ -189,7 +193,8 @@ MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<Moc
}

MockEndpointConfig::MockEndpointConfig(const MockEndpointConfig & other) :
id(other.id), clusters(other.clusters), mEmberClusters(other.mEmberClusters), mEmberEndpoint(other.mEmberEndpoint)
id(other.id), clusters(other.clusters), mEmberClusters(other.mEmberClusters), mDeviceTypes(other.mDeviceTypes),
mEmberEndpoint(other.mEmberEndpoint)
{
// fix self-referencing pointers
mEmberEndpoint.cluster = mEmberClusters.data();
Expand Down
8 changes: 7 additions & 1 deletion src/app/util/mock/MockNodeConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,26 @@ struct MockClusterConfig

struct MockEndpointConfig
{
MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters = {});
MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters = {},
std::initializer_list<EmberAfDeviceType> aDeviceTypes = {});

// Endpoint-config is self-referential: mEmberEndpoint.clusters references mEmberClusters.data()
MockEndpointConfig(const MockEndpointConfig & other);
MockEndpointConfig & operator=(const MockEndpointConfig &) = delete;

const MockClusterConfig * clusterById(ClusterId clusterId, ptrdiff_t * outIndex = nullptr) const;
const EmberAfEndpointType * emberEndpoint() const { return &mEmberEndpoint; }
Span<const EmberAfDeviceType> deviceTypes() const
{
return Span<const EmberAfDeviceType>(mDeviceTypes.data(), mDeviceTypes.size());
}

const EndpointId id;
const std::vector<MockClusterConfig> clusters;

private:
std::vector<EmberAfCluster> mEmberClusters;
std::vector<EmberAfDeviceType> mDeviceTypes;
EmberAfEndpointType mEmberEndpoint;
};

Expand Down
22 changes: 22 additions & 0 deletions src/app/util/mock/attribute-storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,28 @@ DataVersion * emberAfDataVersionStorage(const chip::app::ConcreteClusterPath & a
return &dataVersion;
}

chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(chip::EndpointId endpointId, CHIP_ERROR & err)
{
auto endpoint = GetMockNodeConfig().endpointById(endpointId);

if (endpoint == nullptr)
{
return chip::Span<const EmberAfDeviceType>();
}

return endpoint->deviceTypes();
}

chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned index, CHIP_ERROR & err)
{
if (index >= GetMockNodeConfig().endpoints.size())
{
return chip::Span<const EmberAfDeviceType>();
}

return GetMockNodeConfig().endpoints[index].deviceTypes();
}

void emberAfAttributeChanged(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId,
AttributesChangedListener * listener)
{
Expand Down
Loading
Loading