Skip to content

Commit bab86fe

Browse files
committed
Add support for notifying the delegate when device configuration changes
- 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
1 parent a456135 commit bab86fe

File tree

6 files changed

+192
-0
lines changed

6 files changed

+192
-0
lines changed

src/darwin/Framework/CHIP/MTRDevice.h

+10
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,16 @@ MTR_EXTERN NSString * const MTRDataVersionKey MTR_NEWLY_AVAILABLE;
423423
*/
424424
- (void)deviceCachePrimed:(MTRDevice *)device MTR_NEWLY_AVAILABLE;
425425

426+
/**
427+
* Notifies delegate when the device configuration changes. Device configuration changes include updates in parts list, device list,
428+
* server list, feature map or cluster revision attributes in the descriptor cluster.
429+
*
430+
* This is called when the MTRDevice object detects a change in the device configuration and reports that to the delegate.
431+
*
432+
* The intention is that after this is called, the client should re-enumerate the device topology.
433+
*/
434+
- (void)deviceConfigurationChanged:(MTRDevice *)device MTR_NEWLY_AVAILABLE;
435+
426436
@end
427437

428438
@interface MTRDevice (Deprecated)

src/darwin/Framework/CHIP/MTRDevice.mm

+61
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,11 @@ @implementation MTRDevice {
371371
// ReadClient). Nil if we have had no such failures.
372372
NSDate * _Nullable _lastSubscriptionFailureTime;
373373
MTRDeviceConnectivityMonitor * _connectivityMonitor;
374+
375+
// This boolean keeps track of any device configuration changes received in an attribute report
376+
// and when the report ends, we notify the delegate. Device configuration changes include parts
377+
// list, server list, device list, cluster revision and feature map updates.
378+
BOOL _deviceConfigurationChanged;
374379
}
375380

376381
- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller
@@ -1063,6 +1068,19 @@ - (void)_handleReportEnd
10631068
_clustersToPersist = nil;
10641069
}
10651070

1071+
// After the handling of the report, if we detected a device configuration change, notify the delegate
1072+
// of the same.
1073+
if (_deviceConfigurationChanged)
1074+
{
1075+
id<MTRDeviceDelegate> delegate = _weakDelegate.strongObject;
1076+
if (delegate) {
1077+
dispatch_async(_delegateQueue, ^{
1078+
if ([delegate respondsToSelector:@selector(deviceConfigurationChanged:)])
1079+
[delegate deviceConfigurationChanged:self];
1080+
});
1081+
}
1082+
}
1083+
10661084
// For unit testing only
10671085
#ifdef DEBUG
10681086
id delegate = _weakDelegate.strongObject;
@@ -1090,10 +1108,44 @@ - (void)_reportAttributes:(NSArray<NSDictionary<NSString *, id> *> *)attributes
10901108
}
10911109
}
10921110

1111+
// When we receive an attribute report, check if there are any changes in parts list, server list, device type list, cluster revision
1112+
// or feature map attributes of the descriptor cluster. If yes, make a note that the device configuration changed.
1113+
- (void) _noteDeviceConfigurationChanged:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
1114+
{
1115+
for (NSDictionary<NSString *, id> * attribute in attributeReport) {
1116+
MTRAttributePath * attributePath = attribute[MTRAttributePathKey];
1117+
1118+
if (attributePath.cluster.unsignedLongValue != MTRClusterDescriptorID)
1119+
{
1120+
return;
1121+
}
1122+
1123+
switch (attributePath.attribute.unsignedLongValue)
1124+
{
1125+
case MTRClusterDescriptorAttributePartsListID:
1126+
case MTRClusterDescriptorAttributeServerListID:
1127+
case MTRClusterDescriptorAttributeDeviceTypeListID:
1128+
case MTRClusterDescriptorAttributeClusterRevisionID:
1129+
case MTRClusterDescriptorAttributeFeatureMapID:
1130+
{
1131+
// If changes are detected, note that the device configuration has changed.
1132+
NSDictionary * cachedAttributeDataValue = [self _cachedAttributeValueForPath:attributePath];
1133+
if (cachedAttributeDataValue != nil && ![self _attributeDataValue:attribute[MTRDataKey] isEqualToDataValue:cachedAttributeDataValue])
1134+
{
1135+
_deviceConfigurationChanged = YES;
1136+
break;
1137+
}
1138+
}
1139+
}
1140+
}
1141+
}
1142+
10931143
- (void)_handleAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
10941144
{
10951145
std::lock_guard lock(_lock);
10961146

1147+
[self _noteDeviceConfigurationChanged:attributeReport];
1148+
10971149
// _getAttributesToReportWithReportedValues will log attribute paths reported
10981150
[self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeReport]];
10991151
}
@@ -1105,6 +1157,15 @@ - (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eve
11051157
[self _handleEventReport:eventReport];
11061158
});
11071159
}
1160+
1161+
- (void)unitTestInjectAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport
1162+
{
1163+
dispatch_async(self.queue, ^{
1164+
[self _handleReportBegin];
1165+
[self _handleAttributeReport:attributeReport];
1166+
[self _handleReportEnd];
1167+
});
1168+
}
11081169
#endif
11091170

11101171
- (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport

src/darwin/Framework/CHIPTests/MTRDeviceTests.m

+110
Original file line numberDiff line numberDiff line change
@@ -2974,6 +2974,7 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage
29742974
}
29752975
NSUInteger storedAttributeCountDifferenceFromMTRDeviceReport = dataStoreAttributeCountAfterSecondSubscription - attributesReportedWithSecondSubscription;
29762976
XCTAssertTrue(storedAttributeCountDifferenceFromMTRDeviceReport > 300);
2977+
[sController removeDevice:device];
29772978
}
29782979

29792980
- (void)test032_MTRPathClassesEncoding
@@ -3020,6 +3021,115 @@ - (void)test032_MTRPathClassesEncoding
30203021
XCTAssertEqualObjects(originalCommandPath, decodedCommandPath);
30213022
}
30223023

3024+
#ifdef DEBUG
3025+
- (void)test033_TestMTRDeviceDeviceConfigurationChanged
3026+
{
3027+
// Ensure the test starts with clean slate.
3028+
[sController.controllerDataStore clearAllStoredClusterData];
3029+
NSDictionary * storedClusterDataAfterClear = [sController.controllerDataStore getStoredClusterDataForNodeID:@(kDeviceId)];
3030+
XCTAssertEqual(storedClusterDataAfterClear.count, 0);
3031+
3032+
__auto_type * device = [MTRDevice deviceWithNodeID:kDeviceId deviceController:sController];
3033+
dispatch_queue_t queue = dispatch_get_main_queue();
3034+
3035+
// Check if subscription is set up and initial reports are received.
3036+
XCTestExpectation * subscriptionExpectation = [self expectationWithDescription:@"Subscription has been set up"];
3037+
XCTestExpectation * gotInitialReportsExpectation = [self expectationWithDescription:@"Initial Attribute and Event reports have been received"];
3038+
3039+
__auto_type * delegate = [[MTRDeviceTestDelegate alloc] init];
3040+
delegate.onReachable = ^() {
3041+
[subscriptionExpectation fulfill];
3042+
};
3043+
3044+
delegate.onReportEnd = ^() {
3045+
[gotInitialReportsExpectation fulfill];
3046+
};
3047+
3048+
[device setDelegate:delegate queue:queue];
3049+
3050+
// Wait for subscription set up and intitial reports received.
3051+
[self waitForExpectations:@[ subscriptionExpectation, gotInitialReportsExpectation, ] timeout:60];
3052+
3053+
XCTestExpectation * gotAttributeReportExpectation = [self expectationWithDescription:@"Attribute report has been received"];
3054+
XCTestExpectation * gotAttributeReportEndExpectation = [self expectationWithDescription:@"Attribute report has ended"];
3055+
XCTestExpectation * deviceConfigurationChangedExpectation = [self expectationWithDescription:@"Device configuration changed was receieved"];
3056+
__block unsigned attributeReportsReceived = 0;
3057+
delegate.onAttributeDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * attributeReport) {
3058+
attributeReportsReceived += attributeReport.count;
3059+
XCTAssert(attributeReportsReceived > 0);
3060+
for (NSDictionary<NSString *, id> * attributeDict in attributeReport) {
3061+
MTRAttributePath * attributePath = attributeDict[MTRAttributePathKey];
3062+
XCTAssert(attributePath != nil);
3063+
3064+
XCTAssert(attributePath.cluster.unsignedLongValue == MTRClusterDescriptorID);
3065+
XCTAssert(attributePath.attribute.unsignedLongValue == MTRClusterDescriptorAttributePartsListID);
3066+
3067+
NSDictionary * dataValue = attributeDict[MTRDataKey];
3068+
XCTAssert(dataValue != nil);
3069+
NSArray<NSNumber *> * partsList = dataValue[MTRValueKey];
3070+
XCTAssert([partsList isEqual:(@[
3071+
@{
3072+
MTRDataKey : @ {
3073+
MTRTypeKey : MTRUnsignedIntegerValueType,
3074+
MTRValueKey : @1,
3075+
}
3076+
},
3077+
@{
3078+
MTRDataKey : @ {
3079+
MTRTypeKey : MTRUnsignedIntegerValueType,
3080+
MTRValueKey : @2,
3081+
}
3082+
},
3083+
@{
3084+
MTRDataKey : @ {
3085+
MTRTypeKey : MTRUnsignedIntegerValueType,
3086+
MTRValueKey : @3,
3087+
}
3088+
},
3089+
])]);
3090+
[gotAttributeReportExpectation fulfill];
3091+
}
3092+
};
3093+
3094+
delegate.onReportEnd = ^() {
3095+
[gotAttributeReportEndExpectation fulfill];
3096+
};
3097+
3098+
delegate.onDeviceConfigurationChanged = ^() {
3099+
[deviceConfigurationChangedExpectation fulfill];
3100+
};
3101+
3102+
// Inject the attribute report with parts list changed.
3103+
[device unitTestInjectAttributeReport:@[ @{
3104+
MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(0x001D) attributeID:@(3)],
3105+
MTRDataKey : @{
3106+
MTRTypeKey : MTRArrayValueType,
3107+
MTRValueKey : @[
3108+
@{
3109+
MTRDataKey : @ {
3110+
MTRTypeKey : MTRUnsignedIntegerValueType,
3111+
MTRValueKey : @1,
3112+
}
3113+
},
3114+
@{
3115+
MTRDataKey : @ {
3116+
MTRTypeKey : MTRUnsignedIntegerValueType,
3117+
MTRValueKey : @2,
3118+
}
3119+
},
3120+
@{
3121+
MTRDataKey : @ {
3122+
MTRTypeKey : MTRUnsignedIntegerValueType,
3123+
MTRValueKey : @3,
3124+
}
3125+
},
3126+
],
3127+
}}]];
3128+
3129+
[self waitForExpectations:@[ gotAttributeReportExpectation, gotAttributeReportEndExpectation, deviceConfigurationChangedExpectation ] timeout:60];
3130+
}
3131+
#endif
3132+
30233133
@end
30243134

30253135
@interface MTRDeviceEncoderTests : XCTestCase

src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.h

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ typedef void (^MTRDeviceTestDelegateDataHandler)(NSArray<NSDictionary<NSString *
3030
@property (nonatomic, nullable) dispatch_block_t onDeviceCachePrimed;
3131
@property (nonatomic) BOOL skipExpectedValuesForWrite;
3232
@property (nonatomic) BOOL forceAttributeReportsIfMatchingCache;
33+
@property (nonatomic, nullable) dispatch_block_t onDeviceConfigurationChanged;
3334
@end
3435

3536
NS_ASSUME_NONNULL_END

src/darwin/Framework/CHIPTests/TestHelpers/MTRDeviceTestDelegate.m

+9
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,13 @@ - (BOOL)unitTestForceAttributeReportsIfMatchingCache:(MTRDevice *)device
6969
{
7070
return self.forceAttributeReportsIfMatchingCache;
7171
}
72+
73+
- (void)deviceConfigurationChanged:(MTRDevice *)device
74+
{
75+
if (self.onDeviceConfigurationChanged != nil) {
76+
self.onDeviceConfigurationChanged();
77+
}
78+
}
79+
80+
7281
@end

src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
6262

6363
@interface MTRDevice (TestDebug)
6464
- (void)unitTestInjectEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport;
65+
- (void)unitTestInjectAttributeReport:(NSArray<NSDictionary<NSString *, id> *> *)attributeReport;
6566
- (NSUInteger)unitTestAttributesReportedSinceLastCheck;
6667
@end
6768
#endif

0 commit comments

Comments
 (0)