diff --git a/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h b/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h index 9b88ce50514724..ef834f6bdb1691 100644 --- a/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h +++ b/examples/darwin-framework-tool/commands/clusters/ReportCommandBridge.h @@ -184,6 +184,24 @@ class SubscribeEvent : public ModelCommand { public: SubscribeEvent() : ModelCommand("subscribe-all-events") + { + AddCommonArguments(); + } + + SubscribeEvent(chip::ClusterId clusterId, bool isClusterAny = false) + : ModelCommand("subscribe-event-by-id") + , mClusterId(clusterId) + { + if (isClusterAny == true) { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterId); + } + AddArgument("event-id", 0, UINT32_MAX, &mEventId); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + AddArgument("is-urgent", 0, 1, &mIsUrgent); + AddCommonArguments(); + } + + void AddCommonArguments() { AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); @@ -199,34 +217,61 @@ class SubscribeEvent : public ModelCommand { dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); MTRSubscribeParams * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(mMinInterval) maxInterval:@(mMaxInterval)]; + if (mEventNumber.HasValue()) { + params.minimumEventNumber = [NSNumber numberWithUnsignedLongLong:mEventNumber.Value()]; + } if (mKeepSubscriptions.HasValue()) { params.replaceExistingSubscriptions = !mKeepSubscriptions.Value(); } + if (mIsUrgent.HasValue()) { + params.reportEventsUrgently = mIsUrgent.Value(); + } if (mAutoResubscribe.HasValue()) { params.resubscribeIfLost = mAutoResubscribe.Value(); } - [device subscribeWithQueue:callbackQueue - params:params - clusterStateCacheContainer:nil - attributeReportHandler:^(NSArray * value) { - SetCommandExitStatus(CHIP_NO_ERROR); - } - eventReportHandler:^(NSArray * value) { - for (id item in value) { - NSLog(@"Response Item: %@", [item description]); + if (strcmp(GetName(), "subscribe-event-by-id") == 0) { + [device subscribeToEventsWithEndpointID:(endpointId == chip::kInvalidEndpointId) + ? nil + : [NSNumber numberWithUnsignedShort:endpointId] + clusterID:(mClusterId == chip::kInvalidClusterId) ? nil : [NSNumber numberWithUnsignedInteger:mClusterId] + eventID:(mEventId == chip::kInvalidEventId) ? nil : [NSNumber numberWithUnsignedInteger:mEventId] + params:params + queue:callbackQueue + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + if (values) { + for (id item in values) { + NSLog(@"Response Item: %@", [item description]); + } + } + SetCommandExitStatus(error); } - SetCommandExitStatus(CHIP_NO_ERROR); - } - errorHandler:^(NSError * error) { - SetCommandExitStatus(error); - } - subscriptionEstablished:^() { - mSubscriptionEstablished = YES; - } - resubscriptionScheduled:^(NSError * error, NSNumber * resubscriptionDelay) { - NSLog(@"Subscription dropped with error %@. Resubscription in %@ms", error, resubscriptionDelay); - }]; + subscriptionEstablished:^() { + mSubscriptionEstablished = YES; + }]; + } else { + [device subscribeWithQueue:callbackQueue + params:params + clusterStateCacheContainer:nil + attributeReportHandler:^(NSArray * value) { + SetCommandExitStatus(CHIP_NO_ERROR); + } + eventReportHandler:^(NSArray * value) { + for (id item in value) { + NSLog(@"Response Item: %@", [item description]); + } + SetCommandExitStatus(CHIP_NO_ERROR); + } + errorHandler:^(NSError * error) { + SetCommandExitStatus(error); + } + subscriptionEstablished:^() { + mSubscriptionEstablished = YES; + } + resubscriptionScheduled:^(NSError * error, NSNumber * resubscriptionDelay) { + NSLog(@"Subscription dropped with error %@. Resubscription in %@ms", error, resubscriptionDelay); + }]; + } return CHIP_NO_ERROR; } @@ -237,7 +282,82 @@ class SubscribeEvent : public ModelCommand { chip::Optional mKeepSubscriptions; chip::Optional mAutoResubscribe; chip::Optional mEventNumber; + chip::Optional mIsUrgent; bool mSubscriptionEstablished = NO; uint16_t mMinInterval; uint16_t mMaxInterval; + + void Shutdown() override + { + mSubscriptionEstablished = NO; + ModelCommand::Shutdown(); + } + + bool DeferInteractiveCleanup() override { return mSubscriptionEstablished; } + +private: + chip::ClusterId mClusterId; + chip::EventId mEventId; +}; + +class ReadEvent : public ModelCommand { +public: + ReadEvent() + : ModelCommand("read-event-by-id") + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterId); + AddArgument("event-id", 0, UINT32_MAX, &mEventId); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ModelCommand::AddArguments(); + } + + ReadEvent(chip::ClusterId clusterId) + : ModelCommand("read-event-by-id") + , mClusterId(clusterId) + { + AddArgument("event-id", 0, UINT32_MAX, &mEventId); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ModelCommand::AddArguments(); + } + + ~ReadEvent() {} + + CHIP_ERROR SendCommand(MTRBaseDevice * _Nonnull device, chip::EndpointId endpointId) override + { + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); + MTRReadParams * params = [[MTRReadParams alloc] init]; + if (mFabricFiltered.HasValue()) { + params.filterByFabric = mFabricFiltered.Value(); + } + if (mEventNumber.HasValue()) { + params.minimumEventNumber = [NSNumber numberWithUnsignedLongLong:mEventNumber.Value()]; + } + + [device + readEventsWithEndpointID:(endpointId == chip::kInvalidEndpointId) ? nil : [NSNumber numberWithUnsignedShort:endpointId] + clusterID:(mClusterId == chip::kInvalidClusterId) ? nil : [NSNumber numberWithUnsignedInteger:mClusterId] + eventID:(mEventId == chip::kInvalidEventId) ? nil : [NSNumber numberWithUnsignedInteger:mEventId] + params:params + queue:callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + if (error != nil) { + LogNSError("Error reading event", error); + } + if (values) { + for (id item in values) { + NSLog(@"Response Item: %@", [item description]); + } + } + SetCommandExitStatus(error); + }]; + return CHIP_NO_ERROR; + } + +protected: + chip::Optional mFabricFiltered; + chip::Optional mEventNumber; + +private: + chip::ClusterId mClusterId; + chip::AttributeId mEventId; }; diff --git a/examples/darwin-framework-tool/templates/commands.zapt b/examples/darwin-framework-tool/templates/commands.zapt index 0b6f7367cb3d98..75c8acbf62eff5 100644 --- a/examples/darwin-framework-tool/templates/commands.zapt +++ b/examples/darwin-framework-tool/templates/commands.zapt @@ -292,6 +292,12 @@ void registerCluster{{asUpperCamelCase name}}(Commands & commands) {{/if}} {{/unless}} {{/chip_server_cluster_attributes}} + {{#zcl_events}} + {{#first}} + make_unique(Id), // + make_unique(Id), // + {{/first}} + {{/zcl_events}} }; commands.Register(clusterName, clusterCommands); @@ -308,6 +314,8 @@ void registerClusterAny(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(), // + make_unique(chip::kInvalidClusterId, true), // make_unique(), // }; diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index c93a7e48b002d4..9bc630d831fd1c 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -315,6 +315,47 @@ typedef NS_ENUM(uint8_t, MTRTransportType) { completion:(MTRDeviceOpenCommissioningWindowHandler)completion API_AVAILABLE(ios(16.2), macos(13.1), watchos(9.2), tvos(16.2)); +/** + * Reads events from the device. + * + * Nil values for endpointID, clusterID, eventID indicate wildcards + * (e.g. nil eventID means "read all the events from the endpoint(s) and + * cluster(s) that match endpointID/clusterID"). + * + * If all of endpointID, clusterID, eventID are non-nil, all the matching instances of a single + * event will be read. + * + * If all of endpointID, clusterID, eventID are nil, all events on the + * device will be read. + */ + +- (void)readEventsWithEndpointID:(NSNumber * _Nullable)endpointID + clusterID:(NSNumber * _Nullable)clusterID + eventID:(NSNumber * _Nullable)eventID + params:(MTRReadParams * _Nullable)params + queue:(dispatch_queue_t)queue + completion:(MTRDeviceResponseHandler)completion MTR_NEWLY_AVAILABLE; + +/** + * Subscribes to the specified events on the device. + * + * Nil values for endpointID, clusterID, eventID indicate wildcards + * (e.g. nil eventID means "subscribe to all the events from the + * endpoint(s) and cluster(s) that match endpointID/clusterID"). + * + * If all of endpointID, clusterID, eventID are non-nil, a single event + * will be subscribed to. + * + * If all of endpointID, clusterID, eventID are nil, all events on the + * device will be subscribed to. + */ +- (void)subscribeToEventsWithEndpointID:(NSNumber * _Nullable)endpointID + clusterID:(NSNumber * _Nullable)clusterID + eventID:(NSNumber * _Nullable)eventID + params:(MTRSubscribeParams * _Nullable)params + queue:(dispatch_queue_t)queue + reportHandler:(MTRDeviceResponseHandler)reportHandler + subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished MTR_NEWLY_AVAILABLE; @end /** diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 3a360307df8fd5..65651b7fc28541 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -30,6 +30,7 @@ #include "app/ConcreteAttributePath.h" #include "app/ConcreteCommandPath.h" +#include "app/ConcreteEventPath.h" #include "lib/core/CHIPError.h" #include "lib/core/DataModelTypes.h" @@ -79,6 +80,7 @@ @interface MTRReadClientContainer : NSObject @property (nonatomic, readwrite) app::ReadClient * readClientPtr; @property (nonatomic, readwrite) app::AttributePathParams * pathParams; +@property (nonatomic, readwrite) app::EventPathParams * eventPathParams; @property (nonatomic, readwrite) uint64_t deviceID; - (void)onDone; @end @@ -335,8 +337,7 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue // Wildcard endpoint, cluster, attribute, event. auto attributePath = std::make_unique(); auto eventPath = std::make_unique(); - // We want to get event reports at the minInterval, not the maxInterval. - eventPath->mIsUrgentEvent = true; + eventPath->mIsUrgentEvent = params.reportEventsUrgently; ReadPrepareParams readParams(session.Value()); [params toReadPrepareParams:readParams]; readParams.mpAttributePathParamsList = attributePath.get(); @@ -727,19 +728,20 @@ CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const static void OnSuccessFn(void * context, id value) { DispatchSuccess(context, value); } }; -template class BufferedReadAttributeCallback final : public app::ReadClient::Callback { +template class BufferedReadClientCallback final : public app::ReadClient::Callback { public: using OnSuccessCallbackType - = std::function; - using OnErrorCallbackType = std::function; - using OnDoneCallbackType = std::function; + = std::function; + using OnErrorCallbackType + = std::function; + using OnDoneCallbackType = std::function; using OnSubscriptionEstablishedCallbackType = std::function; - BufferedReadAttributeCallback(ClusterId aClusterId, AttributeId aAttributeId, OnSuccessCallbackType aOnSuccess, + BufferedReadClientCallback(ClusterId aClusterId, uint32_t aValueId, OnSuccessCallbackType aOnSuccess, OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone, OnSubscriptionEstablishedCallbackType aOnSubscriptionEstablished = nullptr) : mClusterId(aClusterId) - , mAttributeId(aAttributeId) + , mValueId(aValueId) , mOnSuccess(aOnSuccess) , mOnError(aOnError) , mOnDone(aOnDone) @@ -748,7 +750,7 @@ CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const { } - ~BufferedReadAttributeCallback() + ~BufferedReadClientCallback() { // Ensure we release the ReadClient before we tear down anything else, // so it can call our OnDeallocatePaths properly. @@ -764,7 +766,7 @@ void OnAttributeData( const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const app::StatusIB & aStatus) override { CHIP_ERROR err = CHIP_NO_ERROR; - DecodableAttributeType value; + DecodableValueType value; // // We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read @@ -774,21 +776,41 @@ void OnAttributeData( VerifyOrExit(aStatus.IsSuccess(), err = aStatus.ToChipError()); VerifyOrExit((aPath.mClusterId == mClusterId || mClusterId == kInvalidClusterId) - && (aPath.mAttributeId == mAttributeId || mAttributeId == kInvalidAttributeId), + && (aPath.mAttributeId == mValueId || mValueId == kInvalidAttributeId), err = CHIP_ERROR_SCHEMA_MISMATCH); VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); SuccessOrExit(err = app::DataModel::Decode(*apData, value)); - mOnSuccess(aPath, value); + mOnSuccess(aPath, aPath.mAttributeId, value); exit: if (err != CHIP_NO_ERROR) { - mOnError(&aPath, err); + mOnError(&aPath, aPath.mAttributeId, err); } } - void OnError(CHIP_ERROR aError) override { mOnError(nullptr, aError); } + void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override + { + CHIP_ERROR err = CHIP_NO_ERROR; + DecodableValueType value; + + VerifyOrExit((aEventHeader.mPath.mClusterId == mClusterId || mClusterId == kInvalidClusterId) + && (aEventHeader.mPath.mEventId == mValueId || mValueId == kInvalidEventId), + err = CHIP_ERROR_SCHEMA_MISMATCH); + VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + SuccessOrExit(err = app::DataModel::Decode(*apData, value)); + + mOnSuccess(aEventHeader.mPath, aEventHeader.mPath.mEventId, value); + + exit: + if (err != CHIP_NO_ERROR) { + mOnError(&aEventHeader.mPath, aEventHeader.mPath.mEventId, err); + } + } + + void OnError(CHIP_ERROR aError) override { mOnError(nullptr, kInvalidAttributeId, aError); } void OnDone(ReadClient *) override { mOnDone(this); } @@ -802,7 +824,7 @@ void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override {} ClusterId mClusterId; - AttributeId mAttributeId; + uint32_t mValueId; OnSuccessCallbackType mOnSuccess; OnErrorCallbackType mOnError; OnDoneCallbackType mOnDone; @@ -828,8 +850,9 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID auto resultArray = [[NSMutableArray alloc] init]; auto resultSuccess = [[NSMutableArray alloc] init]; auto resultFailure = [[NSMutableArray alloc] init]; - auto onSuccessCb = [resultArray, resultSuccess](const app::ConcreteAttributePath & attribPath, + auto onSuccessCb = [resultArray, resultSuccess](const app::ConcreteClusterPath & clusterPath, const uint32_t aValueId, const MTRDataValueDictionaryDecodableType & aData) { + app::ConcreteAttributePath attribPath(clusterPath.mEndpointId, clusterPath.mClusterId, aValueId); [resultArray addObject:@ { MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:attribPath], MTRDataKey : aData.GetDecodedObject() @@ -839,10 +862,12 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID } }; - auto onFailureCb = [resultArray, resultFailure](const app::ConcreteAttributePath * attribPath, CHIP_ERROR aError) { - if (attribPath) { + auto onFailureCb = [resultArray, resultFailure]( + const app::ConcreteClusterPath * clusterPath, const uint32_t aValueId, CHIP_ERROR aError) { + if (clusterPath) { + app::ConcreteAttributePath attribPath(clusterPath->mEndpointId, clusterPath->mClusterId, aValueId); [resultArray addObject:@ { - MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:*attribPath], + MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:attribPath], MTRErrorKey : [MTRError errorForCHIPErrorCode:aError] }]; } else if ([resultFailure count] == 0) { @@ -869,7 +894,7 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID readParams.mAttributePathParamsListSize = 1; auto onDone = [resultArray, resultSuccess, resultFailure, bridge, successCb, failureCb]( - BufferedReadAttributeCallback * callback) { + BufferedReadClientCallback * callback) { if ([resultFailure count] > 0 || [resultSuccess count] == 0) { // Failure if (failureCb) { @@ -890,7 +915,7 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID chip::Platform::Delete(callback); }; - auto callback = chip::Platform::MakeUnique>( + auto callback = chip::Platform::MakeUnique>( attributePath.mClusterId, attributePath.mAttributeId, onSuccessCb, onFailureCb, onDone, nullptr); VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY); @@ -1181,10 +1206,10 @@ - (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID return; } - auto onReportCb = [queue, reportHandler](const app::ConcreteAttributePath & attribPath, + auto onReportCb = [queue, reportHandler](const app::ConcreteClusterPath & clusterPath, const uint32_t aValueId, const MTRDataValueDictionaryDecodableType & data) { id valueObject = data.GetDecodedObject(); - app::ConcreteAttributePath pathCopy = attribPath; + app::ConcreteAttributePath pathCopy(clusterPath.mEndpointId, clusterPath.mClusterId, aValueId); dispatch_async(queue, ^{ reportHandler(@[ @ { MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:pathCopy], @@ -1196,7 +1221,7 @@ - (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID auto establishedOrFailed = chip::Platform::MakeShared(NO); auto onFailureCb = [establishedOrFailed, queue, subscriptionEstablished, reportHandler]( - const app::ConcreteAttributePath * attribPath, CHIP_ERROR error) { + const app::ConcreteClusterPath * clusterPath, const uint32_t aValueId, CHIP_ERROR error) { if (!(*establishedOrFailed)) { *establishedOrFailed = YES; if (subscriptionEstablished) { @@ -1241,14 +1266,14 @@ - (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID readParams.mpAttributePathParamsList = container.pathParams; readParams.mAttributePathParamsListSize = 1; - auto onDone = [container](BufferedReadAttributeCallback * callback) { + auto onDone = [container](BufferedReadClientCallback * callback) { [container onDone]; // Make sure we delete callback last, because doing that actually destroys our // lambda, so we can't access captured values after that. chip::Platform::Delete(callback); }; - auto callback = chip::Platform::MakeUnique>( + auto callback = chip::Platform::MakeUnique>( container.pathParams->mClusterId, container.pathParams->mAttributeId, onReportCb, onFailureCb, onDone, onEstablishedCb); @@ -1475,6 +1500,246 @@ + (id)CHIPEncodeAndDecodeNSObject:(id)object return decodedData.GetDecodedObject(); } +- (void)readEventsWithEndpointID:(NSNumber * _Nullable)endpointID + clusterID:(NSNumber * _Nullable)clusterID + eventID:(NSNumber * _Nullable)eventID + params:(MTRReadParams * _Nullable)params + queue:(dispatch_queue_t)queue + completion:(MTRDeviceResponseHandler)completion +{ + endpointID = (endpointID == nil) ? nil : [endpointID copy]; + clusterID = (clusterID == nil) ? nil : [clusterID copy]; + eventID = (eventID == nil) ? nil : [eventID copy]; + params = (params == nil) ? nil : [params copy]; + auto * bridge = new MTRDataValueDictionaryCallbackBridge(queue, completion, + ^(ExchangeManager & exchangeManager, const SessionHandle & session, MTRDataValueDictionaryCallback successCb, + MTRErrorCallback failureCb, MTRCallbackBridgeBase * bridge) { + auto resultArray = [[NSMutableArray alloc] init]; + auto resultSuccess = [[NSMutableArray alloc] init]; + auto resultFailure = [[NSMutableArray alloc] init]; + auto onSuccessCb = [resultArray, resultSuccess](const app::ConcreteClusterPath & clusterPath, const uint32_t aValueId, + const MTRDataValueDictionaryDecodableType & aData) { + app::ConcreteEventPath eventPath(clusterPath.mEndpointId, clusterPath.mClusterId, aValueId); + [resultArray addObject:@ { + MTREventPathKey : [[MTREventPath alloc] initWithPath:eventPath], + MTRDataKey : aData.GetDecodedObject() + }]; + if ([resultSuccess count] == 0) { + [resultSuccess addObject:[NSNumber numberWithBool:YES]]; + } + }; + + auto onFailureCb = [resultArray, resultFailure]( + const app::ConcreteClusterPath * clusterPath, const uint32_t aValueId, CHIP_ERROR aError) { + if (clusterPath) { + app::ConcreteEventPath eventPath(clusterPath->mEndpointId, clusterPath->mClusterId, aValueId); + [resultArray addObject:@ { + MTREventPathKey : [[MTREventPath alloc] initWithPath:eventPath], + MTRErrorKey : [MTRError errorForCHIPErrorCode:aError] + }]; + } else if ([resultFailure count] == 0) { + [resultFailure addObject:[MTRError errorForCHIPErrorCode:aError]]; + } + }; + + app::EventPathParams eventPath; + if (endpointID) { + eventPath.mEndpointId = static_cast([endpointID unsignedShortValue]); + } + if (clusterID) { + eventPath.mClusterId = static_cast([clusterID unsignedLongValue]); + } + if (eventID) { + eventPath.mEventId = static_cast([eventID unsignedLongValue]); + } + app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); + CHIP_ERROR err = CHIP_NO_ERROR; + + chip::app::ReadPrepareParams readParams(session); + [params toReadPrepareParams:readParams]; + readParams.mpEventPathParamsList = &eventPath; + readParams.mEventPathParamsListSize = 1; + + auto onDone = [resultArray, resultSuccess, resultFailure, bridge, successCb, failureCb]( + BufferedReadClientCallback * callback) { + if ([resultFailure count] > 0 || [resultSuccess count] == 0) { + // Failure + if (failureCb) { + if ([resultFailure count] > 0) { + failureCb(bridge, [MTRError errorToCHIPErrorCode:resultFailure[0]]); + } else if ([resultArray count] > 0) { + failureCb(bridge, [MTRError errorToCHIPErrorCode:resultArray[0][MTRErrorKey]]); + } else { + failureCb(bridge, CHIP_ERROR_READ_FAILED); + } + } + } else { + // Success + if (successCb) { + successCb(bridge, resultArray); + } + } + chip::Platform::Delete(callback); + }; + + auto callback = chip::Platform::MakeUnique>( + eventPath.mClusterId, eventPath.mEventId, onSuccessCb, onFailureCb, onDone, nullptr); + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY); + + auto readClient = chip::Platform::MakeUnique( + engine, &exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read); + VerifyOrReturnError(readClient != nullptr, CHIP_ERROR_NO_MEMORY); + + err = readClient->SendRequest(readParams); + + if (err != CHIP_NO_ERROR) { + return err; + } + + // + // At this point, we'll get a callback through the OnDone callback above regardless of success or failure + // of the read operation to permit us to free up the callback object. So, release ownership of the callback + // object now to prevent it from being reclaimed at the end of this scoped block. + // + callback->AdoptReadClient(std::move(readClient)); + callback.release(); + return err; + }); + std::move(*bridge).DispatchAction(self); +} + +- (void)subscribeToEventsWithEndpointID:(NSNumber * _Nullable)endpointID + clusterID:(NSNumber * _Nullable)clusterID + eventID:(NSNumber * _Nullable)eventID + params:(MTRSubscribeParams * _Nullable)params + queue:(dispatch_queue_t)queue + reportHandler:(MTRDeviceResponseHandler)reportHandler + subscriptionEstablished:(MTRSubscriptionEstablishedHandler)subscriptionEstablished +{ + if (self.isPASEDevice) { + // We don't support subscriptions over PASE. + dispatch_async(queue, ^{ + reportHandler(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE]); + }); + return; + } + + // Copy params before going async. + endpointID = (endpointID == nil) ? nil : [endpointID copy]; + clusterID = (clusterID == nil) ? nil : [clusterID copy]; + eventID = (eventID == nil) ? nil : [eventID copy]; + params = (params == nil) ? nil : [params copy]; + + [self.deviceController + getSessionForNode:self.nodeID + completion:^(ExchangeManager * _Nullable exchangeManager, const Optional & session, + NSError * _Nullable error) { + if (error != nil) { + if (reportHandler) { + dispatch_async(queue, ^{ + reportHandler(nil, error); + }); + } + return; + } + + auto onReportCb = [queue, reportHandler](const app::ConcreteClusterPath & clusterPath, const uint32_t aValueId, + const MTRDataValueDictionaryDecodableType & data) { + id valueObject = data.GetDecodedObject(); + app::ConcreteEventPath pathCopy(clusterPath.mEndpointId, clusterPath.mClusterId, aValueId); + dispatch_async(queue, ^{ + reportHandler( + @[ @ { MTREventPathKey : [[MTREventPath alloc] initWithPath:pathCopy], MTRDataKey : valueObject } ], + nil); + }); + }; + + auto establishedOrFailed = chip::Platform::MakeShared(NO); + auto onFailureCb = [establishedOrFailed, queue, subscriptionEstablished, reportHandler]( + const app::ConcreteClusterPath * clusterPath, const uint32_t aValueId, CHIP_ERROR error) { + if (!(*establishedOrFailed)) { + *establishedOrFailed = YES; + if (subscriptionEstablished) { + dispatch_async(queue, subscriptionEstablished); + } + } + if (reportHandler) { + dispatch_async(queue, ^{ + reportHandler(nil, [MTRError errorForCHIPErrorCode:error]); + }); + } + }; + + auto onEstablishedCb = [establishedOrFailed, queue, subscriptionEstablished]() { + if (*establishedOrFailed) { + return; + } + *establishedOrFailed = YES; + if (subscriptionEstablished) { + dispatch_async(queue, subscriptionEstablished); + } + }; + + MTRReadClientContainer * container = [[MTRReadClientContainer alloc] init]; + container.deviceID = self.nodeID; + container.eventPathParams = Platform::New(); + if (endpointID) { + container.eventPathParams->mEndpointId = static_cast([endpointID unsignedShortValue]); + } + if (clusterID) { + container.eventPathParams->mClusterId = static_cast([clusterID unsignedLongValue]); + } + if (eventID) { + container.eventPathParams->mEventId = static_cast([eventID unsignedLongValue]); + } + container.eventPathParams->mIsUrgentEvent = params.reportEventsUrgently; + + app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); + CHIP_ERROR err = CHIP_NO_ERROR; + + chip::app::ReadPrepareParams readParams(session.Value()); + [params toReadPrepareParams:readParams]; + readParams.mpEventPathParamsList = container.eventPathParams; + readParams.mEventPathParamsListSize = 1; + + auto onDone = [container](BufferedReadClientCallback * callback) { + [container onDone]; + // Make sure we delete callback last, because doing that actually destroys our + // lambda, so we can't access captured values after that. + chip::Platform::Delete(callback); + }; + + auto callback = chip::Platform::MakeUnique>( + container.eventPathParams->mClusterId, container.eventPathParams->mEventId, onReportCb, onFailureCb, onDone, + onEstablishedCb); + + auto readClient = Platform::New( + engine, exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Subscribe); + + if (!params.resubscribeIfLost) { + err = readClient->SendRequest(readParams); + } else { + err = readClient->SendAutoResubscribeRequest(std::move(readParams)); + } + + if (err != CHIP_NO_ERROR) { + if (reportHandler) { + dispatch_async(queue, ^{ + reportHandler(nil, [MTRError errorForCHIPErrorCode:err]); + }); + } + Platform::Delete(readClient); + Platform::Delete(container.eventPathParams); + container.eventPathParams = nullptr; + return; + } + + // Read clients will be purged when deregistered. + container.readClientPtr = readClient; + AddReadClientContainer(container.deviceID, container); + callback.release(); + }]; +} @end @implementation MTRBaseDevice (Deprecated) @@ -1707,6 +1972,13 @@ - (instancetype)initWithPath:(const ConcreteEventPath &)path return self; } +- (NSString *)description +{ + return + [NSString stringWithFormat:@" endpoint %u cluster %u event %u", (uint16_t) self.endpoint.unsignedShortValue, + (uint32_t) self.cluster.unsignedLongValue, (uint32_t) _event.unsignedLongValue]; +} + + (instancetype)eventPathWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID eventID:(NSNumber *)eventID { ConcreteEventPath path(static_cast([endpointID unsignedShortValue]), diff --git a/src/darwin/Framework/CHIP/MTRCluster.h b/src/darwin/Framework/CHIP/MTRCluster.h index 8b07be11feeddb..bfe42b062f5cfa 100644 --- a/src/darwin/Framework/CHIP/MTRCluster.h +++ b/src/darwin/Framework/CHIP/MTRCluster.h @@ -77,7 +77,7 @@ NS_ASSUME_NONNULL_BEGIN /** * MTRReadParams - * This is used to control the behavior of attribute reads and subscribes. + * This is used to control the behavior of attribute/event reads and subscribes. * If not provided (i.e. nil passed for the MTRReadParams argument), will be * treated as if a default-initialized object was passed in. */ @@ -94,11 +94,20 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign, getter=shouldFilterByFabric) BOOL filterByFabric MTR_NEWLY_AVAILABLE; +/** + * Sets a filter for which events will be reported in the read/subscribe interaction. + * + * If nil (the default value), all of the queued events will be reported from lowest to highest event number. + * + * If not nil, queued events with an event number smaller than minimumEventNumber will not be reported. + */ +@property (nonatomic, copy, nullable) NSNumber * minimumEventNumber MTR_NEWLY_AVAILABLE; + @end /** * MTRSubscribeParams - * This is used to control the behavior of attribute subscribes. If not + * This is used to control the behavior of attribute/event subscribes. If not * provided (i.e. nil passed for the MTRSubscribeParams argument), will be * treated as if a default-initialized object was passed in. */ @@ -144,6 +153,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, copy) NSNumber * maxInterval; +/** + * Controls whether events will be reported urgently. The default value is YES. + * + * If YES, the events will be reported as soon as the minInterval does not prevent it. + * + * If NO, the events will be reported at the maximum interval. + */ +@property (nonatomic, assign) BOOL reportEventsUrgently MTR_NEWLY_AVAILABLE; + /** * Initialize an MTRSubscribeParams. Must provide a minInterval and * maxInterval; there are no default values for those. diff --git a/src/darwin/Framework/CHIP/MTRCluster.mm b/src/darwin/Framework/CHIP/MTRCluster.mm index 1f7faec7ed7cc4..042b3a79a36bee 100644 --- a/src/darwin/Framework/CHIP/MTRCluster.mm +++ b/src/darwin/Framework/CHIP/MTRCluster.mm @@ -74,12 +74,16 @@ - (id)copyWithZone:(NSZone * _Nullable)zone { auto other = [[MTRReadParams alloc] init]; other.filterByFabric = self.filterByFabric; + other.minimumEventNumber = self.minimumEventNumber; return other; } - (void)toReadPrepareParams:(chip::app::ReadPrepareParams &)readPrepareParams { readPrepareParams.mIsFabricFiltered = self.filterByFabric; + if (self.minimumEventNumber) { + readPrepareParams.mEventNumber.SetValue(static_cast([self.minimumEventNumber unsignedLongLongValue])); + } } @end @@ -88,6 +92,7 @@ @implementation MTRSubscribeParams - (instancetype)initWithMinInterval:(NSNumber *)minInterval maxInterval:(NSNumber *)maxInterval { if (self = [super init]) { + _reportEventsUrgently = YES; _replaceExistingSubscriptions = YES; _resubscribeIfLost = YES; _minInterval = [minInterval copy]; @@ -100,7 +105,9 @@ - (id)copyWithZone:(NSZone * _Nullable)zone { auto other = [[MTRSubscribeParams alloc] initWithMinInterval:self.minInterval maxInterval:self.maxInterval]; other.filterByFabric = self.filterByFabric; + other.minimumEventNumber = self.minimumEventNumber; other.replaceExistingSubscriptions = self.replaceExistingSubscriptions; + other.reportEventsUrgently = self.reportEventsUrgently; other.resubscribeIfLost = self.resubscribeIfLost; return other; } diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h index d34afdbcb5e437..13633301d99821 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h @@ -97854,6 +97854,8 @@ void registerClusterAccessControl(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -97897,6 +97899,8 @@ void registerClusterActions(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -97965,6 +97969,8 @@ void registerClusterBasicInformation(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98028,6 +98034,8 @@ void registerClusterOtaSoftwareUpdateRequestor(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98233,6 +98241,8 @@ void registerClusterPowerSource(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98390,6 +98400,8 @@ void registerClusterGeneralDiagnostics(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98424,6 +98436,8 @@ void registerClusterSoftwareDiagnostics(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98576,6 +98590,8 @@ void registerClusterThreadNetworkDiagnostics(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98628,6 +98644,8 @@ void registerClusterWiFiNetworkDiagnostics(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98728,6 +98746,8 @@ void registerClusterBridgedDeviceBasicInformation(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98759,6 +98779,8 @@ void registerClusterSwitch(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -98958,6 +98980,8 @@ void registerClusterBooleanState(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -99131,6 +99155,8 @@ void registerClusterDoorLock(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -99336,6 +99362,8 @@ void registerClusterPumpConfigurationAndControl(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -101011,6 +101039,8 @@ void registerClusterUnitTesting(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(Id), // + make_unique(Id), // }; commands.Register(clusterName, clusterCommands); @@ -101025,6 +101055,8 @@ void registerClusterAny(Commands & commands) make_unique(), // make_unique(), // make_unique(), // + make_unique(), // + make_unique(chip::kInvalidClusterId, true), // make_unique(), // };