Skip to content

Commit e3c6522

Browse files
Add an API on MTRDevice to wait for attributes to reach certain values. (project-chip#36205)
* Add an API on MTRDevice to wait for attributes to reach certain values. * Address review comments. * Address review comments. * Implement cancellation of attribute waiters. * Addressing more review comments. * Address review comments. * Fix build failure
1 parent ffaeda4 commit e3c6522

File tree

11 files changed

+577
-9
lines changed

11 files changed

+577
-9
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
#import <Matter/MTRDefines.h>
19+
20+
NS_ASSUME_NONNULL_BEGIN
21+
22+
MTR_NEWLY_AVAILABLE
23+
@interface MTRAttributeValueWaiter : NSObject
24+
- (instancetype)init NS_UNAVAILABLE;
25+
+ (instancetype)new NS_UNAVAILABLE;
26+
27+
/**
28+
* Cancel the wait for the set of attribute path/value pairs represented by this
29+
* MTRAttributeValueWaiter. If the completion has not been called yet, it will
30+
* becalled with MTRErrorCodeCancelled.
31+
*/
32+
- (void)cancel;
33+
34+
@property (readonly, nonatomic) NSUUID * UUID;
35+
36+
@end
37+
38+
NS_ASSUME_NONNULL_END
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
#import <Matter/MTRError.h>
20+
21+
#import "MTRAttributeValueWaiter_Internal.h"
22+
#import "MTRDevice_Internal.h"
23+
#import "MTRLogging_Internal.h"
24+
25+
@implementation MTRAwaitedAttributeState
26+
- (instancetype)initWithValue:(MTRDeviceDataValueDictionary)value
27+
{
28+
if (self = [super init]) {
29+
_valueSatisfied = NO;
30+
_value = value;
31+
}
32+
33+
return self;
34+
}
35+
@end
36+
37+
MTR_DIRECT_MEMBERS
38+
@interface MTRAttributeValueWaiter ()
39+
@property (nonatomic, retain) NSDictionary<MTRAttributePath *, MTRAwaitedAttributeState *> * valueExpectations;
40+
// Protected by the MTRDevice's lock.
41+
@property (nonatomic, readwrite, retain) dispatch_queue_t queue;
42+
@property (nonatomic, readwrite, copy, nullable) MTRStatusCompletion completion;
43+
@property (nonatomic, readonly, retain) MTRDevice * device;
44+
@end
45+
46+
@implementation MTRAttributeValueWaiter
47+
48+
- (instancetype)initWithDevice:(MTRDevice *)device values:(NSDictionary<MTRAttributePath *, MTRDeviceDataValueDictionary> *)values queue:(dispatch_queue_t)queue completion:(MTRStatusCompletion)completion
49+
{
50+
if (self = [super init]) {
51+
auto * valueExpectations = [NSMutableDictionary dictionaryWithCapacity:values.count];
52+
for (MTRAttributePath * path in values) {
53+
auto * valueExpectation = [[MTRAwaitedAttributeState alloc] initWithValue:values[path]];
54+
valueExpectations[path] = valueExpectation;
55+
}
56+
_valueExpectations = valueExpectations;
57+
_queue = queue;
58+
_completion = completion;
59+
_device = device;
60+
_UUID = [NSUUID UUID];
61+
}
62+
63+
return self;
64+
}
65+
66+
- (void)dealloc
67+
{
68+
[self cancel];
69+
}
70+
71+
- (void)cancel
72+
{
73+
[self.device _attributeWaitCanceled:self];
74+
}
75+
76+
- (BOOL)_attributeValue:(MTRDeviceDataValueDictionary)value reportedForPath:(MTRAttributePath *)path byDevice:(MTRDevice *)device
77+
{
78+
MTRAwaitedAttributeState * valueExpectation = self.valueExpectations[path];
79+
if (!valueExpectation) {
80+
// We don't care about this one.
81+
return NO;
82+
}
83+
84+
MTRDeviceDataValueDictionary expectedValue = valueExpectation.value;
85+
valueExpectation.valueSatisfied = [device _attributeDataValue:value satisfiesValueExpectation:expectedValue];
86+
return valueExpectation.valueSatisfied;
87+
}
88+
89+
- (BOOL)allValuesSatisfied
90+
{
91+
for (MTRAwaitedAttributeState * valueExpectation in [self.valueExpectations allValues]) {
92+
if (!valueExpectation.valueSatisfied) {
93+
return NO;
94+
}
95+
}
96+
97+
return YES;
98+
}
99+
100+
- (void)_notifyWithError:(NSError * _Nullable)error
101+
{
102+
// This is always called with the device lock held, so checking and mutating
103+
// self.completion here is atomic.
104+
if (!self.completion) {
105+
return;
106+
}
107+
108+
if (self.expirationTimer != nil) {
109+
dispatch_source_cancel(self.expirationTimer);
110+
self.expirationTimer = nil;
111+
}
112+
113+
if (!error) {
114+
MTR_LOG("%@ %p wait for attribute values completed", self, self);
115+
} else if (error.domain == MTRErrorDomain && error.code == MTRErrorCodeTimeout) {
116+
MTR_LOG("%@ %p wait for attribute values timed out", self, self);
117+
} else if (error.domain == MTRErrorDomain && error.code == MTRErrorCodeCancelled) {
118+
MTR_LOG("%@ %p wait for attribute values canceled", self, self);
119+
} else {
120+
MTR_LOG("%@ %p wait for attribute values unknown error: %@", self, self, error);
121+
}
122+
123+
auto completion = self.completion;
124+
auto queue = self.queue;
125+
self.completion = nil;
126+
self.queue = nil;
127+
dispatch_async(queue, ^{
128+
completion(error);
129+
});
130+
}
131+
132+
- (NSString *)description
133+
{
134+
return [NSString stringWithFormat:@"<%@: %@>", NSStringFromClass(self.class), self.UUID];
135+
}
136+
137+
@end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright (c) 2024 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Matter/MTRAttributeValueWaiter.h>
18+
#import <Matter/MTRBaseDevice.h> // For MTRAttributePath.
19+
#import <Matter/MTRCluster.h>
20+
#import <Matter/MTRDevice.h>
21+
22+
#import "MTRDefines_Internal.h"
23+
24+
NS_ASSUME_NONNULL_BEGIN
25+
26+
// Represents the state of a single attribute being waited for: has the path and
27+
// value being waited for in the response-value and whether the value has been
28+
// reached.
29+
MTR_DIRECT_MEMBERS
30+
@interface MTRAwaitedAttributeState : NSObject
31+
@property (nonatomic, assign, readwrite) BOOL valueSatisfied;
32+
@property (nonatomic, retain, readonly) MTRDeviceDataValueDictionary value;
33+
34+
- (instancetype)initWithValue:(MTRDeviceDataValueDictionary)value;
35+
@end
36+
37+
@interface MTRAttributeValueWaiter ()
38+
39+
@property (nonatomic, readonly) BOOL allValuesSatisfied;
40+
@property (nonatomic, retain, readwrite, nullable) dispatch_source_t expirationTimer;
41+
42+
- (instancetype)initWithDevice:(MTRDevice *)device values:(NSDictionary<MTRAttributePath *, MTRDeviceDataValueDictionary> *)values queue:(dispatch_queue_t)queue completion:(MTRStatusCompletion)completion;
43+
44+
// Returns YES if after this report the waiter might be done waiting, NO otherwise.
45+
- (BOOL)_attributeValue:(MTRDeviceDataValueDictionary)value reportedForPath:(MTRAttributePath *)path byDevice:(MTRDevice *)device;
46+
47+
- (void)_notifyWithError:(NSError * _Nullable)error;
48+
49+
@end
50+
51+
NS_ASSUME_NONNULL_END

src/darwin/Framework/CHIP/MTRDevice.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
#import <Foundation/Foundation.h>
19+
#import <Matter/MTRAttributeValueWaiter.h>
1920
#import <Matter/MTRBaseDevice.h>
2021
#import <Matter/MTRDefines.h>
2122

@@ -337,6 +338,26 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
337338
queue:(dispatch_queue_t)queue
338339
completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion
339340
MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6));
341+
342+
/**
343+
* Sets up the provided completion to be called when any of the following
344+
* happens:
345+
*
346+
* 1) A set of attributes reaches certain values: completion called with nil.
347+
* 2) The provided timeout expires: completion called with MTRErrorCodeTimeout error.
348+
* 3) The wait is canceled: completion called with MTRErrorCodeCancelled error.
349+
*
350+
* If the MTRAttributeValueWaiter is destroyed before the
351+
* completion is called, that is treated the same as canceling the waiter.
352+
*
353+
* The attributes and values to wait for are represented as a dictionary which
354+
* has the attribute paths as keys and the expected data-values as values.
355+
*/
356+
- (MTRAttributeValueWaiter *)waitForAttributeValues:(NSDictionary<MTRAttributePath *, NSDictionary<NSString *, id> *> *)values
357+
timeout:(NSTimeInterval)timeout
358+
queue:(dispatch_queue_t)queue
359+
completion:(void (^)(NSError * _Nullable error))completion MTR_NEWLY_AVAILABLE;
360+
340361
@end
341362

342363
MTR_EXTERN NSString * const MTRPreviousDataKey MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6));

0 commit comments

Comments
 (0)