diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 28874a560b287f..d696c4d5138cbe 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -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 diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm index 98a217eb77e2d8..529962bdb9f260 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm @@ -133,6 +133,30 @@ - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID [self fetchProxyHandleWithQueue:queue completion:workBlock]; } +- (void)readAttributePaths:(NSArray * _Nullable)attributePaths + eventPaths:(NSArray * _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 @@ -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 @@ -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]); }); @@ -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]); }); diff --git a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m index 6ae678264c2143..35403d1290d483 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m @@ -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 * (^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"];