Skip to content

Commit 3289872

Browse files
committed
Merge remote-tracking branch 'origin/camera-zone' into camera-zone
2 parents bef5f90 + 37c168d commit 3289872

13 files changed

+260
-10
lines changed

src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp

+103
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,57 @@ std::optional<DataModel::CommandEntry> EnumeratorCommandFinder::FindCommandEntry
292292
return (*id == kInvalidCommandId) ? DataModel::CommandEntry::kInvalid : CommandEntryFrom(path, *id);
293293
}
294294

295+
// TODO: DeviceTypeEntry content is IDENTICAL to EmberAfDeviceType, so centralizing
296+
// to a common type is probably better. Need to figure out dependencies since
297+
// this would make ember return datamodel-provider types.
298+
// See: https://github.com/project-chip/connectedhomeip/issues/35889
299+
DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & other)
300+
{
301+
DataModel::DeviceTypeEntry entry;
302+
303+
entry.deviceTypeId = other.deviceId;
304+
entry.deviceTypeVersion = other.deviceVersion;
305+
306+
return entry;
307+
}
308+
309+
// Explicitly compare for identical entries. note that types are different,
310+
// so you must do `a == b` and the `b == a` will not work.
311+
bool operator==(const DataModel::DeviceTypeEntry & a, const EmberAfDeviceType & b)
312+
{
313+
return (a.deviceTypeId == b.deviceId) && (a.deviceTypeVersion == b.deviceVersion);
314+
}
315+
316+
/// Find the `index` where one of the following holds:
317+
/// - types[index - 1] == previous OR
318+
/// - index == types.size() // i.e. not found or there is no next
319+
///
320+
/// hintWherePreviousMayBe represents a search hint where previous may exist.
321+
unsigned FindNextDeviceTypeIndex(Span<const EmberAfDeviceType> types, const DataModel::DeviceTypeEntry previous,
322+
unsigned hintWherePreviousMayBe)
323+
{
324+
if (hintWherePreviousMayBe < types.size())
325+
{
326+
// this is a valid hint ... see if we are lucky
327+
if (previous == types[hintWherePreviousMayBe])
328+
{
329+
return hintWherePreviousMayBe + 1; // return the next index
330+
}
331+
}
332+
333+
// hint was not useful. We have to do a full search
334+
for (unsigned idx = 0; idx < types.size(); idx++)
335+
{
336+
if (previous == types[idx])
337+
{
338+
return idx + 1;
339+
}
340+
}
341+
342+
// cast should be safe as we know we do not have that many types
343+
return static_cast<unsigned>(types.size());
344+
}
345+
295346
const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);
296347

297348
} // namespace
@@ -731,6 +782,58 @@ ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const Concret
731782
return ConcreteCommandPath(before.mEndpointId, before.mClusterId, *commandId);
732783
}
733784

785+
std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint)
786+
{
787+
// Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
788+
// index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
789+
// during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
790+
//
791+
// Not actually needed for `First`, however this makes First and Next consistent.
792+
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
793+
if (!endpoint_index.has_value())
794+
{
795+
return std::nullopt;
796+
}
797+
798+
CHIP_ERROR err = CHIP_NO_ERROR;
799+
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
800+
801+
if (deviceTypes.empty())
802+
{
803+
return std::nullopt;
804+
}
805+
806+
// we start at the beginning
807+
mDeviceTypeIterationHint = 0;
808+
return DeviceTypeEntryFromEmber(deviceTypes[0]);
809+
}
810+
811+
std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::NextDeviceType(EndpointId endpoint,
812+
const DataModel::DeviceTypeEntry & previous)
813+
{
814+
// Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
815+
// index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
816+
// during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
817+
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
818+
if (!endpoint_index.has_value())
819+
{
820+
return std::nullopt;
821+
}
822+
823+
CHIP_ERROR err = CHIP_NO_ERROR;
824+
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
825+
826+
unsigned idx = FindNextDeviceTypeIndex(deviceTypes, previous, mDeviceTypeIterationHint);
827+
828+
if (idx >= deviceTypes.size())
829+
{
830+
return std::nullopt;
831+
}
832+
833+
mDeviceTypeIterationHint = idx;
834+
return DeviceTypeEntryFromEmber(deviceTypes[idx]);
835+
}
836+
734837
bool CodegenDataModelProvider::EventPathIncludesAccessibleConcretePath(const EventPathParams & path,
735838
const Access::SubjectDescriptor & descriptor)
736839
{

src/app/codegen-data-model-provider/CodegenDataModelProvider.h

+8-3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider
9898
EndpointId NextEndpoint(EndpointId before) override;
9999
bool EndpointExists(EndpointId endpoint) override;
100100

101+
std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
102+
std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
103+
const DataModel::DeviceTypeEntry & previous) override;
104+
101105
DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override;
102106
DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override;
103107
std::optional<DataModel::ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) override;
@@ -116,9 +120,10 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider
116120
private:
117121
// Iteration is often done in a tight loop going through all values.
118122
// To avoid N^2 iterations, cache a hint of where something is positioned
119-
uint16_t mEndpointIterationHint = 0;
120-
unsigned mClusterIterationHint = 0;
121-
unsigned mAttributeIterationHint = 0;
123+
uint16_t mEndpointIterationHint = 0;
124+
unsigned mClusterIterationHint = 0;
125+
unsigned mAttributeIterationHint = 0;
126+
unsigned mDeviceTypeIterationHint = 0;
122127
EmberCommandListIterator mAcceptedCommandsIterator;
123128
EmberCommandListIterator mGeneratedCommandsIterator;
124129

src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include <lib/support/Span.h>
6464
#include <protocols/interaction_model/StatusCode.h>
6565

66+
#include <optional>
6667
#include <vector>
6768

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

8788
constexpr AttributeId kReadOnlyAttributeId = 0x5001;
8889

90+
constexpr DeviceTypeId kDeviceTypeId1 = 123;
91+
constexpr uint8_t kDeviceTypeId1Version = 10;
92+
93+
constexpr DeviceTypeId kDeviceTypeId2 = 1122;
94+
constexpr uint8_t kDeviceTypeId2Version = 11;
95+
96+
constexpr DeviceTypeId kDeviceTypeId3 = 3;
97+
constexpr uint8_t kDeviceTypeId3Version = 33;
98+
8999
static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId);
90100
static_assert(kEndpointIdThatIsMissing != kMockEndpoint1);
91101
static_assert(kEndpointIdThatIsMissing != kMockEndpoint2);
@@ -270,6 +280,10 @@ const MockNodeConfig gTestNodeConfig({
270280
MockClusterConfig(MockClusterId(2), {
271281
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
272282
}),
283+
}, {
284+
{ kDeviceTypeId1, kDeviceTypeId1Version},
285+
{ kDeviceTypeId2, kDeviceTypeId2Version},
286+
{ kDeviceTypeId3, kDeviceTypeId3Version},
273287
}),
274288
MockEndpointConfig(kMockEndpoint2, {
275289
MockClusterConfig(MockClusterId(1), {
@@ -296,6 +310,8 @@ const MockNodeConfig gTestNodeConfig({
296310
{11}, /* acceptedCommands */
297311
{4, 6} /* generatedCommands */
298312
),
313+
}, {
314+
{ kDeviceTypeId2, kDeviceTypeId2Version},
299315
}),
300316
MockEndpointConfig(kMockEndpoint3, {
301317
MockClusterConfig(MockClusterId(1), {
@@ -2580,3 +2596,48 @@ TEST(TestCodegenModelViaMocks, EmberWriteInvalidDataType)
25802596
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure);
25812597
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
25822598
}
2599+
2600+
TEST(TestCodegenModelViaMocks, DeviceTypeIteration)
2601+
{
2602+
UseMockNodeConfig config(gTestNodeConfig);
2603+
CodegenDataModelProviderWithContext model;
2604+
2605+
// Mock endpoint 1 has 3 device types
2606+
std::optional<DeviceTypeEntry> entry = model.FirstDeviceType(kMockEndpoint1);
2607+
ASSERT_EQ(entry,
2608+
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version }));
2609+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
2610+
entry = model.NextDeviceType(kMockEndpoint1, *entry);
2611+
ASSERT_EQ(entry,
2612+
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }));
2613+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
2614+
entry = model.NextDeviceType(kMockEndpoint1, *entry);
2615+
ASSERT_EQ(entry,
2616+
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));
2617+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
2618+
entry = model.NextDeviceType(kMockEndpoint1, *entry);
2619+
ASSERT_FALSE(entry.has_value());
2620+
2621+
// Mock endpoint 2 has 1 device types
2622+
entry = model.FirstDeviceType(kMockEndpoint2);
2623+
ASSERT_EQ(entry,
2624+
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }));
2625+
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
2626+
entry = model.NextDeviceType(kMockEndpoint2, *entry);
2627+
ASSERT_FALSE(entry.has_value());
2628+
2629+
// out of order query works
2630+
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version });
2631+
entry = model.NextDeviceType(kMockEndpoint1, *entry);
2632+
ASSERT_EQ(entry,
2633+
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));
2634+
2635+
// invalid query fails
2636+
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version });
2637+
entry = model.NextDeviceType(kMockEndpoint2, *entry);
2638+
ASSERT_FALSE(entry.has_value());
2639+
2640+
// empty endpoint works
2641+
entry = model.FirstDeviceType(kMockEndpoint3);
2642+
ASSERT_FALSE(entry.has_value());
2643+
}

src/app/data-model-provider/MetadataTypes.h

+17
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@ struct CommandEntry
105105
static const CommandEntry kInvalid;
106106
};
107107

108+
/// Represents a device type that resides on an endpoint
109+
struct DeviceTypeEntry
110+
{
111+
DeviceTypeId deviceTypeId;
112+
uint8_t deviceTypeVersion;
113+
114+
bool operator==(const DeviceTypeEntry & other) const
115+
{
116+
return (deviceTypeId == other.deviceTypeId) && (deviceTypeVersion == other.deviceTypeVersion);
117+
}
118+
};
119+
108120
/// Provides metadata information for a data model
109121
///
110122
/// The data model can be viewed as a tree of endpoint/cluster/(attribute+commands+events)
@@ -130,6 +142,11 @@ class ProviderMetadataTree
130142
virtual EndpointId NextEndpoint(EndpointId before) = 0;
131143
virtual bool EndpointExists(EndpointId id);
132144

145+
// This iteration describes device types registered on an endpoint
146+
virtual std::optional<DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) = 0;
147+
virtual std::optional<DeviceTypeEntry> NextDeviceType(EndpointId endpoint, const DeviceTypeEntry & previous) = 0;
148+
149+
// This iteration will list all clusters on a given endpoint
133150
virtual ClusterEntry FirstCluster(EndpointId endpoint) = 0;
134151
virtual ClusterEntry NextCluster(const ConcreteClusterPath & before) = 0;
135152
virtual std::optional<ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) = 0;

src/app/tests/test-interaction-model-api.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,17 @@ EndpointId TestImCustomDataModel::NextEndpoint(EndpointId before)
216216
return CodegenDataModelProviderInstance()->NextEndpoint(before);
217217
}
218218

219+
std::optional<DataModel::DeviceTypeEntry> TestImCustomDataModel::FirstDeviceType(EndpointId endpoint)
220+
{
221+
return std::nullopt;
222+
}
223+
224+
std::optional<DataModel::DeviceTypeEntry> TestImCustomDataModel::NextDeviceType(EndpointId endpoint,
225+
const DataModel::DeviceTypeEntry & previous)
226+
{
227+
return std::nullopt;
228+
}
229+
219230
ClusterEntry TestImCustomDataModel::FirstCluster(EndpointId endpoint)
220231
{
221232
return CodegenDataModelProviderInstance()->FirstCluster(endpoint);

src/app/tests/test-interaction-model-api.h

+3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class TestImCustomDataModel : public DataModel::Provider
131131

132132
EndpointId FirstEndpoint() override;
133133
EndpointId NextEndpoint(EndpointId before) override;
134+
std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
135+
std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
136+
const DataModel::DeviceTypeEntry & previous) override;
134137
DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override;
135138
DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override;
136139
std::optional<DataModel::ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) override;

src/app/util/attribute-storage.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -1007,13 +1007,15 @@ uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpoint)
10071007

10081008
Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(EndpointId endpoint, CHIP_ERROR & err)
10091009
{
1010-
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
1011-
Span<const EmberAfDeviceType> ret;
1010+
return emberAfDeviceTypeListFromEndpointIndex(emberAfIndexFromEndpoint(endpoint), err);
1011+
}
10121012

1013+
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err)
1014+
{
10131015
if (endpointIndex == 0xFFFF)
10141016
{
10151017
err = CHIP_ERROR_INVALID_ARGUMENT;
1016-
return ret;
1018+
return Span<const EmberAfDeviceType>();
10171019
}
10181020

10191021
err = CHIP_NO_ERROR;

src/app/util/attribute-storage.h

+1
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ const EmberAfCluster * emberAfGetNthCluster(chip::EndpointId endpoint, uint8_t n
281281
// Retrieve the device type list associated with a specific endpoint.
282282
//
283283
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(chip::EndpointId endpoint, CHIP_ERROR & err);
284+
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err);
284285

285286
//
286287
// Override the device type list current associated with an endpoint with a user-provided list. The buffers backing

src/app/util/mock/MockNodeConfig.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
* limitations under the License.
1717
*/
1818

19+
#include "app/util/af-types.h"
1920
#include <app/util/mock/MockNodeConfig.h>
2021

2122
#include <app/util/att-storage.h>
2223
#include <app/util/attribute-storage.h>
24+
#include <initializer_list>
2325
#include <lib/support/CodeUtils.h>
2426

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

177-
MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters) :
178-
id(aId), clusters(aClusters), mEmberEndpoint{}
179+
MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters,
180+
std::initializer_list<EmberAfDeviceType> aDeviceTypes) :
181+
id(aId),
182+
clusters(aClusters), mDeviceTypes(aDeviceTypes), mEmberEndpoint{}
179183
{
180184
VerifyOrDie(aClusters.size() < UINT8_MAX);
181185

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

191195
MockEndpointConfig::MockEndpointConfig(const MockEndpointConfig & other) :
192-
id(other.id), clusters(other.clusters), mEmberClusters(other.mEmberClusters), mEmberEndpoint(other.mEmberEndpoint)
196+
id(other.id), clusters(other.clusters), mEmberClusters(other.mEmberClusters), mDeviceTypes(other.mDeviceTypes),
197+
mEmberEndpoint(other.mEmberEndpoint)
193198
{
194199
// fix self-referencing pointers
195200
mEmberEndpoint.cluster = mEmberClusters.data();

src/app/util/mock/MockNodeConfig.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,26 @@ struct MockClusterConfig
101101

102102
struct MockEndpointConfig
103103
{
104-
MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters = {});
104+
MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters = {},
105+
std::initializer_list<EmberAfDeviceType> aDeviceTypes = {});
105106

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

110111
const MockClusterConfig * clusterById(ClusterId clusterId, ptrdiff_t * outIndex = nullptr) const;
111112
const EmberAfEndpointType * emberEndpoint() const { return &mEmberEndpoint; }
113+
Span<const EmberAfDeviceType> deviceTypes() const
114+
{
115+
return Span<const EmberAfDeviceType>(mDeviceTypes.data(), mDeviceTypes.size());
116+
}
112117

113118
const EndpointId id;
114119
const std::vector<MockClusterConfig> clusters;
115120

116121
private:
117122
std::vector<EmberAfCluster> mEmberClusters;
123+
std::vector<EmberAfDeviceType> mDeviceTypes;
118124
EmberAfEndpointType mEmberEndpoint;
119125
};
120126

0 commit comments

Comments
 (0)