homebridge 2.0.0-alpha.7 → 2.0.0-alpha.70

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 (112) hide show
  1. package/README.md +1 -1
  2. package/bin/homebridge.js +22 -0
  3. package/dist/api.d.ts +250 -3
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/api.js +92 -0
  6. package/dist/api.js.map +1 -1
  7. package/dist/bridgeService.d.ts +11 -3
  8. package/dist/bridgeService.d.ts.map +1 -1
  9. package/dist/bridgeService.js +9 -5
  10. package/dist/bridgeService.js.map +1 -1
  11. package/dist/childBridgeFork.d.ts +30 -3
  12. package/dist/childBridgeFork.d.ts.map +1 -1
  13. package/dist/childBridgeFork.js +278 -5
  14. package/dist/childBridgeFork.js.map +1 -1
  15. package/dist/childBridgeService.d.ts +22 -0
  16. package/dist/childBridgeService.d.ts.map +1 -1
  17. package/dist/childBridgeService.js +85 -26
  18. package/dist/childBridgeService.js.map +1 -1
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +3 -2
  21. package/dist/cli.js.map +1 -1
  22. package/dist/externalPortService.d.ts +27 -6
  23. package/dist/externalPortService.d.ts.map +1 -1
  24. package/dist/externalPortService.js +73 -7
  25. package/dist/externalPortService.js.map +1 -1
  26. package/dist/index.d.ts +48 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +15 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/ipcService.d.ts +20 -0
  31. package/dist/ipcService.d.ts.map +1 -1
  32. package/dist/ipcService.js.map +1 -1
  33. package/dist/logger.d.ts +6 -0
  34. package/dist/logger.d.ts.map +1 -1
  35. package/dist/logger.js +8 -0
  36. package/dist/logger.js.map +1 -1
  37. package/dist/matter/index.d.ts +123 -0
  38. package/dist/matter/index.d.ts.map +1 -0
  39. package/dist/matter/index.js +19 -0
  40. package/dist/matter/index.js.map +1 -0
  41. package/dist/matter/matterAccessoryCache.d.ts +77 -0
  42. package/dist/matter/matterAccessoryCache.d.ts.map +1 -0
  43. package/dist/matter/matterAccessoryCache.js +176 -0
  44. package/dist/matter/matterAccessoryCache.js.map +1 -0
  45. package/dist/matter/matterBehaviors.d.ts +177 -0
  46. package/dist/matter/matterBehaviors.d.ts.map +1 -0
  47. package/dist/matter/matterBehaviors.js +639 -0
  48. package/dist/matter/matterBehaviors.js.map +1 -0
  49. package/dist/matter/matterConfigValidator.d.ts +81 -0
  50. package/dist/matter/matterConfigValidator.d.ts.map +1 -0
  51. package/dist/matter/matterConfigValidator.js +240 -0
  52. package/dist/matter/matterConfigValidator.js.map +1 -0
  53. package/dist/matter/matterErrorHandler.d.ts +94 -0
  54. package/dist/matter/matterErrorHandler.d.ts.map +1 -0
  55. package/dist/matter/matterErrorHandler.js +485 -0
  56. package/dist/matter/matterErrorHandler.js.map +1 -0
  57. package/dist/matter/matterLogFormatter.d.ts +19 -0
  58. package/dist/matter/matterLogFormatter.d.ts.map +1 -0
  59. package/dist/matter/matterLogFormatter.js +126 -0
  60. package/dist/matter/matterLogFormatter.js.map +1 -0
  61. package/dist/matter/matterNetworkMonitor.d.ts +68 -0
  62. package/dist/matter/matterNetworkMonitor.d.ts.map +1 -0
  63. package/dist/matter/matterNetworkMonitor.js +249 -0
  64. package/dist/matter/matterNetworkMonitor.js.map +1 -0
  65. package/dist/matter/matterServer.d.ts +644 -0
  66. package/dist/matter/matterServer.d.ts.map +1 -0
  67. package/dist/matter/matterServer.js +1459 -0
  68. package/dist/matter/matterServer.js.map +1 -0
  69. package/dist/matter/matterServerHelpers.d.ts +76 -0
  70. package/dist/matter/matterServerHelpers.d.ts.map +1 -0
  71. package/dist/matter/matterServerHelpers.js +298 -0
  72. package/dist/matter/matterServerHelpers.js.map +1 -0
  73. package/dist/matter/matterSharedTypes.d.ts +165 -0
  74. package/dist/matter/matterSharedTypes.d.ts.map +1 -0
  75. package/dist/matter/matterSharedTypes.js +51 -0
  76. package/dist/matter/matterSharedTypes.js.map +1 -0
  77. package/dist/matter/matterStorage.d.ts +126 -0
  78. package/dist/matter/matterStorage.d.ts.map +1 -0
  79. package/dist/matter/matterStorage.js +419 -0
  80. package/dist/matter/matterStorage.js.map +1 -0
  81. package/dist/matter/matterTypes.d.ts +668 -0
  82. package/dist/matter/matterTypes.d.ts.map +1 -0
  83. package/dist/matter/matterTypes.js +174 -0
  84. package/dist/matter/matterTypes.js.map +1 -0
  85. package/dist/platformAccessory.d.ts +1 -0
  86. package/dist/platformAccessory.d.ts.map +1 -1
  87. package/dist/platformAccessory.js +8 -1
  88. package/dist/platformAccessory.js.map +1 -1
  89. package/dist/plugin.d.ts +0 -1
  90. package/dist/plugin.d.ts.map +1 -1
  91. package/dist/plugin.js +8 -11
  92. package/dist/plugin.js.map +1 -1
  93. package/dist/pluginManager.d.ts.map +1 -1
  94. package/dist/pluginManager.js +22 -21
  95. package/dist/pluginManager.js.map +1 -1
  96. package/dist/server.d.ts +23 -1
  97. package/dist/server.d.ts.map +1 -1
  98. package/dist/server.js +374 -10
  99. package/dist/server.js.map +1 -1
  100. package/dist/storageService.js +8 -8
  101. package/dist/storageService.js.map +1 -1
  102. package/dist/user.d.ts +1 -0
  103. package/dist/user.d.ts.map +1 -1
  104. package/dist/user.js +10 -7
  105. package/dist/user.js.map +1 -1
  106. package/dist/util/mac.d.ts.map +1 -1
  107. package/dist/util/mac.js +2 -2
  108. package/dist/util/mac.js.map +1 -1
  109. package/dist/version.js +2 -2
  110. package/dist/version.js.map +1 -1
  111. package/package.json +23 -22
  112. package/bin/homebridge +0 -19
package/dist/server.d.ts CHANGED
@@ -32,6 +32,8 @@ export declare class Server {
32
32
  private readonly externalPortService;
33
33
  private readonly config;
34
34
  private readonly childBridges;
35
+ private matterServer?;
36
+ private readonly externalMatterServers;
35
37
  private serverStatus;
36
38
  constructor(options?: HomebridgeOptions);
37
39
  /**
@@ -40,8 +42,28 @@ export declare class Server {
40
42
  */
41
43
  private setServerStatus;
42
44
  start(): Promise<void>;
43
- teardown(): void;
45
+ /**
46
+ * Initialize Matter server for main bridge if enabled
47
+ */
48
+ private initializeMatterServer;
49
+ teardown(): Promise<void>;
44
50
  private publishBridge;
51
+ private handlePublishExternalAccessories;
52
+ /**
53
+ * Handle external Matter accessories - each gets its own dedicated Matter server
54
+ * This is required for devices like Robotic Vacuum Cleaners that Apple Home
55
+ * requires to be on their own bridge.
56
+ */
57
+ private handlePublishExternalMatterAccessories;
58
+ private handleRegisterPlatformAccessories;
59
+ private handleUnregisterPlatformAccessories;
60
+ private handleRegisterMatterPlatformAccessories;
61
+ private handleUnregisterMatterPlatformAccessories;
62
+ private handleUpdateMatterAccessoryState;
63
+ /**
64
+ * Restore cached Matter accessories (matching HAP pattern)
65
+ */
66
+ private restoreCachedMatterAccessories;
45
67
  private static loadConfig;
46
68
  private loadAccessories;
47
69
  private loadPlatforms;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,iBAAiB;IAChC,6BAA6B,CAAC,EAAE,OAAO,CAAA;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAGD,0BAAkB,YAAY;IAC5B;;OAEG;IACH,OAAO,YAAY;IAEnB;;OAEG;IACH,EAAE,OAAO;IAET;;OAEG;IACH,IAAI,SAAS;CACd;AAED,qBAAa,MAAM;IAgBf,OAAO,CAAC,OAAO;IAfjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAGzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiD;IAG9E,OAAO,CAAC,YAAY,CAAqC;gBAG/C,OAAO,GAAE,iBAAsB;IAuDzC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAYV,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC5B,QAAQ,IAAI,IAAI;IAKvB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,MAAM,CAAC,UAAU;IAoFzB,OAAO,CAAC,eAAe;IAoGvB,OAAO,CAAC,aAAa;IA4FrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAiCjC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAoClC,OAAO,CAAC,cAAc;CAqBvB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,iBAAiB;IAChC,6BAA6B,CAAC,EAAE,OAAO,CAAA;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAA;CACjC;AAGD,0BAAkB,YAAY;IAC5B;;OAEG;IACH,OAAO,YAAY;IAEnB;;OAEG;IACH,EAAE,OAAO;IAET;;OAEG;IACH,IAAI,SAAS;CACd;AAED,qBAAa,MAAM;IAwBf,OAAO,CAAC,OAAO;IAvBjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAIzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6C;IAG1E,OAAO,CAAC,YAAY,CAAC,CAAc;IAInC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuC;IAG7E,OAAO,CAAC,YAAY,CAAqC;gBAG/C,OAAO,GAAE,iBAAsB;IAmEzC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAsCV,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCnC;;OAEG;YACW,sBAAsB;IA8FvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BtC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,gCAAgC;IAMxC;;;;OAIG;YACW,sCAAsC;IA6EpD,OAAO,CAAC,iCAAiC;IAKzC,OAAO,CAAC,mCAAmC;IAK3C,OAAO,CAAC,uCAAuC;IAU/C,OAAO,CAAC,yCAAyC;IAUjD,OAAO,CAAC,gCAAgC;IAUxC;;OAEG;IACH,OAAO,CAAC,8BAA8B;IA8CtC,OAAO,CAAC,MAAM,CAAC,UAAU;IAiJzB,OAAO,CAAC,eAAe;IAoGvB,OAAO,CAAC,aAAa;IA4FrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAyCjC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAuClC,OAAO,CAAC,cAAc;CAqBvB"}
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import fs from 'node:fs';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
2
  import process from 'node:process';
3
3
  import chalk from 'chalk';
4
4
  import qrcode from 'qrcode-terminal';
@@ -8,9 +8,10 @@ import { ChildBridgeService } from './childBridgeService.js';
8
8
  import { ExternalPortService } from './externalPortService.js';
9
9
  import { IpcService } from './ipcService.js';
10
10
  import { Logger } from './logger.js';
11
+ import { MatterConfigValidator, MatterServer } from './matter/index.js';
11
12
  import { PluginManager } from './pluginManager.js';
12
13
  import { User } from './user.js';
13
- import { validMacAddress } from './util/mac.js';
14
+ import { generate, validMacAddress } from './util/mac.js';
14
15
  const log = Logger.internal;
15
16
  // eslint-disable-next-line no-restricted-syntax
16
17
  export var ServerStatus;
@@ -37,7 +38,13 @@ export class Server {
37
38
  externalPortService;
38
39
  config;
39
40
  // used to keep track of child bridges
41
+ // Key is HAP username (MAC address)
40
42
  childBridges = new Map();
43
+ // Matter server instance for main bridge (if enabled)
44
+ matterServer;
45
+ // External Matter servers for accessories that need their own bridge
46
+ // Key is accessory UUID, value is MatterServer instance
47
+ externalMatterServers = new Map();
41
48
  // current server status
42
49
  serverStatus = "pending" /* ServerStatus.PENDING */;
43
50
  constructor(options = {}) {
@@ -46,7 +53,7 @@ export class Server {
46
53
  // object we feed to Plugins and BridgeService
47
54
  this.api = new HomebridgeAPI();
48
55
  this.ipcService = new IpcService();
49
- this.externalPortService = new ExternalPortService(this.config.ports);
56
+ this.externalPortService = new ExternalPortService(this.config.ports, this.config.matterPorts);
50
57
  // set status to pending
51
58
  this.setServerStatus("pending" /* ServerStatus.PENDING */);
52
59
  // create new plugin manager
@@ -64,7 +71,17 @@ export class Server {
64
71
  };
65
72
  // shallow copy the homebridge options to the bridge options object
66
73
  Object.assign(bridgeConfig, this.options);
67
- this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge, this.config);
74
+ this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge);
75
+ // Handle platform accessory registration
76
+ this.api.on("registerPlatformAccessories" /* InternalAPIEvent.REGISTER_PLATFORM_ACCESSORIES */, this.handleRegisterPlatformAccessories.bind(this));
77
+ this.api.on("unregisterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_PLATFORM_ACCESSORIES */, this.handleUnregisterPlatformAccessories.bind(this));
78
+ // Handle external accessories (cameras, etc.)
79
+ this.api.on("publishExternalAccessories" /* InternalAPIEvent.PUBLISH_EXTERNAL_ACCESSORIES */, this.handlePublishExternalAccessories.bind(this));
80
+ // Handle Matter accessory registration (matching HAP pattern)
81
+ this.api.on("publishExternalMatterAccessories" /* InternalAPIEvent.PUBLISH_EXTERNAL_MATTER_ACCESSORIES */, this.handlePublishExternalMatterAccessories.bind(this));
82
+ this.api.on("registerMatterPlatformAccessories" /* InternalAPIEvent.REGISTER_MATTER_PLATFORM_ACCESSORIES */, this.handleRegisterMatterPlatformAccessories.bind(this));
83
+ this.api.on("unregisterMatterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_MATTER_PLATFORM_ACCESSORIES */, this.handleUnregisterMatterPlatformAccessories.bind(this));
84
+ this.api.on("updateMatterAccessoryState" /* InternalAPIEvent.UPDATE_MATTER_ACCESSORY_STATE */, this.handleUpdateMatterAccessoryState.bind(this));
68
85
  // watch bridge events to check when server is online
69
86
  this.bridgeService.bridge.on("advertised" /* AccessoryEventTypes.ADVERTISED */, () => {
70
87
  this.setServerStatus("ok" /* ServerStatus.OK */);
@@ -84,14 +101,38 @@ export class Server {
84
101
  */
85
102
  setServerStatus(status) {
86
103
  this.serverStatus = status;
87
- this.ipcService.sendMessage("serverStatusUpdate" /* IpcOutgoingEvent.SERVER_STATUS_UPDATE */, {
104
+ const statusUpdate = {
88
105
  status: this.serverStatus,
89
106
  paired: this.bridgeService?.bridge?._accessoryInfo?.paired() ?? null,
90
107
  setupUri: this.bridgeService?.bridge?.setupURI() ?? null,
91
- name: this.bridgeService?.bridge?.displayName || this.config.bridge.name,
108
+ name: this.config.bridge.name,
92
109
  username: this.config.bridge.username,
93
110
  pin: this.config.bridge.pin,
94
- });
111
+ matter: {
112
+ enabled: false,
113
+ },
114
+ };
115
+ // Include Matter commissioning info if Matter is enabled
116
+ if (this.matterServer) {
117
+ const commissioningInfo = this.matterServer.getCommissioningInfo();
118
+ statusUpdate.matter = {
119
+ enabled: true,
120
+ port: this.config.bridge.matter?.port,
121
+ setupUri: commissioningInfo.qrCode,
122
+ pin: commissioningInfo.manualPairingCode,
123
+ serialNumber: commissioningInfo.serialNumber,
124
+ commissioned: commissioningInfo.commissioned || false,
125
+ deviceCount: this.matterServer.getAccessories().length,
126
+ };
127
+ }
128
+ else if (this.config.bridge.matter) {
129
+ // Matter is configured but not yet started (or failed to start)
130
+ statusUpdate.matter = {
131
+ enabled: false,
132
+ port: this.config.bridge.matter?.port,
133
+ };
134
+ }
135
+ this.ipcService.sendMessage("serverStatusUpdate" /* IpcOutgoingEvent.SERVER_STATUS_UPDATE */, statusUpdate);
95
136
  }
96
137
  async start() {
97
138
  if (this.config.bridge.disableIpc !== true) {
@@ -102,6 +143,8 @@ export class Server {
102
143
  await this.bridgeService.loadCachedPlatformAccessoriesFromDisk();
103
144
  // initialize plugins
104
145
  await this.pluginManager.initializeInstalledPlugins();
146
+ // Initialize Matter server for main bridge if enabled
147
+ await this.initializeMatterServer();
105
148
  if (this.config.platforms.length > 0) {
106
149
  promises.push(...this.loadPlatforms());
107
150
  }
@@ -114,19 +157,282 @@ export class Server {
114
157
  }
115
158
  // restore cached accessories
116
159
  this.bridgeService.restoreCachedPlatformAccessories();
160
+ this.restoreCachedMatterAccessories();
117
161
  this.api.signalFinished();
118
162
  // wait for all platforms to publish their accessories before we publish the bridge
119
163
  await Promise.all(promises)
120
164
  .then(() => this.publishBridge());
121
165
  }
122
- teardown() {
166
+ /**
167
+ * Initialize Matter server for main bridge if enabled
168
+ */
169
+ async initializeMatterServer() {
170
+ // Check if main bridge has matter configuration
171
+ if (!this.config.bridge.matter) {
172
+ return;
173
+ }
174
+ // Declare matterPort outside try block so it's accessible in catch
175
+ let matterPort;
176
+ try {
177
+ log.info('Initializing Matter server for main bridge...');
178
+ // Allocate port from pool if not explicitly configured
179
+ matterPort = this.config.bridge.matter.port;
180
+ if (!matterPort) {
181
+ matterPort = await this.externalPortService.requestPort(`${this.config.bridge.username}:MATTER`);
182
+ if (!matterPort) {
183
+ matterPort = 5540; // Default Matter port
184
+ log.warn('No port available from pool for main Matter bridge, using default port 5540');
185
+ }
186
+ else {
187
+ log.info(`Allocated port ${matterPort} from pool for main Matter bridge`);
188
+ }
189
+ }
190
+ // Create Matter server instance with config inheritance from main bridge
191
+ const serialNumber = this.config.bridge.username.replace(/:/g, '');
192
+ this.matterServer = new MatterServer({
193
+ storagePath: User.matterPath(),
194
+ port: matterPort,
195
+ uniqueId: serialNumber,
196
+ manufacturer: this.config.bridge.manufacturer,
197
+ model: this.config.bridge.model,
198
+ firmwareRevision: this.config.bridge.firmwareRevision,
199
+ serialNumber,
200
+ debugModeEnabled: this.options.debugModeEnabled,
201
+ });
202
+ // Start the Matter server
203
+ await this.matterServer.start();
204
+ log.info('Matter server initialized for main bridge');
205
+ // Inform the API that Matter is enabled
206
+ this.api._setMatterEnabled(true);
207
+ // Set the Matter server reference for API methods like getAccessoryState
208
+ this.api._setMatterServer(this.matterServer);
209
+ // Listen for Matter commissioning events to update status
210
+ this.matterServer.on('commissioning-changed', (commissioned, fabricCount) => {
211
+ log.info(`Matter commissioning state changed: commissioned=${commissioned}, fabricCount=${fabricCount}`);
212
+ this.setServerStatus(this.serverStatus);
213
+ });
214
+ }
215
+ catch (error) {
216
+ log.error('Failed to initialize Matter server for main bridge:', error);
217
+ // Provide user-friendly guidance for common errors
218
+ if (error.message && error.message.includes('corrupted')) {
219
+ log.error('');
220
+ log.error('╔════════════════════════════════════════════════════════════════════════════╗');
221
+ log.error('║ MATTER STORAGE CORRUPTED ║');
222
+ log.error('╠════════════════════════════════════════════════════════════════════════════╣');
223
+ log.error('║ Your Matter storage has become corrupted. This can happen when: ║');
224
+ log.error('║ • Matter.js library version changes ║');
225
+ log.error('║ • Storage format upgrades occur ║');
226
+ log.error('║ • Incomplete writes during shutdown ║');
227
+ log.error('║ ║');
228
+ log.error('║ To fix this, delete the corrupted storage directory: ║');
229
+ log.error(`║ rm -rf ~/.homebridge/matter/${this.config.bridge.username} ║`);
230
+ log.error('║ ║');
231
+ log.error('║ Note: You will need to re-pair your Matter devices after deletion. ║');
232
+ log.error('╚════════════════════════════════════════════════════════════════════════════╝');
233
+ log.error('');
234
+ }
235
+ else if (error.code === 'EADDRINUSE' || (error.message && error.message.includes('address already in use'))) {
236
+ log.error('');
237
+ log.error('╔════════════════════════════════════════════════════════════════════════════╗');
238
+ log.error('║ MATTER PORT ALREADY IN USE ║');
239
+ log.error('╠════════════════════════════════════════════════════════════════════════════╣');
240
+ log.error(`║ Port ${matterPort} is already in use by another application. ║`);
241
+ log.error('║ ║');
242
+ log.error('║ To fix this: ║');
243
+ log.error('║ 1. Stop the application using this port, or ║');
244
+ log.error('║ 2. Configure a different port in your config.json: ║');
245
+ log.error('║ "bridge": { ║');
246
+ log.error('║ "matter": { ║');
247
+ log.error('║ "port": <different-port> ║');
248
+ log.error('║ } ║');
249
+ log.error('║ } ║');
250
+ log.error('╚════════════════════════════════════════════════════════════════════════════╝');
251
+ log.error('');
252
+ }
253
+ }
254
+ }
255
+ async teardown() {
123
256
  this.bridgeService.teardown();
257
+ // Stop main Matter server if running
258
+ if (this.matterServer) {
259
+ try {
260
+ await this.matterServer.stop();
261
+ }
262
+ catch (error) {
263
+ log.error('Failed to stop Matter server:', error);
264
+ }
265
+ }
266
+ // Stop all external Matter servers
267
+ for (const [uuid, matterServer] of this.externalMatterServers) {
268
+ try {
269
+ await matterServer.stop();
270
+ log.debug(`Stopped external Matter server for ${uuid}`);
271
+ }
272
+ catch (error) {
273
+ log.error(`Failed to stop external Matter server for ${uuid}:`, error);
274
+ }
275
+ }
276
+ this.externalMatterServers.clear();
277
+ // Child bridge Matter servers are stopped by their own forked processes
124
278
  this.setServerStatus("down" /* ServerStatus.DOWN */);
125
279
  }
126
280
  publishBridge() {
127
281
  this.bridgeService.publishBridge();
128
282
  this.printSetupInfo(this.config.bridge.pin);
129
283
  }
284
+ handlePublishExternalAccessories(accessories) {
285
+ log.info(`Publishing ${accessories.length} external accessories`);
286
+ // External accessories are published via HAP
287
+ // Plugins should use api.matter to register Matter accessories explicitly
288
+ }
289
+ /**
290
+ * Handle external Matter accessories - each gets its own dedicated Matter server
291
+ * This is required for devices like Robotic Vacuum Cleaners that Apple Home
292
+ * requires to be on their own bridge.
293
+ */
294
+ async handlePublishExternalMatterAccessories(accessories) {
295
+ log.info(`Publishing ${accessories.length} external Matter accessor${accessories.length === 1 ? 'y' : 'ies'}`);
296
+ for (const accessory of accessories) {
297
+ try {
298
+ // Validate accessory has required fields
299
+ if (!accessory.uuid) {
300
+ log.error('External Matter accessory missing UUID - skipping');
301
+ continue;
302
+ }
303
+ if (!accessory.displayName) {
304
+ log.error(`External Matter accessory ${accessory.uuid} missing displayName - skipping`);
305
+ continue;
306
+ }
307
+ // Check if already published
308
+ if (this.externalMatterServers.has(accessory.uuid)) {
309
+ log.warn(`External Matter accessory ${accessory.displayName} (${accessory.uuid}) is already published`);
310
+ continue;
311
+ }
312
+ // Generate deterministic MAC address from UUID (same pattern as HAP external accessories)
313
+ const advertiseAddress = generate(accessory.uuid);
314
+ // For Matter, use the MAC without colons as uniqueId
315
+ const uniqueId = advertiseAddress.replace(/:/g, '');
316
+ // Allocate Matter port for the external Matter server
317
+ const port = await this.externalPortService.requestMatterPort(uniqueId);
318
+ if (!port) {
319
+ log.error(`Failed to allocate Matter port for external Matter accessory ${accessory.displayName}`);
320
+ log.error('Please configure matterPorts in config.json or free up ports in the default range (5530-5541)');
321
+ continue;
322
+ }
323
+ log.info(`Allocated port ${port} for external Matter accessory: ${accessory.displayName}`);
324
+ // Create dedicated Matter server for this accessory
325
+ const matterServer = new MatterServer({
326
+ port,
327
+ uniqueId,
328
+ storagePath: User.matterPath(),
329
+ manufacturer: accessory.manufacturer,
330
+ model: accessory.model,
331
+ firmwareRevision: accessory.firmwareRevision,
332
+ serialNumber: accessory.serialNumber || uniqueId, // Use uniqueId as fallback serial number
333
+ debugModeEnabled: this.options.debugModeEnabled,
334
+ });
335
+ // Start the Matter server
336
+ await matterServer.start();
337
+ // Get plugin identifier from accessory
338
+ const pluginIdentifier = accessory._associatedPlugin || 'unknown';
339
+ // Register the accessory to this dedicated server
340
+ await matterServer.registerPlatformAccessories(pluginIdentifier, 'ExternalMatter', [accessory]);
341
+ // Store the server instance
342
+ this.externalMatterServers.set(accessory.uuid, matterServer);
343
+ log.info(`✓ External Matter accessory published: ${accessory.displayName} on port ${port}`);
344
+ // Log commissioning info
345
+ const commissioningInfo = matterServer.getCommissioningInfo();
346
+ if (commissioningInfo.qrCode && commissioningInfo.manualPairingCode) {
347
+ log.info(`📱 Commissioning codes for ${accessory.displayName}:`);
348
+ log.info(` QR Code: ${commissioningInfo.qrCode}`);
349
+ log.info(` Manual Code: ${commissioningInfo.manualPairingCode}`);
350
+ }
351
+ }
352
+ catch (error) {
353
+ log.error(`Failed to publish external Matter accessory ${accessory.displayName}:`, error);
354
+ }
355
+ }
356
+ }
357
+ handleRegisterPlatformAccessories(accessories) {
358
+ // Route to HAP bridge
359
+ this.bridgeService.handleRegisterPlatformAccessories(accessories);
360
+ }
361
+ handleUnregisterPlatformAccessories(accessories) {
362
+ // Route to HAP bridge
363
+ this.bridgeService.handleUnregisterPlatformAccessories(accessories);
364
+ }
365
+ handleRegisterMatterPlatformAccessories(pluginIdentifier, platformName, accessories) {
366
+ if (!this.matterServer) {
367
+ log.warn('Cannot register Matter accessories - Matter server is not running');
368
+ return;
369
+ }
370
+ this.matterServer.registerPlatformAccessories(pluginIdentifier, platformName, accessories).catch((error) => {
371
+ log.error(`Failed to register Matter accessories for ${pluginIdentifier}:`, error);
372
+ });
373
+ }
374
+ handleUnregisterMatterPlatformAccessories(pluginIdentifier, platformName, accessories) {
375
+ if (!this.matterServer) {
376
+ log.warn('Cannot unregister Matter accessories - Matter server is not running');
377
+ return;
378
+ }
379
+ this.matterServer.unregisterPlatformAccessories(pluginIdentifier, platformName, accessories).catch((error) => {
380
+ log.error(`Failed to unregister Matter accessories for ${pluginIdentifier}:`, error);
381
+ });
382
+ }
383
+ handleUpdateMatterAccessoryState(uuid, cluster, attributes) {
384
+ if (!this.matterServer) {
385
+ log.warn('Cannot update Matter accessory state - Matter server is not running');
386
+ return;
387
+ }
388
+ this.matterServer.updateAccessoryState(uuid, cluster, attributes).catch((error) => {
389
+ log.error(`Failed to update Matter accessory state for ${uuid}:`, error);
390
+ });
391
+ }
392
+ /**
393
+ * Restore cached Matter accessories (matching HAP pattern)
394
+ */
395
+ restoreCachedMatterAccessories() {
396
+ if (!this.matterServer) {
397
+ log.debug('Matter server not available for restoring cached accessories');
398
+ return;
399
+ }
400
+ const cachedAccessories = this.matterServer.getAllCachedAccessories();
401
+ log.debug(`Restoring ${cachedAccessories.length} cached Matter accessories`);
402
+ for (const cachedAccessory of cachedAccessories) {
403
+ let plugin = this.pluginManager.getPlugin(cachedAccessory.plugin);
404
+ if (!plugin) {
405
+ try {
406
+ // Try to find plugin by platform name (handles plugin renames)
407
+ plugin = this.pluginManager.getPluginByActiveDynamicPlatform(cachedAccessory.platform);
408
+ if (plugin) {
409
+ log.info(`When searching for the associated plugin of the Matter accessory '${cachedAccessory.displayName}' `
410
+ + `it seems like the plugin name changed from '${cachedAccessory.plugin}' to '${plugin.getPluginIdentifier()}'. Plugin association is now being transformed!`);
411
+ }
412
+ }
413
+ catch (error) {
414
+ log.warn(`Could not find the associated plugin for the Matter accessory '${cachedAccessory.displayName}'. `
415
+ + `Tried to find the plugin by the platform name but ${error.message}`);
416
+ }
417
+ }
418
+ const platformPlugin = plugin && plugin.getActiveDynamicPlatform(cachedAccessory.platform);
419
+ if (!platformPlugin) {
420
+ log.warn(`Failed to find plugin to handle Matter accessory ${cachedAccessory.displayName} (plugin: ${cachedAccessory.plugin}, platform: ${cachedAccessory.platform})`);
421
+ // Note: Matter accessories are not added to the bridge here - they're registered via plugin's didFinishLaunching
422
+ // The plugin can check if this accessory still exists and re-register or remove it
423
+ }
424
+ else {
425
+ // Call configureMatterAccessory if the plugin implements it
426
+ if (platformPlugin.configureMatterAccessory) {
427
+ log.debug(`Calling configureMatterAccessory for ${cachedAccessory.displayName}`);
428
+ platformPlugin.configureMatterAccessory(cachedAccessory);
429
+ }
430
+ else {
431
+ log.debug(`Platform ${cachedAccessory.platform} does not implement configureMatterAccessory`);
432
+ }
433
+ }
434
+ }
435
+ }
130
436
  static loadConfig() {
131
437
  // Look for the configuration file
132
438
  const configPath = User.configPath();
@@ -135,7 +441,7 @@ export class Server {
135
441
  username: 'CC:22:3D:E3:CE:30',
136
442
  pin: '031-45-154',
137
443
  };
138
- if (!fs.existsSync(configPath)) {
444
+ if (!existsSync(configPath)) {
139
445
  log.warn('config.json (%s) not found.', configPath);
140
446
  return {
141
447
  bridge: defaultBridge,
@@ -145,7 +451,7 @@ export class Server {
145
451
  }
146
452
  let config;
147
453
  try {
148
- config = JSON.parse(fs.readFileSync(configPath, { encoding: 'utf8' }));
454
+ config = JSON.parse(readFileSync(configPath, { encoding: 'utf8' }));
149
455
  }
150
456
  catch (error) {
151
457
  log.error('There was a problem reading your config.json file.');
@@ -165,6 +471,18 @@ export class Server {
165
471
  config.ports = undefined;
166
472
  }
167
473
  }
474
+ if (config.matterPorts !== undefined) {
475
+ if (config.matterPorts.start && config.matterPorts.end) {
476
+ if (config.matterPorts.start > config.matterPorts.end) {
477
+ log.error('Invalid Matter port pool configuration. End should be greater than or equal to start.');
478
+ config.matterPorts = undefined;
479
+ }
480
+ }
481
+ else {
482
+ log.error('Invalid configuration for \'matterPorts\'. Missing \'start\' and \'end\' properties! Ignoring it!');
483
+ config.matterPorts = undefined;
484
+ }
485
+ }
168
486
  const bridge = config.bridge || defaultBridge;
169
487
  bridge.name = bridge.name || defaultBridge.name;
170
488
  bridge.username = bridge.username || defaultBridge.username;
@@ -185,6 +503,44 @@ export class Server {
185
503
  config.platforms = [];
186
504
  }
187
505
  log.info('Loaded config.json with %s accessories and %s platforms.', config.accessories.length, config.platforms.length);
506
+ // Validate Matter configuration for port conflicts
507
+ if (config.bridge.matter || config.platforms.some((p) => p._bridge?.matter) || config.accessories.some((a) => a._bridge?.matter)) {
508
+ // Validate main bridge Matter config
509
+ if (config.bridge.matter) {
510
+ const validation = MatterConfigValidator.validate(config.bridge.matter);
511
+ if (!validation.isValid) {
512
+ log.error('Main bridge Matter configuration is invalid. Matter will not be enabled for the main bridge.');
513
+ delete config.bridge.matter;
514
+ }
515
+ }
516
+ // Validate all child bridge Matter configs and check for port conflicts
517
+ const childMatterValidation = MatterConfigValidator.validateAllChildMatterConfigs(config.platforms, config.accessories);
518
+ if (!childMatterValidation.isValid) {
519
+ log.error('Some child bridge Matter configurations are invalid. Check the errors above.');
520
+ }
521
+ // Additionally, check for conflicts between main bridge Matter port and child bridge ports
522
+ if (config.bridge.matter?.port) {
523
+ const mainMatterPort = config.bridge.matter.port;
524
+ const childMatterPorts = [];
525
+ for (const platform of config.platforms) {
526
+ if (platform._bridge?.matter?.port) {
527
+ childMatterPorts.push(platform._bridge.matter.port);
528
+ }
529
+ }
530
+ for (const accessory of config.accessories) {
531
+ if (accessory._bridge?.matter?.port) {
532
+ childMatterPorts.push(accessory._bridge.matter.port);
533
+ }
534
+ }
535
+ if (childMatterPorts.includes(mainMatterPort)) {
536
+ log.error(`Main bridge Matter port ${mainMatterPort} conflicts with a child bridge Matter port. Please use unique ports.`);
537
+ }
538
+ // Check for conflict with main bridge HAP port
539
+ if (config.bridge.port && Math.abs(config.bridge.port - mainMatterPort) < 10) {
540
+ log.warn(`Main bridge HAP port ${config.bridge.port} and Matter port ${mainMatterPort} are very close. Consider spacing them further apart.`);
541
+ }
542
+ }
543
+ }
188
544
  if (config.bridge.advertiser) {
189
545
  if (![
190
546
  "bonjour-hap" /* MDNSAdvertiser.BONJOUR */,
@@ -353,6 +709,11 @@ export class Server {
353
709
  * Validate an external bridge config
354
710
  */
355
711
  validateChildBridgeConfig(type, identifier, bridgeConfig) {
712
+ // All child bridges require username
713
+ if (!bridgeConfig.username) {
714
+ throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
715
+ + 'Missing required field "_bridge.username".');
716
+ }
356
717
  if (!validMacAddress(bridgeConfig.username)) {
357
718
  throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
358
719
  + `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.`);
@@ -383,6 +744,7 @@ export class Server {
383
744
  this.ipcService.start();
384
745
  // handle restart child bridge event
385
746
  this.ipcService.on("restartChildBridge" /* IpcIncomingEvent.RESTART_CHILD_BRIDGE */, (username) => {
747
+ // noinspection SuspiciousTypeOfGuard
386
748
  if (typeof username === 'string') {
387
749
  const childBridge = this.childBridges.get(username.toUpperCase());
388
750
  childBridge?.restartChildBridge();
@@ -390,6 +752,7 @@ export class Server {
390
752
  });
391
753
  // handle stop child bridge event
392
754
  this.ipcService.on("stopChildBridge" /* IpcIncomingEvent.STOP_CHILD_BRIDGE */, (username) => {
755
+ // noinspection SuspiciousTypeOfGuard
393
756
  if (typeof username === 'string') {
394
757
  const childBridge = this.childBridges.get(username.toUpperCase());
395
758
  childBridge?.stopChildBridge();
@@ -397,6 +760,7 @@ export class Server {
397
760
  });
398
761
  // handle start child bridge event
399
762
  this.ipcService.on("startChildBridge" /* IpcIncomingEvent.START_CHILD_BRIDGE */, (username) => {
763
+ // noinspection SuspiciousTypeOfGuard
400
764
  if (typeof username === 'string') {
401
765
  const childBridge = this.childBridges.get(username.toUpperCase());
402
766
  childBridge?.startChildBridge();