Skip to content

Commit

Permalink
Add a wildcard attribute read API on MTRDevice.
Browse files Browse the repository at this point in the history
  • Loading branch information
bzbarsky-apple committed Sep 12, 2024
1 parent a62b5db commit bc1add1
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 10 deletions.
14 changes: 14 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,20 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
expectedValueInterval:(NSNumber *)expectedValueInterval
timedWriteTimeout:(NSNumber * _Nullable)timeout;

/**
* Read the attributes identified by the provided attribute paths. The paths
* can include wildcards.
*
* Paths that do not correspond to any existing attributes, or that the
* MTRDevice does not have attribute values for, will not be present in the
* return value from this function.
*
* @return an array of response-value dictionaries as described in the
* documentation for MTRDeviceResponseHandler. Each one will have an
* MTRAttributePathKey and an MTRDataKey.
*/
- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths MTR_NEWLY_AVAILABLE;

/**
* Invoke a command with a designated command path
*
Expand Down
11 changes: 11 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,17 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
#undef MTRDeviceErrorStr
}

- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths
{
#define MTRDeviceErrorStr "MTRDevice readAttributePaths: must be handled by subclasses"
MTR_LOG_ERROR(MTRDeviceErrorStr);
#ifdef DEBUG
NSAssert(NO, @MTRDeviceErrorStr);
#endif // DEBUG
#undef MTRDeviceErrorStr
return [NSArray array];
}

- (void)invokeCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
Expand Down
129 changes: 119 additions & 10 deletions src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2949,6 +2949,79 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
[_asyncWorkQueue enqueueWorkItem:workItem descriptionWithFormat:@"write %@ 0x%llx 0x%llx", endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue];
}

- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths
{
// Determine the set of what the spec calls "existent paths" that correspond
// to the request paths. Building the whole set in-memory is OK, because
// we're going to need all those paths for our return value anyway.
NSMutableSet<MTRAttributePath *> * existentPaths = [[NSMutableSet alloc] init];
{
std::lock_guard lock(_lock);
for (MTRAttributeRequestPath * path in attributePaths) {
[self _addExistentPathsFor:path to:existentPaths];
}
}

NSMutableArray<NSDictionary<NSString *, id> *> * result = [NSMutableArray arrayWithCapacity:existentPaths.count];
for (MTRAttributePath * path in existentPaths) {
auto * value = [self readAttributeWithEndpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute params:nil];
if (!value) {
continue;
}
[result addObject:@{
MTRAttributePathKey : path,
MTRDataKey : value,
}];
}

return result;
}

- (void)_addExistentPathsFor:(MTRAttributeRequestPath *)path to:(NSMutableSet<MTRAttributePath *> *)set
{
os_unfair_lock_assert_owner(&_lock);

if (path.endpoint != nil) {
[self _addExistentPathsForEndpoint:path.endpoint path:path to:set];
return;
}

NSArray<NSNumber *> * endpointList = [self _endpointList];
for (NSNumber * endpoint in endpointList) {
[self _addExistentPathsForEndpoint:endpoint path:path to:set];
}
}

- (void)_addExistentPathsForEndpoint:(NSNumber *)endpoint path:(MTRAttributeRequestPath *)path to:(NSMutableSet<MTRAttributePath *> *)set
{
os_unfair_lock_assert_owner(&_lock);

if (path.cluster != nil) {
[self _addExistentPathsForEndpoint:endpoint cluster:path.cluster attribute:path.attribute to:set];
return;
}

auto * clusterList = [self _serverListForEndpointID:endpoint];
for (NSNumber * cluster in clusterList) {
[self _addExistentPathsForEndpoint:endpoint cluster:cluster attribute:path.attribute to:set];
}
}

- (void)_addExistentPathsForEndpoint:(NSNumber *)endpoint cluster:(NSNumber *)cluster attribute:(NSNumber * _Nullable)attribute to:(NSMutableSet<MTRAttributePath *> *)set
{
os_unfair_lock_assert_owner(&_lock);

if (attribute != nil) {
[set addObject:[MTRAttributePath attributePathWithEndpointID:endpoint clusterID:cluster attributeID:attribute]];
return;
}

auto * attributeList = [self _attributeListForEndpointID:endpoint clusterID:cluster];
for (NSNumber * existentAttribute in attributeList) {
[set addObject:[MTRAttributePath attributePathWithEndpointID:endpoint clusterID:cluster attributeID:existentAttribute]];
}
}

- (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
Expand Down Expand Up @@ -3954,19 +4027,55 @@ - (void)_updateAttributeDependentDescriptionData
{
os_unfair_lock_assert_owner(&_lock);

auto * partsListPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId)
clusterID:@(MTRClusterIDTypeDescriptorID)
attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)];
auto * partsList = [self _cachedAttributeValueForPath:partsListPath];
NSMutableArray<NSNumber *> * endpointsOnDevice = [self arrayOfNumbersFromAttributeValue:partsList];
if (!endpointsOnDevice) {
endpointsOnDevice = [[NSMutableArray<NSNumber *> alloc] init];
}
// Add Root node!
[endpointsOnDevice addObject:@(0)];
auto * partsList = [self _cachedListOfNumbersValueForEndpointID:@(kRootEndpointId)
clusterID:@(MTRClusterIDTypeDescriptorID)
attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)];
NSMutableArray<NSNumber *> * endpointsOnDevice = [partsList mutableCopy];
// Add Root Node endpoint.
[endpointsOnDevice addObject:@(kRootEndpointId)];
return endpointsOnDevice;
}

/**
* Returns the cached value of the relevant attribute as a list of numbers.
* Returns an empty list if the value does not exist or can't be converted to a
* list of numbers.
*/
- (NSArray<NSNumber *> *)_cachedListOfNumbersValueForEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
attributeID:(NSNumber *)attributeID
{
os_unfair_lock_assert_owner(&_lock);

auto * path = [MTRAttributePath attributePathWithEndpointID:endpointID
clusterID:clusterID
attributeID:attributeID];
auto * value = [self _cachedAttributeValueForPath:path];
NSArray<NSNumber *> * arrayValue = [self arrayOfNumbersFromAttributeValue:value];
if (arrayValue) {
return arrayValue;
}
return [NSArray array];
}

- (NSArray<NSNumber *> *)_serverListForEndpointID:(NSNumber *)endpointID
{
os_unfair_lock_assert_owner(&_lock);

return [self _cachedListOfNumbersValueForEndpointID:endpointID
clusterID:@(MTRClusterIDTypeDescriptorID)
attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributeServerListID)];
}

- (NSArray<NSNumber *> *)_attributeListForEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
os_unfair_lock_assert_owner(&_lock);

return [self _cachedListOfNumbersValueForEndpointID:endpointID
clusterID:clusterID
attributeID:@(MTRAttributeIDTypeGlobalAttributeAttributeListID)];
}

- (NSNumber * _Nullable)_networkFeatures
{
NSNumber * _Nullable result = nil;
Expand Down
6 changes: 6 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice_XPC.mm
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ - (oneway void)device:(NSNumber *)nodeID internalStateUpdated:(NSDictionary *)di
: expectedValueInterval timedWriteTimeout
: timeout)

- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths
{
// TODO: Implement
return [NSArray array];
}

- (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
Expand Down
48 changes: 48 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1451,8 +1451,23 @@ - (void)test017_TestMTRDeviceBasics
[subscriptionExpectation fulfill];
};

NSMutableSet<NSNumber *> * endpoints = [[NSMutableSet alloc] init];
NSMutableSet<MTRClusterPath *> * clusters = [[NSMutableSet alloc] init];
NSMutableSet<NSNumber *> * rootBasicInformationAttributes = [[NSMutableSet alloc] init];
__block unsigned attributeReportsReceived = 0;
delegate.onAttributeDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * data) {
for (NSDictionary<NSString *, id> * dataItem in data) {
MTRAttributePath * path = dataItem[MTRAttributePathKey];
XCTAssertNotNil(path);
if (dataItem[MTRDataKey]) {
[endpoints addObject:path.endpoint];
[clusters addObject:[MTRClusterPath clusterPathWithEndpointID:path.endpoint clusterID:path.cluster]];
if (path.endpoint.unsignedLongValue == 0 && path.cluster.unsignedLongValue == MTRClusterIDTypeBasicInformationID) {
[rootBasicInformationAttributes addObject:path.attribute];
}
}
}

attributeReportsReceived += data.count;
};

Expand Down Expand Up @@ -1539,6 +1554,39 @@ - (void)test017_TestMTRDeviceBasics
XCTAssertNotEqual(attributeReportsReceived, 0);
XCTAssertNotEqual(eventReportsReceived, 0);

// Test readAttributePaths. First, try DeviceTypeList across all endpoints.
__auto_type * deviceTypeListPath = [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@(MTRClusterIDTypeDescriptorID) attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID)];
__auto_type * deviceTypes = [device readAttributePaths:@[ deviceTypeListPath ]];
XCTAssertEqual(deviceTypes.count, endpoints.count);

// Now try ClusterRevision across all clusters.
__auto_type * clusterRevisionsPath = [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:nil attributeID:@(MTRAttributeIDTypeGlobalAttributeClusterRevisionID)];
__auto_type * clusterRevisions = [device readAttributePaths:@[ clusterRevisionsPath ]];
XCTAssertEqual(clusterRevisions.count, clusters.count);

// Now try BasicInformation in a few different ways:
__auto_type * basicInformationAllAttributesPath = [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@(MTRClusterIDTypeBasicInformationID) attributeID:nil];
__auto_type * basicInformationAllAttributes = [device readAttributePaths:@[ basicInformationAllAttributesPath ]];

__auto_type * basicInformationAllRootAttributesPath = [MTRAttributeRequestPath requestPathWithEndpointID:@(0) clusterID:@(MTRClusterIDTypeBasicInformationID) attributeID:nil];
__auto_type * basicInformationAllRootAttributes = [device readAttributePaths:@[ basicInformationAllRootAttributesPath ]];
// Should have gotten the same things, because Basic Information only exists
// on the root endpoint.
XCTAssertEqualObjects([NSSet setWithArray:basicInformationAllAttributes], [NSSet setWithArray:basicInformationAllRootAttributes]);
XCTAssertEqual(basicInformationAllAttributes.count, rootBasicInformationAttributes.count);

// Now try multiple paths. Should just get the union of all the things for
// each path.
__auto_type * variousThings = [device readAttributePaths:@[ deviceTypeListPath, basicInformationAllRootAttributesPath ]];
XCTAssertEqualObjects([NSSet setWithArray:variousThings],
[[NSSet setWithArray:deviceTypes] setByAddingObjectsFromSet:[NSSet setWithArray:basicInformationAllRootAttributes]]);

// And similar if the paths expand to overlapping sets of existent paths
// (e.g. because Basic Information has a ClusterRevision).
variousThings = [device readAttributePaths:@[ clusterRevisionsPath, basicInformationAllRootAttributesPath ]];
XCTAssertEqualObjects([NSSet setWithArray:variousThings],
[[NSSet setWithArray:clusterRevisions] setByAddingObjectsFromSet:[NSSet setWithArray:basicInformationAllRootAttributes]]);

// Before resubscribe, first test write failure and expected value effects
NSNumber * testEndpointID = @(1);
NSNumber * testClusterID = @(8);
Expand Down

0 comments on commit bc1add1

Please sign in to comment.