diff --git a/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h b/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h index 2744c7d11cbfeb..113dedb1a28eae 100644 --- a/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h +++ b/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h @@ -216,6 +216,9 @@ class SubscribeEvent : public ModelCommand { } subscriptionEstablished:^() { mSubscriptionEstablished = YES; + } + resubscriptionScheduled:^(NSError * error, NSNumber * resubscriptionDelay) { + NSLog(@"Subscription dropped with error %@. Resubscription in %@ms", error, resubscriptionDelay); }]; return CHIP_NO_ERROR; diff --git a/src/darwin/CHIPTool/CHIPTool/View Controllers/Temperature Sensor/TemperatureSensorViewController.m b/src/darwin/CHIPTool/CHIPTool/View Controllers/Temperature Sensor/TemperatureSensorViewController.m index e9c8f17113db8d..6ee75f013459f0 100644 --- a/src/darwin/CHIPTool/CHIPTool/View Controllers/Temperature Sensor/TemperatureSensorViewController.m +++ b/src/darwin/CHIPTool/CHIPTool/View Controllers/Temperature Sensor/TemperatureSensorViewController.m @@ -250,8 +250,9 @@ - (void)reportFromUserEnteredSettings errorHandler:^(NSError * error) { NSLog(@"Status: update reportAttributeMeasuredValue completed with error %@", [error description]); } - subscriptionEstablished:^ { - }]; + subscriptionEstablished:^{ + } + resubscriptionScheduled:nil]; } else { NSLog(@"Status: Failed to establish a connection with the device"); } diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index 9f40958a24f030..f2b3e01a7e6fcf 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -77,6 +77,16 @@ typedef void (^MTRDeviceResponseHandler)(NSArray *> typedef void (^MTRDeviceReportHandler)(NSArray * values); typedef void (^MTRDeviceErrorHandler)(NSError * error); +/** + * Handler for subscribeWithQueue: resubscription scheduling notifications. + * This will be called when subscription loss is detected. + * + * @param error An error indicating the reason the subscription has been lost. + * @param resubscriptionDelay A delay, in milliseconds, before the next + * automatic resubscription will be attempted. + */ +typedef void (^MTRDeviceResubscriptionScheduledHandler)(NSError * error, NSNumber * resubscriptionDelay); + extern NSString * const MTRAttributePathKey; extern NSString * const MTRCommandPathKey; extern NSString * const MTREventPathKey; @@ -126,15 +136,23 @@ extern NSString * const MTRArrayValueType; * instances. Errors for specific paths, not the whole subscription, will be * reported via those objects. * - * errorHandler will be called any time there is an error for the - * entire subscription (with a non-nil "error"), and terminate the subscription. + * errorHandler will be called any time there is an error for the entire + * subscription (with a non-nil "error"), and terminate the subscription. This + * will generally not be invoked if auto-resubscription is enabled, unless there + * is a fatal error during a resubscription attempt. * * Both report handlers are not supported over XPC at the moment. * - * subscriptionEstablished block, if not nil, will be called once the + * The subscriptionEstablished block, if not nil, will be called once the * subscription is established. This will be _after_ the first (priming) call * to both report handlers. Note that if the MTRSubscribeParams are set to * automatically resubscribe this can end up being called more than once. + * + * The resubscriptionScheduled block, if not nil, will be called if + * auto-resubscription is enabled, subscription loss is detected, and a + * resubscription is scheduled. This can be called multiple times in a row + * without an intervening subscriptionEstablished call if the resubscription + * attempts fail. */ - (void)subscribeWithQueue:(dispatch_queue_t)queue minInterval:(uint16_t)minInterval @@ -144,7 +162,8 @@ extern NSString * const MTRArrayValueType; attributeReportHandler:(MTRDeviceReportHandler _Nullable)attributeReportHandler eventReportHandler:(MTRDeviceReportHandler _Nullable)eventReportHandler errorHandler:(MTRDeviceErrorHandler)errorHandler - subscriptionEstablished:(dispatch_block_t _Nullable)subscriptionEstablishedHandler; + subscriptionEstablished:(dispatch_block_t _Nullable)subscriptionEstablishedHandler + resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler; /** * Read attribute in a designated attribute path diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 668e1412be3864..631291b9963fec 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -273,10 +273,10 @@ - (void)invalidateCASESession class SubscriptionCallback final : public MTRBaseSubscriptionCallback { public: SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, - ErrorCallback errorCallback, SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, - OnDoneHandler _Nullable onDoneHandler) - : MTRBaseSubscriptionCallback( - queue, attributeReportCallback, eventReportCallback, errorCallback, nil, subscriptionEstablishedHandler, onDoneHandler) + ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionScheduledHandler, + SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler) + : MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback, + resubscriptionScheduledHandler, subscriptionEstablishedHandler, onDoneHandler) { } @@ -296,6 +296,7 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue eventReportHandler:(nullable void (^)(NSArray * value))eventReportHandler errorHandler:(void (^)(NSError * error))errorHandler subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler + resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler { if (self.paseDevice != nil) { // We don't support subscriptions over PASE. @@ -308,78 +309,79 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue // Copy params before going async. params = [params copy]; - [self.deviceController getSessionForNode:self.nodeID - completionHandler:^(ExchangeManager * _Nullable exchangeManager, const Optional & session, - NSError * _Nullable error) { - if (error != nil) { - dispatch_async(queue, ^{ - errorHandler(error); - }); - return; - } - - // Wildcard endpoint, cluster, attribute, event. - auto attributePath = std::make_unique(); - auto eventPath = std::make_unique(); - ReadPrepareParams readParams(session.Value()); - readParams.mMinIntervalFloorSeconds = minInterval; - readParams.mMaxIntervalCeilingSeconds = maxInterval; - readParams.mpAttributePathParamsList = attributePath.get(); - readParams.mAttributePathParamsListSize = 1; - readParams.mpEventPathParamsList = eventPath.get(); - readParams.mEventPathParamsListSize = 1; - readParams.mKeepSubscriptions = [params.keepPreviousSubscriptions boolValue]; - - std::unique_ptr callback; - std::unique_ptr readClient; - std::unique_ptr attributeCache; - if (attributeCacheContainer) { - __weak MTRAttributeCacheContainer * weakPtr = attributeCacheContainer; - callback = std::make_unique(queue, attributeReportHandler, - eventReportHandler, errorHandler, subscriptionEstablishedHandler, ^{ - MTRAttributeCacheContainer * container = weakPtr; - if (container) { - container.cppAttributeCache = nullptr; - } - }); - attributeCache = std::make_unique(*callback.get()); - readClient = std::make_unique(InteractionModelEngine::GetInstance(), exchangeManager, - attributeCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); - } else { - callback = std::make_unique(queue, attributeReportHandler, - eventReportHandler, errorHandler, subscriptionEstablishedHandler, nil); - readClient = std::make_unique(InteractionModelEngine::GetInstance(), exchangeManager, - callback->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); - } - - CHIP_ERROR err; - if (params != nil && params.autoResubscribe != nil && ![params.autoResubscribe boolValue]) { - err = readClient->SendRequest(readParams); - } else { - // SendAutoResubscribeRequest cleans up the params, even on failure. - attributePath.release(); - eventPath.release(); - err = readClient->SendAutoResubscribeRequest(std::move(readParams)); - } - - if (err != CHIP_NO_ERROR) { - dispatch_async(queue, ^{ - errorHandler([MTRError errorForCHIPErrorCode:err]); - }); - - return; - } - - if (attributeCacheContainer) { - attributeCacheContainer.cppAttributeCache = attributeCache.get(); - // ClusterStateCache will be deleted when OnDone is called or an error is encountered as well. - callback->AdoptAttributeCache(std::move(attributeCache)); - } - // Callback and ReadClient will be deleted when OnDone is called or an error is - // encountered. - callback->AdoptReadClient(std::move(readClient)); - callback.release(); - }]; + [self.deviceController + getSessionForNode:self.nodeID + completionHandler:^( + ExchangeManager * _Nullable exchangeManager, const Optional & session, NSError * _Nullable error) { + if (error != nil) { + dispatch_async(queue, ^{ + errorHandler(error); + }); + return; + } + + // Wildcard endpoint, cluster, attribute, event. + auto attributePath = std::make_unique(); + auto eventPath = std::make_unique(); + ReadPrepareParams readParams(session.Value()); + readParams.mMinIntervalFloorSeconds = minInterval; + readParams.mMaxIntervalCeilingSeconds = maxInterval; + readParams.mpAttributePathParamsList = attributePath.get(); + readParams.mAttributePathParamsListSize = 1; + readParams.mpEventPathParamsList = eventPath.get(); + readParams.mEventPathParamsListSize = 1; + readParams.mKeepSubscriptions = [params.keepPreviousSubscriptions boolValue]; + + std::unique_ptr callback; + std::unique_ptr readClient; + std::unique_ptr attributeCache; + if (attributeCacheContainer) { + __weak MTRAttributeCacheContainer * weakPtr = attributeCacheContainer; + callback = std::make_unique(queue, attributeReportHandler, eventReportHandler, errorHandler, + resubscriptionScheduledHandler, subscriptionEstablishedHandler, ^{ + MTRAttributeCacheContainer * container = weakPtr; + if (container) { + container.cppAttributeCache = nullptr; + } + }); + attributeCache = std::make_unique(*callback.get()); + readClient = std::make_unique(InteractionModelEngine::GetInstance(), exchangeManager, + attributeCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); + } else { + callback = std::make_unique(queue, attributeReportHandler, eventReportHandler, errorHandler, + resubscriptionScheduledHandler, subscriptionEstablishedHandler, nil); + readClient = std::make_unique(InteractionModelEngine::GetInstance(), exchangeManager, + callback->GetBufferedCallback(), ReadClient::InteractionType::Subscribe); + } + + CHIP_ERROR err; + if (params != nil && params.autoResubscribe != nil && ![params.autoResubscribe boolValue]) { + err = readClient->SendRequest(readParams); + } else { + // SendAutoResubscribeRequest cleans up the params, even on failure. + attributePath.release(); + eventPath.release(); + err = readClient->SendAutoResubscribeRequest(std::move(readParams)); + } + + if (err != CHIP_NO_ERROR) { + dispatch_async(queue, ^{ + errorHandler([MTRError errorForCHIPErrorCode:err]); + }); + + return; + } + + if (attributeCacheContainer) { + attributeCacheContainer.cppAttributeCache = attributeCache.get(); + // ClusterStateCache will be deleted when OnDone is called or an error is encountered as well. + callback->AdoptAttributeCache(std::move(attributeCache)); + } + // Callback and ReadClient will be deleted when OnDone is called or an error is + // encountered. + callback->AdoptReadClient(std::move(readClient)); + callback.release(); + }]; } // Convert TLV data into data-value dictionary as described in MTRDeviceResponseHandler diff --git a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h index 856691f0f511f6..38d665453ad0a6 100644 --- a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h +++ b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.h @@ -17,6 +17,7 @@ #pragma once #import "Foundation/Foundation.h" +#import "MTRBaseDevice.h" #include #include @@ -43,7 +44,6 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^DataReportCallback)(NSArray * value); typedef void (^ErrorCallback)(NSError * error); -typedef void (^ResubscriptionCallback)(void); typedef void (^SubscriptionEstablishedHandler)(void); typedef void (^OnDoneHandler)(void); @@ -51,14 +51,14 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac public: MTRBaseSubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, ErrorCallback errorCallback, - ResubscriptionCallback _Nullable resubscriptionCallback, + MTRDeviceResubscriptionScheduledHandler _Nullable resubscriptionCallback, SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler, OnDoneHandler _Nullable onDoneHandler) : mQueue(queue) , mAttributeReportCallback(attributeReportCallback) , mEventReportCallback(eventReportCallback) , mErrorCallback(errorCallback) - , mSubscriptionEstablishedHandler(subscriptionEstablishedHandler) , mResubscriptionCallback(resubscriptionCallback) + , mSubscriptionEstablishedHandler(subscriptionEstablishedHandler) , mBufferedReadAdapter(*this) , mOnDoneHandler(onDoneHandler) { @@ -123,8 +123,8 @@ class MTRBaseSubscriptionCallback : public chip::app::ClusterStateCache::Callbac // We set mErrorCallback to nil when queueing error reports, so we // make sure to only report one error. ErrorCallback _Nullable mErrorCallback = nil; + MTRDeviceResubscriptionScheduledHandler _Nullable mResubscriptionCallback = nil; SubscriptionEstablishedHandler _Nullable mSubscriptionEstablishedHandler = nil; - ResubscriptionCallback _Nullable mResubscriptionCallback = nil; chip::app::BufferedReadCallback mBufferedReadAdapter; // Our lifetime management is a little complicated. On errors that don't diff --git a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm index 6882ec37b22f0f..60dd3b9dde3aba 100644 --- a/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm +++ b/src/darwin/Framework/CHIP/MTRBaseSubscriptionCallback.mm @@ -102,7 +102,18 @@ CHIP_ERROR MTRBaseSubscriptionCallback::OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) { - return apReadClient->DefaultResubscribePolicy(aTerminationCause); + CHIP_ERROR err = ClusterStateCache::Callback::OnResubscriptionNeeded(apReadClient, aTerminationCause); + ReturnErrorOnFailure(err); + + if (mResubscriptionCallback != nil) { + auto callback = mResubscriptionCallback; + auto error = [MTRError errorForCHIPErrorCode:aTerminationCause]; + auto delayMs = @(apReadClient->ComputeTimeTillNextSubscription()); + dispatch_async(mQueue, ^{ + callback(error, delayMs); + }); + } + return CHIP_NO_ERROR; } void MTRBaseSubscriptionCallback::ReportError(CHIP_ERROR aError, bool aCancelSubscription) diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 2bfa2e62495cce..7a84d514185329 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -102,8 +102,8 @@ - (id)strongObject class SubscriptionCallback final : public MTRBaseSubscriptionCallback { public: SubscriptionCallback(dispatch_queue_t queue, DataReportCallback attributeReportCallback, DataReportCallback eventReportCallback, - ErrorCallback errorCallback, SubscriptionEstablishedHandler subscriptionEstablishedHandler, - ResubscriptionCallback resubscriptionCallback, OnDoneHandler onDoneHandler) + ErrorCallback errorCallback, MTRDeviceResubscriptionScheduledHandler resubscriptionCallback, + SubscriptionEstablishedHandler subscriptionEstablishedHandler, OnDoneHandler onDoneHandler) : MTRBaseSubscriptionCallback(queue, attributeReportCallback, eventReportCallback, errorCallback, resubscriptionCallback, subscriptionEstablishedHandler, onDoneHandler) { @@ -327,14 +327,14 @@ - (void)subscribeWithMinInterval:(uint16_t)minInterval maxInterval:(uint16_t)max // OnError [self _handleSubscriptionError:error]; }, + ^(NSError * error, NSNumber * resubscriptionDelay) { + // OnResubscriptionNeeded + [self _handleResubscriptionNeeded]; + }, ^(void) { // OnSubscriptionEstablished [self _handleSubscriptionEstablished]; }, - ^(void) { - // OnResubscriptionNeeded - [self _handleResubscriptionNeeded]; - }, ^(void) { // OnDone [self _handleSubscriptionReset]; diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m index d6008fdf603799..2f0a7c47918652 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m +++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m @@ -54,9 +54,11 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue attributeReportHandler:(nullable void (^)(NSArray * value))attributeReportHandler eventReportHandler:(nullable void (^)(NSArray * value))eventReportHandler errorHandler:(void (^)(NSError * error))errorHandler - subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler; + subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler + resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler { - MTR_LOG_DEBUG("Subscribing all attributes... Note that reportHandler is not supported."); + MTR_LOG_DEBUG("Subscribing all attributes... Note that attributeReportHandler, eventReportHandler, and resubscriptionScheduled " + "are not supported."); if (attributeCacheContainer) { [attributeCacheContainer setXPCConnection:_xpcConnection controllerId:self.controller deviceId:self.nodeId]; } diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index b2aaf80d9753ad..1e95514ecc1e34 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -776,7 +776,8 @@ - (void)test011_ReadCachedAttribute } subscriptionEstablished:^() { [subscribeExpectation fulfill]; - }]; + } + resubscriptionScheduled:nil]; [self waitForExpectations:@[ subscribeExpectation ] timeout:60]; // Invoke command to set the attribute to a known state diff --git a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m index 6536ed5bf7b772..812436e84a7670 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m @@ -395,7 +395,8 @@ - (void)subscribeWithController:(id _Nullable)controller established[0] = @YES; completion(nil); } - }]; + } + resubscriptionScheduled:nil]; }]; } else { NSLog(@"Failed to get shared controller"); @@ -1794,7 +1795,8 @@ - (void)test900_SubscribeAttributeCache subscriptionEstablished:^() { NSLog(@"Attribute cache subscribed attributes"); [expectation fulfill]; - }]; + } + resubscriptionScheduled:nil]; // Wait for subscription establishment. This can take very long to collect initial reports. NSLog(@"Waiting for initial report..."); [self waitForExpectations:@[ expectation ] timeout:120]; diff --git a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m index 3b93b4e046ba01..4fba591fcf32ef 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m @@ -130,7 +130,8 @@ - (void)subscribeWithDeviceController:(MTRDeviceController *)deviceController established[0] = @YES; completionHandler(nil); } - }]; + } + resubscriptionScheduled:nil]; }]; } @end