Skip to content

Commit 887119c

Browse files
authored
[darwin-framework-tool] Add XPC connectivity support for HomeKit on iOS (#37794)
* [darwin-framework-tool] Add some HomeKit entitlements * [darwin-framework-tool] Ensure that darwin-framework-tool calls dispatch_main() * [darwin] Update src/darwin/Framework/Matter.xcodeproj/project.pbxproj * [darwin-framework-tool] Add XPC connectivity support for HomeKit on iOS * [darwin-framework-tool] Add some logging code to HomeKit support on iOS
1 parent 5adee57 commit 887119c

File tree

7 files changed

+317
-32
lines changed

7 files changed

+317
-32
lines changed

examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm

+16-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
#import <lib/support/logging/CHIPLogging.h>
2222

23+
#if TARGET_OS_MACCATALYST || TARGET_OS_IOS
24+
#import "HomeKitConnector.h"
25+
#endif
26+
2327
@interface DeviceControllerXPCServerImpl : NSObject <MTRDeviceControllerServerProtocol>
2428
@property (nonatomic, strong) id<MTRDeviceControllerClientProtocol> clientProxy;
2529
@property (nonatomic, strong) NSArray<MTRDeviceController *> * controllers;
@@ -331,19 +335,25 @@ - (void)start
331335

332336
- (void)stop
333337
{
338+
#if TARGET_OS_MACCATALYST || TARGET_OS_IOS
339+
[[HomeKitConnector sharedInstance] stop];
340+
#endif
334341
}
335342

336343
- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error
337344
{
338-
__auto_type connectBlock = ^NSXPCConnection *
339-
{
345+
NSXPCConnection * (^connectBlock)(void) = nil;
340346
#if TARGET_OS_OSX
347+
connectBlock = ^NSXPCConnection *
348+
{
341349
return [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0];
342-
#else
343-
ChipLogError(chipTool, "NSXPCConnection::initWithMachServiceName is not supported on this platform.");
344-
return nil;
345-
#endif // TARGET_OS_OSX
346350
};
351+
#elif TARGET_OS_MACCATALYST || TARGET_OS_IOS
352+
connectBlock = [[HomeKitConnector sharedInstance] connectBlockFor:controllerID];
353+
controllerID = [[HomeKitConnector sharedInstance] homeControllerIDFor:controllerID];
354+
#endif
355+
356+
VerifyOrReturnValue(nil != connectBlock, nil, ChipLogError(chipTool, "DeviceControllerXPCServerWithServiceName is not supported on this platform."));
347357
return [MTRDeviceController sharedControllerWithID:controllerID xpcConnectBlock:connectBlock];
348358
}
349359
@end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2025 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
#import <Foundation/Foundation.h>
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
@interface HomeKitConnector : NSObject
24+
- (instancetype)init NS_UNAVAILABLE;
25+
+ (instancetype)new NS_UNAVAILABLE;
26+
+ (instancetype)sharedInstance;
27+
28+
- (void)start;
29+
- (void)stop;
30+
- (NSString *)homeControllerIDFor:(NSString *)controllerID;
31+
- (NSXPCConnection * (^)(void) )connectBlockFor:(NSString *)controllerID;
32+
@end
33+
34+
NS_ASSUME_NONNULL_END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright (c) 2025 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
#import "HomeKitConnector.h"
20+
#import "../CHIPCommandBridge.h"
21+
#import <lib/support/logging/CHIPLogging.h>
22+
23+
#import <HomeKit/HomeKit.h>
24+
25+
const int64_t kHomeManagerSetupTimeout = 10LL * NSEC_PER_SEC;
26+
NSString * kControllerIdPrefixStr = @(kControllerIdPrefix);
27+
28+
@interface HomeKitConnector () <HMHomeManagerDelegate>
29+
@property (nonatomic, assign) BOOL connectorStarted;
30+
@property (nonatomic, strong) HMHomeManager * homeManager;
31+
@property (nonatomic, assign) BOOL homeManagerReady;
32+
@end
33+
34+
@implementation HomeKitConnector {
35+
dispatch_group_t _homeManagerReadyGroup;
36+
}
37+
38+
+ (instancetype)sharedInstance
39+
{
40+
static HomeKitConnector * sharedInstance = nil;
41+
static dispatch_once_t onceToken;
42+
dispatch_once(&onceToken, ^{
43+
sharedInstance = [[HomeKitConnector alloc] init];
44+
});
45+
return sharedInstance;
46+
}
47+
48+
- (void)start
49+
{
50+
VerifyOrReturn(!_connectorStarted);
51+
_connectorStarted = YES;
52+
53+
_homeManagerReady = NO;
54+
_homeManagerReadyGroup = dispatch_group_create();
55+
dispatch_group_enter(_homeManagerReadyGroup);
56+
57+
_homeManager = [[HMHomeManager alloc] init];
58+
_homeManager.delegate = self;
59+
60+
// Wait until homeManagerDidUpdateHomes is called or timeout
61+
dispatch_group_wait(_homeManagerReadyGroup, dispatch_time(DISPATCH_TIME_NOW, kHomeManagerSetupTimeout));
62+
63+
[self printHomes];
64+
}
65+
66+
- (void)stop
67+
{
68+
VerifyOrReturn(_connectorStarted);
69+
70+
_homeManager.delegate = nil;
71+
_homeManager = nil;
72+
}
73+
74+
- (void)homeManagerDidUpdateHomes:(HMHomeManager *)manager
75+
{
76+
VerifyOrReturn(!_homeManagerReady);
77+
dispatch_group_leave(_homeManagerReadyGroup);
78+
}
79+
80+
- (HMHome *)homeFor:(NSString *)controllerID
81+
{
82+
[[HomeKitConnector sharedInstance] start];
83+
84+
__auto_type * homes = _homeManager.homes;
85+
VerifyOrReturnValue(0 != homes.count, nil, ChipLogError(chipTool, "HomeKit is not configured with any homes."));
86+
87+
NSNumber * fabricId = nil;
88+
if ([controllerID hasPrefix:kControllerIdPrefixStr]) {
89+
__auto_type * fabricIdString = [controllerID substringFromIndex:kControllerIdPrefixStr.length];
90+
fabricId = @([fabricIdString integerValue]);
91+
} else {
92+
fabricId = CHIPCommandBridge::GetCommissionerFabricId([controllerID UTF8String]);
93+
}
94+
95+
// When multiple homes exist, the first controller corresponds to the first home, the second controller to the second home, etc.
96+
// If there are fewer homes than controllers, controllers beyond the last home will be associated with the final home in the list.
97+
NSUInteger index = [fabricId unsignedShortValue] - 1;
98+
if (index >= homes.count) {
99+
index = homes.count - 1;
100+
}
101+
102+
return homes[index];
103+
}
104+
105+
- (NSString *)paddedString:(NSString *)string width:(NSUInteger)width
106+
{
107+
// Using length might not account for all unicode width details, but it's a simple approximation.
108+
NSUInteger length = string.length;
109+
if (length >= width) {
110+
return string;
111+
}
112+
NSMutableString * result = [NSMutableString stringWithString:string];
113+
for (NSUInteger i = 0; i < (width - length); i++) {
114+
[result appendString:@" "];
115+
}
116+
return result;
117+
}
118+
119+
- (NSString *)repeatString:(NSString *)string count:(NSUInteger)count
120+
{
121+
NSMutableString * result = [NSMutableString string];
122+
for (NSUInteger i = 0; i < count; i++) {
123+
[result appendString:string];
124+
}
125+
return result;
126+
}
127+
128+
- (void)printHomes
129+
{
130+
for (HMHome * home in _homeManager.homes) {
131+
NSUInteger maxNameLength = 0;
132+
NSUInteger maxNodeIDLength = 0;
133+
NSUInteger maxManufacturerLength = 0;
134+
NSUInteger maxModelLength = 0;
135+
136+
__auto_type * sortedAccessories = [home.accessories sortedArrayUsingComparator:^NSComparisonResult(HMAccessory * a, HMAccessory * b) {
137+
return [a.name localizedCaseInsensitiveCompare:b.name];
138+
}];
139+
140+
for (HMAccessory * accessory in sortedAccessories) {
141+
maxNameLength = MAX(maxNameLength, accessory.name.length);
142+
maxManufacturerLength = MAX(maxManufacturerLength, accessory.manufacturer.length);
143+
maxModelLength = MAX(maxModelLength, accessory.model.length);
144+
maxNodeIDLength = MAX(maxNodeIDLength, [accessory.matterNodeID stringValue].length);
145+
}
146+
147+
__auto_type * rows = [NSMutableArray arrayWithCapacity:sortedAccessories.count];
148+
[sortedAccessories enumerateObjectsUsingBlock:^(HMAccessory * accessory, NSUInteger idx, BOOL * stop) {
149+
if (accessory.matterNodeID == nil || [accessory.matterNodeID integerValue] == 0) {
150+
return;
151+
}
152+
153+
__auto_type * name = [self paddedString:accessory.name width:maxNameLength];
154+
__auto_type * manufacturer = [self paddedString:accessory.manufacturer width:maxManufacturerLength];
155+
__auto_type * model = [self paddedString:accessory.model width:maxModelLength];
156+
__auto_type * nodeID = [self paddedString:[accessory.matterNodeID stringValue] width:maxNodeIDLength];
157+
__auto_type * formattedString = [NSString stringWithFormat:@" %@%@%@%@ ", name, manufacturer, model, nodeID];
158+
[rows addObject:formattedString];
159+
}];
160+
161+
NSUInteger tableWidth = 1 + maxNameLength + 3 + maxManufacturerLength + 3 + maxModelLength + 3 + maxNodeIDLength + 1;
162+
NSLog(@"%@", [self repeatString:@"" count:tableWidth]);
163+
NSLog(@"%@", [self paddedString:[NSString stringWithFormat:@" %@ [%@] ", home.name, home.matterControllerID] width:tableWidth]);
164+
NSLog(@"%@", [self repeatString:@"" count:tableWidth]);
165+
for (NSString * row in rows) {
166+
NSLog(@"%@", row);
167+
}
168+
NSLog(@"%@", [self repeatString:@"" count:tableWidth]);
169+
}
170+
}
171+
172+
- (NSString *)homeControllerIDFor:(NSString *)controllerID
173+
{
174+
__auto_type * home = [self homeFor:controllerID];
175+
return home.matterControllerID;
176+
}
177+
178+
- (NSXPCConnection * (^)(void) )connectBlockFor:(NSString *)controllerID;
179+
{
180+
__auto_type * home = [self homeFor:controllerID];
181+
ChipLogProgress(chipTool, "Controller '%s' will be associated with home '%s'.", [controllerID UTF8String], [home.matterControllerID UTF8String]);
182+
183+
if ([controllerID hasPrefix:kControllerIdPrefixStr]) {
184+
if ([home respondsToSelector:NSSelectorFromString(@"matterStartupParametersXPCConnectBlock")]) {
185+
return [home valueForKey:@"matterStartupParametersXPCConnectBlock"];
186+
}
187+
188+
ChipLogError(chipTool, "Error: 'matterStartupParametersXPCConnectBlock' not available for controller '%s'.", [controllerID UTF8String]);
189+
return nil;
190+
} else {
191+
return home.matterControllerXPCConnectBlock;
192+
}
193+
}
194+
@end

examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm

+17-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
#import <lib/support/logging/CHIPLogging.h>
2222

23+
#if TARGET_OS_MACCATALYST || TARGET_OS_IOS
24+
#import "HomeKitConnector.h"
25+
#endif
26+
2327
@interface XPCServerImpl : NSObject <MTRXPCServerProtocol_MTRDevice>
2428
@property (nonatomic, strong) id<MTRXPCClientProtocol> clientProxy;
2529
@property (nonatomic, strong) NSArray<MTRDeviceController *> * controllers;
@@ -239,19 +243,26 @@ - (void)start
239243

240244
- (void)stop
241245
{
246+
#if TARGET_OS_MACCATALYST || TARGET_OS_IOS
247+
[[HomeKitConnector sharedInstance] stop];
248+
#endif
242249
}
243250

244251
- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error
245252
{
246-
__auto_type connectBlock = ^NSXPCConnection *
247-
{
253+
NSXPCConnection * (^connectBlock)(void) = nil;
248254
#if TARGET_OS_OSX
255+
connectBlock = ^NSXPCConnection *
256+
{
249257
return [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0];
250-
#else
251-
ChipLogError(chipTool, "NSXPCConnection::initWithMachServiceName is not supported on this platform.");
252-
return nil;
253-
#endif // TARGET_OS_OSX
254258
};
259+
#elif TARGET_OS_MACCATALYST || TARGET_OS_IOS
260+
connectBlock = [[HomeKitConnector sharedInstance] connectBlockFor:controllerID];
261+
controllerID = [[HomeKitConnector sharedInstance] homeControllerIDFor:controllerID];
262+
#endif
263+
264+
VerifyOrReturnValue(nil != connectBlock, nil, ChipLogError(chipTool, "XPCServerWithServiceName is not supported on this platform."));
265+
255266
__auto_type * uniqueIdentifier = [[NSUUID alloc] initWithUUIDString:controllerID];
256267
__auto_type * xpcParams = [[MTRXPCDeviceControllerParameters alloc] initWithXPConnectionBlock:connectBlock uniqueIdentifier:uniqueIdentifier];
257268
return [[MTRDeviceController alloc] initWithParameters:xpcParams error:error];

examples/darwin-framework-tool/entitlements/darwin-framework-tool.entitlements

+4
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@
88
<array>
99
<string>group.com.appleinternal.chip-tool</string>
1010
</array>
11+
<key>com.apple.developer.homekit</key>
12+
<true/>
13+
<key>com.apple.developer.homekit.background-mode</key>
14+
<true/>
1115
</dict>
1216
</plist>

examples/darwin-framework-tool/main.mm

+25-19
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,30 @@
3838

3939
int main(int argc, const char * argv[])
4040
{
41-
int exitCode = EXIT_SUCCESS;
42-
@autoreleasepool {
43-
dft::logging::Setup();
41+
__auto_type * runQueue = dispatch_queue_create("com.chip.main.dft", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
42+
dispatch_async(runQueue, ^{
43+
int exitCode = EXIT_SUCCESS;
4444

45-
Commands commands;
46-
registerCommandsBdx(commands);
47-
registerCommandsPairing(commands);
48-
registerCommandsDCL(commands);
49-
registerCommandsDelay(commands);
50-
registerCommandsDiscover(commands);
51-
registerCommandsInteractive(commands);
52-
registerCommandsMemory(commands);
53-
registerCommandsPayload(commands);
54-
registerClusterOtaSoftwareUpdateProviderInteractive(commands);
55-
registerCommandsStorage(commands);
56-
registerCommandsConfiguration(commands);
57-
registerClusters(commands);
58-
exitCode = commands.Run(argc, (char **) argv);
59-
}
60-
return ConditionalLeaksCheck(exitCode);
45+
@autoreleasepool {
46+
dft::logging::Setup();
47+
Commands commands;
48+
registerCommandsBdx(commands);
49+
registerCommandsPairing(commands);
50+
registerCommandsDCL(commands);
51+
registerCommandsDelay(commands);
52+
registerCommandsDiscover(commands);
53+
registerCommandsInteractive(commands);
54+
registerCommandsMemory(commands);
55+
registerCommandsPayload(commands);
56+
registerClusterOtaSoftwareUpdateProviderInteractive(commands);
57+
registerCommandsStorage(commands);
58+
registerCommandsConfiguration(commands);
59+
registerClusters(commands);
60+
exitCode = commands.Run(argc, (char **) argv);
61+
}
62+
exit(ConditionalLeaksCheck(exitCode));
63+
});
64+
65+
dispatch_main();
66+
return EXIT_SUCCESS;
6167
}

0 commit comments

Comments
 (0)