[Darwin] Implement multiple read and subscribe (#25840)
* Implement darwin multiple attribute, event read / subscribe
* restyle
* Add Exception check
* Remove unused code
* Restyle
* Modify from comment
* Restyle
* Add MTRAttributeRequestPath, EventRequestPath
* restyle
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* fix build error
* restyle
* Add set null eventPath
* Modify memort allocation
* restyle
* Modify delete -> memoryfree
* Update code
* remove unused code
* restyle
* Fix crash in Test
* Add TestCases, modify some issues
* restyle
* Add comment, modify testcase
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.h
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* modify code location, add some patch
* modify typo
* Add assert code
* restyle
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Update src/darwin/Framework/CHIP/MTRBaseDevice.mm
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Add subscribe null check
* modify
* Modify using uniqur_ptr
* restyle
* Revert "restyle"
This reverts commit f855ea573ccbfa8b136b6fc349ee235ab00cc91c.
* Revert "Modify using uniqur_ptr"
This reverts commit 40eca604e7d71c71580aa67c89902f6abab21e1d.
* modify using scopeBuffer
* restyle
* Dispatch resubscription callback to the right queue.
---------
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/EventPathParams.h b/src/app/EventPathParams.h
index 0a8cea3..29af405 100644
--- a/src/app/EventPathParams.h
+++ b/src/app/EventPathParams.h
@@ -43,6 +43,9 @@
inline bool HasWildcardEndpointId() const { return mEndpointId == kInvalidEndpointId; }
inline bool HasWildcardClusterId() const { return mClusterId == kInvalidClusterId; }
inline bool HasWildcardEventId() const { return mEventId == kInvalidEventId; }
+ inline void SetWildcardEndpointId() { mEndpointId = kInvalidEndpointId; }
+ inline void SetWildcardClusterId() { mClusterId = kInvalidClusterId; }
+ inline void SetWildcardEventId() { mEventId = kInvalidEventId; }
bool IsEventPathSupersetOf(const ConcreteEventPath & other) const
{
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h
index a0ff37f..fecf875 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.h
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h
@@ -130,6 +130,38 @@
MTRTransportTypeTCP,
} API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
+/**
+ * A path indicating an attribute being requested (for read or subscribe).
+ *
+ * nil is used to represent wildcards.
+ */
+MTR_NEWLY_AVAILABLE
+@interface MTRAttributeRequestPath : NSObject <NSCopying>
+@property (nonatomic, readonly, copy, nullable) NSNumber * endpoint;
+@property (nonatomic, readonly, copy, nullable) NSNumber * cluster;
+@property (nonatomic, readonly, copy, nullable) NSNumber * attribute;
+
++ (MTRAttributeRequestPath *)requestPathWithEndpointID:(NSNumber * _Nullable)endpointID
+ clusterID:(NSNumber * _Nullable)clusterID
+ attributeID:(NSNumber * _Nullable)attributeID MTR_NEWLY_AVAILABLE;
+@end
+
+/**
+ * A path indicating an event being requested (for read or subscribe).
+ *
+ * nil is used to represent wildcards.
+ */
+MTR_NEWLY_AVAILABLE
+@interface MTREventRequestPath : NSObject <NSCopying>
+@property (nonatomic, readonly, copy, nullable) NSNumber * endpoint;
+@property (nonatomic, readonly, copy, nullable) NSNumber * cluster;
+@property (nonatomic, readonly, copy, nullable) NSNumber * event;
+
++ (MTREventRequestPath *)requestPathWithEndpointID:(NSNumber * _Nullable)endpointID
+ clusterID:(NSNumber * _Nullable)clusterID
+ eventID:(NSNumber * _Nullable)eventID MTR_NEWLY_AVAILABLE;
+@end
+
@interface MTRBaseDevice : NSObject
- (instancetype)init NS_UNAVAILABLE;
@@ -230,6 +262,26 @@
API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
/**
+ * Reads multiple attribute or event paths from the device.
+ *
+ * Nil is treated as an empty array for attributePaths and eventPaths.
+ *
+ * Lists of attribute and event paths to read can be provided via attributePaths and eventPaths.
+ *
+ * The completion will be called with an error if the input parameters are invalid (e.g., both attributePaths and eventPaths are
+ * empty.) or the entire read interaction fails. Otherwise it will be called with values, which may be empty (e.g. if no paths
+ * matched the wildcard paths passed in) or may include per-path errors if particular paths failed.
+ *
+ * If the sum of the lengths of attributePaths and eventPaths exceeds 9, the read may fail due to the device not supporting that
+ * many read paths.
+ */
+- (void)readAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullable)attributePaths
+ eventPaths:(NSArray<MTREventRequestPath *> * _Nullable)eventPaths
+ params:(MTRReadParams * _Nullable)params
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceResponseHandler)completion MTR_NEWLY_AVAILABLE;
+
+/**
* Write to attribute in a designated attribute path
*
* @param value A data-value NSDictionary object as described in
@@ -306,6 +358,27 @@
API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4));
/**
+ * Subscribes to multiple attribute or event paths.
+ *
+ * Nil is treated as an empty array for attributePaths and eventPaths.
+ *
+ * Lists of attribute and event paths to subscribe to can be provided via attributePaths and eventPaths.
+ *
+ * The reportHandler will be called with an error if the inputs are invalid (e.g., both attributePaths and eventPaths are
+ * empty), or if the subscription fails entirely.
+ *
+ * If the sum of the lengths of attributePaths and eventPaths exceeds 3, the subscribe may fail due to the device not supporting
+ * that many paths for a subscription.
+ */
+- (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullable)attributePaths
+ eventPaths:(NSArray<MTREventRequestPath *> * _Nullable)eventPaths
+ params:(MTRSubscribeParams * _Nullable)params
+ queue:(dispatch_queue_t)queue
+ reportHandler:(MTRDeviceResponseHandler)reportHandler
+ subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished
+ resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduled MTR_NEWLY_AVAILABLE;
+
+/**
* Deregister all local report handlers for a remote device
*
* This method is applicable only for a remote device. For a local device, the stack has to be shutdown to stop report handlers.
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
index 09f5ea5..a8e585d 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
@@ -147,9 +147,17 @@
container.readClientPtr = nullptr;
}
if (container.pathParams) {
- Platform::Delete(container.pathParams);
+ static_assert(std::is_trivially_destructible<AttributePathParams>::value,
+ "AttributePathParams destructors won't get run");
+ Platform::MemoryFree(container.pathParams);
container.pathParams = nullptr;
}
+ if (container.eventPathParams) {
+ static_assert(
+ std::is_trivially_destructible<EventPathParams>::value, "EventPathParams destructors won't get run");
+ Platform::MemoryFree(container.eventPathParams);
+ container.eventPathParams = nullptr;
+ }
}
[listToDelete removeAllObjects];
if (completion) {
@@ -223,9 +231,15 @@
_readClientPtr = nullptr;
}
if (_pathParams) {
- Platform::Delete(_pathParams);
+ static_assert(std::is_trivially_destructible<AttributePathParams>::value, "AttributePathParams destructors won't get run");
+ Platform::MemoryFree(_pathParams);
_pathParams = nullptr;
}
+ if (_eventPathParams) {
+ static_assert(std::is_trivially_destructible<EventPathParams>::value, "EventPathParams destructors won't get run");
+ Platform::MemoryFree(_eventPathParams);
+ _eventPathParams = nullptr;
+ }
PurgeCompletedReadClientContainers(_deviceID);
}
@@ -236,9 +250,15 @@
_readClientPtr = nullptr;
}
if (_pathParams) {
- Platform::Delete(_pathParams);
+ static_assert(std::is_trivially_destructible<AttributePathParams>::value, "AttributePathParams destructors won't get run");
+ Platform::MemoryFree(_pathParams);
_pathParams = nullptr;
}
+ if (_eventPathParams) {
+ static_assert(std::is_trivially_destructible<EventPathParams>::value, "EventPathParams destructors won't get run");
+ Platform::MemoryFree(_eventPathParams);
+ _eventPathParams = nullptr;
+ }
}
@end
@@ -730,22 +750,31 @@
template <typename DecodableValueType> class BufferedReadClientCallback final : public app::ReadClient::Callback {
public:
- using OnSuccessCallbackType
- = std::function<void(const app::ConcreteClusterPath & aPath, const uint32_t aValueId, const DecodableValueType & aData)>;
- using OnErrorCallbackType
- = std::function<void(const app::ConcreteClusterPath * aPath, const uint32_t aValueId, CHIP_ERROR aError)>;
+ using OnSuccessAttributeCallbackType
+ = std::function<void(const ConcreteAttributePath & aPath, const DecodableValueType & aData)>;
+ using OnSuccessEventCallbackType = std::function<void(const ConcreteEventPath & aPath, const DecodableValueType & aData)>;
+ using OnErrorCallbackType = std::function<void(
+ const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, CHIP_ERROR aError)>;
using OnDoneCallbackType = std::function<void(BufferedReadClientCallback * callback)>;
using OnSubscriptionEstablishedCallbackType = std::function<void()>;
+ using OnDeviceResubscriptionScheduledCallbackType = std::function<void(NSError * error, NSNumber * resubscriptionDelay)>;
- BufferedReadClientCallback(ClusterId aClusterId, uint32_t aValueId, OnSuccessCallbackType aOnSuccess,
+ BufferedReadClientCallback(app::AttributePathParams * aAttributePathParamsList, size_t aAttributePathParamsSize,
+ app::EventPathParams * aEventPathParamsList, size_t aEventPathParamsSize,
+ OnSuccessAttributeCallbackType aOnAttributeSuccess, OnSuccessEventCallbackType aOnEventSuccess,
OnErrorCallbackType aOnError, OnDoneCallbackType aOnDone,
- OnSubscriptionEstablishedCallbackType aOnSubscriptionEstablished = nullptr)
- : mClusterId(aClusterId)
- , mValueId(aValueId)
- , mOnSuccess(aOnSuccess)
+ OnSubscriptionEstablishedCallbackType aOnSubscriptionEstablished = nullptr,
+ OnDeviceResubscriptionScheduledCallbackType aOnDeviceResubscriptionScheduled = nullptr)
+ : mAttributePathParamsList(aAttributePathParamsList)
+ , mAttributePathParamsSize(aAttributePathParamsSize)
+ , mEventPathParamsList(aEventPathParamsList)
+ , mEventPathParamsSize(aEventPathParamsSize)
+ , mOnAttributeSuccess(aOnAttributeSuccess)
+ , mOnEventSuccess(aOnEventSuccess)
, mOnError(aOnError)
, mOnDone(aOnDone)
, mOnSubscriptionEstablished(aOnSubscriptionEstablished)
+ , mOnDeviceResubscriptionScheduled(aOnDeviceResubscriptionScheduled)
, mBufferedReadAdapter(*this)
{
}
@@ -768,6 +797,10 @@
CHIP_ERROR err = CHIP_NO_ERROR;
DecodableValueType value;
+ VerifyOrExit(mOnAttributeSuccess != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+ VerifyOrExit(mAttributePathParamsList != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
//
// We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read
// callback. If we do, that's a bug.
@@ -775,18 +808,20 @@
VerifyOrDie(!aPath.IsListItemOperation());
VerifyOrExit(aStatus.IsSuccess(), err = aStatus.ToChipError());
- VerifyOrExit((aPath.mClusterId == mClusterId || mClusterId == kInvalidClusterId)
- && (aPath.mAttributeId == mValueId || mValueId == kInvalidAttributeId),
+ VerifyOrExit(
+ std::find_if(mAttributePathParamsList, mAttributePathParamsList + mAttributePathParamsSize,
+ [aPath](app::AttributePathParams & pathParam) -> bool { return pathParam.IsAttributePathSupersetOf(aPath); })
+ != mAttributePathParamsList + mAttributePathParamsSize,
err = CHIP_ERROR_SCHEMA_MISMATCH);
VerifyOrExit(apData != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
SuccessOrExit(err = app::DataModel::Decode(*apData, value));
- mOnSuccess(aPath, aPath.mAttributeId, value);
+ mOnAttributeSuccess(aPath, value);
exit:
if (err != CHIP_NO_ERROR) {
- mOnError(&aPath, aPath.mAttributeId, err);
+ mOnError(&aPath, nullptr, err);
}
}
@@ -795,22 +830,29 @@
CHIP_ERROR err = CHIP_NO_ERROR;
DecodableValueType value;
- VerifyOrExit((aEventHeader.mPath.mClusterId == mClusterId || mClusterId == kInvalidClusterId)
- && (aEventHeader.mPath.mEventId == mValueId || mValueId == kInvalidEventId),
+ VerifyOrExit(mOnEventSuccess != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+ VerifyOrExit(mEventPathParamsList != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+ VerifyOrExit(std::find_if(mEventPathParamsList, mEventPathParamsList + mEventPathParamsSize,
+ [aEventHeader](app::EventPathParams & pathParam) -> bool {
+ return pathParam.IsEventPathSupersetOf(aEventHeader.mPath);
+ })
+ != mEventPathParamsList + mEventPathParamsSize,
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);
+ mOnEventSuccess(aEventHeader.mPath, value);
exit:
if (err != CHIP_NO_ERROR) {
- mOnError(&aEventHeader.mPath, aEventHeader.mPath.mEventId, err);
+ mOnError(nullptr, &aEventHeader.mPath, err);
}
}
- void OnError(CHIP_ERROR aError) override { mOnError(nullptr, kInvalidAttributeId, aError); }
+ void OnError(CHIP_ERROR aError) override { mOnError(nullptr, nullptr, aError); }
void OnDone(ReadClient *) override { mOnDone(this); }
@@ -821,16 +863,34 @@
}
}
+ CHIP_ERROR OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override
+ {
+ CHIP_ERROR err = ReadClient::Callback::OnResubscriptionNeeded(apReadClient, aTerminationCause);
+ ReturnErrorOnFailure(err);
+
+ if (mOnDeviceResubscriptionScheduled != nullptr) {
+ auto callback = mOnDeviceResubscriptionScheduled;
+ auto error = [MTRError errorForCHIPErrorCode:aTerminationCause];
+ auto delayMs = @(apReadClient->ComputeTimeTillNextSubscription());
+ callback(error, delayMs);
+ }
+ return CHIP_NO_ERROR;
+ }
+
void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override {}
- ClusterId mClusterId;
- uint32_t mValueId;
- OnSuccessCallbackType mOnSuccess;
+ OnSuccessAttributeCallbackType mOnAttributeSuccess;
+ OnSuccessEventCallbackType mOnEventSuccess;
OnErrorCallbackType mOnError;
OnDoneCallbackType mOnDone;
OnSubscriptionEstablishedCallbackType mOnSubscriptionEstablished;
+ OnDeviceResubscriptionScheduledCallbackType mOnDeviceResubscriptionScheduled;
app::BufferedReadCallback mBufferedReadAdapter;
Platform::UniquePtr<app::ReadClient> mReadClient;
+ app::AttributePathParams * mAttributePathParamsList;
+ app::EventPathParams * mEventPathParamsList;
+ size_t mAttributePathParamsSize;
+ size_t mEventPathParamsSize;
};
- (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID
@@ -840,9 +900,39 @@
queue:(dispatch_queue_t)queue
completion:(MTRDeviceResponseHandler)completion
{
- endpointID = (endpointID == nil) ? nil : [endpointID copy];
- clusterID = (clusterID == nil) ? nil : [clusterID copy];
- attributeID = (attributeID == nil) ? nil : [attributeID copy];
+ NSArray<MTRAttributeRequestPath *> * attributePaths = [NSArray
+ arrayWithObject:[MTRAttributeRequestPath requestPathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID]];
+ [self readAttributePaths:attributePaths eventPaths:nil params:params queue:queue completion:completion];
+}
+
+- (void)readAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullable)attributePaths
+ eventPaths:(NSArray<MTREventRequestPath *> * _Nullable)eventPaths
+ params:(MTRReadParams * _Nullable)params
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceResponseHandler)completion
+{
+ if ((attributePaths == nil || [attributePaths count] == 0) && (eventPaths == nil || [eventPaths count] == 0)) {
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]);
+ });
+ return;
+ }
+
+ NSMutableArray<MTRAttributeRequestPath *> * attributes = nil;
+ if (attributePaths != nil) {
+ attributes = [[NSMutableArray alloc] init];
+ for (MTRAttributeRequestPath * attributePath in attributePaths) {
+ [attributes addObject:[attributePath copy]];
+ }
+ }
+
+ NSMutableArray<MTREventRequestPath *> * events = nil;
+ if (eventPaths != nil) {
+ events = [[NSMutableArray alloc] init];
+ for (MTRAttributeRequestPath * eventPath in eventPaths) {
+ [events addObject:[eventPath copy]];
+ }
+ }
params = (params == nil) ? nil : [params copy];
auto * bridge = new MTRDataValueDictionaryCallbackBridge(queue, completion,
^(ExchangeManager & exchangeManager, const SessionHandle & session, MTRDataValueDictionaryCallback successCb,
@@ -853,21 +943,32 @@
auto interactionStatus = std::make_shared<CHIP_ERROR>(CHIP_NO_ERROR);
auto resultArray = [[NSMutableArray alloc] init];
- auto onSuccessCb = [resultArray](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()
- }];
- };
+ auto onAttributeSuccessCb
+ = [resultArray](const ConcreteAttributePath & attributePath, const MTRDataValueDictionaryDecodableType & aData) {
+ [resultArray addObject:@ {
+ MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:attributePath],
+ MTRDataKey : aData.GetDecodedObject()
+ }];
+ };
- auto onFailureCb = [resultArray, interactionStatus](
- const app::ConcreteClusterPath * clusterPath, const uint32_t aValueId, CHIP_ERROR aError) {
- if (clusterPath) {
- app::ConcreteAttributePath attribPath(clusterPath->mEndpointId, clusterPath->mClusterId, aValueId);
+ auto onEventSuccessCb
+ = [resultArray](const ConcreteEventPath & eventPath, const MTRDataValueDictionaryDecodableType & aData) {
+ [resultArray addObject:@ {
+ MTREventPathKey : [[MTREventPath alloc] initWithPath:eventPath],
+ MTRDataKey : aData.GetDecodedObject()
+ }];
+ };
+
+ auto onFailureCb = [resultArray, interactionStatus](const app::ConcreteAttributePath * attributePath,
+ const app::ConcreteEventPath * eventPath, CHIP_ERROR aError) {
+ if (attributePath != nullptr) {
[resultArray addObject:@ {
- MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:attribPath],
+ MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:*attributePath],
+ MTRErrorKey : [MTRError errorForCHIPErrorCode:aError]
+ }];
+ } else if (eventPath != nullptr) {
+ [resultArray addObject:@ {
+ MTREventPathKey : [[MTREventPath alloc] initWithPath:*eventPath],
MTRErrorKey : [MTRError errorForCHIPErrorCode:aError]
}];
} else {
@@ -878,38 +979,60 @@
}
};
- app::AttributePathParams attributePath;
- if (endpointID) {
- attributePath.mEndpointId = static_cast<chip::EndpointId>([endpointID unsignedShortValue]);
+ Platform::ScopedMemoryBuffer<AttributePathParams> attributePathParamsList;
+ Platform::ScopedMemoryBuffer<EventPathParams> eventPathParamsList;
+
+ if (attributes != nil) {
+ size_t count = 0;
+ VerifyOrReturnError(attributePathParamsList.Calloc([attributes count]), CHIP_ERROR_NO_MEMORY);
+ for (MTRAttributeRequestPath * attribute in attributes) {
+ [attribute convertToAttributePathParams:attributePathParamsList[count++]];
+ }
}
- if (clusterID) {
- attributePath.mClusterId = static_cast<chip::ClusterId>([clusterID unsignedLongValue]);
+
+ if (events != nil) {
+ size_t count = 0;
+ VerifyOrReturnError(eventPathParamsList.Calloc([events count]), CHIP_ERROR_NO_MEMORY);
+ for (MTREventRequestPath * event in events) {
+ [event convertToEventPathParams:eventPathParamsList[count++]];
+ }
}
- if (attributeID) {
- attributePath.mAttributeId = static_cast<chip::AttributeId>([attributeID unsignedLongValue]);
- }
+
app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
CHIP_ERROR err = CHIP_NO_ERROR;
chip::app::ReadPrepareParams readParams(session);
[params toReadPrepareParams:readParams];
- readParams.mpAttributePathParamsList = &attributePath;
- readParams.mAttributePathParamsListSize = 1;
+ readParams.mpAttributePathParamsList = attributePathParamsList.Get();
+ readParams.mAttributePathParamsListSize = [attributePaths count];
+ readParams.mpEventPathParamsList = eventPathParamsList.Get();
+ readParams.mEventPathParamsListSize = [eventPaths count];
- auto onDone = [resultArray, interactionStatus, bridge, successCb, failureCb](
- BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> * callback) {
- if (*interactionStatus != CHIP_NO_ERROR) {
- // Failure
- failureCb(bridge, *interactionStatus);
- } else {
- // Success
- successCb(bridge, resultArray);
- }
- chip::Platform::Delete(callback);
- };
+ AttributePathParams * attributePathParamsListToFree = attributePathParamsList.Get();
+ EventPathParams * eventPathParamsListToFree = eventPathParamsList.Get();
+
+ auto onDone
+ = [resultArray, interactionStatus, bridge, successCb, failureCb, attributePathParamsListToFree,
+ eventPathParamsListToFree](BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> * callback) {
+ if (*interactionStatus != CHIP_NO_ERROR) {
+ // Failure
+ failureCb(bridge, *interactionStatus);
+ } else {
+ // Success
+ successCb(bridge, resultArray);
+ }
+ if (attributePathParamsListToFree != nullptr) {
+ Platform::MemoryFree(attributePathParamsListToFree);
+ }
+ if (eventPathParamsListToFree != nullptr) {
+ Platform::MemoryFree(eventPathParamsListToFree);
+ }
+ chip::Platform::Delete(callback);
+ };
auto callback = chip::Platform::MakeUnique<BufferedReadClientCallback<MTRDataValueDictionaryDecodableType>>(
- attributePath.mClusterId, attributePath.mAttributeId, onSuccessCb, onFailureCb, onDone, nullptr);
+ attributePathParamsList.Get(), readParams.mAttributePathParamsListSize, eventPathParamsList.Get(),
+ readParams.mEventPathParamsListSize, onAttributeSuccessCb, onEventSuccessCb, onFailureCb, onDone, nullptr);
VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);
auto readClient = chip::Platform::MakeUnique<app::ReadClient>(
@@ -929,6 +1052,8 @@
//
callback->AdoptReadClient(std::move(readClient));
callback.release();
+ attributePathParamsList.Release();
+ eventPathParamsList.Release();
return err;
});
std::move(*bridge).DispatchAction(self);
@@ -1141,6 +1266,32 @@
reportHandler:(MTRDeviceResponseHandler)reportHandler
subscriptionEstablished:(MTRSubscriptionEstablishedHandler)subscriptionEstablished
{
+ NSArray<MTRAttributeRequestPath *> * attributePaths = [NSArray
+ arrayWithObject:[MTRAttributeRequestPath requestPathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID]];
+ [self subscribeToAttributePaths:attributePaths
+ eventPaths:nil
+ params:params
+ queue:queue
+ reportHandler:reportHandler
+ subscriptionEstablished:subscriptionEstablished
+ resubscriptionScheduled:nil];
+}
+
+- (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullable)attributePaths
+ eventPaths:(NSArray<MTREventRequestPath *> * _Nullable)eventPaths
+ params:(MTRSubscribeParams * _Nullable)params
+ queue:(dispatch_queue_t)queue
+ reportHandler:(MTRDeviceResponseHandler)reportHandler
+ subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished
+ resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduled
+{
+ if ((attributePaths == nil || [attributePaths count] == 0) && (eventPaths == nil || [eventPaths count] == 0)) {
+ dispatch_async(queue, ^{
+ reportHandler(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]);
+ });
+ return;
+ }
+
if (self.isPASEDevice) {
// We don't support subscriptions over PASE.
dispatch_async(queue, ^{
@@ -1150,9 +1301,22 @@
}
// Copy params before going async.
- endpointID = (endpointID == nil) ? nil : [endpointID copy];
- clusterID = (clusterID == nil) ? nil : [clusterID copy];
- attributeID = (attributeID == nil) ? nil : [attributeID copy];
+ NSMutableArray<MTRAttributeRequestPath *> * attributes = nil;
+ if (attributePaths != nil) {
+ attributes = [[NSMutableArray alloc] init];
+ for (MTRAttributeRequestPath * attributePath in attributePaths) {
+ [attributes addObject:[attributePath copy]];
+ }
+ }
+
+ NSMutableArray<MTREventRequestPath *> * events = nil;
+ if (eventPaths != nil) {
+ events = [[NSMutableArray alloc] init];
+ for (MTRAttributeRequestPath * eventPath in eventPaths) {
+ [events addObject:[eventPath copy]];
+ }
+ }
+
params = (params == nil) ? nil : [params copy];
[self.deviceController
@@ -1168,10 +1332,10 @@
return;
}
- auto onReportCb = [queue, reportHandler](const app::ConcreteClusterPath & clusterPath, const uint32_t aValueId,
- const MTRDataValueDictionaryDecodableType & data) {
+ auto onAttributeReportCb = [queue, reportHandler](const ConcreteAttributePath & attributePath,
+ const MTRDataValueDictionaryDecodableType & data) {
id valueObject = data.GetDecodedObject();
- app::ConcreteAttributePath pathCopy(clusterPath.mEndpointId, clusterPath.mClusterId, aValueId);
+ ConcreteAttributePath pathCopy(attributePath);
dispatch_async(queue, ^{
reportHandler(@[ @ {
MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:pathCopy],
@@ -1181,9 +1345,22 @@
});
};
+ auto onEventReportCb = [queue, reportHandler](const ConcreteEventPath & eventPath,
+ const MTRDataValueDictionaryDecodableType & data) {
+ id valueObject = data.GetDecodedObject();
+ ConcreteEventPath pathCopy(eventPath);
+ dispatch_async(queue, ^{
+ reportHandler(
+ @[ @ { MTREventPathKey : [[MTREventPath alloc] initWithPath:pathCopy], MTRDataKey : valueObject } ],
+ nil);
+ });
+ };
+
auto establishedOrFailed = chip::Platform::MakeShared<BOOL>(NO);
auto onFailureCb = [establishedOrFailed, queue, subscriptionEstablished, reportHandler](
- const app::ConcreteClusterPath * clusterPath, const uint32_t aValueId, CHIP_ERROR error) {
+ const app::ConcreteAttributePath * attributePath,
+ const app::ConcreteEventPath * eventPath, CHIP_ERROR error) {
+ // TODO, Requires additional logic if attributePath or eventPath is not null
if (!(*establishedOrFailed)) {
*establishedOrFailed = YES;
if (subscriptionEstablished) {
@@ -1207,17 +1384,45 @@
}
};
+ auto onResubscriptionScheduledCb
+ = [queue, resubscriptionScheduled](NSError * error, NSNumber * resubscriptionDelay) {
+ if (resubscriptionScheduled) {
+ dispatch_async(queue, ^{
+ resubscriptionScheduled(error, resubscriptionDelay);
+ });
+ }
+ };
+
MTRReadClientContainer * container = [[MTRReadClientContainer alloc] init];
container.deviceID = self.nodeID;
- container.pathParams = Platform::New<app::AttributePathParams>();
- if (endpointID) {
- container.pathParams->mEndpointId = static_cast<chip::EndpointId>([endpointID unsignedShortValue]);
+
+ size_t attributePathSize = 0;
+ if (attributes != nil) {
+ container.pathParams = static_cast<AttributePathParams *>(
+ Platform::MemoryCalloc([attributes count], sizeof(AttributePathParams)));
+ if (container.pathParams == nullptr) {
+ dispatch_async(queue, ^{
+ reportHandler(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_NO_MEMORY]);
+ });
+ return;
+ }
+ for (MTRAttributeRequestPath * attribute in attributes) {
+ [attribute convertToAttributePathParams:container.pathParams[attributePathSize++]];
+ }
}
- if (clusterID) {
- container.pathParams->mClusterId = static_cast<chip::ClusterId>([clusterID unsignedLongValue]);
- }
- if (attributeID) {
- container.pathParams->mAttributeId = static_cast<chip::AttributeId>([attributeID unsignedLongValue]);
+ size_t eventPathSize = 0;
+ if (events != nil) {
+ container.eventPathParams
+ = static_cast<EventPathParams *>(Platform::MemoryCalloc([events count], sizeof(EventPathParams)));
+ if (container.eventPathParams == nullptr) {
+ dispatch_async(queue, ^{
+ reportHandler(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_NO_MEMORY]);
+ });
+ return;
+ }
+ for (MTREventRequestPath * event in events) {
+ [event convertToEventPathParams:container.eventPathParams[eventPathSize++]];
+ }
}
app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
@@ -1226,7 +1431,9 @@
chip::app::ReadPrepareParams readParams(session.Value());
[params toReadPrepareParams:readParams];
readParams.mpAttributePathParamsList = container.pathParams;
- readParams.mAttributePathParamsListSize = 1;
+ readParams.mAttributePathParamsListSize = attributePathSize;
+ readParams.mpEventPathParamsList = container.eventPathParams;
+ readParams.mEventPathParamsListSize = eventPathSize;
auto onDone = [container](BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> * callback) {
[container onDone];
@@ -1236,8 +1443,8 @@
};
auto callback = chip::Platform::MakeUnique<BufferedReadClientCallback<MTRDataValueDictionaryDecodableType>>(
- container.pathParams->mClusterId, container.pathParams->mAttributeId, onReportCb, onFailureCb, onDone,
- onEstablishedCb);
+ container.pathParams, attributePathSize, container.eventPathParams, eventPathSize, onAttributeReportCb,
+ onEventReportCb, onFailureCb, onDone, onEstablishedCb, onResubscriptionScheduledCb);
auto readClient = Platform::New<app::ReadClient>(
engine, exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Subscribe);
@@ -1255,8 +1462,15 @@
});
}
Platform::Delete(readClient);
- Platform::Delete(container.pathParams);
+ if (container.pathParams != nullptr) {
+ Platform::MemoryFree(container.pathParams);
+ }
+
+ if (container.eventPathParams != nullptr) {
+ Platform::MemoryFree(container.eventPathParams);
+ }
container.pathParams = nullptr;
+ container.eventPathParams = nullptr;
return;
}
@@ -1498,97 +1712,10 @@
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) {
- // interactionStatus tracks whether the whole read interaction has failed.
- //
- // Make sure interactionStatus survives even if this block scope is destroyed.
- auto interactionStatus = std::make_shared<CHIP_ERROR>(CHIP_NO_ERROR);
-
- auto resultArray = [[NSMutableArray alloc] init];
- auto onSuccessCb = [resultArray](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()
- }];
- };
-
- auto onFailureCb = [resultArray, interactionStatus](
- 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 {
- // This will only happen once per read interaction, and
- // after that there will be no more calls to onFailureCb or
- // onSuccessCb.
- *interactionStatus = aError;
- }
- };
-
- app::EventPathParams eventPath;
- if (endpointID) {
- eventPath.mEndpointId = static_cast<chip::EndpointId>([endpointID unsignedShortValue]);
- }
- if (clusterID) {
- eventPath.mClusterId = static_cast<chip::ClusterId>([clusterID unsignedLongValue]);
- }
- if (eventID) {
- eventPath.mEventId = static_cast<chip::EventId>([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, interactionStatus, bridge, successCb, failureCb](
- BufferedReadClientCallback<MTRDataValueDictionaryDecodableType> * callback) {
- if (*interactionStatus != CHIP_NO_ERROR) {
- // Failure
- failureCb(bridge, *interactionStatus);
- } else {
- successCb(bridge, resultArray);
- }
- chip::Platform::Delete(callback);
- };
-
- auto callback = chip::Platform::MakeUnique<BufferedReadClientCallback<MTRDataValueDictionaryDecodableType>>(
- eventPath.mClusterId, eventPath.mEventId, onSuccessCb, onFailureCb, onDone, nullptr);
- VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY);
-
- auto readClient = chip::Platform::MakeUnique<app::ReadClient>(
- 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);
+ NSArray<MTREventRequestPath *> * eventPaths = [NSArray arrayWithObject:[MTREventRequestPath requestPathWithEndpointID:endpointID
+ clusterID:clusterID
+ eventID:eventID]];
+ [self readAttributePaths:nil eventPaths:eventPaths params:params queue:queue completion:completion];
}
- (void)subscribeToEventsWithEndpointID:(NSNumber * _Nullable)endpointID
@@ -1599,129 +1726,16 @@
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<SessionHandle> & 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<BOOL>(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<app::EventPathParams>();
- if (endpointID) {
- container.eventPathParams->mEndpointId = static_cast<chip::EndpointId>([endpointID unsignedShortValue]);
- }
- if (clusterID) {
- container.eventPathParams->mClusterId = static_cast<chip::ClusterId>([clusterID unsignedLongValue]);
- }
- if (eventID) {
- container.eventPathParams->mEventId = static_cast<chip::EventId>([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<MTRDataValueDictionaryDecodableType> * 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<BufferedReadClientCallback<MTRDataValueDictionaryDecodableType>>(
- container.eventPathParams->mClusterId, container.eventPathParams->mEventId, onReportCb, onFailureCb, onDone,
- onEstablishedCb);
-
- auto readClient = Platform::New<app::ReadClient>(
- engine, exchangeManager, callback->GetBufferedCallback(), chip::app::ReadClient::InteractionType::Subscribe);
-
- if (!params.resubscribeAutomatically) {
- 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();
- }];
+ NSArray<MTREventRequestPath *> * eventPaths = [NSArray arrayWithObject:[MTREventRequestPath requestPathWithEndpointID:endpointID
+ clusterID:clusterID
+ eventID:eventID]];
+ [self subscribeToAttributePaths:nil
+ eventPaths:eventPaths
+ params:params
+ queue:queue
+ reportHandler:reportHandler
+ subscriptionEstablished:subscriptionEstablished
+ resubscriptionScheduled:nil];
}
@end
@@ -1837,6 +1851,150 @@
@end
+@implementation MTRAttributeRequestPath
+- (instancetype)initWithEndpointID:(NSNumber * _Nullable)endpointID
+ clusterID:(NSNumber * _Nullable)clusterID
+ attributeID:(NSNumber * _Nullable)attributeID
+{
+ _endpoint = [endpointID copy];
+ _cluster = [clusterID copy];
+ _attribute = [attributeID copy];
+ return self;
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<MTRAttributeRequestPath> endpoint %u cluster %u attribute %u",
+ (uint16_t) _endpoint.unsignedShortValue, (uint32_t) _cluster.unsignedLongValue,
+ (uint32_t) _attribute.unsignedLongValue];
+}
+
++ (MTRAttributeRequestPath *)requestPathWithEndpointID:(NSNumber * _Nullable)endpointID
+ clusterID:(NSNumber * _Nullable)clusterID
+ attributeID:(NSNumber * _Nullable)attributeID
+{
+
+ return [[MTRAttributeRequestPath alloc] initWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID];
+}
+
+- (BOOL)isEqualToAttributeRequestPath:(MTRAttributeRequestPath *)path
+{
+ return [_endpoint isEqualToNumber:path.endpoint] && [_cluster isEqualToNumber:path.cluster] &&
+ [_attribute isEqualToNumber:path.attribute];
+}
+
+- (BOOL)isEqual:(id)object
+{
+ if (![object isKindOfClass:[self class]]) {
+ return NO;
+ }
+ return [self isEqualToAttributeRequestPath:object];
+}
+
+- (NSUInteger)hash
+{
+ return _endpoint.unsignedShortValue ^ _cluster.unsignedLongValue ^ _attribute.unsignedLongValue;
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ return [MTRAttributeRequestPath requestPathWithEndpointID:_endpoint clusterID:_cluster attributeID:_attribute];
+}
+
+- (void)convertToAttributePathParams:(chip::app::AttributePathParams &)params
+{
+ if (_endpoint != nil) {
+ params.mEndpointId = static_cast<chip::EndpointId>(_endpoint.unsignedShortValue);
+ } else {
+ params.SetWildcardEndpointId();
+ }
+
+ if (_cluster != nil) {
+ params.mClusterId = static_cast<chip::ClusterId>(_cluster.unsignedLongValue);
+ } else {
+ params.SetWildcardClusterId();
+ }
+
+ if (_attribute != nil) {
+ params.mAttributeId = static_cast<chip::AttributeId>(_attribute.unsignedLongValue);
+ } else {
+ params.SetWildcardAttributeId();
+ }
+}
+@end
+
+@implementation MTREventRequestPath
+- (instancetype)initWithEndpointID:(NSNumber * _Nullable)endpointID
+ clusterID:(NSNumber * _Nullable)clusterID
+ eventID:(NSNumber * _Nullable)eventID
+{
+ _endpoint = [endpointID copy];
+ _cluster = [clusterID copy];
+ _event = [eventID copy];
+ return self;
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<MTREventRequestPath> endpoint %u cluster %u event %u",
+ (uint16_t) _endpoint.unsignedShortValue, (uint32_t) _cluster.unsignedLongValue,
+ (uint32_t) _event.unsignedLongValue];
+}
+
++ (MTREventRequestPath *)requestPathWithEndpointID:(NSNumber * _Nullable)endpointID
+ clusterID:(NSNumber * _Nullable)clusterID
+ eventID:(NSNumber * _Nullable)eventID
+{
+
+ return [[MTREventRequestPath alloc] initWithEndpointID:endpointID clusterID:clusterID eventID:eventID];
+}
+
+- (BOOL)isEqualToEventRequestPath:(MTREventRequestPath *)path
+{
+ return
+ [_endpoint isEqualToNumber:path.endpoint] && [_cluster isEqualToNumber:path.cluster] && [_event isEqualToNumber:path.event];
+}
+
+- (BOOL)isEqual:(id)object
+{
+ if (![object isKindOfClass:[self class]]) {
+ return NO;
+ }
+ return [self isEqualToEventRequestPath:object];
+}
+
+- (NSUInteger)hash
+{
+ return _endpoint.unsignedShortValue ^ _cluster.unsignedLongValue ^ _event.unsignedLongValue;
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ return [MTREventRequestPath requestPathWithEndpointID:_endpoint clusterID:_cluster eventID:_event];
+}
+
+- (void)convertToEventPathParams:(chip::app::EventPathParams &)params
+{
+ if (_endpoint != nil) {
+ params.mEndpointId = static_cast<chip::EndpointId>(_endpoint.unsignedShortValue);
+ } else {
+ params.SetWildcardEndpointId();
+ }
+
+ if (_cluster != nil) {
+ params.mClusterId = static_cast<chip::ClusterId>(_cluster.unsignedLongValue);
+ } else {
+ params.SetWildcardClusterId();
+ }
+
+ if (_event != nil) {
+ params.mEventId = static_cast<chip::EventId>(_event.unsignedLongValue);
+ } else {
+ params.SetWildcardEventId();
+ }
+}
+@end
+
@implementation MTRClusterPath
- (instancetype)initWithPath:(const ConcreteClusterPath &)path
{
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
index 9fb062a..cf5944a 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
@@ -18,11 +18,13 @@
#import "MTRBaseDevice.h"
#import <Foundation/Foundation.h>
+#include <app/AttributePathParams.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
#include <app/ConcreteEventPath.h>
#include <app/DeviceProxy.h>
#include <app/EventLoggingTypes.h>
+#include <app/EventPathParams.h>
@class MTRDeviceController;
@@ -106,6 +108,14 @@
error:(NSError * _Nullable)error;
@end
+@interface MTRAttributeRequestPath ()
+- (void)convertToAttributePathParams:(chip::app::AttributePathParams &)params;
+@end
+
+@interface MTREventRequestPath ()
+- (void)convertToEventPathParams:(chip::app::EventPathParams &)params;
+@end
+
// Exported utility function
// Convert TLV data into data-value dictionary as described in MTRDeviceResponseHandler
id _Nullable MTRDecodeDataValueDictionaryFromCHIPTLV(chip::TLV::TLVReader * data);
diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
index 8075738..f649a04 100644
--- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
@@ -1617,6 +1617,280 @@
enforceOrder:YES];
}
+- (void)test020_ReadMultipleAttributes
+{
+ XCTestExpectation * expectation =
+ [self expectationWithDescription:@"read Multiple Attributes (Descriptor, Basic Information Cluster) for all endpoints"];
+
+ MTRBaseDevice * device = GetConnectedDevice();
+ dispatch_queue_t queue = dispatch_get_main_queue();
+
+ NSArray<MTRAttributeRequestPath *> * attributePaths =
+ [NSArray arrayWithObjects:[MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@29 attributeID:@0],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@29 attributeID:@1],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@29 attributeID:@2],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@29 attributeID:@3],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@29 attributeID:@4],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@5],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@6],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@7], nil];
+
+ NSArray<MTREventRequestPath *> * eventPaths =
+ [NSArray arrayWithObjects:[MTREventRequestPath requestPathWithEndpointID:nil clusterID:@40 eventID:@0], nil];
+
+ [device readAttributePaths:attributePaths
+ eventPaths:eventPaths
+ params:nil
+ queue:queue
+ completion:^(id _Nullable values, NSError * _Nullable error) {
+ NSLog(@"read attribute: DeviceType values: %@, error: %@", values, error);
+
+ XCTAssertNil(error);
+ XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0);
+
+ {
+ XCTAssertTrue([values isKindOfClass:[NSArray class]]);
+ NSArray * resultArray = values;
+ BOOL includeEventPath = NO;
+ for (NSDictionary * result in resultArray) {
+ if ([result objectForKey:@"eventPath"]) {
+ MTREventPath * path = result[@"eventPath"];
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 40);
+ XCTAssertEqual([path.event unsignedIntegerValue], 0);
+ XCTAssertNotNil(result[@"data"]);
+ XCTAssertNil(result[@"error"]);
+ XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
+ includeEventPath = YES;
+ } else if ([result objectForKey:@"attributePath"]) {
+ MTRAttributePath * path = result[@"attributePath"];
+ if ([path.attribute unsignedIntegerValue] < 5) {
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 29);
+ } else {
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 40);
+ }
+ XCTAssertNotNil(result[@"data"]);
+ XCTAssertNil(result[@"error"]);
+ XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
+ }
+ }
+ XCTAssertTrue(includeEventPath);
+ XCTAssertTrue([resultArray count] > 0);
+ }
+
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil];
+}
+
+- (void)test021_ReadMultipleAttributesIncludeUnsupportedAttribute
+{
+ XCTestExpectation * expectation =
+ [self expectationWithDescription:@"read Basic Information Cluster's attributes and include 1 unsupported attribute"];
+
+ MTRBaseDevice * device = GetConnectedDevice();
+ dispatch_queue_t queue = dispatch_get_main_queue();
+
+ NSNumber * failAttributeID = @10000;
+
+ NSArray<MTRAttributeRequestPath *> * attributePaths =
+ [NSArray arrayWithObjects:[MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@0],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@1],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@2],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@3],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@4],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:failAttributeID] // Fail Case
+ ,
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@5],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@6],
+ [MTRAttributeRequestPath requestPathWithEndpointID:nil clusterID:@40 attributeID:@7], nil];
+
+ [device readAttributePaths:attributePaths
+ eventPaths:nil
+ params:nil
+ queue:queue
+ completion:^(id _Nullable values, NSError * _Nullable error) {
+ NSLog(@"read attribute: DeviceType values: %@, error: %@", values, error);
+
+ XCTAssertNil(error);
+ XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0);
+
+ {
+ XCTAssertTrue([values isKindOfClass:[NSArray class]]);
+ NSArray * resultArray = values;
+ for (NSDictionary * result in resultArray) {
+ MTRAttributePath * path = result[@"attributePath"];
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 40);
+ if (path.attribute.unsignedIntegerValue != failAttributeID.unsignedIntegerValue) {
+ XCTAssertNotNil(result[@"data"]);
+ XCTAssertNil(result[@"error"]);
+ XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
+ } else {
+ XCTAssertNil(result[@"data"]);
+ XCTAssertNotNil(result[@"error"]);
+ }
+ }
+ XCTAssertTrue([resultArray count] > 0);
+ }
+
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil];
+}
+
+- (void)test022_SubscribeMultipleAttributes
+{
+ MTRBaseDevice * device = GetConnectedDevice();
+ dispatch_queue_t queue = dispatch_get_main_queue();
+
+ // Subscribe
+ XCTestExpectation * expectation = [self expectationWithDescription:@"subscribe OnOff attribute"];
+ __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(1) maxInterval:@(10)];
+
+ NSArray<MTRAttributeRequestPath *> * attributePaths =
+ [NSArray arrayWithObjects:[MTRAttributeRequestPath requestPathWithEndpointID:@1 clusterID:@6 attributeID:@0],
+ [MTRAttributeRequestPath requestPathWithEndpointID:@0 clusterID:@40 attributeID:@5], nil];
+
+ [device subscribeToAttributePaths:attributePaths
+ eventPaths:nil
+ params:params
+ queue:queue
+ reportHandler:^(id _Nullable values, NSError * _Nullable error) {
+ NSLog(@"report attributes: values: %@, error: %@", values, error);
+
+ if (globalReportHandler) {
+ __auto_type callback = globalReportHandler;
+ callback(values, error);
+ }
+ }
+ subscriptionEstablished:^{
+ NSLog(@"subscribe attribute");
+ [expectation fulfill];
+ }
+ resubscriptionScheduled:nil];
+
+ // Wait till establishment
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kTimeoutInSeconds];
+
+ // Set up expectation for report
+ XCTestExpectation * reportExpectation = [self expectationWithDescription:@"report received"];
+ globalReportHandler = ^(id _Nullable values, NSError * _Nullable error) {
+ XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0);
+ XCTAssertTrue([values isKindOfClass:[NSArray class]]);
+ NSDictionary * result = values[0];
+ MTRAttributePath * path = result[@"attributePath"];
+ if (path.endpoint.unsignedShortValue == 1) {
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 6);
+ XCTAssertEqual([path.attribute unsignedIntegerValue], 0);
+ XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
+ XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]);
+ if ([result[@"data"][@"value"] boolValue] == YES) {
+ [reportExpectation fulfill];
+ globalReportHandler = nil;
+ }
+ } else if (path.endpoint.unsignedShortValue == 0) {
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 40);
+ XCTAssertEqual([path.attribute unsignedIntegerValue], 5);
+ XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
+ XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"UTF8String"]);
+ } else {
+ XCTAssertTrue(NO);
+ }
+ };
+
+ // Send commands to trigger attribute change
+ XCTestExpectation * commandExpectation = [self expectationWithDescription:@"command responded"];
+ NSDictionary * fields = @{ @"type" : @"Structure", @"value" : [NSArray array] };
+ [device invokeCommandWithEndpointID:@1
+ clusterID:@6
+ commandID:@1
+ commandFields:fields
+ timedInvokeTimeout:nil
+ queue:queue
+ completion:^(id _Nullable values, NSError * _Nullable error) {
+ NSLog(@"invoke command: On values: %@, error: %@", values, error);
+
+ XCTAssertNil(error);
+ XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0);
+
+ {
+ XCTAssertTrue([values isKindOfClass:[NSArray class]]);
+ NSArray * resultArray = values;
+ for (NSDictionary * result in resultArray) {
+ MTRCommandPath * path = result[@"commandPath"];
+ XCTAssertEqual([path.endpoint unsignedIntegerValue], 1);
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 6);
+ XCTAssertEqual([path.command unsignedIntegerValue], 1);
+ XCTAssertNil(result[@"error"]);
+ }
+ XCTAssertEqual([resultArray count], 1);
+ }
+ [commandExpectation fulfill];
+ }];
+
+ [self waitForExpectations:[NSArray arrayWithObject:commandExpectation] timeout:kTimeoutInSeconds];
+
+ // Wait for report
+ [self waitForExpectations:[NSArray arrayWithObject:reportExpectation] timeout:kTimeoutInSeconds];
+
+ // Set up expectation for 2nd report
+ reportExpectation = [self expectationWithDescription:@"receive OnOff attribute report"];
+ globalReportHandler = ^(id _Nullable values, NSError * _Nullable error) {
+ XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0);
+ XCTAssertTrue([values isKindOfClass:[NSArray class]]);
+ NSDictionary * result = values[0];
+ MTRAttributePath * path = result[@"attributePath"];
+ XCTAssertEqual([path.endpoint unsignedIntegerValue], 1);
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 6);
+ XCTAssertEqual([path.attribute unsignedIntegerValue], 0);
+ XCTAssertTrue([result[@"data"] isKindOfClass:[NSDictionary class]]);
+ XCTAssertTrue([result[@"data"][@"type"] isEqualToString:@"Boolean"]);
+ if ([result[@"data"][@"value"] boolValue] == NO) {
+ [reportExpectation fulfill];
+ globalReportHandler = nil;
+ }
+ };
+
+ // Send command to trigger attribute change
+ fields = [NSDictionary dictionaryWithObjectsAndKeys:@"Structure", @"type", [NSArray array], @"value", nil];
+ [device invokeCommandWithEndpointID:@1
+ clusterID:@6
+ commandID:@0
+ commandFields:fields
+ timedInvokeTimeout:nil
+ queue:queue
+ completion:^(id _Nullable values, NSError * _Nullable error) {
+ NSLog(@"invoke command: On values: %@, error: %@", values, error);
+
+ XCTAssertNil(error);
+ XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], 0);
+
+ {
+ XCTAssertTrue([values isKindOfClass:[NSArray class]]);
+ NSArray * resultArray = values;
+ for (NSDictionary * result in resultArray) {
+ MTRCommandPath * path = result[@"commandPath"];
+ XCTAssertEqual([path.endpoint unsignedIntegerValue], 1);
+ XCTAssertEqual([path.cluster unsignedIntegerValue], 6);
+ XCTAssertEqual([path.command unsignedIntegerValue], 0);
+ XCTAssertNil(result[@"error"]);
+ }
+ XCTAssertEqual([resultArray count], 1);
+ }
+ }];
+
+ // Wait for report
+ [self waitForExpectations:[NSArray arrayWithObject:reportExpectation] timeout:kTimeoutInSeconds];
+
+ expectation = [self expectationWithDescription:@"Report handler deregistered"];
+ [device deregisterReportHandlersWithQueue:queue
+ completion:^{
+ [expectation fulfill];
+ }];
+ [self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds];
+}
+
- (void)test900_SubscribeAllAttributes
{
MTRBaseDevice * device = GetConnectedDevice();
@@ -1637,7 +1911,7 @@
params.resubscribeAutomatically = NO;
[device subscribeToAttributesWithEndpointID:@1
clusterID:@6
- attributeID:@0xffffffff
+ attributeID:nil
params:params
queue:queue
reportHandler:^(id _Nullable values, NSError * _Nullable error) {