homebridge 2.0.0-alpha.41 → 2.0.0-alpha.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +47 -20
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +43 -11
- package/dist/api.js.map +1 -1
- package/dist/bridgeService.d.ts +5 -10
- package/dist/bridgeService.d.ts.map +1 -1
- package/dist/bridgeService.js +3 -0
- package/dist/bridgeService.js.map +1 -1
- package/dist/childBridgeFork.d.ts +19 -0
- package/dist/childBridgeFork.d.ts.map +1 -1
- package/dist/childBridgeFork.js +198 -4
- package/dist/childBridgeFork.js.map +1 -1
- package/dist/childBridgeService.d.ts +27 -1
- package/dist/childBridgeService.d.ts.map +1 -1
- package/dist/childBridgeService.js +43 -1
- package/dist/childBridgeService.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/ipcService.d.ts +3 -1
- package/dist/ipcService.d.ts.map +1 -1
- package/dist/ipcService.js +2 -0
- package/dist/ipcService.js.map +1 -1
- package/dist/matter/index.d.ts +4 -6
- package/dist/matter/index.d.ts.map +1 -1
- package/dist/matter/index.js +4 -5
- package/dist/matter/index.js.map +1 -1
- package/dist/matter/matterConfigValidator.d.ts +2 -3
- package/dist/matter/matterConfigValidator.d.ts.map +1 -1
- package/dist/matter/matterConfigValidator.js +47 -38
- package/dist/matter/matterConfigValidator.js.map +1 -1
- package/dist/matter/matterErrorHandler.d.ts +6 -25
- package/dist/matter/matterErrorHandler.d.ts.map +1 -1
- package/dist/matter/matterErrorHandler.js +89 -99
- package/dist/matter/matterErrorHandler.js.map +1 -1
- package/dist/matter/matterServer.d.ts +125 -39
- package/dist/matter/matterServer.d.ts.map +1 -1
- package/dist/matter/matterServer.js +463 -223
- package/dist/matter/matterServer.js.map +1 -1
- package/dist/matter/matterSharedTypes.d.ts +1 -21
- package/dist/matter/matterSharedTypes.d.ts.map +1 -1
- package/dist/matter/matterSharedTypes.js +0 -4
- package/dist/matter/matterSharedTypes.js.map +1 -1
- package/dist/matter/matterStorage.d.ts +112 -0
- package/dist/matter/matterStorage.d.ts.map +1 -0
- package/dist/matter/matterStorage.js +355 -0
- package/dist/matter/matterStorage.js.map +1 -0
- package/dist/matter/matterTypes.d.ts +148 -20
- package/dist/matter/matterTypes.d.ts.map +1 -1
- package/dist/matter/matterTypes.js +91 -263
- package/dist/matter/matterTypes.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +4 -2
- package/dist/plugin.js.map +1 -1
- package/dist/server.d.ts +12 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +310 -332
- package/dist/server.js.map +1 -1
- package/dist/user.d.ts +1 -0
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +3 -0
- package/dist/user.js.map +1 -1
- package/package.json +4 -5
- package/dist/childMatterBridgeFork.d.ts +0 -108
- package/dist/childMatterBridgeFork.d.ts.map +0 -1
- package/dist/childMatterBridgeFork.js +0 -330
- package/dist/childMatterBridgeFork.js.map +0 -1
- package/dist/childMatterBridgeService.d.ts +0 -166
- package/dist/childMatterBridgeService.d.ts.map +0 -1
- package/dist/childMatterBridgeService.js +0 -623
- package/dist/childMatterBridgeService.js.map +0 -1
- package/dist/matter/matterBridge.d.ts +0 -64
- package/dist/matter/matterBridge.d.ts.map +0 -1
- package/dist/matter/matterBridge.js +0 -154
- package/dist/matter/matterBridge.js.map +0 -1
- package/dist/matter/matterDevice.d.ts +0 -107
- package/dist/matter/matterDevice.d.ts.map +0 -1
- package/dist/matter/matterDevice.js +0 -913
- package/dist/matter/matterDevice.js.map +0 -1
- package/dist/matter/portAllocator.d.ts +0 -85
- package/dist/matter/portAllocator.d.ts.map +0 -1
- package/dist/matter/portAllocator.js +0 -296
- package/dist/matter/portAllocator.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -5,11 +5,10 @@ import qrcode from 'qrcode-terminal';
|
|
|
5
5
|
import { HomebridgeAPI } from './api.js';
|
|
6
6
|
import { BridgeService } from './bridgeService.js';
|
|
7
7
|
import { ChildBridgeService } from './childBridgeService.js';
|
|
8
|
-
import { ChildMatterBridgeService } from './childMatterBridgeService.js';
|
|
9
8
|
import { ExternalPortService } from './externalPortService.js';
|
|
10
9
|
import { IpcService } from './ipcService.js';
|
|
11
10
|
import { Logger } from './logger.js';
|
|
12
|
-
import { MatterConfigValidator } from './matter/
|
|
11
|
+
import { MatterConfigValidator, MatterServer } from './matter/index.js';
|
|
13
12
|
import { PluginManager } from './pluginManager.js';
|
|
14
13
|
import { User } from './user.js';
|
|
15
14
|
import { validMacAddress } from './util/mac.js';
|
|
@@ -40,7 +39,10 @@ export class Server {
|
|
|
40
39
|
config;
|
|
41
40
|
// used to keep track of child bridges
|
|
42
41
|
childBridges = new Map();
|
|
43
|
-
|
|
42
|
+
// Matter server instance for main bridge (if enabled)
|
|
43
|
+
matterServer;
|
|
44
|
+
// Cache for Matter bridge identifier -> ChildBridgeService lookups
|
|
45
|
+
matterBridgeCache = new Map();
|
|
44
46
|
// Track platform configurations for routing accessories
|
|
45
47
|
platformConfigs = new Map();
|
|
46
48
|
// current server status
|
|
@@ -70,12 +72,15 @@ export class Server {
|
|
|
70
72
|
// shallow copy the homebridge options to the bridge options object
|
|
71
73
|
Object.assign(bridgeConfig, this.options);
|
|
72
74
|
this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge, this.config);
|
|
73
|
-
//
|
|
74
|
-
// Intercept platform accessory registration to route to Matter bridges if needed
|
|
75
|
+
// Handle platform accessory registration
|
|
75
76
|
this.api.on("registerPlatformAccessories" /* InternalAPIEvent.REGISTER_PLATFORM_ACCESSORIES */, this.handleRegisterPlatformAccessories.bind(this));
|
|
76
77
|
this.api.on("unregisterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_PLATFORM_ACCESSORIES */, this.handleUnregisterPlatformAccessories.bind(this));
|
|
77
|
-
// Handle external accessories (cameras, etc.)
|
|
78
|
+
// Handle external accessories (cameras, etc.)
|
|
78
79
|
this.api.on("publishExternalAccessories" /* InternalAPIEvent.PUBLISH_EXTERNAL_ACCESSORIES */, this.handlePublishExternalAccessories.bind(this));
|
|
80
|
+
// Handle Matter accessory registration
|
|
81
|
+
this.api.on("registerMatterAccessory" /* InternalAPIEvent.REGISTER_MATTER_ACCESSORY */, this.handleRegisterMatterAccessory.bind(this));
|
|
82
|
+
this.api.on("unregisterMatterAccessory" /* InternalAPIEvent.UNREGISTER_MATTER_ACCESSORY */, this.handleUnregisterMatterAccessory.bind(this));
|
|
83
|
+
this.api.on("updateMatterAccessoryState" /* InternalAPIEvent.UPDATE_MATTER_ACCESSORY_STATE */, this.handleUpdateMatterAccessoryState.bind(this));
|
|
79
84
|
// watch bridge events to check when server is online
|
|
80
85
|
this.bridgeService.bridge.on("advertised" /* AccessoryEventTypes.ADVERTISED */, () => {
|
|
81
86
|
this.setServerStatus("ok" /* ServerStatus.OK */);
|
|
@@ -95,14 +100,6 @@ export class Server {
|
|
|
95
100
|
*/
|
|
96
101
|
setServerStatus(status) {
|
|
97
102
|
this.serverStatus = status;
|
|
98
|
-
// Collect child Matter bridge statuses
|
|
99
|
-
const childMatterBridgeStatuses = Array.from(this.childMatterBridges.values()).map(bridge => bridge.getMetadata());
|
|
100
|
-
// Get Matter bridges status (child bridges only)
|
|
101
|
-
const matterStatus = childMatterBridgeStatuses.length > 0
|
|
102
|
-
? {
|
|
103
|
-
children: childMatterBridgeStatuses,
|
|
104
|
-
}
|
|
105
|
-
: undefined;
|
|
106
103
|
this.ipcService.sendMessage("serverStatusUpdate" /* IpcOutgoingEvent.SERVER_STATUS_UPDATE */, {
|
|
107
104
|
type: 'hap', // Main bridge is HAP
|
|
108
105
|
status: this.serverStatus,
|
|
@@ -111,78 +108,29 @@ export class Server {
|
|
|
111
108
|
name: this.bridgeService?.bridge?.displayName || this.config.bridge.name,
|
|
112
109
|
username: this.config.bridge.username,
|
|
113
110
|
pin: this.config.bridge.pin,
|
|
114
|
-
matterStatus,
|
|
115
111
|
});
|
|
116
112
|
}
|
|
117
113
|
async start() {
|
|
118
114
|
if (this.config.bridge.disableIpc !== true) {
|
|
119
115
|
this.initializeIpcEventHandlers();
|
|
120
116
|
}
|
|
121
|
-
// Track existing Matter bridges before loading new configuration
|
|
122
|
-
const existingMatterBridgeIds = new Set(this.childMatterBridges.keys());
|
|
123
|
-
// Validate child Matter configurations
|
|
124
|
-
const matterValidation = MatterConfigValidator.validateAllChildMatterConfigs(this.config.platforms, this.config.accessories);
|
|
125
|
-
if (!matterValidation.isValid) {
|
|
126
|
-
log.error('Child Matter configuration validation failed. Please fix the errors above.');
|
|
127
|
-
// Continue anyway but child Matter bridges may not work properly
|
|
128
|
-
}
|
|
129
117
|
const promises = [];
|
|
130
118
|
// load the cached accessories
|
|
131
119
|
await this.bridgeService.loadCachedPlatformAccessoriesFromDisk();
|
|
132
120
|
// initialize plugins
|
|
133
121
|
await this.pluginManager.initializeInstalledPlugins();
|
|
122
|
+
// Initialize Matter server for main bridge if enabled
|
|
123
|
+
await this.initializeMatterServer();
|
|
134
124
|
if (this.config.platforms.length > 0) {
|
|
135
125
|
promises.push(...this.loadPlatforms());
|
|
136
126
|
}
|
|
137
127
|
if (this.config.accessories.length > 0) {
|
|
138
128
|
this.loadAccessories();
|
|
139
129
|
}
|
|
140
|
-
// Track which Matter bridges are still in the configuration
|
|
141
|
-
const currentMatterBridgeIds = new Set();
|
|
142
|
-
// Check platforms for Matter bridges
|
|
143
|
-
this.config.platforms.forEach((platformConfig) => {
|
|
144
|
-
if (platformConfig._matter && platformConfig.platform) {
|
|
145
|
-
try {
|
|
146
|
-
const plugin = this.pluginManager.getPluginForPlatform(platformConfig.platform);
|
|
147
|
-
const matterIdentifier = `${plugin.getPluginIdentifier()}-${platformConfig.platform}`;
|
|
148
|
-
currentMatterBridgeIds.add(matterIdentifier);
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// Plugin not found, skip
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
// Check accessories for Matter bridges
|
|
156
|
-
this.config.accessories.forEach((accessoryConfig) => {
|
|
157
|
-
if (accessoryConfig._matter && accessoryConfig.accessory) {
|
|
158
|
-
try {
|
|
159
|
-
const plugin = this.pluginManager.getPluginForAccessory(accessoryConfig.accessory);
|
|
160
|
-
const matterIdentifier = `${plugin.getPluginIdentifier()}-${accessoryConfig.accessory}`;
|
|
161
|
-
currentMatterBridgeIds.add(matterIdentifier);
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
// Plugin not found, skip
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
// Clean up Matter bridges that were removed from configuration
|
|
169
|
-
for (const bridgeId of existingMatterBridgeIds) {
|
|
170
|
-
if (!currentMatterBridgeIds.has(bridgeId) && this.childMatterBridges.has(bridgeId)) {
|
|
171
|
-
const removedBridge = this.childMatterBridges.get(bridgeId);
|
|
172
|
-
log.info(`Matter bridge "${bridgeId}" was removed from configuration, cleaning up...`);
|
|
173
|
-
// Stop and clean up the removed bridge (with permanent flag)
|
|
174
|
-
await removedBridge.teardown(true);
|
|
175
|
-
this.childMatterBridges.delete(bridgeId);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
130
|
// start child HAP bridges
|
|
179
131
|
for (const childBridge of this.childBridges.values()) {
|
|
180
132
|
childBridge.start();
|
|
181
133
|
}
|
|
182
|
-
// start child Matter bridges
|
|
183
|
-
for (const childMatterBridge of this.childMatterBridges.values()) {
|
|
184
|
-
promises.push(childMatterBridge.start());
|
|
185
|
-
}
|
|
186
134
|
// restore cached accessories
|
|
187
135
|
this.bridgeService.restoreCachedPlatformAccessories();
|
|
188
136
|
this.api.signalFinished();
|
|
@@ -191,157 +139,141 @@ export class Server {
|
|
|
191
139
|
.then(() => this.publishBridge());
|
|
192
140
|
}
|
|
193
141
|
/**
|
|
194
|
-
*
|
|
142
|
+
* Initialize Matter server for main bridge if enabled
|
|
195
143
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
144
|
+
async initializeMatterServer() {
|
|
145
|
+
// Check if main bridge has matter configuration
|
|
146
|
+
if (!this.config.bridge.matter) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
log.info('Initializing Matter server for main bridge...');
|
|
151
|
+
// Allocate port from pool if not explicitly configured
|
|
152
|
+
let matterPort = this.config.bridge.matter.port;
|
|
153
|
+
if (!matterPort) {
|
|
154
|
+
matterPort = await this.externalPortService.requestPort(`${this.config.bridge.username}:MATTER`);
|
|
155
|
+
if (!matterPort) {
|
|
156
|
+
matterPort = 5540; // Default Matter port
|
|
157
|
+
log.warn('No port available from pool for main Matter bridge, using default port 5540');
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
log.info(`Allocated port ${matterPort} from pool for main Matter bridge`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Create Matter server instance
|
|
164
|
+
this.matterServer = new MatterServer({
|
|
165
|
+
storagePath: User.matterPath(),
|
|
166
|
+
port: matterPort,
|
|
167
|
+
uniqueId: 'main-bridge',
|
|
168
|
+
name: this.config.bridge.matter.name || (this.config.bridge.name ? `${this.config.bridge.name} (Matter)` : 'Homebridge Matter Bridge'),
|
|
169
|
+
});
|
|
170
|
+
// Start the Matter server
|
|
171
|
+
await this.matterServer.start();
|
|
172
|
+
log.info('Matter server initialized for main bridge');
|
|
173
|
+
// Send Matter status update to notify UI that Matter server is ready
|
|
174
|
+
if (this.config.bridge.disableIpc !== true) {
|
|
175
|
+
this.sendMainBridgeMatterStatusUpdate();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
log.error('Failed to initialize Matter server for main bridge:', error);
|
|
180
|
+
// Provide user-friendly guidance for common errors
|
|
181
|
+
if (error.message && error.message.includes('corrupted')) {
|
|
182
|
+
log.error('');
|
|
183
|
+
log.error('╔════════════════════════════════════════════════════════════════════════════╗');
|
|
184
|
+
log.error('║ MATTER STORAGE CORRUPTED ║');
|
|
185
|
+
log.error('╠════════════════════════════════════════════════════════════════════════════╣');
|
|
186
|
+
log.error('║ Your Matter storage has become corrupted. This can happen when: ║');
|
|
187
|
+
log.error('║ • Matter.js library version changes ║');
|
|
188
|
+
log.error('║ • Storage format upgrades occur ║');
|
|
189
|
+
log.error('║ • Incomplete writes during shutdown ║');
|
|
190
|
+
log.error('║ ║');
|
|
191
|
+
log.error('║ To fix this, delete the corrupted storage directory: ║');
|
|
192
|
+
log.error('║ rm -rf ~/.homebridge/matter/main-bridge ║');
|
|
193
|
+
log.error('║ ║');
|
|
194
|
+
log.error('║ Note: You will need to re-pair your Matter devices after deletion. ║');
|
|
195
|
+
log.error('╚════════════════════════════════════════════════════════════════════════════╝');
|
|
196
|
+
log.error('');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Send Matter status update for main bridge
|
|
202
|
+
*/
|
|
203
|
+
sendMainBridgeMatterStatusUpdate() {
|
|
204
|
+
if (!this.matterServer || !this.matterServer.isServerRunning()) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const commissioningInfo = this.matterServer.getCommissioningInfo();
|
|
208
|
+
// Transform property names to match UI expectations
|
|
209
|
+
const statusUpdate = {
|
|
210
|
+
type: 'matter',
|
|
211
|
+
status: 'ok',
|
|
212
|
+
port: this.config.bridge.matter?.port || 5540,
|
|
213
|
+
setupUri: commissioningInfo.qrCode, // Map qrCode -> setupUri for UI
|
|
214
|
+
pin: commissioningInfo.manualPairingCode, // Map manualPairingCode -> pin for UI
|
|
215
|
+
serialNumber: commissioningInfo.serialNumber,
|
|
216
|
+
passcode: commissioningInfo.passcode,
|
|
217
|
+
discriminator: commissioningInfo.discriminator,
|
|
218
|
+
name: this.config.bridge.matter?.name || (this.config.bridge.name ? `${this.config.bridge.name} (Matter)` : 'Homebridge Matter Bridge'),
|
|
219
|
+
plugin: 'main-bridge',
|
|
220
|
+
identifier: 'main-bridge',
|
|
221
|
+
deviceCount: this.matterServer.getAccessories().length,
|
|
222
|
+
commissioned: commissioningInfo.commissioned,
|
|
223
|
+
};
|
|
224
|
+
this.ipcService.sendMessage("matterBridgeStatusUpdate" /* IpcOutgoingEvent.MATTER_BRIDGE_STATUS_UPDATE */, statusUpdate);
|
|
225
|
+
}
|
|
226
|
+
async teardown() {
|
|
200
227
|
this.bridgeService.teardown();
|
|
201
|
-
// Stop
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
228
|
+
// Stop main Matter server if running
|
|
229
|
+
if (this.matterServer) {
|
|
230
|
+
try {
|
|
231
|
+
await this.matterServer.stop();
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
log.error('Failed to stop Matter server:', error);
|
|
235
|
+
}
|
|
206
236
|
}
|
|
237
|
+
// Child bridge Matter servers are stopped by their own forked processes
|
|
207
238
|
this.setServerStatus("down" /* ServerStatus.DOWN */);
|
|
208
239
|
}
|
|
209
240
|
publishBridge() {
|
|
210
241
|
this.bridgeService.publishBridge();
|
|
211
242
|
this.printSetupInfo(this.config.bridge.pin);
|
|
212
243
|
}
|
|
213
|
-
// Matter accessories are managed via _matter configuration, not API methods
|
|
214
244
|
handlePublishExternalAccessories(accessories) {
|
|
215
245
|
log.info(`Publishing ${accessories.length} external accessories`);
|
|
216
|
-
// External accessories are
|
|
217
|
-
//
|
|
218
|
-
const accessoriesByPlugin = new Map();
|
|
219
|
-
accessories.forEach((accessory) => {
|
|
220
|
-
const pluginIdentifier = accessory._associatedPlugin;
|
|
221
|
-
if (!pluginIdentifier) {
|
|
222
|
-
log.warn(`External accessory "${accessory.displayName}" has no plugin identifier`);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (!accessoriesByPlugin.has(pluginIdentifier)) {
|
|
226
|
-
accessoriesByPlugin.set(pluginIdentifier, []);
|
|
227
|
-
}
|
|
228
|
-
accessoriesByPlugin.get(pluginIdentifier).push(accessory);
|
|
229
|
-
});
|
|
230
|
-
// Process each plugin's external accessories
|
|
231
|
-
accessoriesByPlugin.forEach((pluginAccessories, pluginIdentifier) => {
|
|
232
|
-
// Check if there's already an external Matter bridge for this plugin
|
|
233
|
-
const externalMatterBridgeId = `${pluginIdentifier}-external`;
|
|
234
|
-
let externalMatterBridge = this.childMatterBridges.get(externalMatterBridgeId);
|
|
235
|
-
if (!externalMatterBridge) {
|
|
236
|
-
// Check if this plugin has any Matter-enabled platform config
|
|
237
|
-
let hasMatterConfig = false;
|
|
238
|
-
let baseMatterConfig = {};
|
|
239
|
-
// Look for any platform with Matter config from this plugin
|
|
240
|
-
for (const [bridgeId, childBridge] of this.childMatterBridges) {
|
|
241
|
-
if (bridgeId.startsWith(`${pluginIdentifier}-`) && childBridge.type === "platform" /* PluginType.PLATFORM */) {
|
|
242
|
-
hasMatterConfig = true;
|
|
243
|
-
// Use existing Matter bridge configuration as template
|
|
244
|
-
baseMatterConfig = {
|
|
245
|
-
// Port will be auto-allocated
|
|
246
|
-
};
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// If plugin has Matter-enabled platforms, create external bridge automatically
|
|
251
|
-
if (hasMatterConfig) {
|
|
252
|
-
log.info(`Automatically creating external Matter bridge for ${pluginIdentifier}`);
|
|
253
|
-
const plugin = this.pluginManager.getPlugin(pluginIdentifier);
|
|
254
|
-
if (!plugin) {
|
|
255
|
-
log.warn(`Could not find plugin ${pluginIdentifier} to create external Matter bridge`);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
// Create synthetic config for external bridge
|
|
259
|
-
const externalConfig = {
|
|
260
|
-
platform: '__external__',
|
|
261
|
-
name: `${pluginIdentifier} External`,
|
|
262
|
-
_matter: {
|
|
263
|
-
...baseMatterConfig,
|
|
264
|
-
name: `${pluginIdentifier} External Matter`,
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
externalMatterBridge = new ChildMatterBridgeService("platform" /* PluginType.PLATFORM */, plugin, externalConfig._matter, externalConfig, this.api, this.externalPortService, this.options, this.ipcService, this.config);
|
|
268
|
-
this.childMatterBridges.set(externalMatterBridgeId, externalMatterBridge);
|
|
269
|
-
// Start the external bridge
|
|
270
|
-
externalMatterBridge.start().catch((error) => {
|
|
271
|
-
log.error(`Failed to start external Matter bridge for ${pluginIdentifier}:`, error);
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Publish accessories to appropriate bridge
|
|
276
|
-
if (externalMatterBridge) {
|
|
277
|
-
pluginAccessories.forEach((accessory) => {
|
|
278
|
-
externalMatterBridge.addAccessory(accessory);
|
|
279
|
-
log.info(`External accessory "${accessory.displayName}" added to automatic external Matter bridge`);
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
// Check if any child Matter bridge from this plugin can handle it
|
|
284
|
-
let publishedToChildBridge = false;
|
|
285
|
-
for (const [bridgeId, childBridge] of this.childMatterBridges) {
|
|
286
|
-
if (bridgeId.startsWith(`${pluginIdentifier}-`)) {
|
|
287
|
-
pluginAccessories.forEach((accessory) => {
|
|
288
|
-
childBridge.addAccessory(accessory);
|
|
289
|
-
log.info(`External accessory "${accessory.displayName}" added to plugin's Matter bridge`);
|
|
290
|
-
});
|
|
291
|
-
publishedToChildBridge = true;
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
// External accessories are only published to child Matter bridges, not main bridge
|
|
296
|
-
if (!publishedToChildBridge) {
|
|
297
|
-
pluginAccessories.forEach((accessory) => {
|
|
298
|
-
log.debug(`External accessory "${accessory.displayName}" not published to Matter - configure _matter property for Matter support`);
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
});
|
|
246
|
+
// External accessories are published via HAP
|
|
247
|
+
// Plugins should use api.matter to register Matter accessories explicitly
|
|
303
248
|
}
|
|
304
249
|
handleRegisterPlatformAccessories(accessories) {
|
|
305
|
-
// Route to HAP bridge
|
|
250
|
+
// Route to HAP bridge
|
|
306
251
|
this.bridgeService.handleRegisterPlatformAccessories(accessories);
|
|
307
|
-
// Check if we need to also route to Matter bridges
|
|
308
|
-
for (const accessory of accessories) {
|
|
309
|
-
const platformKey = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
|
|
310
|
-
const platformConfig = this.platformConfigs.get(platformKey);
|
|
311
|
-
if (platformConfig) {
|
|
312
|
-
// Check if platform has child Matter bridge
|
|
313
|
-
if (platformConfig._matter && typeof platformConfig._matter === 'object') {
|
|
314
|
-
const matterIdentifier = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
|
|
315
|
-
const childMatterBridge = this.childMatterBridges.get(matterIdentifier);
|
|
316
|
-
if (childMatterBridge) {
|
|
317
|
-
childMatterBridge.addAccessory(accessory).catch((error) => {
|
|
318
|
-
log.error(`Failed to add accessory "${accessory.displayName}" to child Matter bridge:`, error);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
252
|
}
|
|
325
253
|
handleUnregisterPlatformAccessories(accessories) {
|
|
326
|
-
// Route to HAP bridge
|
|
254
|
+
// Route to HAP bridge
|
|
327
255
|
this.bridgeService.handleUnregisterPlatformAccessories(accessories);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
256
|
+
}
|
|
257
|
+
handleRegisterMatterAccessory(accessory) {
|
|
258
|
+
if (!this.matterServer) {
|
|
259
|
+
log.warn('Cannot register Matter accessory - Matter server is not running');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this.matterServer.registerAccessory(accessory);
|
|
263
|
+
}
|
|
264
|
+
handleUnregisterMatterAccessory(uuid) {
|
|
265
|
+
if (!this.matterServer) {
|
|
266
|
+
log.warn('Cannot unregister Matter accessory - Matter server is not running');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.matterServer.unregisterAccessory(uuid);
|
|
270
|
+
}
|
|
271
|
+
handleUpdateMatterAccessoryState(uuid, cluster, attributes) {
|
|
272
|
+
if (!this.matterServer) {
|
|
273
|
+
log.warn('Cannot update Matter accessory state - Matter server is not running');
|
|
274
|
+
return;
|
|
344
275
|
}
|
|
276
|
+
this.matterServer.updateAccessoryState(uuid, cluster, attributes);
|
|
345
277
|
}
|
|
346
278
|
static loadConfig() {
|
|
347
279
|
// Look for the configuration file
|
|
@@ -401,6 +333,48 @@ export class Server {
|
|
|
401
333
|
config.platforms = [];
|
|
402
334
|
}
|
|
403
335
|
log.info('Loaded config.json with %s accessories and %s platforms.', config.accessories.length, config.platforms.length);
|
|
336
|
+
// Validate Matter configuration for port conflicts
|
|
337
|
+
if (config.bridge.matter || config.platforms.some((p) => p._bridge?.matter) || config.accessories.some((a) => a._bridge?.matter)) {
|
|
338
|
+
// Validate main bridge Matter config
|
|
339
|
+
if (config.bridge.matter) {
|
|
340
|
+
// Apply default name before validation if not set
|
|
341
|
+
if (!config.bridge.matter.name) {
|
|
342
|
+
config.bridge.matter.name = config.bridge.name ? `${config.bridge.name} (Matter)` : 'Homebridge Matter Bridge';
|
|
343
|
+
}
|
|
344
|
+
const validation = MatterConfigValidator.validate(config.bridge.matter);
|
|
345
|
+
if (!validation.isValid) {
|
|
346
|
+
log.error('Main bridge Matter configuration is invalid. Matter will not be enabled for the main bridge.');
|
|
347
|
+
delete config.bridge.matter;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Validate all child bridge Matter configs and check for port conflicts
|
|
351
|
+
const childMatterValidation = MatterConfigValidator.validateAllChildMatterConfigs(config.platforms, config.accessories);
|
|
352
|
+
if (!childMatterValidation.isValid) {
|
|
353
|
+
log.error('Some child bridge Matter configurations are invalid. Check the errors above.');
|
|
354
|
+
}
|
|
355
|
+
// Additionally, check for conflicts between main bridge Matter port and child bridge ports
|
|
356
|
+
if (config.bridge.matter?.port) {
|
|
357
|
+
const mainMatterPort = config.bridge.matter.port;
|
|
358
|
+
const childMatterPorts = [];
|
|
359
|
+
for (const platform of config.platforms) {
|
|
360
|
+
if (platform._bridge?.matter?.port) {
|
|
361
|
+
childMatterPorts.push(platform._bridge.matter.port);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const accessory of config.accessories) {
|
|
365
|
+
if (accessory._bridge?.matter?.port) {
|
|
366
|
+
childMatterPorts.push(accessory._bridge.matter.port);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (childMatterPorts.includes(mainMatterPort)) {
|
|
370
|
+
log.error(`Main bridge Matter port ${mainMatterPort} conflicts with a child bridge Matter port. Please use unique ports.`);
|
|
371
|
+
}
|
|
372
|
+
// Check for conflict with main bridge HAP port
|
|
373
|
+
if (config.bridge.port && Math.abs(config.bridge.port - mainMatterPort) < 10) {
|
|
374
|
+
log.warn(`Main bridge HAP port ${config.bridge.port} and Matter port ${mainMatterPort} are very close. Consider spacing them further apart.`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
404
378
|
if (config.bridge.advertiser) {
|
|
405
379
|
if (![
|
|
406
380
|
"bonjour-hap" /* MDNSAdvertiser.BONJOUR */,
|
|
@@ -455,6 +429,7 @@ export class Server {
|
|
|
455
429
|
}
|
|
456
430
|
const logger = Logger.withPrefix(displayName);
|
|
457
431
|
logger('Initializing %s accessory...', accessoryIdentifier);
|
|
432
|
+
// Handle child HAP bridge
|
|
458
433
|
if (accessoryConfig._bridge) {
|
|
459
434
|
// ensure the username is always uppercase
|
|
460
435
|
accessoryConfig._bridge.username = accessoryConfig._bridge.username.toUpperCase();
|
|
@@ -477,41 +452,9 @@ export class Server {
|
|
|
477
452
|
}
|
|
478
453
|
// add config to child bridge service
|
|
479
454
|
childBridge.addConfig(accessoryConfig);
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
// Handle child Matter bridge for accessories
|
|
486
|
-
if (accessoryConfig._matter && typeof accessoryConfig._matter === 'object') {
|
|
487
|
-
const matterIdentifier = `${plugin.getPluginIdentifier()}-${accessoryIdentifier}`;
|
|
488
|
-
logger(`Initializing child Matter bridge for accessory ${accessoryIdentifier}`);
|
|
489
|
-
// Ensure Matter config has proper name
|
|
490
|
-
// Try to get a human-friendly name from the accessory
|
|
491
|
-
// Convert CamelCase accessory identifier to spaces
|
|
492
|
-
const formattedAccessoryId = accessoryIdentifier.replace(/([A-Z])/g, ' $1').trim();
|
|
493
|
-
// Get plugin name without 'homebridge-' prefix and replace dashes with spaces
|
|
494
|
-
const pluginDisplayName = plugin.getPluginIdentifier()
|
|
495
|
-
.replace('homebridge-', '')
|
|
496
|
-
.replace(/-/g, ' ')
|
|
497
|
-
.split(' ')
|
|
498
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
499
|
-
.join(' ');
|
|
500
|
-
const defaultName = accessoryConfig.name || formattedAccessoryId || pluginDisplayName;
|
|
501
|
-
const matterConfig = {
|
|
502
|
-
...accessoryConfig._matter,
|
|
503
|
-
name: accessoryConfig._matter.name || defaultName,
|
|
504
|
-
};
|
|
505
|
-
const childMatterBridge = new ChildMatterBridgeService("accessory" /* PluginType.ACCESSORY */, plugin, matterConfig, accessoryConfig, this.api, this.externalPortService, this.options, this.ipcService);
|
|
506
|
-
this.childMatterBridges.set(matterIdentifier, childMatterBridge);
|
|
507
|
-
// If _bridge is also defined, we're done - accessory uses both protocols
|
|
508
|
-
if (accessoryConfig._bridge) {
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
// If both _bridge and _matter are defined, the accessory is handled by child bridges
|
|
513
|
-
if (accessoryConfig._bridge || accessoryConfig._matter) {
|
|
514
|
-
return;
|
|
455
|
+
// Matter for child bridges is handled inside the forked child process (childBridgeFork.ts)
|
|
456
|
+
// to maintain isolation - each child process manages its own Matter server
|
|
457
|
+
return; // Done - child bridge
|
|
515
458
|
}
|
|
516
459
|
const accessoryInstance = new constructor(logger, accessoryConfig, this.api);
|
|
517
460
|
// pass accessoryIdentifier for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
|
|
@@ -568,9 +511,6 @@ export class Server {
|
|
|
568
511
|
}
|
|
569
512
|
const logger = Logger.withPrefix(displayName);
|
|
570
513
|
logger('Initializing %s platform...', platformIdentifier);
|
|
571
|
-
// Store platform config for later routing
|
|
572
|
-
const platformKey = `${plugin.getPluginIdentifier()}-${platformIdentifier}`;
|
|
573
|
-
this.platformConfigs.set(platformKey, platformConfig);
|
|
574
514
|
// Handle child HAP bridge
|
|
575
515
|
if (platformConfig._bridge) {
|
|
576
516
|
// ensure the username is always uppercase
|
|
@@ -587,40 +527,8 @@ export class Server {
|
|
|
587
527
|
this.childBridges.set(platformConfig._bridge.username, childBridge);
|
|
588
528
|
// add config to child bridge service
|
|
589
529
|
childBridge.addConfig(platformConfig);
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
// Handle child Matter bridge
|
|
596
|
-
if (platformConfig._matter && typeof platformConfig._matter === 'object') {
|
|
597
|
-
const matterIdentifier = `${plugin.getPluginIdentifier()}-${platformIdentifier}`;
|
|
598
|
-
logger(`Initializing child Matter bridge for ${platformIdentifier}`);
|
|
599
|
-
// Ensure Matter config has proper name
|
|
600
|
-
// Try to get a human-friendly name from the platform
|
|
601
|
-
// Convert CamelCase platform identifier to spaces (e.g., "Ring" stays "Ring", "MySmartHome" becomes "My Smart Home")
|
|
602
|
-
const formattedPlatformId = platformIdentifier.replace(/([A-Z])/g, ' $1').trim();
|
|
603
|
-
// Get plugin name without 'homebridge-' prefix and replace dashes with spaces
|
|
604
|
-
const pluginDisplayName = plugin.getPluginIdentifier()
|
|
605
|
-
.replace('homebridge-', '')
|
|
606
|
-
.replace(/-/g, ' ')
|
|
607
|
-
.split(' ')
|
|
608
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
609
|
-
.join(' ');
|
|
610
|
-
const defaultName = platformConfig.name || formattedPlatformId || pluginDisplayName;
|
|
611
|
-
const matterConfig = {
|
|
612
|
-
...platformConfig._matter,
|
|
613
|
-
name: platformConfig._matter.name || defaultName,
|
|
614
|
-
};
|
|
615
|
-
const childMatterBridge = new ChildMatterBridgeService("platform" /* PluginType.PLATFORM */, plugin, matterConfig, platformConfig, this.api, this.externalPortService, this.options, this.ipcService);
|
|
616
|
-
this.childMatterBridges.set(matterIdentifier, childMatterBridge);
|
|
617
|
-
// If _bridge is also defined, we're done - platform uses both protocols
|
|
618
|
-
if (platformConfig._bridge) {
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
// If both _bridge and _matter are defined, the platform is handled by child bridges
|
|
623
|
-
if (platformConfig._bridge || platformConfig._matter) {
|
|
530
|
+
// Matter for child bridges is handled inside the forked child process (childBridgeFork.ts)
|
|
531
|
+
// to maintain isolation - each child process manages its own Matter server
|
|
624
532
|
return;
|
|
625
533
|
}
|
|
626
534
|
const platform = new constructor(logger, platformConfig, this.api);
|
|
@@ -674,7 +582,9 @@ export class Server {
|
|
|
674
582
|
// noinspection SuspiciousTypeOfGuard
|
|
675
583
|
if (typeof username === 'string') {
|
|
676
584
|
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
677
|
-
childBridge
|
|
585
|
+
if (childBridge) {
|
|
586
|
+
childBridge.restartChildBridge();
|
|
587
|
+
}
|
|
678
588
|
}
|
|
679
589
|
});
|
|
680
590
|
// handle stop child bridge event
|
|
@@ -682,7 +592,9 @@ export class Server {
|
|
|
682
592
|
// noinspection SuspiciousTypeOfGuard
|
|
683
593
|
if (typeof username === 'string') {
|
|
684
594
|
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
685
|
-
childBridge
|
|
595
|
+
if (childBridge) {
|
|
596
|
+
childBridge.stopChildBridge();
|
|
597
|
+
}
|
|
686
598
|
}
|
|
687
599
|
});
|
|
688
600
|
// handle start child bridge event
|
|
@@ -690,110 +602,176 @@ export class Server {
|
|
|
690
602
|
// noinspection SuspiciousTypeOfGuard
|
|
691
603
|
if (typeof username === 'string') {
|
|
692
604
|
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
693
|
-
childBridge
|
|
605
|
+
if (childBridge) {
|
|
606
|
+
childBridge.startChildBridge();
|
|
607
|
+
}
|
|
694
608
|
}
|
|
695
609
|
});
|
|
696
610
|
this.ipcService.on("childBridgeMetadataRequest" /* IpcIncomingEvent.CHILD_BRIDGE_METADATA_REQUEST */, () => {
|
|
697
|
-
|
|
611
|
+
const childBridgeMetadata = Array.from(this.childBridges.values()).map(x => x.getMetadata());
|
|
612
|
+
this.ipcService.sendMessage("childBridgeMetadataResponse" /* IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE */, childBridgeMetadata);
|
|
698
613
|
});
|
|
699
614
|
// Matter bridge IPC handlers
|
|
615
|
+
// Main bridge Matter server runs in this process alongside HAP
|
|
616
|
+
// Child bridge Matter servers run in forked child processes (see childBridgeFork.ts)
|
|
700
617
|
this.ipcService.on("restartMatterBridge" /* IpcIncomingEvent.RESTART_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
618
|
+
if (matterBridgeId === 'main-bridge') {
|
|
619
|
+
// Main Matter bridge runs in the main process, so restart the entire Homebridge instance
|
|
620
|
+
log.info('Restarting Homebridge (Matter bridge restart requested)...');
|
|
621
|
+
process.kill(process.pid, 'SIGTERM');
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
// Handle child bridge using cache for efficient lookup
|
|
625
|
+
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
626
|
+
if (childBridge) {
|
|
627
|
+
childBridge.restartChildBridge();
|
|
707
628
|
}
|
|
708
629
|
else {
|
|
709
|
-
log.warn(`
|
|
710
|
-
log.debug('Available Matter bridges:', Array.from(this.childMatterBridges.keys()));
|
|
630
|
+
log.warn(`Child bridge ${matterBridgeId} not found for Matter restart`);
|
|
711
631
|
}
|
|
712
632
|
}
|
|
713
633
|
});
|
|
714
634
|
this.ipcService.on("stopMatterBridge" /* IpcIncomingEvent.STOP_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
635
|
+
if (matterBridgeId === 'main-bridge') {
|
|
636
|
+
log.warn('Stop requested for main Matter bridge - this is not supported');
|
|
637
|
+
log.info('The main Matter bridge runs alongside the HAP bridge and cannot be stopped independently');
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
// Handle child bridge using cache for efficient lookup
|
|
641
|
+
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
642
|
+
if (childBridge) {
|
|
643
|
+
childBridge.stopChildBridge();
|
|
721
644
|
}
|
|
722
645
|
else {
|
|
723
|
-
log.warn(`
|
|
646
|
+
log.warn(`Child bridge ${matterBridgeId} not found for Matter stop`);
|
|
724
647
|
}
|
|
725
648
|
}
|
|
726
649
|
});
|
|
727
650
|
this.ipcService.on("startMatterBridge" /* IpcIncomingEvent.START_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
651
|
+
if (matterBridgeId === 'main-bridge') {
|
|
652
|
+
log.warn('Start requested for main Matter bridge - this is not supported');
|
|
653
|
+
log.info('The main Matter bridge starts automatically with Homebridge');
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
// Handle child bridge using cache for efficient lookup
|
|
657
|
+
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
658
|
+
if (childBridge) {
|
|
659
|
+
childBridge.startChildBridge();
|
|
734
660
|
}
|
|
735
661
|
else {
|
|
736
|
-
log.warn(`
|
|
662
|
+
log.warn(`Child bridge ${matterBridgeId} not found for Matter start`);
|
|
737
663
|
}
|
|
738
664
|
}
|
|
739
665
|
});
|
|
740
666
|
this.ipcService.on("matterBridgeMetadataRequest" /* IpcIncomingEvent.MATTER_BRIDGE_METADATA_REQUEST */, () => {
|
|
741
|
-
|
|
742
|
-
|
|
667
|
+
const matterBridges = [];
|
|
668
|
+
// Add main bridge Matter metadata if enabled
|
|
669
|
+
if (this.matterServer && this.matterServer.isServerRunning()) {
|
|
670
|
+
const commissioningInfo = this.matterServer.getCommissioningInfo();
|
|
671
|
+
matterBridges.push({
|
|
672
|
+
type: 'matter',
|
|
673
|
+
status: 'ok',
|
|
674
|
+
port: this.config.bridge.matter?.port || 5540,
|
|
675
|
+
setupUri: commissioningInfo.qrCode,
|
|
676
|
+
pin: commissioningInfo.manualPairingCode,
|
|
677
|
+
serialNumber: commissioningInfo.serialNumber,
|
|
678
|
+
name: this.config.bridge.name,
|
|
679
|
+
plugin: 'main-bridge',
|
|
680
|
+
identifier: 'main-bridge',
|
|
681
|
+
deviceCount: this.matterServer.getAccessories().length,
|
|
682
|
+
commissioned: commissioningInfo.commissioned,
|
|
683
|
+
matterEnabled: true,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
// Add child bridge Matter metadata
|
|
687
|
+
for (const childBridge of this.childBridges.values()) {
|
|
688
|
+
const matterInfo = childBridge.getCommissioningInfo();
|
|
689
|
+
const matterConfig = childBridge.getMatterConfig();
|
|
690
|
+
if (matterInfo && matterConfig) {
|
|
691
|
+
const metadata = childBridge.getMetadata();
|
|
692
|
+
// Use plugin-identifier format to match storage
|
|
693
|
+
const childIdentifier = `${metadata.plugin}-${childBridge.identifier}`;
|
|
694
|
+
// Populate cache for efficient lookups
|
|
695
|
+
this.matterBridgeCache.set(childIdentifier, childBridge);
|
|
696
|
+
matterBridges.push({
|
|
697
|
+
type: 'matter',
|
|
698
|
+
status: metadata.status === 'ok' ? 'ok' : 'pending',
|
|
699
|
+
port: matterConfig.port,
|
|
700
|
+
setupUri: matterInfo.qrCode,
|
|
701
|
+
pin: matterInfo.manualPairingCode,
|
|
702
|
+
serialNumber: matterInfo.serialNumber,
|
|
703
|
+
name: matterConfig.name || metadata.name,
|
|
704
|
+
plugin: metadata.plugin,
|
|
705
|
+
identifier: childIdentifier,
|
|
706
|
+
commissioned: matterInfo.commissioned,
|
|
707
|
+
username: metadata.username,
|
|
708
|
+
matterEnabled: true,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
this.ipcService.sendMessage("matterBridgeMetadataResponse" /* IpcOutgoingEvent.MATTER_BRIDGE_METADATA_RESPONSE */, matterBridges);
|
|
743
713
|
});
|
|
744
714
|
this.ipcService.on("matterAccessoriesRequest" /* IpcIncomingEvent.MATTER_ACCESSORIES_REQUEST */, () => {
|
|
745
|
-
//
|
|
715
|
+
// Return Matter accessories from all bridges
|
|
746
716
|
const allMatterAccessories = {
|
|
747
717
|
children: {},
|
|
748
718
|
};
|
|
749
|
-
// Add
|
|
750
|
-
|
|
751
|
-
allMatterAccessories.children[
|
|
719
|
+
// Add main bridge accessories
|
|
720
|
+
if (this.matterServer) {
|
|
721
|
+
allMatterAccessories.children['main-bridge'] = this.matterServer.getAccessories();
|
|
752
722
|
}
|
|
723
|
+
// Child bridge accessories are managed by child processes
|
|
753
724
|
this.ipcService.sendMessage("matterAccessoriesResponse" /* IpcOutgoingEvent.MATTER_ACCESSORIES_RESPONSE */, allMatterAccessories);
|
|
754
725
|
});
|
|
755
|
-
// Handle new Matter IPC events
|
|
756
|
-
this.ipcService.on("toggleMatterDevice" /* IpcIncomingEvent.TOGGLE_MATTER_DEVICE */, (data) => {
|
|
757
|
-
if (data && data.uuid) {
|
|
758
|
-
// This would toggle Matter for a specific device
|
|
759
|
-
// Implementation depends on how devices are managed
|
|
760
|
-
log.debug(`Toggle Matter for device ${data.uuid}: ${data.enabled}`);
|
|
761
|
-
// Send status update
|
|
762
|
-
this.ipcService.sendMessage("matterDeviceStatusUpdate" /* IpcOutgoingEvent.MATTER_DEVICE_STATUS_UPDATE */, {
|
|
763
|
-
uuid: data.uuid,
|
|
764
|
-
matterEnabled: data.enabled,
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
726
|
this.ipcService.on("matterCommissioningInfoRequest" /* IpcIncomingEvent.MATTER_COMMISSIONING_INFO_REQUEST */, (matterBridgeId) => {
|
|
769
|
-
let
|
|
770
|
-
//
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
727
|
+
let rawInfo = { commissioned: false };
|
|
728
|
+
// Handle main bridge
|
|
729
|
+
if (matterBridgeId === 'main-bridge') {
|
|
730
|
+
if (this.matterServer && this.matterServer.isServerRunning()) {
|
|
731
|
+
rawInfo = this.matterServer.getCommissioningInfo();
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
log.debug(`[Matter] Server not available when commissioning info requested for: ${matterBridgeId}`);
|
|
735
|
+
}
|
|
774
736
|
}
|
|
775
|
-
|
|
737
|
+
else {
|
|
738
|
+
// Handle child bridge using plugin-identifier format (e.g., homebridge-virtual-accessories-VirtualAccessoriesForHomebridge)
|
|
739
|
+
// Use cache for efficient O(1) lookup
|
|
740
|
+
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
741
|
+
if (childBridge) {
|
|
742
|
+
rawInfo = childBridge.getCommissioningInfo() || { commissioned: false };
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
log.debug(`[Matter] Child bridge ${matterBridgeId} not found when commissioning info requested`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Transform property names to match MatterCommissioningInfo interface
|
|
749
|
+
const commissioningInfo = {
|
|
750
|
+
commissioned: rawInfo.commissioned || false,
|
|
751
|
+
pin: rawInfo.manualPairingCode, // Map manualPairingCode -> pin
|
|
752
|
+
setupUri: rawInfo.qrCode, // Map qrCode -> setupUri
|
|
753
|
+
serialNumber: rawInfo.serialNumber,
|
|
754
|
+
};
|
|
755
|
+
this.ipcService.sendMessage("matterCommissioningInfoResponse" /* IpcOutgoingEvent.MATTER_COMMISSIONING_INFO_RESPONSE */, commissioningInfo);
|
|
776
756
|
});
|
|
777
757
|
}
|
|
778
758
|
printSetupInfo(pin) {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
console.log(this.bridgeService.bridge.setupURI());
|
|
759
|
+
log.info('Setup Payload:');
|
|
760
|
+
log.info(this.bridgeService.bridge.setupURI());
|
|
782
761
|
if (!this.options.hideQRCode) {
|
|
783
|
-
|
|
762
|
+
log.info('Scan this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
784
763
|
qrcode.setErrorLevel('M'); // HAP specifies level M or higher for ECC
|
|
785
764
|
qrcode.generate(this.bridgeService.bridge.setupURI());
|
|
786
|
-
|
|
765
|
+
log.info('Or enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
787
766
|
}
|
|
788
767
|
else {
|
|
789
|
-
|
|
768
|
+
log.info('Enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
790
769
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
/* eslint-enable no-console */
|
|
770
|
+
log.info(chalk.black.bgWhite(' '));
|
|
771
|
+
log.info(chalk.black.bgWhite(' ┌────────────┐ '));
|
|
772
|
+
log.info(chalk.black.bgWhite(` │ ${pin} │ `));
|
|
773
|
+
log.info(chalk.black.bgWhite(' └────────────┘ '));
|
|
774
|
+
log.info(chalk.black.bgWhite(' '));
|
|
797
775
|
}
|
|
798
776
|
}
|
|
799
777
|
//# sourceMappingURL=server.js.map
|