homebridge 2.0.0-alpha.43 → 2.0.0-alpha.45
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 +37 -7
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +30 -4
- package/dist/api.js.map +1 -1
- package/dist/bridgeService.d.ts +5 -16
- package/dist/bridgeService.d.ts.map +1 -1
- package/dist/bridgeService.js +8 -24
- package/dist/bridgeService.js.map +1 -1
- package/dist/childBridgeFork.d.ts +3 -15
- package/dist/childBridgeFork.d.ts.map +1 -1
- package/dist/childBridgeFork.js +46 -181
- package/dist/childBridgeFork.js.map +1 -1
- package/dist/childBridgeService.d.ts +20 -43
- package/dist/childBridgeService.d.ts.map +1 -1
- package/dist/childBridgeService.js +23 -66
- package/dist/childBridgeService.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ipcService.d.ts +23 -21
- package/dist/ipcService.d.ts.map +1 -1
- package/dist/ipcService.js +0 -15
- package/dist/ipcService.js.map +1 -1
- package/dist/matter/index.d.ts +1 -1
- package/dist/matter/index.d.ts.map +1 -1
- package/dist/matter/index.js.map +1 -1
- package/dist/matter/matterAccessoryCache.d.ts +73 -0
- package/dist/matter/matterAccessoryCache.d.ts.map +1 -0
- package/dist/matter/matterAccessoryCache.js +168 -0
- package/dist/matter/matterAccessoryCache.js.map +1 -0
- package/dist/matter/matterBehaviors.d.ts +123 -0
- package/dist/matter/matterBehaviors.d.ts.map +1 -0
- package/dist/matter/matterBehaviors.js +582 -0
- package/dist/matter/matterBehaviors.js.map +1 -0
- package/dist/matter/matterConfigValidator.d.ts +0 -1
- package/dist/matter/matterConfigValidator.d.ts.map +1 -1
- package/dist/matter/matterConfigValidator.js +15 -45
- package/dist/matter/matterConfigValidator.js.map +1 -1
- package/dist/matter/matterErrorHandler.d.ts +1 -1
- package/dist/matter/matterErrorHandler.d.ts.map +1 -1
- package/dist/matter/matterErrorHandler.js +35 -22
- package/dist/matter/matterErrorHandler.js.map +1 -1
- package/dist/matter/matterNetworkMonitor.d.ts +3 -0
- package/dist/matter/matterNetworkMonitor.d.ts.map +1 -1
- package/dist/matter/matterNetworkMonitor.js +49 -26
- package/dist/matter/matterNetworkMonitor.js.map +1 -1
- package/dist/matter/matterServer.d.ts +79 -9
- package/dist/matter/matterServer.d.ts.map +1 -1
- package/dist/matter/matterServer.js +491 -111
- package/dist/matter/matterServer.js.map +1 -1
- package/dist/matter/matterSharedTypes.d.ts +36 -16
- package/dist/matter/matterSharedTypes.d.ts.map +1 -1
- package/dist/matter/matterSharedTypes.js +0 -3
- package/dist/matter/matterSharedTypes.js.map +1 -1
- package/dist/matter/matterStorage.d.ts +11 -1
- package/dist/matter/matterStorage.d.ts.map +1 -1
- package/dist/matter/matterStorage.js +12 -2
- package/dist/matter/matterStorage.js.map +1 -1
- package/dist/matter/matterTypes.d.ts +69 -20
- package/dist/matter/matterTypes.d.ts.map +1 -1
- package/dist/matter/matterTypes.js.map +1 -1
- package/dist/matter/matterValidation.d.ts +57 -0
- package/dist/matter/matterValidation.d.ts.map +1 -0
- package/dist/matter/matterValidation.js +97 -0
- package/dist/matter/matterValidation.js.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -4
- package/dist/plugin.js.map +1 -1
- package/dist/server.d.ts +0 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +97 -280
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
- package/dist/bridgeTypes.d.ts +0 -54
- package/dist/bridgeTypes.d.ts.map +0 -1
- package/dist/bridgeTypes.js +0 -8
- package/dist/bridgeTypes.js.map +0 -1
- package/dist/matter/matterDiagnostics.d.ts +0 -121
- package/dist/matter/matterDiagnostics.d.ts.map +0 -1
- package/dist/matter/matterDiagnostics.js +0 -323
- package/dist/matter/matterDiagnostics.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -38,14 +38,10 @@ export class Server {
|
|
|
38
38
|
externalPortService;
|
|
39
39
|
config;
|
|
40
40
|
// used to keep track of child bridges
|
|
41
|
-
// Key is
|
|
41
|
+
// Key is HAP username (MAC address)
|
|
42
42
|
childBridges = new Map();
|
|
43
43
|
// Matter server instance for main bridge (if enabled)
|
|
44
44
|
matterServer;
|
|
45
|
-
// Cache for Matter bridge identifier -> ChildBridgeService lookups
|
|
46
|
-
matterBridgeCache = new Map();
|
|
47
|
-
// Track platform configurations for routing accessories
|
|
48
|
-
platformConfigs = new Map();
|
|
49
45
|
// current server status
|
|
50
46
|
serverStatus = "pending" /* ServerStatus.PENDING */;
|
|
51
47
|
constructor(options = {}) {
|
|
@@ -72,7 +68,7 @@ export class Server {
|
|
|
72
68
|
};
|
|
73
69
|
// shallow copy the homebridge options to the bridge options object
|
|
74
70
|
Object.assign(bridgeConfig, this.options);
|
|
75
|
-
this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge
|
|
71
|
+
this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge);
|
|
76
72
|
// Handle platform accessory registration
|
|
77
73
|
this.api.on("registerPlatformAccessories" /* InternalAPIEvent.REGISTER_PLATFORM_ACCESSORIES */, this.handleRegisterPlatformAccessories.bind(this));
|
|
78
74
|
this.api.on("unregisterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_PLATFORM_ACCESSORIES */, this.handleUnregisterPlatformAccessories.bind(this));
|
|
@@ -101,15 +97,38 @@ export class Server {
|
|
|
101
97
|
*/
|
|
102
98
|
setServerStatus(status) {
|
|
103
99
|
this.serverStatus = status;
|
|
104
|
-
|
|
105
|
-
type: 'hap', // Main bridge is HAP
|
|
100
|
+
const statusUpdate = {
|
|
106
101
|
status: this.serverStatus,
|
|
107
102
|
paired: this.bridgeService?.bridge?._accessoryInfo?.paired() ?? null,
|
|
108
103
|
setupUri: this.bridgeService?.bridge?.setupURI() ?? null,
|
|
109
|
-
name: this.
|
|
104
|
+
name: this.config.bridge.name,
|
|
110
105
|
username: this.config.bridge.username,
|
|
111
106
|
pin: this.config.bridge.pin,
|
|
112
|
-
|
|
107
|
+
matter: {
|
|
108
|
+
enabled: false,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
// Include Matter commissioning info if Matter is enabled
|
|
112
|
+
if (this.matterServer) {
|
|
113
|
+
const commissioningInfo = this.matterServer.getCommissioningInfo();
|
|
114
|
+
statusUpdate.matter = {
|
|
115
|
+
enabled: true,
|
|
116
|
+
port: this.config.bridge.matter?.port,
|
|
117
|
+
setupUri: commissioningInfo.qrCode,
|
|
118
|
+
pin: commissioningInfo.manualPairingCode,
|
|
119
|
+
serialNumber: commissioningInfo.serialNumber,
|
|
120
|
+
commissioned: commissioningInfo.commissioned || false,
|
|
121
|
+
deviceCount: this.matterServer.getAccessories().length,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
else if (this.config.bridge.matter) {
|
|
125
|
+
// Matter is configured but not yet started (or failed to start)
|
|
126
|
+
statusUpdate.matter = {
|
|
127
|
+
enabled: false,
|
|
128
|
+
port: this.config.bridge.matter?.port,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
this.ipcService.sendMessage("serverStatusUpdate" /* IpcOutgoingEvent.SERVER_STATUS_UPDATE */, statusUpdate);
|
|
113
132
|
}
|
|
114
133
|
async start() {
|
|
115
134
|
if (this.config.bridge.disableIpc !== true) {
|
|
@@ -128,7 +147,7 @@ export class Server {
|
|
|
128
147
|
if (this.config.accessories.length > 0) {
|
|
129
148
|
this.loadAccessories();
|
|
130
149
|
}
|
|
131
|
-
// start child
|
|
150
|
+
// start child bridges
|
|
132
151
|
for (const childBridge of this.childBridges.values()) {
|
|
133
152
|
childBridge.start();
|
|
134
153
|
}
|
|
@@ -147,10 +166,12 @@ export class Server {
|
|
|
147
166
|
if (!this.config.bridge.matter) {
|
|
148
167
|
return;
|
|
149
168
|
}
|
|
169
|
+
// Declare matterPort outside try block so it's accessible in catch
|
|
170
|
+
let matterPort;
|
|
150
171
|
try {
|
|
151
172
|
log.info('Initializing Matter server for main bridge...');
|
|
152
173
|
// Allocate port from pool if not explicitly configured
|
|
153
|
-
|
|
174
|
+
matterPort = this.config.bridge.matter.port;
|
|
154
175
|
if (!matterPort) {
|
|
155
176
|
matterPort = await this.externalPortService.requestPort(`${this.config.bridge.username}:MATTER`);
|
|
156
177
|
if (!matterPort) {
|
|
@@ -161,22 +182,25 @@ export class Server {
|
|
|
161
182
|
log.info(`Allocated port ${matterPort} from pool for main Matter bridge`);
|
|
162
183
|
}
|
|
163
184
|
}
|
|
164
|
-
// Create Matter server instance
|
|
185
|
+
// Create Matter server instance with config inheritance from main bridge
|
|
165
186
|
this.matterServer = new MatterServer({
|
|
166
187
|
storagePath: User.matterPath(),
|
|
167
188
|
port: matterPort,
|
|
168
|
-
uniqueId:
|
|
169
|
-
|
|
189
|
+
uniqueId: this.config.bridge.username,
|
|
190
|
+
manufacturer: this.config.bridge.manufacturer,
|
|
191
|
+
model: this.config.bridge.model,
|
|
192
|
+
firmwareRevision: this.config.bridge.firmwareRevision,
|
|
193
|
+
serialNumber: this.config.bridge.serialNumber,
|
|
194
|
+
debugModeEnabled: this.options.debugModeEnabled,
|
|
170
195
|
});
|
|
171
196
|
// Start the Matter server
|
|
172
197
|
await this.matterServer.start();
|
|
173
198
|
log.info('Matter server initialized for main bridge');
|
|
174
199
|
// Inform the API that Matter is enabled
|
|
175
200
|
this.api._setMatterEnabled(true);
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
201
|
+
// Set the Matter server reference for API methods like getAccessoryState
|
|
202
|
+
this.api._setMatterServer(this.matterServer);
|
|
203
|
+
// Matter server is running - status updates will be handled through unified child bridge events
|
|
180
204
|
}
|
|
181
205
|
catch (error) {
|
|
182
206
|
log.error('Failed to initialize Matter server for main bridge:', error);
|
|
@@ -192,40 +216,32 @@ export class Server {
|
|
|
192
216
|
log.error('║ • Incomplete writes during shutdown ║');
|
|
193
217
|
log.error('║ ║');
|
|
194
218
|
log.error('║ To fix this, delete the corrupted storage directory: ║');
|
|
195
|
-
log.error(
|
|
219
|
+
log.error(`║ rm -rf ~/.homebridge/matter/${this.config.bridge.username} ║`);
|
|
196
220
|
log.error('║ ║');
|
|
197
221
|
log.error('║ Note: You will need to re-pair your Matter devices after deletion. ║');
|
|
198
222
|
log.error('╚════════════════════════════════════════════════════════════════════════════╝');
|
|
199
223
|
log.error('');
|
|
200
224
|
}
|
|
225
|
+
else if (error.code === 'EADDRINUSE' || (error.message && error.message.includes('address already in use'))) {
|
|
226
|
+
log.error('');
|
|
227
|
+
log.error('╔════════════════════════════════════════════════════════════════════════════╗');
|
|
228
|
+
log.error('║ MATTER PORT ALREADY IN USE ║');
|
|
229
|
+
log.error('╠════════════════════════════════════════════════════════════════════════════╣');
|
|
230
|
+
log.error(`║ Port ${matterPort} is already in use by another application. ║`);
|
|
231
|
+
log.error('║ ║');
|
|
232
|
+
log.error('║ To fix this: ║');
|
|
233
|
+
log.error('║ 1. Stop the application using this port, or ║');
|
|
234
|
+
log.error('║ 2. Configure a different port in your config.json: ║');
|
|
235
|
+
log.error('║ "bridge": { ║');
|
|
236
|
+
log.error('║ "matter": { ║');
|
|
237
|
+
log.error('║ "port": <different-port> ║');
|
|
238
|
+
log.error('║ } ║');
|
|
239
|
+
log.error('║ } ║');
|
|
240
|
+
log.error('╚════════════════════════════════════════════════════════════════════════════╝');
|
|
241
|
+
log.error('');
|
|
242
|
+
}
|
|
201
243
|
}
|
|
202
244
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Send Matter status update for main bridge
|
|
205
|
-
*/
|
|
206
|
-
sendMainBridgeMatterStatusUpdate() {
|
|
207
|
-
if (!this.matterServer || !this.matterServer.isServerRunning()) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const commissioningInfo = this.matterServer.getCommissioningInfo();
|
|
211
|
-
// Transform property names to match UI expectations
|
|
212
|
-
const statusUpdate = {
|
|
213
|
-
type: 'matter',
|
|
214
|
-
status: 'ok',
|
|
215
|
-
port: this.config.bridge.matter?.port || 5540,
|
|
216
|
-
setupUri: commissioningInfo.qrCode, // Map qrCode -> setupUri for UI
|
|
217
|
-
pin: commissioningInfo.manualPairingCode, // Map manualPairingCode -> pin for UI
|
|
218
|
-
serialNumber: commissioningInfo.serialNumber,
|
|
219
|
-
passcode: commissioningInfo.passcode,
|
|
220
|
-
discriminator: commissioningInfo.discriminator,
|
|
221
|
-
name: this.config.bridge.matter?.name || (this.config.bridge.name ? `${this.config.bridge.name} (Matter)` : 'Homebridge Matter Bridge'),
|
|
222
|
-
plugin: 'main-bridge',
|
|
223
|
-
identifier: 'main-bridge',
|
|
224
|
-
deviceCount: this.matterServer.getAccessories().length,
|
|
225
|
-
commissioned: commissioningInfo.commissioned,
|
|
226
|
-
};
|
|
227
|
-
this.ipcService.sendMessage("matterBridgeStatusUpdate" /* IpcOutgoingEvent.MATTER_BRIDGE_STATUS_UPDATE */, statusUpdate);
|
|
228
|
-
}
|
|
229
245
|
async teardown() {
|
|
230
246
|
this.bridgeService.teardown();
|
|
231
247
|
// Stop main Matter server if running
|
|
@@ -242,7 +258,6 @@ export class Server {
|
|
|
242
258
|
}
|
|
243
259
|
publishBridge() {
|
|
244
260
|
this.bridgeService.publishBridge();
|
|
245
|
-
// Main bridge always has a pin (validated in loadConfig)
|
|
246
261
|
this.printSetupInfo(this.config.bridge.pin);
|
|
247
262
|
}
|
|
248
263
|
handlePublishExternalAccessories(accessories) {
|
|
@@ -322,7 +337,6 @@ export class Server {
|
|
|
322
337
|
bridge.username = bridge.username || defaultBridge.username;
|
|
323
338
|
bridge.pin = bridge.pin || defaultBridge.pin;
|
|
324
339
|
config.bridge = bridge;
|
|
325
|
-
// Main bridge username is always required and must be valid
|
|
326
340
|
const username = config.bridge.username;
|
|
327
341
|
if (!validMacAddress(username)) {
|
|
328
342
|
throw new Error(`Not a valid username: ${username}. Must be 6 pairs of colon-separated hexadecimal chars (A-F 0-9), like a MAC address.`);
|
|
@@ -342,10 +356,6 @@ export class Server {
|
|
|
342
356
|
if (config.bridge.matter || config.platforms.some((p) => p._bridge?.matter) || config.accessories.some((a) => a._bridge?.matter)) {
|
|
343
357
|
// Validate main bridge Matter config
|
|
344
358
|
if (config.bridge.matter) {
|
|
345
|
-
// Apply default name before validation if not set
|
|
346
|
-
if (!config.bridge.matter.name) {
|
|
347
|
-
config.bridge.matter.name = config.bridge.name ? `${config.bridge.name} (Matter)` : 'Homebridge Matter Bridge';
|
|
348
|
-
}
|
|
349
359
|
const validation = MatterConfigValidator.validate(config.bridge.matter);
|
|
350
360
|
if (!validation.isValid) {
|
|
351
361
|
log.error('Main bridge Matter configuration is invalid. Matter will not be enabled for the main bridge.');
|
|
@@ -434,37 +444,29 @@ export class Server {
|
|
|
434
444
|
}
|
|
435
445
|
const logger = Logger.withPrefix(displayName);
|
|
436
446
|
logger('Initializing %s accessory...', accessoryIdentifier);
|
|
437
|
-
// Handle child bridge (HAP, Matter-only, or HAP+Matter)
|
|
438
447
|
if (accessoryConfig._bridge) {
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
accessoryConfig._bridge.username = accessoryConfig._bridge.username.toUpperCase();
|
|
442
|
-
}
|
|
443
|
-
// Generate unique key for this child bridge
|
|
444
|
-
const bridgeKey = this.getChildBridgeKey("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, plugin.getPluginIdentifier(), accessoryConfig._bridge);
|
|
448
|
+
// ensure the username is always uppercase
|
|
449
|
+
accessoryConfig._bridge.username = accessoryConfig._bridge.username.toUpperCase();
|
|
445
450
|
try {
|
|
446
|
-
this.validateChildBridgeConfig("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier,
|
|
451
|
+
this.validateChildBridgeConfig("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, accessoryConfig._bridge);
|
|
447
452
|
}
|
|
448
453
|
catch (error) {
|
|
449
454
|
log.error(error.message);
|
|
450
455
|
return;
|
|
451
456
|
}
|
|
452
457
|
let childBridge;
|
|
453
|
-
if (this.childBridges.has(
|
|
454
|
-
childBridge = this.childBridges.get(
|
|
455
|
-
logger(`Adding to existing child bridge ${
|
|
458
|
+
if (this.childBridges.has(accessoryConfig._bridge.username)) {
|
|
459
|
+
childBridge = this.childBridges.get(accessoryConfig._bridge.username);
|
|
460
|
+
logger(`Adding to existing child bridge ${accessoryConfig._bridge.username}`);
|
|
456
461
|
}
|
|
457
462
|
else {
|
|
458
|
-
|
|
459
|
-
logger(`Initializing ${bridgeType} child bridge ${bridgeKey}`);
|
|
463
|
+
logger(`Initializing child bridge ${accessoryConfig._bridge.username}`);
|
|
460
464
|
childBridge = new ChildBridgeService("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, plugin, accessoryConfig._bridge, this.config, this.options, this.api, this.ipcService, this.externalPortService);
|
|
461
|
-
this.childBridges.set(
|
|
465
|
+
this.childBridges.set(accessoryConfig._bridge.username, childBridge);
|
|
462
466
|
}
|
|
463
467
|
// add config to child bridge service
|
|
464
468
|
childBridge.addConfig(accessoryConfig);
|
|
465
|
-
|
|
466
|
-
// to maintain isolation - each child process manages its own Matter server
|
|
467
|
-
return; // Done - child bridge
|
|
469
|
+
return;
|
|
468
470
|
}
|
|
469
471
|
const accessoryInstance = new constructor(logger, accessoryConfig, this.api);
|
|
470
472
|
// pass accessoryIdentifier for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
|
|
@@ -521,29 +523,21 @@ export class Server {
|
|
|
521
523
|
}
|
|
522
524
|
const logger = Logger.withPrefix(displayName);
|
|
523
525
|
logger('Initializing %s platform...', platformIdentifier);
|
|
524
|
-
// Handle child bridge (HAP, Matter-only, or HAP+Matter)
|
|
525
526
|
if (platformConfig._bridge) {
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
platformConfig._bridge.username = platformConfig._bridge.username.toUpperCase();
|
|
529
|
-
}
|
|
530
|
-
// Generate unique key for this child bridge
|
|
531
|
-
const bridgeKey = this.getChildBridgeKey("platform" /* PluginType.PLATFORM */, platformIdentifier, plugin.getPluginIdentifier(), platformConfig._bridge);
|
|
527
|
+
// ensure the username is always uppercase
|
|
528
|
+
platformConfig._bridge.username = platformConfig._bridge.username.toUpperCase();
|
|
532
529
|
try {
|
|
533
|
-
this.validateChildBridgeConfig("platform" /* PluginType.PLATFORM */, platformIdentifier,
|
|
530
|
+
this.validateChildBridgeConfig("platform" /* PluginType.PLATFORM */, platformIdentifier, platformConfig._bridge);
|
|
534
531
|
}
|
|
535
532
|
catch (error) {
|
|
536
533
|
log.error(error.message);
|
|
537
534
|
return;
|
|
538
535
|
}
|
|
539
|
-
|
|
540
|
-
logger(`Initializing ${bridgeType} child bridge ${bridgeKey}`);
|
|
536
|
+
logger(`Initializing child bridge ${platformConfig._bridge.username}`);
|
|
541
537
|
const childBridge = new ChildBridgeService("platform" /* PluginType.PLATFORM */, platformIdentifier, plugin, platformConfig._bridge, this.config, this.options, this.api, this.ipcService, this.externalPortService);
|
|
542
|
-
this.childBridges.set(
|
|
538
|
+
this.childBridges.set(platformConfig._bridge.username, childBridge);
|
|
543
539
|
// add config to child bridge service
|
|
544
540
|
childBridge.addConfig(platformConfig);
|
|
545
|
-
// Matter for child bridges is handled inside the forked child process (childBridgeFork.ts)
|
|
546
|
-
// to maintain isolation - each child process manages its own Matter server
|
|
547
541
|
return;
|
|
548
542
|
}
|
|
549
543
|
const platform = new constructor(logger, platformConfig, this.api);
|
|
@@ -560,48 +554,21 @@ export class Server {
|
|
|
560
554
|
});
|
|
561
555
|
return promises;
|
|
562
556
|
}
|
|
563
|
-
/**
|
|
564
|
-
* Generate a unique identifier for a child bridge
|
|
565
|
-
* - For HAP bridges: returns the uppercase MAC address
|
|
566
|
-
* - For Matter-only bridges: returns "matter-{plugin}-{identifier}"
|
|
567
|
-
*/
|
|
568
|
-
getChildBridgeKey(type, identifier, pluginName, bridgeConfig) {
|
|
569
|
-
if (bridgeConfig.matterOnly) {
|
|
570
|
-
// Matter-only bridge: generate identifier from plugin and platform/accessory name
|
|
571
|
-
return `matter-${pluginName}-${identifier}`;
|
|
572
|
-
}
|
|
573
|
-
// HAP bridge: use MAC address (already validated and uppercased)
|
|
574
|
-
return bridgeConfig.username.toUpperCase();
|
|
575
|
-
}
|
|
576
557
|
/**
|
|
577
558
|
* Validate an external bridge config
|
|
578
559
|
*/
|
|
579
|
-
validateChildBridgeConfig(type, identifier,
|
|
580
|
-
//
|
|
581
|
-
if (bridgeConfig.matterOnly) {
|
|
582
|
-
if (!bridgeConfig.matter) {
|
|
583
|
-
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
584
|
-
+ 'Matter-only bridge must have a "matter" configuration block in _bridge.matter.');
|
|
585
|
-
}
|
|
586
|
-
// Check for duplicate Matter-only bridges
|
|
587
|
-
if (this.childBridges.has(bridgeKey)) {
|
|
588
|
-
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
589
|
-
+ 'A Matter-only bridge with this plugin and platform/accessory combination already exists. Each Matter-only bridge must be unique.');
|
|
590
|
-
}
|
|
591
|
-
// Matter-only bridges don't need username/pin validation
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
// HAP bridges (or HAP+Matter bridges) require username and pin
|
|
560
|
+
validateChildBridgeConfig(type, identifier, bridgeConfig) {
|
|
561
|
+
// All child bridges require username
|
|
595
562
|
if (!bridgeConfig.username) {
|
|
596
563
|
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
597
|
-
+ 'Missing required field "_bridge.username".
|
|
564
|
+
+ 'Missing required field "_bridge.username".');
|
|
598
565
|
}
|
|
599
566
|
if (!validMacAddress(bridgeConfig.username)) {
|
|
600
567
|
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
601
568
|
+ `not a valid username in _bridge.username: "${bridgeConfig.username}". Must be 6 pairs of colon-separated hexadecimal chars (A-F 0-9), like a MAC address.`);
|
|
602
569
|
}
|
|
603
|
-
if (this.childBridges.has(
|
|
604
|
-
const childBridge = this.childBridges.get(
|
|
570
|
+
if (this.childBridges.has(bridgeConfig.username)) {
|
|
571
|
+
const childBridge = this.childBridges.get(bridgeConfig.username);
|
|
605
572
|
if (type === "platform" /* PluginType.PLATFORM */) {
|
|
606
573
|
// only a single platform can exist on one child bridge
|
|
607
574
|
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
@@ -613,8 +580,6 @@ export class Server {
|
|
|
613
580
|
+ `Duplicate username found in _bridge.username: "${bridgeConfig.username}". You can only group accessories of the same type in a child bridge.`);
|
|
614
581
|
}
|
|
615
582
|
}
|
|
616
|
-
// Check that child bridge username doesn't conflict with main bridge
|
|
617
|
-
// Main bridge always has a username (validated in loadConfig)
|
|
618
583
|
if (bridgeConfig.username === this.config.bridge.username.toUpperCase()) {
|
|
619
584
|
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
620
585
|
+ `Username found in _bridge.username: "${bridgeConfig.username}" is the same as the main bridge. Each child bridge platform/accessory must have it's own unique username.`);
|
|
@@ -631,9 +596,7 @@ export class Server {
|
|
|
631
596
|
// noinspection SuspiciousTypeOfGuard
|
|
632
597
|
if (typeof username === 'string') {
|
|
633
598
|
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
634
|
-
|
|
635
|
-
childBridge.restartChildBridge();
|
|
636
|
-
}
|
|
599
|
+
childBridge?.restartChildBridge();
|
|
637
600
|
}
|
|
638
601
|
});
|
|
639
602
|
// handle stop child bridge event
|
|
@@ -641,9 +604,7 @@ export class Server {
|
|
|
641
604
|
// noinspection SuspiciousTypeOfGuard
|
|
642
605
|
if (typeof username === 'string') {
|
|
643
606
|
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
644
|
-
|
|
645
|
-
childBridge.stopChildBridge();
|
|
646
|
-
}
|
|
607
|
+
childBridge?.stopChildBridge();
|
|
647
608
|
}
|
|
648
609
|
});
|
|
649
610
|
// handle start child bridge event
|
|
@@ -651,176 +612,32 @@ export class Server {
|
|
|
651
612
|
// noinspection SuspiciousTypeOfGuard
|
|
652
613
|
if (typeof username === 'string') {
|
|
653
614
|
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
654
|
-
|
|
655
|
-
childBridge.startChildBridge();
|
|
656
|
-
}
|
|
615
|
+
childBridge?.startChildBridge();
|
|
657
616
|
}
|
|
658
617
|
});
|
|
659
618
|
this.ipcService.on("childBridgeMetadataRequest" /* IpcIncomingEvent.CHILD_BRIDGE_METADATA_REQUEST */, () => {
|
|
660
|
-
|
|
661
|
-
this.ipcService.sendMessage("childBridgeMetadataResponse" /* IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE */, childBridgeMetadata);
|
|
662
|
-
});
|
|
663
|
-
// Matter bridge IPC handlers
|
|
664
|
-
// Main bridge Matter server runs in this process alongside HAP
|
|
665
|
-
// Child bridge Matter servers run in forked child processes (see childBridgeFork.ts)
|
|
666
|
-
this.ipcService.on("restartMatterBridge" /* IpcIncomingEvent.RESTART_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
667
|
-
if (matterBridgeId === 'main-bridge') {
|
|
668
|
-
// Main Matter bridge runs in the main process, so restart the entire Homebridge instance
|
|
669
|
-
log.info('Restarting Homebridge (Matter bridge restart requested)...');
|
|
670
|
-
process.kill(process.pid, 'SIGTERM');
|
|
671
|
-
}
|
|
672
|
-
else {
|
|
673
|
-
// Handle child bridge using cache for efficient lookup
|
|
674
|
-
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
675
|
-
if (childBridge) {
|
|
676
|
-
childBridge.restartChildBridge();
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
log.warn(`Child bridge ${matterBridgeId} not found for Matter restart`);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
this.ipcService.on("stopMatterBridge" /* IpcIncomingEvent.STOP_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
684
|
-
if (matterBridgeId === 'main-bridge') {
|
|
685
|
-
log.warn('Stop requested for main Matter bridge - this is not supported');
|
|
686
|
-
log.info('The main Matter bridge runs alongside the HAP bridge and cannot be stopped independently');
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
// Handle child bridge using cache for efficient lookup
|
|
690
|
-
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
691
|
-
if (childBridge) {
|
|
692
|
-
childBridge.stopChildBridge();
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
log.warn(`Child bridge ${matterBridgeId} not found for Matter stop`);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
this.ipcService.on("startMatterBridge" /* IpcIncomingEvent.START_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
700
|
-
if (matterBridgeId === 'main-bridge') {
|
|
701
|
-
log.warn('Start requested for main Matter bridge - this is not supported');
|
|
702
|
-
log.info('The main Matter bridge starts automatically with Homebridge');
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
// Handle child bridge using cache for efficient lookup
|
|
706
|
-
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
707
|
-
if (childBridge) {
|
|
708
|
-
childBridge.startChildBridge();
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
log.warn(`Child bridge ${matterBridgeId} not found for Matter start`);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
this.ipcService.on("matterBridgeMetadataRequest" /* IpcIncomingEvent.MATTER_BRIDGE_METADATA_REQUEST */, () => {
|
|
716
|
-
const matterBridges = [];
|
|
717
|
-
// Add main bridge Matter metadata if enabled
|
|
718
|
-
if (this.matterServer && this.matterServer.isServerRunning()) {
|
|
719
|
-
const commissioningInfo = this.matterServer.getCommissioningInfo();
|
|
720
|
-
matterBridges.push({
|
|
721
|
-
type: 'matter',
|
|
722
|
-
status: 'ok',
|
|
723
|
-
port: this.config.bridge.matter?.port || 5540,
|
|
724
|
-
setupUri: commissioningInfo.qrCode,
|
|
725
|
-
pin: commissioningInfo.manualPairingCode,
|
|
726
|
-
serialNumber: commissioningInfo.serialNumber,
|
|
727
|
-
name: this.config.bridge.name,
|
|
728
|
-
plugin: 'main-bridge',
|
|
729
|
-
identifier: 'main-bridge',
|
|
730
|
-
deviceCount: this.matterServer.getAccessories().length,
|
|
731
|
-
commissioned: commissioningInfo.commissioned,
|
|
732
|
-
matterEnabled: true,
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
// Add child bridge Matter metadata
|
|
736
|
-
for (const childBridge of this.childBridges.values()) {
|
|
737
|
-
const matterInfo = childBridge.getCommissioningInfo();
|
|
738
|
-
const matterConfig = childBridge.getMatterConfig();
|
|
739
|
-
if (matterInfo && matterConfig) {
|
|
740
|
-
const metadata = childBridge.getMetadata();
|
|
741
|
-
// Use plugin-identifier format to match storage
|
|
742
|
-
const childIdentifier = `${metadata.plugin}-${childBridge.identifier}`;
|
|
743
|
-
// Populate cache for efficient lookups
|
|
744
|
-
this.matterBridgeCache.set(childIdentifier, childBridge);
|
|
745
|
-
matterBridges.push({
|
|
746
|
-
type: 'matter',
|
|
747
|
-
status: metadata.status === 'ok' ? 'ok' : 'pending',
|
|
748
|
-
port: matterConfig.port,
|
|
749
|
-
setupUri: matterInfo.qrCode,
|
|
750
|
-
pin: matterInfo.manualPairingCode,
|
|
751
|
-
serialNumber: matterInfo.serialNumber,
|
|
752
|
-
name: matterConfig.name || metadata.name,
|
|
753
|
-
plugin: metadata.plugin,
|
|
754
|
-
identifier: childIdentifier,
|
|
755
|
-
commissioned: matterInfo.commissioned,
|
|
756
|
-
username: metadata.username,
|
|
757
|
-
matterEnabled: true,
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
this.ipcService.sendMessage("matterBridgeMetadataResponse" /* IpcOutgoingEvent.MATTER_BRIDGE_METADATA_RESPONSE */, matterBridges);
|
|
762
|
-
});
|
|
763
|
-
this.ipcService.on("matterAccessoriesRequest" /* IpcIncomingEvent.MATTER_ACCESSORIES_REQUEST */, () => {
|
|
764
|
-
// Return Matter accessories from all bridges
|
|
765
|
-
const allMatterAccessories = {
|
|
766
|
-
children: {},
|
|
767
|
-
};
|
|
768
|
-
// Add main bridge accessories
|
|
769
|
-
if (this.matterServer) {
|
|
770
|
-
allMatterAccessories.children['main-bridge'] = this.matterServer.getAccessories();
|
|
771
|
-
}
|
|
772
|
-
// Child bridge accessories are managed by child processes
|
|
773
|
-
this.ipcService.sendMessage("matterAccessoriesResponse" /* IpcOutgoingEvent.MATTER_ACCESSORIES_RESPONSE */, allMatterAccessories);
|
|
774
|
-
});
|
|
775
|
-
this.ipcService.on("matterCommissioningInfoRequest" /* IpcIncomingEvent.MATTER_COMMISSIONING_INFO_REQUEST */, (matterBridgeId) => {
|
|
776
|
-
let rawInfo = { commissioned: false };
|
|
777
|
-
// Handle main bridge
|
|
778
|
-
if (matterBridgeId === 'main-bridge') {
|
|
779
|
-
if (this.matterServer && this.matterServer.isServerRunning()) {
|
|
780
|
-
rawInfo = this.matterServer.getCommissioningInfo();
|
|
781
|
-
}
|
|
782
|
-
else {
|
|
783
|
-
log.debug(`[Matter] Server not available when commissioning info requested for: ${matterBridgeId}`);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
else {
|
|
787
|
-
// Handle child bridge using plugin-identifier format (e.g., homebridge-virtual-accessories-VirtualAccessoriesForHomebridge)
|
|
788
|
-
// Use cache for efficient O(1) lookup
|
|
789
|
-
const childBridge = this.matterBridgeCache.get(matterBridgeId);
|
|
790
|
-
if (childBridge) {
|
|
791
|
-
rawInfo = childBridge.getCommissioningInfo() || { commissioned: false };
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
log.debug(`[Matter] Child bridge ${matterBridgeId} not found when commissioning info requested`);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
// Transform property names to match MatterCommissioningInfo interface
|
|
798
|
-
const commissioningInfo = {
|
|
799
|
-
commissioned: rawInfo.commissioned || false,
|
|
800
|
-
pin: rawInfo.manualPairingCode, // Map manualPairingCode -> pin
|
|
801
|
-
setupUri: rawInfo.qrCode, // Map qrCode -> setupUri
|
|
802
|
-
serialNumber: rawInfo.serialNumber,
|
|
803
|
-
};
|
|
804
|
-
this.ipcService.sendMessage("matterCommissioningInfoResponse" /* IpcOutgoingEvent.MATTER_COMMISSIONING_INFO_RESPONSE */, commissioningInfo);
|
|
619
|
+
this.ipcService.sendMessage("childBridgeMetadataResponse" /* IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE */, Array.from(this.childBridges.values()).map(x => x.getMetadata()));
|
|
805
620
|
});
|
|
806
621
|
}
|
|
807
622
|
printSetupInfo(pin) {
|
|
808
|
-
|
|
809
|
-
log
|
|
623
|
+
/* eslint-disable no-console */
|
|
624
|
+
console.log('Setup Payload:');
|
|
625
|
+
console.log(this.bridgeService.bridge.setupURI());
|
|
810
626
|
if (!this.options.hideQRCode) {
|
|
811
|
-
log
|
|
627
|
+
console.log('Scan this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
812
628
|
qrcode.setErrorLevel('M'); // HAP specifies level M or higher for ECC
|
|
813
629
|
qrcode.generate(this.bridgeService.bridge.setupURI());
|
|
814
|
-
log
|
|
630
|
+
console.log('Or enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
815
631
|
}
|
|
816
632
|
else {
|
|
817
|
-
log
|
|
633
|
+
console.log('Enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
818
634
|
}
|
|
819
|
-
log
|
|
820
|
-
log
|
|
821
|
-
log
|
|
822
|
-
log
|
|
823
|
-
log
|
|
635
|
+
console.log(chalk.black.bgWhite(' '));
|
|
636
|
+
console.log(chalk.black.bgWhite(' ┌────────────┐ '));
|
|
637
|
+
console.log(chalk.black.bgWhite(` │ ${pin} │ `));
|
|
638
|
+
console.log(chalk.black.bgWhite(' └────────────┘ '));
|
|
639
|
+
console.log(chalk.black.bgWhite(' '));
|
|
640
|
+
/* eslint-enable no-console */
|
|
824
641
|
}
|
|
825
642
|
}
|
|
826
643
|
//# sourceMappingURL=server.js.map
|