Skip to content

Commit

Permalink
Hook up some MTRDevice bits to XPC.
Browse files Browse the repository at this point in the history
Specific changes:

* Make MTRDevice get its MTRBaseDevice in a way that is XPC-friendly.
* Implement readAttributePaths (which is what ends up getting called by
  MTRDevice's readAttributeWithEndpointID) over XPC, as long as there is just a
  single attribute path.
* Implement _invokeCommandWithEndpointID (which is what ends up getting called
  by MTRDevice's _invokeKnownCommandWithEndpointID) over XPC, as long as
  serverSideProcessingTimeout is nil.
* Writes already called an MTRBaseDevice function that was implemented over XPC.
* Tests for the new setup; these were checked to fail without the other changes.

This also makes MTRClusters work over XPC.
  • Loading branch information
bzbarsky-apple committed Oct 10, 2023
1 parent f40823f commit 7d4b7dd
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1576,7 +1576,7 @@ - (void)_removeExpectedValueForAttributePath:(MTRAttributePath *)attributePath e

- (MTRBaseDevice *)newBaseDevice
{
return [[MTRBaseDevice alloc] initWithNodeID:self.nodeID controller:self.deviceController];
return [MTRBaseDevice deviceWithNodeID:self.nodeID controller:self.deviceController];
}

@end
Expand Down
54 changes: 52 additions & 2 deletions src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,30 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID
[self fetchProxyHandleWithQueue:queue completion:workBlock];
}

- (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 != 1 || eventPaths != nil) {
MTR_LOG_ERROR("MTRBaseDevice doesn't support reading more than a single attribute path at a time over XPC");
dispatch_async(queue, ^{
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]);
});
return;
}

auto * path = attributePaths[0];

[self readAttributesWithEndpointID:path.endpoint
clusterID:path.cluster
attributeID:path.attribute
params:params
queue:queue
completion:completion];
}

- (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
attributeID:(NSNumber *)attributeID
Expand Down Expand Up @@ -209,6 +233,32 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID
[self fetchProxyHandleWithQueue:queue completion:workBlock];
}

- (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
commandFields:(id)commandFields
timedInvokeTimeout:(NSNumber * _Nullable)timeoutMs
serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout
queue:(dispatch_queue_t)queue
completion:(MTRDeviceResponseHandler)completion
{
if (serverSideProcessingTimeout != nil) {
MTR_LOG_ERROR("MTRBaseDevice doesn't support invokes with a server-side processing timeout over XPC");
dispatch_async(queue, ^{
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]);
});
return;
}

[self invokeCommandWithEndpointID:endpointID
clusterID:clusterID
commandID:commandID
commandFields:commandFields
timedInvokeTimeout:timeoutMs
queue:queue
completion:completion];
}

- (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID
clusterID:(NSNumber * _Nullable)clusterID
attributeID:(NSNumber * _Nullable)attributeID
Expand Down Expand Up @@ -323,7 +373,7 @@ - (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode
queue:(dispatch_queue_t)queue
completion:(MTRDeviceOpenCommissioningWindowHandler)completion
{
MTR_LOG_ERROR("MTRDevice doesn't support openCommissioningWindowWithSetupPasscode over XPC");
MTR_LOG_ERROR("MTRBaseDevice doesn't support openCommissioningWindowWithSetupPasscode over XPC");
dispatch_async(queue, ^{
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]);
});
Expand All @@ -334,7 +384,7 @@ - (void)openCommissioningWindowWithDiscriminator:(NSNumber *)discriminator
queue:(dispatch_queue_t)queue
completion:(MTRDeviceOpenCommissioningWindowHandler)completion
{
MTR_LOG_ERROR("MTRDevice doesn't support openCommissioningWindowWithDiscriminator over XPC");
MTR_LOG_ERROR("MTRBaseDevice doesn't support openCommissioningWindowWithDiscriminator over XPC");
dispatch_async(queue, ^{
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]);
});
Expand Down
120 changes: 120 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1677,6 +1677,126 @@ - (void)test014_TimedInvokeCommand
sleep(1);
}

- (void)test015_MTRDeviceInteraction
{
__auto_type * device = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:mDeviceController];
dispatch_queue_t queue = dispatch_get_main_queue();

__auto_type * endpoint = @(1);

__auto_type * onOffCluster = [[MTRClusterOnOff alloc] initWithDevice:device endpointID:endpoint queue:queue];

// Since we have no subscription, reads don't really work right. We need to
// poll for values instead.
__auto_type pollForValue = ^(NSNumber * attributeID, NSDictionary<NSString *, id> * (^readBlock)(void), NSNumber * value) {
XCTestExpectation * expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Polling for on/off=%@", value]];
__auto_type * path = [MTRAttributePath attributePathWithEndpointID:endpoint clusterID:@(MTRClusterIDTypeOnOffID) attributeID:attributeID];

__block dispatch_block_t poller = ^{
__auto_type * attrValue = readBlock();
if (attrValue == nil) {
dispatch_async(queue, poller);
return;
}

__auto_type * responseValue = @{
MTRAttributePathKey : path,
MTRDataKey : attrValue,
};

NSError * error;
__auto_type * report = [[MTRAttributeReport alloc] initWithResponseValue:responseValue error:&error];
XCTAssertNil(error);
XCTAssertNotNil(report);
XCTAssertNil(report.error);
XCTAssertNotNil(report.value);

if ([report.value isEqual:value]) {
// Break cycle.
poller = nil;
[expectation fulfill];
return;
}

dispatch_async(queue, poller);
};

dispatch_async(queue, poller);
[self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds + 5];
};

pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnOffID), ^{
return [onOffCluster readAttributeOnOffWithParams:nil];
}, @(NO));
pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnTimeID), ^{
return [onOffCluster readAttributeOnTimeWithParams:nil];
}, @(0));

// Test that writes work.
[onOffCluster writeAttributeOnTimeWithValue:@{
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @(100),
}
expectedValueInterval:@(0)];

// Wait until the expected value is removed.
pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnTimeID), ^{
return [onOffCluster readAttributeOnTimeWithParams:nil];
}, @(0));

// Now wait until the new value is read.
pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnTimeID), ^{
return [onOffCluster readAttributeOnTimeWithParams:nil];
}, @(100));

[onOffCluster writeAttributeOnTimeWithValue:@{
MTRTypeKey : MTRUnsignedIntegerValueType,
MTRValueKey : @(0),
}
expectedValueInterval:@(0)];

// Wait until the expected value is removed.
pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnTimeID), ^{
return [onOffCluster readAttributeOnTimeWithParams:nil];
}, @(100));

// Now wait until the new value is read.
pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnTimeID), ^{
return [onOffCluster readAttributeOnTimeWithParams:nil];
}, @(00));

// Test that invokes work.
XCTestExpectation * onExpectation = [self expectationWithDescription:@"Turning on"];
[onOffCluster onWithParams:nil expectedValues:nil expectedValueInterval:nil completion:^(NSError * error) {
XCTAssertNil(error);
[onExpectation fulfill];
}];
[self waitForExpectations:@[ onExpectation ] timeout:kTimeoutInSeconds];

pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnOffID), ^{
return [onOffCluster readAttributeOnOffWithParams:nil];
}, @(YES));

XCTestExpectation * offExpectation = [self expectationWithDescription:@"Turning off"];
[onOffCluster offWithParams:nil expectedValues:nil expectedValueInterval:nil completion:^(NSError * error) {
XCTAssertNil(error);
[offExpectation fulfill];
}];
[self waitForExpectations:@[ offExpectation ] timeout:kTimeoutInSeconds];

pollForValue(
@(MTRAttributeIDTypeClusterOnOffAttributeOnOffID), ^{
return [onOffCluster readAttributeOnOffWithParams:nil];
}, @(NO));
}

- (void)test900_SubscribeClusterStateCache
{
XCTestExpectation * expectation = [self expectationWithDescription:@"subscribe attributes by cache"];
Expand Down

0 comments on commit 7d4b7dd

Please sign in to comment.