From 73fbd5e4d7bd08a1769001753d979e73e0f82a66 Mon Sep 17 00:00:00 2001 From: Seokhee Lee Date: Fri, 27 Jan 2023 04:07:46 +0900 Subject: [PATCH] Add read/subscribe event function for darwin (#24057) * Add functions for darwin-framework-tool Add functions for darwin-framework-tool -discover commissionables -pairing ethernet -read-event-by-id Added Matter.Framework APIs MTRDeviceController -discovercommissionableNodes -setDeviceDiscoveryDelegate MTRBaseDevice -readEventsWithEndpointID * Remove discovery and ethernet pairing changes Discovery and ethernet pairing features are useful for test but not mendatory for Matter certification. So the removed codes will be committed after more verification. * Update PairingCommandBridge.mm * Update ReportCommandBridge.h * Delete Commands.h Removed because I think that this file seems not in pull request scope and it can be generated by using commands.zapt in this pull request. * Rollback Commands.h Rollback Commands.h to open source version. I think that this file seems not in pull request scope and it can be generated by using commands.zapt in this pull request. * Update MTRDeviceController.h * Update commands.zapt Support any cluster for read-event-by-id of darwin-framework-tool * Add Commands.h to fix zap build error * Update Commands.h based on the latest version in upstream * Remove fabric-filtered option in read-event-by-id * Revert "Update Commands.h based on the latest version in upstream" This reverts commit 8b80dd2d846e2927d7dac2721216bcf3273cf635. * Restyled by whitespace * Restyled by clang-format * Restyle zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h * Update Commands.h to fix zap-related error * Added subscribeToEventsWithEndpointID - Added event-min to ReadEvent * Restyled by clang-format * Added Read/SubscribeEvent to PowerSourceCluster * Added BufferedReadClientCallback for both attribte and event - Used ConcreteClusterPath and ValueId instead of ConcreteAttribute/EventPath - Removed BufferedReadEventCallback - Added eventMin to MTRReadParams for EventFilter - Added isUrgentEvent to MTRSubscribeParams for EventRequest - Restyled by clang-format * Modified to support 'any subscribe-event-by-id' - Checked the invlalid id values are treated as wildcards in both subscribeToEventsWithEndpointID and readEventsWithEndpointID. - Added suggested changes. * Restyled by clang-format * Removed the hardcoded true on subscribeWithQeuue - Removed mEventNumber.SetValue on MTRSubscribeParams - Added the wildcard handling as nil for Darwin API - Removed the casts on descriptions * Revert "Removed the casts on descriptions" * Restore the mIsUrgentEvent in subscribeWithQueue. Co-authored-by: Restyled.io Co-authored-by: HyunKoo Ryu Co-authored-by: ready2die4u Co-authored-by: Boris Zbarsky --- .../commands/clusters/ReportCommandBridge.h | 160 +++++++-- .../templates/commands.zapt | 8 + src/darwin/Framework/CHIP/MTRBaseDevice.h | 41 +++ src/darwin/Framework/CHIP/MTRBaseDevice.mm | 324 ++++++++++++++++-- src/darwin/Framework/CHIP/MTRCluster.h | 22 +- src/darwin/Framework/CHIP/MTRCluster.mm | 7 + .../zap-generated/cluster/Commands.h | 32 ++ 7 files changed, 546 insertions(+), 48 deletions(-) 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(), // };