Skip to content

Commit

Permalink
[Darwin] MTRDevice delegate should be notified when cache is primed w…
Browse files Browse the repository at this point in the history
…ith basic info
  • Loading branch information
jtung-apple committed Feb 22, 2024
1 parent f67c1af commit e21e230
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 3 deletions.
5 changes: 5 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@ MTR_EXTERN NSString * const MTRDataVersionKey MTR_NEWLY_AVAILABLE;
*/
- (void)deviceBecameActive:(MTRDevice *)device MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));

/**
* Notifies delegate when the device attribute cache has been primed with initial configuration data of the device
*/
- (void)deviceCachePrimed:(MTRDevice *)device MTR_NEWLY_AVAILABLE;

@end

@interface MTRDevice (Deprecated)
Expand Down
75 changes: 75 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ @implementation MTRDevice {
#ifdef DEBUG
NSUInteger _unitTestAttributesReportedSinceLastCheck;
#endif
BOOL _delegateDeviceCachePrimedCalled;
}

- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller
Expand Down Expand Up @@ -506,6 +507,9 @@ - (void)setDelegate:(id<MTRDeviceDelegate>)delegate queue:(dispatch_queue_t)queu
[self _setupSubscription];
}

// Check if cache is already primed from storage
[self _checkIfCacheIsPrimed];

os_unfair_lock_unlock(&self->_lock);
}

Expand Down Expand Up @@ -574,6 +578,29 @@ - (BOOL)_subscriptionAbleToReport
return (delegate != nil) && (state == MTRDeviceStateReachable);
}

- (BOOL)_callDelegateWithBlock:(void (^)(id<MTRDeviceDelegate>))block
{
os_unfair_lock_assert_owner(&self->_lock);
id<MTRDeviceDelegate> delegate = _weakDelegate.strongObject;
if (delegate) {
dispatch_async(_delegateQueue, ^{
block(delegate);
});
return YES;
}
return NO;
}

- (void)_callDelegateDeviceCachePrimed
{
os_unfair_lock_assert_owner(&self->_lock);
_delegateDeviceCachePrimedCalled = [self _callDelegateWithBlock:^(id<MTRDeviceDelegate> delegate) {
if ([delegate respondsToSelector:@selector(deviceCachePrimed:)]) {
[delegate deviceCachePrimed:self];
}
}];
}

// assume lock is held
- (void)_changeState:(MTRDeviceState)state
{
Expand Down Expand Up @@ -741,6 +768,12 @@ - (void)_handleReportEnd
_receivingReport = NO;
_receivingPrimingReport = NO;
_estimatedStartTimeFromGeneralDiagnosticsUpTime = nil;

// First subscription report is priming report
if (!_delegateDeviceCachePrimedCalled) {
[self _callDelegateDeviceCachePrimed];
}

// For unit testing only
#ifdef DEBUG
id delegate = _weakDelegate.strongObject;
Expand Down Expand Up @@ -2147,6 +2180,48 @@ - (void)_removeExpectedValueForAttributePath:(MTRAttributePath *)attributePath e
}
}

// This method checks if there is a need to inform delegate that the attribute cache has been "primed"
// - The delegate callback is only called once
- (void)_checkIfCacheIsPrimed
{
os_unfair_lock_assert_owner(&self->_lock);

// Only send the callback once per lifetime of MTRDevice
if (_delegateDeviceCachePrimedCalled) {
return;
}

// Check if root node descriptor exists
NSDictionary * rootDescriptorPartsListDataValue = _readCache[[MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(MTRClusterIDTypeDescriptorID) attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)]];
if (!rootDescriptorPartsListDataValue || ![MTRArrayValueType isEqualToString:rootDescriptorPartsListDataValue[MTRTypeKey]]) {
return;
}
NSArray * partsList = rootDescriptorPartsListDataValue[MTRValueKey];
if (![partsList isKindOfClass:[NSArray class]] || !partsList.count) {
MTR_LOG_ERROR("%@ unexpected type %@ for parts list %@", self, [partsList class], partsList);
return;
}

// Check if we have cached descriptor clusters for each listed endpoint
for (NSDictionary * endpointDataValue in partsList) {
if (![MTRUnsignedIntegerValueType isEqual:endpointDataValue[MTRTypeKey]]) {
MTR_LOG_ERROR("%@ unexpected type for parts list item %@", self, endpointDataValue);
continue;
}
NSNumber * endpoint = endpointDataValue[MTRValueKey];
if (![endpoint isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("%@ unexpected type for parts list item %@", self, endpointDataValue);
continue;
}
NSDictionary * descriptorDeviceTypeListDataValue = _readCache[[MTRAttributePath attributePathWithEndpointID:endpoint clusterID:@(MTRClusterIDTypeDescriptorID) attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID)]];
if (![MTRArrayValueType isEqualToString:descriptorDeviceTypeListDataValue[MTRTypeKey]] || !descriptorDeviceTypeListDataValue[MTRValueKey]) {
return;
}
}

[self _callDelegateDeviceCachePrimed];
}

- (MTRBaseDevice *)newBaseDevice
{
return [MTRBaseDevice deviceWithNodeID:self.nodeID controller:self.deviceController];
Expand Down
14 changes: 11 additions & 3 deletions src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -2843,7 +2843,7 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage
{
dispatch_queue_t queue = dispatch_get_main_queue();

// First start with clean slate and
// First start with clean slate by removing the MTRDevice and clearing the persisted cache
__auto_type * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController];
[sController removeDevice:device];
[sController.controllerDataStore clearAllStoredAttributes];
Expand All @@ -2853,16 +2853,20 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage
// Now recreate device and get subscription primed
device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController];
XCTestExpectation * gotReportsExpectation = [self expectationWithDescription:@"Attribute and Event reports have been received"];
XCTestExpectation * gotDeviceCachePrimed = [self expectationWithDescription:@"Device cache primed for the first time"];
__auto_type * delegate = [[MTRDeviceTestDelegate alloc] init];
__weak __auto_type weakDelegate = delegate;
delegate.onReportEnd = ^{
[gotReportsExpectation fulfill];
__strong __auto_type strongDelegate = weakDelegate;
strongDelegate.onReportEnd = nil;
};
delegate.onDeviceCachePrimed = ^{
[gotDeviceCachePrimed fulfill];
};
[device setDelegate:delegate queue:queue];

[self waitForExpectations:@[ gotReportsExpectation ] timeout:60];
[self waitForExpectations:@[ gotReportsExpectation, gotDeviceCachePrimed ] timeout:60];

NSUInteger attributesReportedWithFirstSubscription = [device unitTestAttributesReportedSinceLastCheck];

Expand All @@ -2874,14 +2878,18 @@ - (void)test031_MTRDeviceAttributeCacheLocalTestStorage
device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:sController];

XCTestExpectation * resubGotReportsExpectation = [self expectationWithDescription:@"Attribute and Event reports have been received for resubscription"];
XCTestExpectation * gotDeviceCachePrimedAgain = [self expectationWithDescription:@"Device cache primed upon load from persistence"];
delegate.onReportEnd = ^{
[resubGotReportsExpectation fulfill];
__strong __auto_type strongDelegate = weakDelegate;
strongDelegate.onReportEnd = nil;
};
delegate.onDeviceCachePrimed = ^{
[gotDeviceCachePrimedAgain fulfill];
};
[device setDelegate:delegate queue:queue];

[self waitForExpectations:@[ resubGotReportsExpectation ] timeout:60];
[self waitForExpectations:@[ gotDeviceCachePrimedAgain, resubGotReportsExpectation ] timeout:60];

NSUInteger attributesReportedWithSecondSubscription = [device unitTestAttributesReportedSinceLastCheck];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ typedef void (^MTRDeviceTestDelegateDataHandler)(NSArray<NSDictionary<NSString *
@property (nonatomic, nullable) MTRDeviceTestDelegateDataHandler onAttributeDataReceived;
@property (nonatomic, nullable) MTRDeviceTestDelegateDataHandler onEventDataReceived;
@property (nonatomic, nullable) dispatch_block_t onReportEnd;
@property (nonatomic, nullable) dispatch_block_t onDeviceCachePrimed;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,11 @@ - (NSNumber *)unitTestMaxIntervalOverrideForSubscription:(MTRDevice *)device
return @(2); // seconds
}

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

@end

0 comments on commit e21e230

Please sign in to comment.