homebridge 2.0.0-alpha.41 → 2.0.0-alpha.43

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.
Files changed (91) hide show
  1. package/dist/api.d.ts +81 -20
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +71 -11
  4. package/dist/api.js.map +1 -1
  5. package/dist/bridgeService.d.ts +14 -12
  6. package/dist/bridgeService.d.ts.map +1 -1
  7. package/dist/bridgeService.js +17 -3
  8. package/dist/bridgeService.js.map +1 -1
  9. package/dist/bridgeTypes.d.ts +54 -0
  10. package/dist/bridgeTypes.d.ts.map +1 -0
  11. package/dist/bridgeTypes.js +8 -0
  12. package/dist/bridgeTypes.js.map +1 -0
  13. package/dist/childBridgeFork.d.ts +23 -0
  14. package/dist/childBridgeFork.d.ts.map +1 -1
  15. package/dist/childBridgeFork.js +240 -4
  16. package/dist/childBridgeFork.js.map +1 -1
  17. package/dist/childBridgeService.d.ts +47 -7
  18. package/dist/childBridgeService.d.ts.map +1 -1
  19. package/dist/childBridgeService.js +67 -2
  20. package/dist/childBridgeService.js.map +1 -1
  21. package/dist/cli.d.ts.map +1 -1
  22. package/dist/cli.js +3 -1
  23. package/dist/cli.js.map +1 -1
  24. package/dist/index.d.ts +5 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +4 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/ipcService.d.ts +3 -1
  29. package/dist/ipcService.d.ts.map +1 -1
  30. package/dist/ipcService.js +2 -0
  31. package/dist/ipcService.js.map +1 -1
  32. package/dist/matter/index.d.ts +4 -6
  33. package/dist/matter/index.d.ts.map +1 -1
  34. package/dist/matter/index.js +4 -5
  35. package/dist/matter/index.js.map +1 -1
  36. package/dist/matter/matterConfigValidator.d.ts +2 -3
  37. package/dist/matter/matterConfigValidator.d.ts.map +1 -1
  38. package/dist/matter/matterConfigValidator.js +47 -38
  39. package/dist/matter/matterConfigValidator.js.map +1 -1
  40. package/dist/matter/matterErrorHandler.d.ts +6 -25
  41. package/dist/matter/matterErrorHandler.d.ts.map +1 -1
  42. package/dist/matter/matterErrorHandler.js +89 -99
  43. package/dist/matter/matterErrorHandler.js.map +1 -1
  44. package/dist/matter/matterServer.d.ts +126 -39
  45. package/dist/matter/matterServer.d.ts.map +1 -1
  46. package/dist/matter/matterServer.js +528 -226
  47. package/dist/matter/matterServer.js.map +1 -1
  48. package/dist/matter/matterSharedTypes.d.ts +16 -38
  49. package/dist/matter/matterSharedTypes.d.ts.map +1 -1
  50. package/dist/matter/matterSharedTypes.js +3 -4
  51. package/dist/matter/matterSharedTypes.js.map +1 -1
  52. package/dist/matter/matterStorage.d.ts +116 -0
  53. package/dist/matter/matterStorage.d.ts.map +1 -0
  54. package/dist/matter/matterStorage.js +442 -0
  55. package/dist/matter/matterStorage.js.map +1 -0
  56. package/dist/matter/matterTypes.d.ts +148 -20
  57. package/dist/matter/matterTypes.d.ts.map +1 -1
  58. package/dist/matter/matterTypes.js +91 -263
  59. package/dist/matter/matterTypes.js.map +1 -1
  60. package/dist/plugin.d.ts.map +1 -1
  61. package/dist/plugin.js +4 -2
  62. package/dist/plugin.js.map +1 -1
  63. package/dist/server.d.ts +18 -4
  64. package/dist/server.d.ts.map +1 -1
  65. package/dist/server.js +376 -349
  66. package/dist/server.js.map +1 -1
  67. package/dist/user.d.ts +1 -0
  68. package/dist/user.d.ts.map +1 -1
  69. package/dist/user.js +3 -0
  70. package/dist/user.js.map +1 -1
  71. package/package.json +6 -7
  72. package/dist/childMatterBridgeFork.d.ts +0 -108
  73. package/dist/childMatterBridgeFork.d.ts.map +0 -1
  74. package/dist/childMatterBridgeFork.js +0 -330
  75. package/dist/childMatterBridgeFork.js.map +0 -1
  76. package/dist/childMatterBridgeService.d.ts +0 -166
  77. package/dist/childMatterBridgeService.d.ts.map +0 -1
  78. package/dist/childMatterBridgeService.js +0 -623
  79. package/dist/childMatterBridgeService.js.map +0 -1
  80. package/dist/matter/matterBridge.d.ts +0 -64
  81. package/dist/matter/matterBridge.d.ts.map +0 -1
  82. package/dist/matter/matterBridge.js +0 -154
  83. package/dist/matter/matterBridge.js.map +0 -1
  84. package/dist/matter/matterDevice.d.ts +0 -107
  85. package/dist/matter/matterDevice.d.ts.map +0 -1
  86. package/dist/matter/matterDevice.js +0 -913
  87. package/dist/matter/matterDevice.js.map +0 -1
  88. package/dist/matter/portAllocator.d.ts +0 -85
  89. package/dist/matter/portAllocator.d.ts.map +0 -1
  90. package/dist/matter/portAllocator.js +0 -296
  91. 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/matterConfigValidator.js';
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';
@@ -39,8 +38,12 @@ export class Server {
39
38
  externalPortService;
40
39
  config;
41
40
  // used to keep track of child bridges
41
+ // Key is either HAP username (MAC address) or Matter-only identifier (e.g., "matter-plugin-platform")
42
42
  childBridges = new Map();
43
- childMatterBridges = new Map();
43
+ // Matter server instance for main bridge (if enabled)
44
+ matterServer;
45
+ // Cache for Matter bridge identifier -> ChildBridgeService lookups
46
+ matterBridgeCache = new Map();
44
47
  // Track platform configurations for routing accessories
45
48
  platformConfigs = new Map();
46
49
  // current server status
@@ -70,12 +73,15 @@ export class Server {
70
73
  // shallow copy the homebridge options to the bridge options object
71
74
  Object.assign(bridgeConfig, this.options);
72
75
  this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge, this.config);
73
- // Matter is handled via _matter configuration, not API events
74
- // Intercept platform accessory registration to route to Matter bridges if needed
76
+ // Handle platform accessory registration
75
77
  this.api.on("registerPlatformAccessories" /* InternalAPIEvent.REGISTER_PLATFORM_ACCESSORIES */, this.handleRegisterPlatformAccessories.bind(this));
76
78
  this.api.on("unregisterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_PLATFORM_ACCESSORIES */, this.handleUnregisterPlatformAccessories.bind(this));
77
- // Handle external accessories (cameras, etc.) for Matter
79
+ // Handle external accessories (cameras, etc.)
78
80
  this.api.on("publishExternalAccessories" /* InternalAPIEvent.PUBLISH_EXTERNAL_ACCESSORIES */, this.handlePublishExternalAccessories.bind(this));
81
+ // Handle Matter accessory registration
82
+ this.api.on("registerMatterAccessory" /* InternalAPIEvent.REGISTER_MATTER_ACCESSORY */, this.handleRegisterMatterAccessory.bind(this));
83
+ this.api.on("unregisterMatterAccessory" /* InternalAPIEvent.UNREGISTER_MATTER_ACCESSORY */, this.handleUnregisterMatterAccessory.bind(this));
84
+ this.api.on("updateMatterAccessoryState" /* InternalAPIEvent.UPDATE_MATTER_ACCESSORY_STATE */, this.handleUpdateMatterAccessoryState.bind(this));
79
85
  // watch bridge events to check when server is online
80
86
  this.bridgeService.bridge.on("advertised" /* AccessoryEventTypes.ADVERTISED */, () => {
81
87
  this.setServerStatus("ok" /* ServerStatus.OK */);
@@ -95,14 +101,6 @@ export class Server {
95
101
  */
96
102
  setServerStatus(status) {
97
103
  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
104
  this.ipcService.sendMessage("serverStatusUpdate" /* IpcOutgoingEvent.SERVER_STATUS_UPDATE */, {
107
105
  type: 'hap', // Main bridge is HAP
108
106
  status: this.serverStatus,
@@ -111,78 +109,29 @@ export class Server {
111
109
  name: this.bridgeService?.bridge?.displayName || this.config.bridge.name,
112
110
  username: this.config.bridge.username,
113
111
  pin: this.config.bridge.pin,
114
- matterStatus,
115
112
  });
116
113
  }
117
114
  async start() {
118
115
  if (this.config.bridge.disableIpc !== true) {
119
116
  this.initializeIpcEventHandlers();
120
117
  }
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
118
  const promises = [];
130
119
  // load the cached accessories
131
120
  await this.bridgeService.loadCachedPlatformAccessoriesFromDisk();
132
121
  // initialize plugins
133
122
  await this.pluginManager.initializeInstalledPlugins();
123
+ // Initialize Matter server for main bridge if enabled
124
+ await this.initializeMatterServer();
134
125
  if (this.config.platforms.length > 0) {
135
126
  promises.push(...this.loadPlatforms());
136
127
  }
137
128
  if (this.config.accessories.length > 0) {
138
129
  this.loadAccessories();
139
130
  }
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
131
  // start child HAP bridges
179
132
  for (const childBridge of this.childBridges.values()) {
180
133
  childBridge.start();
181
134
  }
182
- // start child Matter bridges
183
- for (const childMatterBridge of this.childMatterBridges.values()) {
184
- promises.push(childMatterBridge.start());
185
- }
186
135
  // restore cached accessories
187
136
  this.bridgeService.restoreCachedPlatformAccessories();
188
137
  this.api.signalFinished();
@@ -191,157 +140,144 @@ export class Server {
191
140
  .then(() => this.publishBridge());
192
141
  }
193
142
  /**
194
- * Get the status of all Matter bridges (child bridges only)
143
+ * Initialize Matter server for main bridge if enabled
195
144
  */
196
- getMatterBridgesStatus() {
197
- return Array.from(this.childMatterBridges.values()).map(bridge => bridge.getMetadata());
145
+ async initializeMatterServer() {
146
+ // Check if main bridge has matter configuration
147
+ if (!this.config.bridge.matter) {
148
+ return;
149
+ }
150
+ try {
151
+ log.info('Initializing Matter server for main bridge...');
152
+ // Allocate port from pool if not explicitly configured
153
+ let matterPort = this.config.bridge.matter.port;
154
+ if (!matterPort) {
155
+ matterPort = await this.externalPortService.requestPort(`${this.config.bridge.username}:MATTER`);
156
+ if (!matterPort) {
157
+ matterPort = 5540; // Default Matter port
158
+ log.warn('No port available from pool for main Matter bridge, using default port 5540');
159
+ }
160
+ else {
161
+ log.info(`Allocated port ${matterPort} from pool for main Matter bridge`);
162
+ }
163
+ }
164
+ // Create Matter server instance
165
+ this.matterServer = new MatterServer({
166
+ storagePath: User.matterPath(),
167
+ port: matterPort,
168
+ uniqueId: 'main-bridge',
169
+ name: this.config.bridge.matter.name || (this.config.bridge.name ? `${this.config.bridge.name} (Matter)` : 'Homebridge Matter Bridge'),
170
+ });
171
+ // Start the Matter server
172
+ await this.matterServer.start();
173
+ log.info('Matter server initialized for main bridge');
174
+ // Inform the API that Matter is enabled
175
+ this.api._setMatterEnabled(true);
176
+ // Send Matter status update to notify UI that Matter server is ready
177
+ if (this.config.bridge.disableIpc !== true) {
178
+ this.sendMainBridgeMatterStatusUpdate();
179
+ }
180
+ }
181
+ catch (error) {
182
+ log.error('Failed to initialize Matter server for main bridge:', error);
183
+ // Provide user-friendly guidance for common errors
184
+ if (error.message && error.message.includes('corrupted')) {
185
+ log.error('');
186
+ log.error('╔════════════════════════════════════════════════════════════════════════════╗');
187
+ log.error('║ MATTER STORAGE CORRUPTED ║');
188
+ log.error('╠════════════════════════════════════════════════════════════════════════════╣');
189
+ log.error('║ Your Matter storage has become corrupted. This can happen when: ║');
190
+ log.error('║ • Matter.js library version changes ║');
191
+ log.error('║ • Storage format upgrades occur ║');
192
+ log.error('║ • Incomplete writes during shutdown ║');
193
+ log.error('║ ║');
194
+ log.error('║ To fix this, delete the corrupted storage directory: ║');
195
+ log.error('║ rm -rf ~/.homebridge/matter/main-bridge ║');
196
+ log.error('║ ║');
197
+ log.error('║ Note: You will need to re-pair your Matter devices after deletion. ║');
198
+ log.error('╚════════════════════════════════════════════════════════════════════════════╝');
199
+ log.error('');
200
+ }
201
+ }
202
+ }
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);
198
228
  }
199
- teardown() {
229
+ async teardown() {
200
230
  this.bridgeService.teardown();
201
- // Stop all child Matter bridges
202
- for (const childMatterBridge of this.childMatterBridges.values()) {
203
- childMatterBridge.teardown().catch((error) => {
204
- log.error(`Failed to stop child Matter bridge ${childMatterBridge.getDisplayName()}:`, error);
205
- });
231
+ // Stop main Matter server if running
232
+ if (this.matterServer) {
233
+ try {
234
+ await this.matterServer.stop();
235
+ }
236
+ catch (error) {
237
+ log.error('Failed to stop Matter server:', error);
238
+ }
206
239
  }
240
+ // Child bridge Matter servers are stopped by their own forked processes
207
241
  this.setServerStatus("down" /* ServerStatus.DOWN */);
208
242
  }
209
243
  publishBridge() {
210
244
  this.bridgeService.publishBridge();
245
+ // Main bridge always has a pin (validated in loadConfig)
211
246
  this.printSetupInfo(this.config.bridge.pin);
212
247
  }
213
- // Matter accessories are managed via _matter configuration, not API methods
214
248
  handlePublishExternalAccessories(accessories) {
215
249
  log.info(`Publishing ${accessories.length} external accessories`);
216
- // External accessories are only published to child Matter bridges, not main bridge
217
- // Group accessories by plugin
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
- });
250
+ // External accessories are published via HAP
251
+ // Plugins should use api.matter to register Matter accessories explicitly
303
252
  }
304
253
  handleRegisterPlatformAccessories(accessories) {
305
- // Route to HAP bridge (default behavior)
254
+ // Route to HAP bridge
306
255
  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
256
  }
325
257
  handleUnregisterPlatformAccessories(accessories) {
326
- // Route to HAP bridge (default behavior)
258
+ // Route to HAP bridge
327
259
  this.bridgeService.handleUnregisterPlatformAccessories(accessories);
328
- // Check if we need to also unregister from Matter bridges
329
- for (const accessory of accessories) {
330
- const platformKey = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
331
- const platformConfig = this.platformConfigs.get(platformKey);
332
- if (platformConfig) {
333
- // Check if platform has child Matter bridge
334
- if (platformConfig._matter && typeof platformConfig._matter === 'object') {
335
- const matterIdentifier = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
336
- const childMatterBridge = this.childMatterBridges.get(matterIdentifier);
337
- if (childMatterBridge) {
338
- childMatterBridge.removeAccessory(accessory).catch((error) => {
339
- log.error(`Failed to remove accessory "${accessory.displayName}" from child Matter bridge:`, error);
340
- });
341
- }
342
- }
343
- }
260
+ }
261
+ handleRegisterMatterAccessory(accessory) {
262
+ if (!this.matterServer) {
263
+ log.warn('Cannot register Matter accessory - Matter server is not running');
264
+ return;
265
+ }
266
+ this.matterServer.registerAccessory(accessory);
267
+ }
268
+ handleUnregisterMatterAccessory(uuid) {
269
+ if (!this.matterServer) {
270
+ log.warn('Cannot unregister Matter accessory - Matter server is not running');
271
+ return;
344
272
  }
273
+ this.matterServer.unregisterAccessory(uuid);
274
+ }
275
+ handleUpdateMatterAccessoryState(uuid, cluster, attributes) {
276
+ if (!this.matterServer) {
277
+ log.warn('Cannot update Matter accessory state - Matter server is not running');
278
+ return;
279
+ }
280
+ this.matterServer.updateAccessoryState(uuid, cluster, attributes);
345
281
  }
346
282
  static loadConfig() {
347
283
  // Look for the configuration file
@@ -386,6 +322,7 @@ export class Server {
386
322
  bridge.username = bridge.username || defaultBridge.username;
387
323
  bridge.pin = bridge.pin || defaultBridge.pin;
388
324
  config.bridge = bridge;
325
+ // Main bridge username is always required and must be valid
389
326
  const username = config.bridge.username;
390
327
  if (!validMacAddress(username)) {
391
328
  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.`);
@@ -401,6 +338,48 @@ export class Server {
401
338
  config.platforms = [];
402
339
  }
403
340
  log.info('Loaded config.json with %s accessories and %s platforms.', config.accessories.length, config.platforms.length);
341
+ // Validate Matter configuration for port conflicts
342
+ if (config.bridge.matter || config.platforms.some((p) => p._bridge?.matter) || config.accessories.some((a) => a._bridge?.matter)) {
343
+ // Validate main bridge Matter config
344
+ 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
+ const validation = MatterConfigValidator.validate(config.bridge.matter);
350
+ if (!validation.isValid) {
351
+ log.error('Main bridge Matter configuration is invalid. Matter will not be enabled for the main bridge.');
352
+ delete config.bridge.matter;
353
+ }
354
+ }
355
+ // Validate all child bridge Matter configs and check for port conflicts
356
+ const childMatterValidation = MatterConfigValidator.validateAllChildMatterConfigs(config.platforms, config.accessories);
357
+ if (!childMatterValidation.isValid) {
358
+ log.error('Some child bridge Matter configurations are invalid. Check the errors above.');
359
+ }
360
+ // Additionally, check for conflicts between main bridge Matter port and child bridge ports
361
+ if (config.bridge.matter?.port) {
362
+ const mainMatterPort = config.bridge.matter.port;
363
+ const childMatterPorts = [];
364
+ for (const platform of config.platforms) {
365
+ if (platform._bridge?.matter?.port) {
366
+ childMatterPorts.push(platform._bridge.matter.port);
367
+ }
368
+ }
369
+ for (const accessory of config.accessories) {
370
+ if (accessory._bridge?.matter?.port) {
371
+ childMatterPorts.push(accessory._bridge.matter.port);
372
+ }
373
+ }
374
+ if (childMatterPorts.includes(mainMatterPort)) {
375
+ log.error(`Main bridge Matter port ${mainMatterPort} conflicts with a child bridge Matter port. Please use unique ports.`);
376
+ }
377
+ // Check for conflict with main bridge HAP port
378
+ if (config.bridge.port && Math.abs(config.bridge.port - mainMatterPort) < 10) {
379
+ log.warn(`Main bridge HAP port ${config.bridge.port} and Matter port ${mainMatterPort} are very close. Consider spacing them further apart.`);
380
+ }
381
+ }
382
+ }
404
383
  if (config.bridge.advertiser) {
405
384
  if (![
406
385
  "bonjour-hap" /* MDNSAdvertiser.BONJOUR */,
@@ -455,63 +434,37 @@ export class Server {
455
434
  }
456
435
  const logger = Logger.withPrefix(displayName);
457
436
  logger('Initializing %s accessory...', accessoryIdentifier);
437
+ // Handle child bridge (HAP, Matter-only, or HAP+Matter)
458
438
  if (accessoryConfig._bridge) {
459
- // ensure the username is always uppercase
460
- accessoryConfig._bridge.username = accessoryConfig._bridge.username.toUpperCase();
439
+ // Ensure the username is always uppercase if it exists (HAP bridges only)
440
+ if (accessoryConfig._bridge.username) {
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);
461
445
  try {
462
- this.validateChildBridgeConfig("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, accessoryConfig._bridge);
446
+ this.validateChildBridgeConfig("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, plugin.getPluginIdentifier(), accessoryConfig._bridge, bridgeKey);
463
447
  }
464
448
  catch (error) {
465
449
  log.error(error.message);
466
450
  return;
467
451
  }
468
452
  let childBridge;
469
- if (this.childBridges.has(accessoryConfig._bridge.username)) {
470
- childBridge = this.childBridges.get(accessoryConfig._bridge.username);
471
- logger(`Adding to existing child bridge ${accessoryConfig._bridge.username}`);
453
+ if (this.childBridges.has(bridgeKey)) {
454
+ childBridge = this.childBridges.get(bridgeKey);
455
+ logger(`Adding to existing child bridge ${bridgeKey}`);
472
456
  }
473
457
  else {
474
- logger(`Initializing child bridge ${accessoryConfig._bridge.username}`);
458
+ const bridgeType = accessoryConfig._bridge.matterOnly ? 'Matter-only' : 'HAP';
459
+ logger(`Initializing ${bridgeType} child bridge ${bridgeKey}`);
475
460
  childBridge = new ChildBridgeService("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, plugin, accessoryConfig._bridge, this.config, this.options, this.api, this.ipcService, this.externalPortService);
476
- this.childBridges.set(accessoryConfig._bridge.username, childBridge);
461
+ this.childBridges.set(bridgeKey, childBridge);
477
462
  }
478
463
  // add config to child bridge service
479
464
  childBridge.addConfig(accessoryConfig);
480
- // If _matter is not defined, we're done - this accessory only uses HAP child bridge
481
- if (!accessoryConfig._matter) {
482
- return;
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;
465
+ // Matter for child bridges is handled inside the forked child process (childBridgeFork.ts)
466
+ // to maintain isolation - each child process manages its own Matter server
467
+ return; // Done - child bridge
515
468
  }
516
469
  const accessoryInstance = new constructor(logger, accessoryConfig, this.api);
517
470
  // pass accessoryIdentifier for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
@@ -568,59 +521,29 @@ export class Server {
568
521
  }
569
522
  const logger = Logger.withPrefix(displayName);
570
523
  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
- // Handle child HAP bridge
524
+ // Handle child bridge (HAP, Matter-only, or HAP+Matter)
575
525
  if (platformConfig._bridge) {
576
- // ensure the username is always uppercase
577
- platformConfig._bridge.username = platformConfig._bridge.username.toUpperCase();
526
+ // Ensure the username is always uppercase if it exists (HAP bridges only)
527
+ if (platformConfig._bridge.username) {
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);
578
532
  try {
579
- this.validateChildBridgeConfig("platform" /* PluginType.PLATFORM */, platformIdentifier, platformConfig._bridge);
533
+ this.validateChildBridgeConfig("platform" /* PluginType.PLATFORM */, platformIdentifier, plugin.getPluginIdentifier(), platformConfig._bridge, bridgeKey);
580
534
  }
581
535
  catch (error) {
582
536
  log.error(error.message);
583
537
  return;
584
538
  }
585
- logger(`Initializing child bridge ${platformConfig._bridge.username}`);
539
+ const bridgeType = platformConfig._bridge.matterOnly ? 'Matter-only' : 'HAP';
540
+ logger(`Initializing ${bridgeType} child bridge ${bridgeKey}`);
586
541
  const childBridge = new ChildBridgeService("platform" /* PluginType.PLATFORM */, platformIdentifier, plugin, platformConfig._bridge, this.config, this.options, this.api, this.ipcService, this.externalPortService);
587
- this.childBridges.set(platformConfig._bridge.username, childBridge);
542
+ this.childBridges.set(bridgeKey, childBridge);
588
543
  // add config to child bridge service
589
544
  childBridge.addConfig(platformConfig);
590
- // If _matter is not defined, we're done - this platform only uses HAP child bridge
591
- if (!platformConfig._matter) {
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) {
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
624
547
  return;
625
548
  }
626
549
  const platform = new constructor(logger, platformConfig, this.api);
@@ -637,16 +560,48 @@ export class Server {
637
560
  });
638
561
  return promises;
639
562
  }
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
+ }
640
576
  /**
641
577
  * Validate an external bridge config
642
578
  */
643
- validateChildBridgeConfig(type, identifier, bridgeConfig) {
579
+ validateChildBridgeConfig(type, identifier, pluginName, bridgeConfig, bridgeKey) {
580
+ // Matter-only bridges don't require HAP fields
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
595
+ if (!bridgeConfig.username) {
596
+ throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
597
+ + 'Missing required field "_bridge.username". Set "matterOnly": true if you want a Matter-only bridge.');
598
+ }
644
599
  if (!validMacAddress(bridgeConfig.username)) {
645
600
  throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
646
601
  + `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.`);
647
602
  }
648
- if (this.childBridges.has(bridgeConfig.username)) {
649
- const childBridge = this.childBridges.get(bridgeConfig.username);
603
+ if (this.childBridges.has(bridgeKey)) {
604
+ const childBridge = this.childBridges.get(bridgeKey);
650
605
  if (type === "platform" /* PluginType.PLATFORM */) {
651
606
  // only a single platform can exist on one child bridge
652
607
  throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
@@ -658,6 +613,8 @@ export class Server {
658
613
  + `Duplicate username found in _bridge.username: "${bridgeConfig.username}". You can only group accessories of the same type in a child bridge.`);
659
614
  }
660
615
  }
616
+ // Check that child bridge username doesn't conflict with main bridge
617
+ // Main bridge always has a username (validated in loadConfig)
661
618
  if (bridgeConfig.username === this.config.bridge.username.toUpperCase()) {
662
619
  throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
663
620
  + `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.`);
@@ -674,7 +631,9 @@ export class Server {
674
631
  // noinspection SuspiciousTypeOfGuard
675
632
  if (typeof username === 'string') {
676
633
  const childBridge = this.childBridges.get(username.toUpperCase());
677
- childBridge?.restartChildBridge();
634
+ if (childBridge) {
635
+ childBridge.restartChildBridge();
636
+ }
678
637
  }
679
638
  });
680
639
  // handle stop child bridge event
@@ -682,7 +641,9 @@ export class Server {
682
641
  // noinspection SuspiciousTypeOfGuard
683
642
  if (typeof username === 'string') {
684
643
  const childBridge = this.childBridges.get(username.toUpperCase());
685
- childBridge?.stopChildBridge();
644
+ if (childBridge) {
645
+ childBridge.stopChildBridge();
646
+ }
686
647
  }
687
648
  });
688
649
  // handle start child bridge event
@@ -690,110 +651,176 @@ export class Server {
690
651
  // noinspection SuspiciousTypeOfGuard
691
652
  if (typeof username === 'string') {
692
653
  const childBridge = this.childBridges.get(username.toUpperCase());
693
- childBridge?.startChildBridge();
654
+ if (childBridge) {
655
+ childBridge.startChildBridge();
656
+ }
694
657
  }
695
658
  });
696
659
  this.ipcService.on("childBridgeMetadataRequest" /* IpcIncomingEvent.CHILD_BRIDGE_METADATA_REQUEST */, () => {
697
- this.ipcService.sendMessage("childBridgeMetadataResponse" /* IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE */, Array.from(this.childBridges.values()).map(x => x.getMetadata()));
660
+ const childBridgeMetadata = Array.from(this.childBridges.values()).map(x => x.getMetadata());
661
+ this.ipcService.sendMessage("childBridgeMetadataResponse" /* IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE */, childBridgeMetadata);
698
662
  });
699
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)
700
666
  this.ipcService.on("restartMatterBridge" /* IpcIncomingEvent.RESTART_MATTER_BRIDGE */, (matterBridgeId) => {
701
- log.info(`Received restart request for Matter bridge: ${matterBridgeId}`);
702
- if (typeof matterBridgeId === 'string') {
703
- const matterBridge = this.childMatterBridges.get(matterBridgeId);
704
- if (matterBridge) {
705
- log.info(`Restarting Matter bridge: ${matterBridgeId}`);
706
- matterBridge.restartChildBridge();
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();
707
677
  }
708
678
  else {
709
- log.warn(`Matter bridge not found: ${matterBridgeId}`);
710
- log.debug('Available Matter bridges:', Array.from(this.childMatterBridges.keys()));
679
+ log.warn(`Child bridge ${matterBridgeId} not found for Matter restart`);
711
680
  }
712
681
  }
713
682
  });
714
683
  this.ipcService.on("stopMatterBridge" /* IpcIncomingEvent.STOP_MATTER_BRIDGE */, (matterBridgeId) => {
715
- log.info(`Received stop request for Matter bridge: ${matterBridgeId}`);
716
- if (typeof matterBridgeId === 'string') {
717
- const matterBridge = this.childMatterBridges.get(matterBridgeId);
718
- if (matterBridge) {
719
- log.info(`Stopping Matter bridge: ${matterBridgeId}`);
720
- matterBridge.stopChildBridge();
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();
721
693
  }
722
694
  else {
723
- log.warn(`Matter bridge not found: ${matterBridgeId}`);
695
+ log.warn(`Child bridge ${matterBridgeId} not found for Matter stop`);
724
696
  }
725
697
  }
726
698
  });
727
699
  this.ipcService.on("startMatterBridge" /* IpcIncomingEvent.START_MATTER_BRIDGE */, (matterBridgeId) => {
728
- log.info(`Received start request for Matter bridge: ${matterBridgeId}`);
729
- if (typeof matterBridgeId === 'string') {
730
- const matterBridge = this.childMatterBridges.get(matterBridgeId);
731
- if (matterBridge) {
732
- log.info(`Starting Matter bridge: ${matterBridgeId}`);
733
- matterBridge.startChildBridge();
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();
734
709
  }
735
710
  else {
736
- log.warn(`Matter bridge not found: ${matterBridgeId}`);
711
+ log.warn(`Child bridge ${matterBridgeId} not found for Matter start`);
737
712
  }
738
713
  }
739
714
  });
740
715
  this.ipcService.on("matterBridgeMetadataRequest" /* IpcIncomingEvent.MATTER_BRIDGE_METADATA_REQUEST */, () => {
741
- // Return all child Matter bridge metadata
742
- this.ipcService.sendMessage("matterBridgeMetadataResponse" /* IpcOutgoingEvent.MATTER_BRIDGE_METADATA_RESPONSE */, Array.from(this.childMatterBridges.values()).map(x => x.getMetadata()));
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);
743
762
  });
744
763
  this.ipcService.on("matterAccessoriesRequest" /* IpcIncomingEvent.MATTER_ACCESSORIES_REQUEST */, () => {
745
- // Collect Matter accessories from all child bridges
764
+ // Return Matter accessories from all bridges
746
765
  const allMatterAccessories = {
747
766
  children: {},
748
767
  };
749
- // Add child bridge accessories
750
- for (const [bridgeId, childBridge] of this.childMatterBridges) {
751
- allMatterAccessories.children[bridgeId] = childBridge.getMatterAccessories ? childBridge.getMatterAccessories() : [];
768
+ // Add main bridge accessories
769
+ if (this.matterServer) {
770
+ allMatterAccessories.children['main-bridge'] = this.matterServer.getAccessories();
752
771
  }
772
+ // Child bridge accessories are managed by child processes
753
773
  this.ipcService.sendMessage("matterAccessoriesResponse" /* IpcOutgoingEvent.MATTER_ACCESSORIES_RESPONSE */, allMatterAccessories);
754
774
  });
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
775
  this.ipcService.on("matterCommissioningInfoRequest" /* IpcIncomingEvent.MATTER_COMMISSIONING_INFO_REQUEST */, (matterBridgeId) => {
769
- let commissioningInfo = null;
770
- // Get child bridge commissioning info
771
- const childBridge = this.childMatterBridges.get(matterBridgeId);
772
- if (childBridge && childBridge.getCommissioningInfo) {
773
- commissioningInfo = childBridge.getCommissioningInfo();
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
+ }
774
796
  }
775
- this.ipcService.sendMessage("matterCommissioningInfoResponse" /* IpcOutgoingEvent.MATTER_COMMISSIONING_INFO_RESPONSE */, commissioningInfo || { commissioned: false });
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);
776
805
  });
777
806
  }
778
807
  printSetupInfo(pin) {
779
- /* eslint-disable no-console */
780
- console.log('Setup Payload:');
781
- console.log(this.bridgeService.bridge.setupURI());
808
+ log.info('Setup Payload:');
809
+ log.info(this.bridgeService.bridge.setupURI());
782
810
  if (!this.options.hideQRCode) {
783
- console.log('Scan this code with your HomeKit app on your iOS device to pair with Homebridge:');
811
+ log.info('Scan this code with your HomeKit app on your iOS device to pair with Homebridge:');
784
812
  qrcode.setErrorLevel('M'); // HAP specifies level M or higher for ECC
785
813
  qrcode.generate(this.bridgeService.bridge.setupURI());
786
- console.log('Or enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
814
+ log.info('Or enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
787
815
  }
788
816
  else {
789
- console.log('Enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
817
+ log.info('Enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
790
818
  }
791
- console.log(chalk.black.bgWhite(' '));
792
- console.log(chalk.black.bgWhite(' ┌────────────┐ '));
793
- console.log(chalk.black.bgWhite(` │ ${pin} │ `));
794
- console.log(chalk.black.bgWhite(' └────────────┘ '));
795
- console.log(chalk.black.bgWhite(' '));
796
- /* eslint-enable no-console */
819
+ log.info(chalk.black.bgWhite(' '));
820
+ log.info(chalk.black.bgWhite(' ┌────────────┐ '));
821
+ log.info(chalk.black.bgWhite(` │ ${pin} │ `));
822
+ log.info(chalk.black.bgWhite(' └────────────┘ '));
823
+ log.info(chalk.black.bgWhite(' '));
797
824
  }
798
825
  }
799
826
  //# sourceMappingURL=server.js.map