Skip to content

Commit

Permalink
Add support for notifying the delegate when device configuration changes
Browse files Browse the repository at this point in the history
- Device configuration changes include updates in attributes parts list, server list,
  device type list, cluster revision or feature map in the descriptor cluster

- Add a test to verify the delegate is notified when device configuration changes
  • Loading branch information
nivi-apple committed Apr 24, 2024
1 parent a456135 commit bab86fe
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,16 @@ MTR_EXTERN NSString * const MTRDataVersionKey MTR_NEWLY_AVAILABLE;
*/
- (void)deviceCachePrimed:(MTRDevice *)device MTR_NEWLY_AVAILABLE;

/**
* Notifies delegate when the device configuration changes. Device configuration changes include updates in parts list, device list,
* server list, feature map or cluster revision attributes in the descriptor cluster.
*
* This is called when the MTRDevice object detects a change in the device configuration and reports that to the delegate.
*
* The intention is that after this is called, the client should re-enumerate the device topology.
*/
- (void)deviceConfigurationChanged:(MTRDevice *)device MTR_NEWLY_AVAILABLE;

@end

@interface MTRDevice (Deprecated)
Expand Down
61 changes: 61 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ @implementation MTRDevice {
// ReadClient). Nil if we have had no such failures.
NSDate * _Nullable _lastSubscriptionFailureTime;
MTRDeviceConnectivityMonitor * _connectivityMonitor;

// This boolean keeps track of any device configuration changes received in an attribute report
// and when the report ends, we notify the delegate. Device configuration changes include parts
// list, server list, device list, cluster revision and feature map updates.
BOOL _deviceConfigurationChanged;
}

- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller
Expand Down Expand Up @@ -1063,6 +1068,19 @@ - (void)_handleReportEnd
_clustersToPersist = nil;
}

// After the handling of the report, if we detected a device configuration change, notify the delegate
// of the same.
if (_deviceConfigurationChanged)
{
id<MTRDeviceDelegate> delegate = _weakDelegate.strongObject;
if (delegate) {
dispatch_async(_delegateQueue, ^{
if ([delegate respondsToSelector:@selector(deviceConfigurationChanged:)])
[delegate deviceConfigurationChanged:self];
});
}
}

// For unit testing only
#ifdef DEBUG
id delegate = _weakDelegate.strongObject;
Expand Down Expand Up @@ -1090,10 +1108,44 @@ - (void)_reportAttributes:(NSArray<NSDictionary<NSString *, id> *> *)attributes
}
}

// When we receive an attribute report, check if there are any changes in parts list, server list, device type list, cluster revision
// or feature map attributes of the descriptor cluster. If yes, make a note that the device configuration changed.
- (void) _noteDeviceConfigurationChanged:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
{
for (NSDictionary<NSString *, id> * attribute in attributeReport) {
MTRAttributePath * attributePath = attribute[MTRAttributePathKey];

if (attributePath.cluster.unsignedLongValue != MTRClusterDescriptorID)
{
return;
}

switch (attributePath.attribute.unsignedLongValue)
{
case MTRClusterDescriptorAttributePartsListID:
case MTRClusterDescriptorAttributeServerListID:
case MTRClusterDescriptorAttributeDeviceTypeListID:
case MTRClusterDescriptorAttributeClusterRevisionID:
case MTRClusterDescriptorAttributeFeatureMapID:
{
// If changes are detected, note that the device configuration has changed.
NSDictionary * cachedAttributeDataValue = [self _cachedAttributeValueForPath:attributePath];
if (cachedAttributeDataValue != nil && ![self _attributeDataValue:attribute[MTRDataKey] isEqualToDataValue:cachedAttributeDataValue])
{
_deviceConfigurationChanged = YES;
break;
}
}
}
}
}

- (void)_handleAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
{
std::lock_guard lock(_lock);

[self _noteDeviceConfigurationChanged:attributeReport];

// _getAttributesToReportWithReportedValues will log attribute paths reported
[self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeReport]];
}
Expand All @@ -1105,6 +1157,15 @@ - (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eve
[self _handleEventReport:eventReport];
});
}

- (void)unitTestInjectAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
{
dispatch_async(self.queue, ^{
[self _handleReportBegin];
[self _handleAttributeReport:attributeReport];
[self _handleReportEnd];
});
}
#endif

- (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport
Expand Down
110 changes: 110 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -2974,6 +2974,7 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage
}
NSUInteger storedAttributeCountDifferenceFromMTRDeviceReport = dataStoreAttributeCountAfterSecondSubscription - attributesReportedWithSecondSubscription;
XCTAssertTrue(storedAttributeCountDifferenceFromMTRDeviceReport > 300);
[sController removeDevice:device];
}

- (void)test032_MTRPathClassesEncoding
Expand Down Expand Up @@ -3020,6 +3021,115 @@ - (void)test032_MTRPathClassesEncoding
XCTAssertEqualObjects(originalCommandPath, decodedCommandPath);
}

#ifdef DEBUG
- (void)test033_TestMTRDeviceDeviceConfigurationChanged
{
// Ensure the test starts with clean slate.
[sController.controllerDataStore clearAllStoredClusterData];
NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
XCTAssertEqual(storedClusterDataAfterClear.count, 0);

__auto_type * device = [MTRDevice deviceWithNodeID:kDeviceId deviceController:sController];
dispatch_queue_t queue = dispatch_get_main_queue();

// Check if subscription is set up and initial reports are received.
XCTestExpectation * subscriptionExpectation = [self expectationWithDescription:@"Subscription has been set up"];
XCTestExpectation * gotInitialReportsExpectation = [self expectationWithDescription:@"Initial Attribute and Event reports have been received"];

__auto_type * delegate = [[MTRDeviceTestDelegate alloc] init];
delegate.onReachable = ^() {
[subscriptionExpectation fulfill];
};

delegate.onReportEnd = ^() {
[gotInitialReportsExpectation fulfill];
};

[device setDelegate:delegate queue:queue];

// Wait for subscription set up and intitial reports received.
[self waitForExpectations:@[ subscriptionExpectation, gotInitialReportsExpectation, ] timeout:60];

XCTestExpectation * gotAttributeReportExpectation = [self expectationWithDescription:@"Attribute report has been received"];
XCTestExpectation * gotAttributeReportEndExpectation = [self expectationWithDescription:@"Attribute report has ended"];
XCTestExpectation * deviceConfigurationChangedExpectation = [self expectationWithDescription:@"Device configuration changed was receieved"];
__block unsigned attributeReportsReceived = 0;
delegate.onAttributeDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * attributeReport) {
attributeReportsReceived += attributeReport.count;
XCTAssert(attributeReportsReceived > 0);
for (NSDictionary<NSString *, id> * attributeDict in attributeReport) {
MTRAttributePath * attributePath = attributeDict[MTRAttributePathKey];
XCTAssert(attributePath != nil);

XCTAssert(attributePath.cluster.unsignedLongValue == MTRClusterDescriptorID);
XCTAssert(attributePath.attribute.unsignedLongValue == MTRClusterDescriptorAttributePartsListID);

NSDictionary * dataValue = attributeDict[MTRDataKey];
XCTAssert(dataValue != nil);
NSArray<NSNumber *> * partsList = dataValue[MTRValueKey];
XCTAssert([partsList isEqual:(@[
@{
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @1,
}
},
@{
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @2,
}
},
@{
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @3,
}
},
])]);
[gotAttributeReportExpectation fulfill];
}
};

delegate.onReportEnd = ^() {
[gotAttributeReportEndExpectation fulfill];
};

delegate.onDeviceConfigurationChanged = ^() {
[deviceConfigurationChangedExpectation fulfill];
};

// Inject the attribute report with parts list changed.
[device unitTestInjectAttributeReport:@[ @{
MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(0x001D) attributeID:@(3)],
MTRDataKey : @{
MTRTypeKey : MTRArrayValueType,
MTRValueKey : @[
@{
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @1,
}
},
@{
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @2,
}
},
@{
MTRDataKey : @ {
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @3,
}
},
],
}}]];

[self waitForExpectations:@[ gotAttributeReportExpectation, gotAttributeReportEndExpectation, deviceConfigurationChangedExpectation ] timeout:60];
}
#endif

@end

@interface MTRDeviceEncoderTests : XCTestCase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ typedef void (^MTRDeviceTestDelegateDataHandler)(NSArray<NSDictionary<NSString *
@property (nonatomic, nullable) dispatch_block_t onDeviceCachePrimed;
@property (nonatomic) BOOL skipExpectedValuesForWrite;
@property (nonatomic) BOOL forceAttributeReportsIfMatchingCache;
@property (nonatomic, nullable) dispatch_block_t onDeviceConfigurationChanged;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,13 @@ - (BOOL)unitTestForceAttributeReportsIfMatchingCache:(MTRDevice *)device
{
return self.forceAttributeReportsIfMatchingCache;
}

- (void)deviceConfigurationChanged:(MTRDevice *)device
{
if (self.onDeviceConfigurationChanged != nil) {
self.onDeviceConfigurationChanged();
}
}


@end
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface MTRDevice (TestDebug)
- (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport;
- (void)unitTestInjectAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport;
- (NSUInteger)unitTestAttributesReportedSinceLastCheck;
@end
#endif
Expand Down

0 comments on commit bab86fe

Please sign in to comment.