matterbridge 1.7.2 → 2.0.0-edge.2

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 (102) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/dist/cli.js +3 -39
  3. package/dist/cluster/export.js +0 -2
  4. package/dist/defaultConfigSchema.js +0 -23
  5. package/dist/deviceManager.js +1 -26
  6. package/dist/frontend.js +1203 -0
  7. package/dist/index.js +1 -36
  8. package/dist/logger/export.js +0 -1
  9. package/dist/matter/export.js +0 -5
  10. package/dist/matterbridge.js +516 -2678
  11. package/dist/matterbridgeAccessoryPlatform.js +0 -33
  12. package/dist/matterbridgeBehaviors.js +23 -31
  13. package/dist/matterbridgeDeviceTypes.js +11 -82
  14. package/dist/matterbridgeDynamicPlatform.js +0 -33
  15. package/dist/matterbridgeEndpoint.js +45 -1145
  16. package/dist/matterbridgePlatform.js +14 -145
  17. package/dist/matterbridgeTypes.js +3 -24
  18. package/dist/pluginManager.js +5 -246
  19. package/dist/storage/export.js +0 -1
  20. package/dist/utils/colorUtils.js +2 -205
  21. package/dist/utils/export.js +0 -1
  22. package/dist/utils/utils.js +7 -252
  23. package/frontend/build/asset-manifest.json +3 -3
  24. package/frontend/build/index.html +1 -1
  25. package/frontend/build/static/js/{main.08241820.js → main.6204ae54.js} +3 -3
  26. package/frontend/build/static/js/main.6204ae54.js.map +1 -0
  27. package/npm-shrinkwrap.json +9 -9
  28. package/package.json +2 -3
  29. package/dist/cli.d.ts +0 -25
  30. package/dist/cli.d.ts.map +0 -1
  31. package/dist/cli.js.map +0 -1
  32. package/dist/cluster/export.d.ts +0 -2
  33. package/dist/cluster/export.d.ts.map +0 -1
  34. package/dist/cluster/export.js.map +0 -1
  35. package/dist/defaultConfigSchema.d.ts +0 -27
  36. package/dist/defaultConfigSchema.d.ts.map +0 -1
  37. package/dist/defaultConfigSchema.js.map +0 -1
  38. package/dist/deviceManager.d.ts +0 -46
  39. package/dist/deviceManager.d.ts.map +0 -1
  40. package/dist/deviceManager.js.map +0 -1
  41. package/dist/index.d.ts +0 -40
  42. package/dist/index.d.ts.map +0 -1
  43. package/dist/index.js.map +0 -1
  44. package/dist/logger/export.d.ts +0 -2
  45. package/dist/logger/export.d.ts.map +0 -1
  46. package/dist/logger/export.js.map +0 -1
  47. package/dist/matter/export.d.ts +0 -11
  48. package/dist/matter/export.d.ts.map +0 -1
  49. package/dist/matter/export.js.map +0 -1
  50. package/dist/matterbridge.d.ts +0 -483
  51. package/dist/matterbridge.d.ts.map +0 -1
  52. package/dist/matterbridge.js.map +0 -1
  53. package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
  54. package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
  55. package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
  56. package/dist/matterbridgeBehaviors.d.ts +0 -942
  57. package/dist/matterbridgeBehaviors.d.ts.map +0 -1
  58. package/dist/matterbridgeBehaviors.js.map +0 -1
  59. package/dist/matterbridgeDevice.d.ts +0 -7077
  60. package/dist/matterbridgeDevice.d.ts.map +0 -1
  61. package/dist/matterbridgeDevice.js +0 -2736
  62. package/dist/matterbridgeDevice.js.map +0 -1
  63. package/dist/matterbridgeDeviceTypes.d.ts +0 -109
  64. package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
  65. package/dist/matterbridgeDeviceTypes.js.map +0 -1
  66. package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
  67. package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
  68. package/dist/matterbridgeDynamicPlatform.js.map +0 -1
  69. package/dist/matterbridgeEdge.d.ts +0 -91
  70. package/dist/matterbridgeEdge.d.ts.map +0 -1
  71. package/dist/matterbridgeEdge.js +0 -1077
  72. package/dist/matterbridgeEdge.js.map +0 -1
  73. package/dist/matterbridgeEndpoint.d.ts +0 -10156
  74. package/dist/matterbridgeEndpoint.d.ts.map +0 -1
  75. package/dist/matterbridgeEndpoint.js.map +0 -1
  76. package/dist/matterbridgePlatform.d.ts +0 -168
  77. package/dist/matterbridgePlatform.d.ts.map +0 -1
  78. package/dist/matterbridgePlatform.js.map +0 -1
  79. package/dist/matterbridgeTypes.d.ts +0 -172
  80. package/dist/matterbridgeTypes.d.ts.map +0 -1
  81. package/dist/matterbridgeTypes.js.map +0 -1
  82. package/dist/matterbridgeWebsocket.d.ts +0 -49
  83. package/dist/matterbridgeWebsocket.d.ts.map +0 -1
  84. package/dist/matterbridgeWebsocket.js +0 -325
  85. package/dist/matterbridgeWebsocket.js.map +0 -1
  86. package/dist/pluginManager.d.ts +0 -238
  87. package/dist/pluginManager.d.ts.map +0 -1
  88. package/dist/pluginManager.js.map +0 -1
  89. package/dist/storage/export.d.ts +0 -2
  90. package/dist/storage/export.d.ts.map +0 -1
  91. package/dist/storage/export.js.map +0 -1
  92. package/dist/utils/colorUtils.d.ts +0 -61
  93. package/dist/utils/colorUtils.d.ts.map +0 -1
  94. package/dist/utils/colorUtils.js.map +0 -1
  95. package/dist/utils/export.d.ts +0 -3
  96. package/dist/utils/export.d.ts.map +0 -1
  97. package/dist/utils/export.js.map +0 -1
  98. package/dist/utils/utils.d.ts +0 -221
  99. package/dist/utils/utils.d.ts.map +0 -1
  100. package/dist/utils/utils.js.map +0 -1
  101. package/frontend/build/static/js/main.08241820.js.map +0 -1
  102. /package/frontend/build/static/js/{main.08241820.js.LICENSE.txt → main.6204ae54.js.LICENSE.txt} +0 -0
@@ -1,64 +1,24 @@
1
- /**
2
- * This file contains the class Matterbridge.
3
- *
4
- * @file matterbridge.ts
5
- * @author Luca Liguori
6
- * @date 2023-12-29
7
- * @version 1.5.2
8
- *
9
- * Copyright 2023, 2024, 2025 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
- // Node.js modules
24
1
  import { fileURLToPath } from 'url';
25
2
  import { promises as fs } from 'fs';
26
3
  import { exec, spawn } from 'child_process';
27
- import { createServer } from 'http';
28
4
  import EventEmitter from 'events';
29
5
  import os from 'os';
30
6
  import path from 'path';
31
7
  import { randomBytes } from 'crypto';
32
- // Package modules
33
- import https from 'https';
34
- import express from 'express';
35
- import WebSocket, { WebSocketServer } from 'ws';
36
- // NodeStorage and AnsiLogger modules
37
8
  import { NodeStorageManager } from 'node-persist-manager';
38
- import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
39
- // Matterbridge
40
- import { MatterbridgeDevice } from './matterbridgeDevice.js';
41
- import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
42
- import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
9
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
10
+ import { logInterfaces, wait, waiter, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
43
11
  import { PluginManager } from './pluginManager.js';
44
12
  import { DeviceManager } from './deviceManager.js';
45
13
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
46
- // @matter
47
- import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer, StorageService, Environment } from '@matter/main';
48
- import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
49
- import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
50
- import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
51
- // @project-chip
52
- import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
53
- import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
54
- import { aggregator } from './matterbridgeDeviceTypes.js';
55
- // Default colors
14
+ import { bridge } from './matterbridgeDeviceTypes.js';
15
+ import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
16
+ import { FabricAction, PaseClient } from '@matter/main/protocol';
17
+ import { AggregatorEndpoint } from '@matter/main/endpoints';
18
+ import { Frontend } from './frontend.js';
56
19
  const plg = '\u001B[38;5;33m';
57
20
  const dev = '\u001B[38;5;79m';
58
21
  const typ = '\u001B[38;5;207m';
59
- /**
60
- * Represents the Matterbridge application.
61
- */
62
22
  export class Matterbridge extends EventEmitter {
63
23
  systemInformation = {
64
24
  interfaceName: '',
@@ -92,10 +52,9 @@ export class Matterbridge extends EventEmitter {
92
52
  matterbridgeConnected: false,
93
53
  bridgeMode: '',
94
54
  restartMode: '',
95
- edge: hasParameter('edge'),
96
55
  readOnly: hasParameter('readonly'),
97
56
  profile: getParameter('profile'),
98
- loggerLevel: "info" /* LogLevel.INFO */,
57
+ loggerLevel: "info",
99
58
  fileLogger: false,
100
59
  matterLoggerLevel: MatterLogLevel.INFO,
101
60
  matterFileLogger: false,
@@ -124,17 +83,16 @@ export class Matterbridge extends EventEmitter {
124
83
  bridgeMode = '';
125
84
  restartMode = '';
126
85
  profile = getParameter('profile');
127
- edge = hasParameter('edge');
86
+ edge = true;
128
87
  log;
129
88
  matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
130
89
  matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
131
90
  plugins;
132
91
  devices;
92
+ frontend = new Frontend(this);
133
93
  nodeStorage;
134
94
  nodeContext;
135
- matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
136
95
  nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
137
- // Cleanup
138
96
  hasCleanupStarted = false;
139
97
  initialized = false;
140
98
  execRunningCount = 0;
@@ -146,62 +104,34 @@ export class Matterbridge extends EventEmitter {
146
104
  sigtermHandler;
147
105
  exceptionHandler;
148
106
  rejectionHandler;
149
- // Frontend
150
- expressApp;
151
- httpServer;
152
- httpsServer;
153
- webSocketServer;
154
- // Matter
155
- mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
156
- ipv4address; // matter commissioning server listeningAddressIpv4
157
- ipv6address; // matter commissioning server listeningAddressIpv6
158
- port = 5540; // first commissioning server port
159
- passcode; // first commissioning server passcode
160
- discriminator; // first commissioning server discriminator
161
- storageManager;
107
+ environment = Environment.default;
108
+ matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
109
+ matterStorageService;
110
+ matterStorageManager;
162
111
  matterbridgeContext;
163
112
  mattercontrollerContext;
164
- matterServer;
165
- matterAggregator;
166
- commissioningServer;
167
- commissioningController;
113
+ mdnsInterface;
114
+ ipv4address;
115
+ ipv6address;
116
+ port;
117
+ passcode;
118
+ discriminator;
119
+ serverNode;
120
+ aggregatorNode;
168
121
  aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
169
122
  aggregatorProductId = getIntParameter('productId') ?? 0x8000;
170
123
  static instance;
171
- // We load asyncronously so is private
172
124
  constructor() {
173
125
  super();
174
- // Bind the handler to the instance
175
- this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
176
126
  }
177
- /**
178
- * Retrieves the list of Matterbridge devices.
179
- * @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
180
- */
181
127
  getDevices() {
182
128
  return this.devices.array();
183
129
  }
184
- /**
185
- * Retrieves the list of registered plugins.
186
- * @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
187
- */
188
130
  getPlugins() {
189
131
  return this.plugins.array();
190
132
  }
191
- matterbridgeMessageHandler;
192
- /** ***********************************************************************************************************************************/
193
- /** loadInstance() and cleanup() methods */
194
- /** ***********************************************************************************************************************************/
195
- /**
196
- * Loads an instance of the Matterbridge class.
197
- * If an instance already exists, return that instance.
198
- *
199
- * @param initialize - Whether to initialize the Matterbridge instance after loading.
200
- * @returns The loaded Matterbridge instance.
201
- */
202
133
  static async loadInstance(initialize = false) {
203
134
  if (!Matterbridge.instance) {
204
- // eslint-disable-next-line no-console
205
135
  if (hasParameter('debug'))
206
136
  console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
207
137
  Matterbridge.instance = new Matterbridge();
@@ -210,11 +140,6 @@ export class Matterbridge extends EventEmitter {
210
140
  }
211
141
  return Matterbridge.instance;
212
142
  }
213
- /**
214
- * Call cleanup().
215
- * @deprecated This method is deprecated and is only used for jest tests.
216
- *
217
- */
218
143
  async destroyInstance() {
219
144
  await this.cleanup('destroying instance...', false);
220
145
  await waiter('destroying instance...', () => {
@@ -222,60 +147,45 @@ export class Matterbridge extends EventEmitter {
222
147
  }, false, 60000, 100, false);
223
148
  await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
224
149
  }
225
- /**
226
- * Initializes the Matterbridge application.
227
- *
228
- * @remarks
229
- * This method performs the necessary setup and initialization steps for the Matterbridge application.
230
- * It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
231
- * node version, registers signal handlers, initializes storage, and parses the command line.
232
- *
233
- * @returns A Promise that resolves when the initialization is complete.
234
- */
235
150
  async initialize() {
236
- // Set the restart mode
237
151
  if (hasParameter('service'))
238
152
  this.restartMode = 'service';
239
153
  if (hasParameter('docker'))
240
154
  this.restartMode = 'docker';
241
- // Set the matterbridge directory
242
155
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
243
156
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
244
- // Create matterbridge logger
245
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
246
- // Initialize nodeStorage and nodeContext
157
+ this.environment.vars.set('log.level', MatterLogLevel.INFO);
158
+ this.environment.vars.set('log.format', MatterLogFormat.ANSI);
159
+ this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
160
+ this.environment.vars.set('runtime.signals', false);
161
+ this.environment.vars.set('runtime.exitcode', false);
162
+ this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
163
+ this.registerProcessHandlers();
247
164
  try {
248
165
  this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
249
166
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
250
167
  this.log.debug('Creating node storage context for matterbridge');
251
168
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
252
- // TODO: Remove this code when node-persist-manager is updated
253
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
169
  const keys = (await this.nodeStorage?.storage.keys());
255
170
  for (const key of keys) {
256
171
  this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
257
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
172
  await this.nodeStorage?.storage.get(key);
259
173
  }
260
174
  const storages = await this.nodeStorage.getStorageNames();
261
175
  for (const storage of storages) {
262
176
  this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
263
177
  const nodeContext = await this.nodeStorage?.createStorage(storage);
264
- // TODO: Remove this code when node-persist-manager is updated
265
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
178
  const keys = (await nodeContext?.storage.keys());
267
179
  keys.forEach(async (key) => {
268
180
  this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
269
181
  await nodeContext?.get(key);
270
182
  });
271
183
  }
272
- // Creating a backup of the node storage since it is not corrupted
273
184
  this.log.debug('Creating node storage backup...');
274
185
  await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
275
186
  this.log.debug('Created node storage backup');
276
187
  }
277
188
  catch (error) {
278
- // Restoring the backup of the node storage since it is corrupted
279
189
  this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
280
190
  if (hasParameter('norestore')) {
281
191
  this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
@@ -290,51 +200,45 @@ export class Matterbridge extends EventEmitter {
290
200
  this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
291
201
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
292
202
  }
293
- // Set the first port to use for the commissioning server (will be incremented in childbridge mode)
294
203
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
295
- // Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
296
- this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
297
- // Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
298
- this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
204
+ this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
205
+ this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
299
206
  this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
300
- // Set matterbridge logger level (context: matterbridgeLogLevel)
301
207
  if (hasParameter('logger')) {
302
208
  const level = getParameter('logger');
303
209
  if (level === 'debug') {
304
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
210
+ this.log.logLevel = "debug";
305
211
  }
306
212
  else if (level === 'info') {
307
- this.log.logLevel = "info" /* LogLevel.INFO */;
213
+ this.log.logLevel = "info";
308
214
  }
309
215
  else if (level === 'notice') {
310
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
216
+ this.log.logLevel = "notice";
311
217
  }
312
218
  else if (level === 'warn') {
313
- this.log.logLevel = "warn" /* LogLevel.WARN */;
219
+ this.log.logLevel = "warn";
314
220
  }
315
221
  else if (level === 'error') {
316
- this.log.logLevel = "error" /* LogLevel.ERROR */;
222
+ this.log.logLevel = "error";
317
223
  }
318
224
  else if (level === 'fatal') {
319
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
225
+ this.log.logLevel = "fatal";
320
226
  }
321
227
  else {
322
228
  this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
323
- this.log.logLevel = "info" /* LogLevel.INFO */;
229
+ this.log.logLevel = "info";
324
230
  }
325
231
  }
326
232
  else {
327
- this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
233
+ this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
328
234
  }
329
- MatterbridgeDevice.logLevel = this.log.logLevel;
330
- // Create the file logger for matterbridge (context: matterbridgeFileLog)
235
+ MatterbridgeEndpoint.logLevel = this.log.logLevel;
331
236
  if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
332
237
  AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
333
238
  this.matterbridgeInformation.fileLogger = true;
334
239
  }
335
240
  this.log.notice('Matterbridge is starting...');
336
241
  this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
337
- // Set matter.js logger level, format and logger (context: matterLogLevel)
338
242
  if (hasParameter('matterlogger')) {
339
243
  const level = getParameter('matterlogger');
340
244
  if (level === 'debug') {
@@ -365,7 +269,6 @@ export class Matterbridge extends EventEmitter {
365
269
  }
366
270
  Logger.format = MatterLogFormat.ANSI;
367
271
  Logger.setLogger('default', this.createMatterLogger());
368
- // Create the file logger for matter.js (context: matterFileLog)
369
272
  if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
370
273
  this.matterbridgeInformation.matterFileLogger = true;
371
274
  Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
@@ -374,25 +277,35 @@ export class Matterbridge extends EventEmitter {
374
277
  });
375
278
  }
376
279
  this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
377
- // Set the interface to use for the matter server mdnsInterface
378
280
  if (hasParameter('mdnsinterface')) {
379
281
  this.mdnsInterface = getParameter('mdnsinterface');
380
282
  }
381
283
  else {
382
- this.mdnsInterface = await this.nodeContext?.get('mattermdnsinterface', undefined);
284
+ this.mdnsInterface = await this.nodeContext.get('mattermdnsinterface', undefined);
383
285
  if (this.mdnsInterface === '')
384
286
  this.mdnsInterface = undefined;
385
287
  }
386
- // Set the listeningAddressIpv4 for the matter commissioning server
288
+ if (this.mdnsInterface) {
289
+ const networkInterfaces = os.networkInterfaces();
290
+ const availableInterfaces = Object.keys(networkInterfaces);
291
+ if (!availableInterfaces.includes(this.mdnsInterface)) {
292
+ this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
293
+ this.mdnsInterface = undefined;
294
+ }
295
+ else {
296
+ this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
297
+ }
298
+ }
299
+ if (this.mdnsInterface)
300
+ this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
387
301
  if (hasParameter('ipv4address')) {
388
302
  this.ipv4address = getParameter('ipv4address');
389
303
  }
390
304
  else {
391
- this.ipv4address = await this.nodeContext?.get('matteripv4address', undefined);
305
+ this.ipv4address = await this.nodeContext.get('matteripv4address', undefined);
392
306
  if (this.ipv4address === '')
393
307
  this.ipv4address = undefined;
394
308
  }
395
- // Set the listeningAddressIpv6 for the matter commissioning server
396
309
  if (hasParameter('ipv6address')) {
397
310
  this.ipv6address = getParameter('ipv6address');
398
311
  }
@@ -401,28 +314,22 @@ export class Matterbridge extends EventEmitter {
401
314
  if (this.ipv6address === '')
402
315
  this.ipv6address = undefined;
403
316
  }
404
- // Initialize PluginManager
405
317
  this.plugins = new PluginManager(this);
406
318
  await this.plugins.loadFromStorage();
407
- // Initialize DeviceManager
408
319
  this.devices = new DeviceManager(this, this.nodeContext);
409
- // Get the plugins from node storage and create the plugins node storage contexts
410
320
  for (const plugin of this.plugins) {
411
321
  const packageJson = await this.plugins.parse(plugin);
412
- if (packageJson === null && !hasParameter('add')) {
413
- // Try to reinstall the plugin from npm (for Docker pull and external plugins)
414
- // We don't do this when the add parameter is set because we shut down the process after adding the plugin
322
+ if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
415
323
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
416
324
  try {
417
325
  await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
418
326
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
419
327
  plugin.error = false;
420
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
421
328
  }
422
329
  catch (error) {
423
330
  plugin.error = true;
424
331
  plugin.enabled = false;
425
- this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`);
332
+ this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`, error instanceof Error ? error.message : error);
426
333
  }
427
334
  }
428
335
  this.log.debug(`Creating node storage context for plugin ${plg}${plugin.name}${db}`);
@@ -434,7 +341,6 @@ export class Matterbridge extends EventEmitter {
434
341
  await plugin.nodeContext.set('description', plugin.description);
435
342
  await plugin.nodeContext.set('author', plugin.author);
436
343
  }
437
- // Log system info and create .matterbridge directory
438
344
  await this.logNodeAndSystemInfo();
439
345
  this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
440
346
  `${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
@@ -442,7 +348,6 @@ export class Matterbridge extends EventEmitter {
442
348
  `${hasParameter('controller') ? 'mode controller ' : ''}` +
443
349
  `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
444
350
  `running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
445
- // Check node version and throw error
446
351
  const minNodeVersion = 18;
447
352
  const nodeVersion = process.versions.node;
448
353
  const versionMajor = parseInt(nodeVersion.split('.')[0]);
@@ -450,17 +355,9 @@ export class Matterbridge extends EventEmitter {
450
355
  this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
451
356
  throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
452
357
  }
453
- // Register process handlers
454
- this.registerProcessHandlers();
455
- // Parse command line
456
358
  await this.parseCommandLine();
457
359
  this.initialized = true;
458
360
  }
459
- /**
460
- * Parses the command line arguments and performs the corresponding actions.
461
- * @private
462
- * @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
463
- */
464
361
  async parseCommandLine() {
465
362
  if (hasParameter('help')) {
466
363
  this.log.info(`\nUsage: matterbridge [options]\n
@@ -567,75 +464,32 @@ export class Matterbridge extends EventEmitter {
567
464
  return;
568
465
  }
569
466
  if (hasParameter('factoryreset')) {
570
- try {
571
- // Delete old matter storage file
572
- const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
573
- this.log.info(`Unlinking old matter storage file: ${file}`);
574
- await fs.unlink(file);
575
- }
576
- catch (err) {
577
- if (err instanceof Error && err.code !== 'ENOENT') {
578
- this.log.error(`Error unlinking old matter storage file: ${err}`);
579
- }
580
- }
581
- try {
582
- // Delete matter node storage directory with its subdirectories
583
- const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
584
- this.log.info(`Removing matter node storage directory: ${dir}`);
585
- await fs.rm(dir, { recursive: true });
586
- }
587
- catch (err) {
588
- if (err instanceof Error && err.code !== 'ENOENT') {
589
- this.log.error(`Error removing matter storage directory: ${err}`);
590
- }
591
- }
592
- try {
593
- // Delete node storage directory with its subdirectories
594
- const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
595
- this.log.info(`Removing storage directory: ${dir}`);
596
- await fs.rm(dir, { recursive: true });
597
- }
598
- catch (err) {
599
- if (err instanceof Error && err.code !== 'ENOENT') {
600
- this.log.error(`Error removing storage directory: ${err}`);
601
- }
602
- }
603
- this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
604
- this.nodeContext = undefined;
605
- this.nodeStorage = undefined;
606
- this.plugins.clear();
607
- this.devices.clear();
608
- this.emit('shutdown');
467
+ await this.shutdownProcessAndFactoryReset();
609
468
  return;
610
469
  }
611
- // Start the matter storage and create the matterbridge context
612
470
  try {
613
- await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
471
+ await this.startMatterStorage();
614
472
  }
615
473
  catch (error) {
616
474
  this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
617
475
  throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
618
476
  }
619
- // Clear the matterbridge context if the reset parameter is set
620
- if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
621
- this.log.info('Resetting Matterbridge commissioning information...');
622
- await this.matterbridgeContext?.clearAll();
623
- await this.stopMatterStorage();
624
- this.log.info('Reset done! Remove the device from the controller.');
625
- this.emit('shutdown');
477
+ if (hasParameter('reset') && getParameter('reset') === undefined) {
478
+ await this.shutdownProcessAndReset();
626
479
  return;
627
480
  }
628
- // Clear matterbridge plugin context if the reset parameter is set
629
- if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
481
+ if (hasParameter('reset') && getParameter('reset') !== undefined) {
630
482
  this.log.debug(`Reset plugin ${getParameter('reset')}`);
631
483
  const plugin = this.plugins.get(getParameter('reset'));
632
484
  if (plugin) {
633
- if (!this.storageManager)
485
+ const matterStorageManager = await this.matterStorageService?.open(plugin.name);
486
+ if (!matterStorageManager)
634
487
  this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
635
- const context = this.storageManager?.createContext(plugin.name);
636
- if (!context)
637
- this.log.error(`Plugin ${plg}${plugin.name}${er} context not found`);
638
- await context?.clearAll();
488
+ await matterStorageManager?.createContext('events')?.clearAll();
489
+ await matterStorageManager?.createContext('fabrics')?.clearAll();
490
+ await matterStorageManager?.createContext('root')?.clearAll();
491
+ await matterStorageManager?.createContext('sessions')?.clearAll();
492
+ await matterStorageManager?.createContext('persist')?.clearAll();
639
493
  this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
640
494
  }
641
495
  else {
@@ -645,74 +499,55 @@ export class Matterbridge extends EventEmitter {
645
499
  this.emit('shutdown');
646
500
  return;
647
501
  }
648
- // Initialize frontend
649
502
  if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
650
- await this.initializeFrontend(getIntParameter('frontend'));
651
- // Check each 60 minutes the latest versions
503
+ await this.frontend.start(getIntParameter('frontend'));
652
504
  this.checkUpdateInterval = setInterval(() => {
653
505
  this.getMatterbridgeLatestVersion();
654
506
  for (const plugin of this.plugins) {
655
507
  this.getPluginLatestVersion(plugin);
656
508
  }
509
+ this.frontend.wssSendRefreshRequired();
657
510
  }, 60 * 60 * 1000);
658
- // Start the matterbridge in mode test
659
511
  if (hasParameter('test')) {
660
512
  this.bridgeMode = 'bridge';
661
- MatterbridgeDevice.bridgeMode = 'bridge';
513
+ MatterbridgeEndpoint.bridgeMode = 'bridge';
662
514
  return;
663
515
  }
664
- // Start the matterbridge in mode controller
665
516
  if (hasParameter('controller')) {
666
517
  this.bridgeMode = 'controller';
667
518
  await this.startController();
668
519
  return;
669
520
  }
670
- // Check if the bridge mode is set and start matterbridge in bridge mode if not set
671
521
  if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
672
522
  this.log.info('Setting default matterbridge start mode to bridge');
673
523
  await this.nodeContext?.set('bridgeMode', 'bridge');
674
524
  }
675
- // Start matterbridge in bridge mode
676
525
  if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
677
526
  this.bridgeMode = 'bridge';
678
- MatterbridgeDevice.bridgeMode = 'bridge';
679
527
  MatterbridgeEndpoint.bridgeMode = 'bridge';
680
528
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
681
529
  await this.startBridge();
682
530
  return;
683
531
  }
684
- // Start matterbridge in childbridge mode
685
532
  if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
686
533
  this.bridgeMode = 'childbridge';
687
- MatterbridgeDevice.bridgeMode = 'childbridge';
688
534
  MatterbridgeEndpoint.bridgeMode = 'childbridge';
689
535
  this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
690
536
  await this.startChildbridge();
691
537
  return;
692
538
  }
693
539
  }
694
- /**
695
- * Asynchronously loads and starts the registered plugins.
696
- *
697
- * This method is responsible for initializing and staarting all enabled plugins.
698
- * It ensures that each plugin is properly loaded and started before the ridge starts.
699
- *
700
- * @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
701
- */
702
540
  async startPlugins() {
703
- // Check, load and start the plugins
704
541
  for (const plugin of this.plugins) {
705
542
  plugin.configJson = await this.plugins.loadConfig(plugin);
706
543
  plugin.schemaJson = await this.plugins.loadSchema(plugin);
707
- // Check if the plugin is available
708
544
  if (!(await this.plugins.resolve(plugin.path))) {
709
545
  this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
710
546
  plugin.enabled = false;
711
547
  plugin.error = true;
712
548
  continue;
713
549
  }
714
- // Check if the plugin has a new version
715
- this.getPluginLatestVersion(plugin); // No await do it asyncronously
550
+ this.getPluginLatestVersion(plugin);
716
551
  if (!plugin.enabled) {
717
552
  this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
718
553
  continue;
@@ -727,26 +562,20 @@ export class Matterbridge extends EventEmitter {
727
562
  plugin.addedDevices = undefined;
728
563
  plugin.qrPairingCode = undefined;
729
564
  plugin.manualPairingCode = undefined;
730
- this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
565
+ this.plugins.load(plugin, true, 'Matterbridge is starting');
731
566
  }
732
- this.wssSendRefreshRequired();
567
+ this.frontend.wssSendRefreshRequired();
733
568
  }
734
- /**
735
- * Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
736
- * When either of these signals are received, the cleanup method is called with an appropriate message.
737
- */
738
569
  registerProcessHandlers() {
739
570
  this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
740
571
  process.removeAllListeners('uncaughtException');
741
572
  process.removeAllListeners('unhandledRejection');
742
573
  this.exceptionHandler = async (error) => {
743
574
  this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
744
- // await this.cleanup('Unhandled Exception detected, cleaning up...');
745
575
  };
746
576
  process.on('uncaughtException', this.exceptionHandler);
747
577
  this.rejectionHandler = async (reason, promise) => {
748
578
  this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
749
- // await this.cleanup('Unhandled Rejection detected, cleaning up...');
750
579
  };
751
580
  process.on('unhandledRejection', this.rejectionHandler);
752
581
  this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
@@ -759,9 +588,6 @@ export class Matterbridge extends EventEmitter {
759
588
  };
760
589
  process.on('SIGTERM', this.sigtermHandler);
761
590
  }
762
- /**
763
- * Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
764
- */
765
591
  deregisterProcesslHandlers() {
766
592
  this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
767
593
  if (this.exceptionHandler)
@@ -778,15 +604,14 @@ export class Matterbridge extends EventEmitter {
778
604
  process.off('SIGTERM', this.sigtermHandler);
779
605
  this.sigtermHandler = undefined;
780
606
  }
781
- /**
782
- * Logs the node and system information.
783
- */
784
607
  async logNodeAndSystemInfo() {
785
- // IP address information
786
608
  const networkInterfaces = os.networkInterfaces();
609
+ this.systemInformation.interfaceName = '';
787
610
  this.systemInformation.ipv4Address = '';
788
611
  this.systemInformation.ipv6Address = '';
789
612
  for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
613
+ if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
614
+ continue;
790
615
  if (!interfaceDetails) {
791
616
  break;
792
617
  }
@@ -802,7 +627,7 @@ export class Matterbridge extends EventEmitter {
802
627
  this.systemInformation.macAddress = detail.mac;
803
628
  }
804
629
  }
805
- if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
630
+ if (this.systemInformation.ipv4Address !== '' || this.systemInformation.ipv6Address !== '') {
806
631
  this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
807
632
  this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
808
633
  this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
@@ -810,22 +635,19 @@ export class Matterbridge extends EventEmitter {
810
635
  break;
811
636
  }
812
637
  }
813
- // Node information
814
638
  this.systemInformation.nodeVersion = process.versions.node;
815
639
  const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
816
640
  const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
817
641
  const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
818
- // Host system information
819
642
  this.systemInformation.hostname = os.hostname();
820
643
  this.systemInformation.user = os.userInfo().username;
821
- this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
822
- this.systemInformation.osRelease = os.release(); // Kernel version
823
- this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
824
- this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
825
- this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
826
- this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
827
- this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
828
- // Log the system information
644
+ this.systemInformation.osType = os.type();
645
+ this.systemInformation.osRelease = os.release();
646
+ this.systemInformation.osPlatform = os.platform();
647
+ this.systemInformation.osArch = os.arch();
648
+ this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
649
+ this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
650
+ this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
829
651
  this.log.debug('Host System Information:');
830
652
  this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
831
653
  this.log.debug(`- User: ${this.systemInformation.user}`);
@@ -841,19 +663,15 @@ export class Matterbridge extends EventEmitter {
841
663
  this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
842
664
  this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
843
665
  this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
844
- // Home directory
845
666
  this.homeDirectory = getParameter('homedir') ?? os.homedir();
846
667
  this.matterbridgeInformation.homeDirectory = this.homeDirectory;
847
668
  this.log.debug(`Home Directory: ${this.homeDirectory}`);
848
- // Package root directory
849
669
  const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
850
670
  this.rootDirectory = path.resolve(currentFileDirectory, '../');
851
671
  this.matterbridgeInformation.rootDirectory = this.rootDirectory;
852
672
  this.log.debug(`Root Directory: ${this.rootDirectory}`);
853
- // Global node_modules directory
854
673
  if (this.nodeContext)
855
674
  this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
856
- // First run of Matterbridge so the node storage is empty
857
675
  if (this.globalModulesDirectory === '') {
858
676
  try {
859
677
  this.globalModulesDirectory = await this.getGlobalNodeModules();
@@ -877,7 +695,6 @@ export class Matterbridge extends EventEmitter {
877
695
  this.log.error(`Error getting global node_modules directory: ${error}`);
878
696
  });
879
697
  }
880
- // Create the data directory .matterbridge in the home directory
881
698
  this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
882
699
  this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
883
700
  try {
@@ -901,7 +718,6 @@ export class Matterbridge extends EventEmitter {
901
718
  }
902
719
  }
903
720
  this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
904
- // Create the plugin directory Matterbridge in the home directory
905
721
  this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
906
722
  this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
907
723
  try {
@@ -925,28 +741,19 @@ export class Matterbridge extends EventEmitter {
925
741
  }
926
742
  }
927
743
  this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
928
- // Matterbridge version
929
744
  const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
930
745
  this.matterbridgeVersion = packageJson.version;
931
746
  this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
932
747
  this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
933
- // Matterbridge latest version
934
748
  if (this.nodeContext)
935
749
  this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
936
750
  this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
937
751
  this.getMatterbridgeLatestVersion();
938
- // Current working directory
939
752
  const currentDir = process.cwd();
940
753
  this.log.debug(`Current Working Directory: ${currentDir}`);
941
- // Command line arguments (excluding 'node' and the script name)
942
754
  const cmdArgs = process.argv.slice(2).join(' ');
943
755
  this.log.debug(`Command Line Arguments: ${cmdArgs}`);
944
756
  }
945
- /**
946
- * Retrieves the latest version of a package from the npm registry.
947
- * @param packageName - The name of the package.
948
- * @returns A Promise that resolves to the latest version of the package.
949
- */
950
757
  async getLatestVersion(packageName) {
951
758
  return new Promise((resolve, reject) => {
952
759
  this.execRunningCount++;
@@ -961,10 +768,6 @@ export class Matterbridge extends EventEmitter {
961
768
  });
962
769
  });
963
770
  }
964
- /**
965
- * Retrieves the path to the global Node.js modules directory.
966
- * @returns A promise that resolves to the path of the global Node.js modules directory.
967
- */
968
771
  async getGlobalNodeModules() {
969
772
  return new Promise((resolve, reject) => {
970
773
  this.execRunningCount++;
@@ -979,11 +782,6 @@ export class Matterbridge extends EventEmitter {
979
782
  });
980
783
  });
981
784
  }
982
- /**
983
- * Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
984
- * @private
985
- * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
986
- */
987
785
  async getMatterbridgeLatestVersion() {
988
786
  this.getLatestVersion('matterbridge')
989
787
  .then(async (matterbridgeLatestVersion) => {
@@ -1000,19 +798,8 @@ export class Matterbridge extends EventEmitter {
1000
798
  })
1001
799
  .catch((error) => {
1002
800
  this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
1003
- // error.stack && this.log.debug(error.stack);
1004
801
  });
1005
802
  }
1006
- /**
1007
- * Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
1008
- * If the plugin's version is different from the latest version, logs a warning message.
1009
- * If the plugin's version is the same as the latest version, logs an info message.
1010
- * If there is an error retrieving the latest version, logs an error message.
1011
- *
1012
- * @private
1013
- * @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
1014
- * @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
1015
- */
1016
803
  async getPluginLatestVersion(plugin) {
1017
804
  this.getLatestVersion(plugin.name)
1018
805
  .then(async (latestVersion) => {
@@ -1024,54 +811,40 @@ export class Matterbridge extends EventEmitter {
1024
811
  })
1025
812
  .catch((error) => {
1026
813
  this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
1027
- // error.stack && this.log.debug(error.stack);
1028
814
  });
1029
815
  }
1030
- /**
1031
- * Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
1032
- *
1033
- * @returns {Function} The MatterLogger function.
1034
- */
1035
816
  createMatterLogger() {
1036
- const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
817
+ const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
1037
818
  return (_level, formattedLog) => {
1038
819
  const logger = formattedLog.slice(44, 44 + 20).trim();
1039
820
  const message = formattedLog.slice(65);
1040
821
  matterLogger.logName = logger;
1041
822
  switch (_level) {
1042
823
  case MatterLogLevel.DEBUG:
1043
- matterLogger.log("debug" /* LogLevel.DEBUG */, message);
824
+ matterLogger.log("debug", message);
1044
825
  break;
1045
826
  case MatterLogLevel.INFO:
1046
- matterLogger.log("info" /* LogLevel.INFO */, message);
827
+ matterLogger.log("info", message);
1047
828
  break;
1048
829
  case MatterLogLevel.NOTICE:
1049
- matterLogger.log("notice" /* LogLevel.NOTICE */, message);
830
+ matterLogger.log("notice", message);
1050
831
  break;
1051
832
  case MatterLogLevel.WARN:
1052
- matterLogger.log("warn" /* LogLevel.WARN */, message);
833
+ matterLogger.log("warn", message);
1053
834
  break;
1054
835
  case MatterLogLevel.ERROR:
1055
- matterLogger.log("error" /* LogLevel.ERROR */, message);
836
+ matterLogger.log("error", message);
1056
837
  break;
1057
838
  case MatterLogLevel.FATAL:
1058
- matterLogger.log("fatal" /* LogLevel.FATAL */, message);
839
+ matterLogger.log("fatal", message);
1059
840
  break;
1060
841
  default:
1061
- matterLogger.log("debug" /* LogLevel.DEBUG */, message);
842
+ matterLogger.log("debug", message);
1062
843
  break;
1063
844
  }
1064
845
  };
1065
846
  }
1066
- /**
1067
- * Creates a Matter File Logger.
1068
- *
1069
- * @param {string} filePath - The path to the log file.
1070
- * @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
1071
- * @returns {Function} - A function that logs formatted messages to the log file.
1072
- */
1073
847
  async createMatterFileLogger(filePath, unlink = false) {
1074
- // 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
1075
848
  let fileSize = 0;
1076
849
  if (unlink) {
1077
850
  try {
@@ -1120,86 +893,113 @@ export class Matterbridge extends EventEmitter {
1120
893
  }
1121
894
  };
1122
895
  }
1123
- /**
1124
- * Update matterbridge and cleanup.
1125
- */
1126
- async updateProcess() {
1127
- await this.cleanup('updating...', false);
1128
- }
1129
- /**
1130
- * Restarts the process by spawning a new process and exiting the current process.
1131
- */
1132
896
  async restartProcess() {
1133
897
  await this.cleanup('restarting...', true);
1134
898
  }
1135
- /**
1136
- * Shut down the process by exiting the current process.
1137
- */
1138
899
  async shutdownProcess() {
1139
900
  await this.cleanup('shutting down...', false);
1140
901
  }
1141
- /**
1142
- * Shut down the process and reset.
1143
- */
902
+ async updateProcess() {
903
+ this.log.info('Updating matterbridge...');
904
+ try {
905
+ await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
906
+ this.log.info('Matterbridge has been updated. Full restart required.');
907
+ }
908
+ catch (error) {
909
+ this.log.error('Error updating matterbridge:', error instanceof Error ? error.message : error);
910
+ }
911
+ this.frontend.wssSendRestartRequired();
912
+ await this.cleanup('updating...', false);
913
+ }
1144
914
  async unregisterAndShutdownProcess() {
1145
915
  this.log.info('Unregistering all devices and shutting down...');
1146
- for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
1147
- if (this.edge)
1148
- await this.removeAllBridgedEndpoints(plugin.name);
1149
- else
1150
- await this.removeAllBridgedDevices(plugin.name);
916
+ for (const plugin of this.plugins) {
917
+ await this.removeAllBridgedEndpoints(plugin.name);
1151
918
  }
1152
919
  await this.cleanup('unregistered all devices and shutting down...', false);
1153
920
  }
1154
- /**
1155
- * Shut down the process and reset.
1156
- */
1157
921
  async shutdownProcessAndReset() {
922
+ this.log.info('Resetting Matterbridge commissioning information...');
923
+ await this.matterStorageManager?.createContext('events')?.clearAll();
924
+ await this.matterStorageManager?.createContext('fabrics')?.clearAll();
925
+ await this.matterStorageManager?.createContext('root')?.clearAll();
926
+ await this.matterStorageManager?.createContext('sessions')?.clearAll();
927
+ await this.matterbridgeContext?.clearAll();
928
+ await this.stopMatterStorage();
929
+ this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1158
930
  await this.cleanup('shutting down with reset...', false);
1159
931
  }
1160
- /**
1161
- * Shut down the process and factory reset.
1162
- */
1163
932
  async shutdownProcessAndFactoryReset() {
933
+ try {
934
+ const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
935
+ this.log.info(`Unlinking old matter storage file: ${file}`);
936
+ await fs.unlink(file);
937
+ const backup = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup.json');
938
+ this.log.info(`Unlinking old matter storage backup file: ${backup}`);
939
+ await fs.unlink(backup);
940
+ }
941
+ catch (err) {
942
+ if (err instanceof Error && err.code !== 'ENOENT') {
943
+ this.log.error(`Error unlinking old matter storage file: ${err}`);
944
+ }
945
+ }
946
+ try {
947
+ const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
948
+ this.log.info(`Removing matter node storage directory: ${dir}`);
949
+ await fs.rm(dir, { recursive: true });
950
+ const backup = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
951
+ this.log.info(`Removing matter node storage backup directory: ${backup}`);
952
+ await fs.rm(backup, { recursive: true });
953
+ }
954
+ catch (err) {
955
+ if (err instanceof Error && err.code !== 'ENOENT') {
956
+ this.log.error(`Error removing matter storage directory: ${err}`);
957
+ }
958
+ }
959
+ try {
960
+ const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
961
+ this.log.info(`Removing storage directory: ${dir}`);
962
+ await fs.rm(dir, { recursive: true });
963
+ const backup = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
964
+ this.log.info(`Removing storage backup directory: ${backup}`);
965
+ await fs.rm(backup, { recursive: true });
966
+ }
967
+ catch (err) {
968
+ if (err instanceof Error && err.code !== 'ENOENT') {
969
+ this.log.error(`Error removing storage directory: ${err}`);
970
+ }
971
+ }
972
+ this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
973
+ this.nodeContext = undefined;
974
+ this.nodeStorage = undefined;
975
+ this.plugins.clear();
976
+ this.devices.clear();
1164
977
  await this.cleanup('shutting down with factory reset...', false);
1165
978
  }
1166
- /**
1167
- * Cleans up the Matterbridge instance.
1168
- * @param message - The cleanup message.
1169
- * @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
1170
- * @returns A promise that resolves when the cleanup is completed.
1171
- */
1172
979
  async cleanup(message, restart = false) {
1173
980
  if (this.initialized && !this.hasCleanupStarted) {
1174
981
  this.hasCleanupStarted = true;
1175
982
  this.log.info(message);
1176
- // Deregisters the process handlers
1177
- this.deregisterProcesslHandlers();
1178
- // Clear the start matter interval
1179
983
  if (this.startMatterInterval) {
1180
984
  clearInterval(this.startMatterInterval);
1181
985
  this.startMatterInterval = undefined;
1182
986
  this.log.debug('Start matter interval cleared');
1183
987
  }
1184
- // Clear the check update interval
1185
988
  if (this.checkUpdateInterval) {
1186
989
  clearInterval(this.checkUpdateInterval);
1187
990
  this.checkUpdateInterval = undefined;
1188
991
  this.log.debug('Check update interval cleared');
1189
992
  }
1190
- // Clear the configure timeout
1191
993
  if (this.configureTimeout) {
1192
994
  clearTimeout(this.configureTimeout);
1193
995
  this.configureTimeout = undefined;
1194
996
  this.log.debug('Matterbridge configure timeout cleared');
1195
997
  }
1196
- // Clear the reachability timeout
1197
998
  if (this.reachabilityTimeout) {
1198
999
  clearTimeout(this.reachabilityTimeout);
1199
1000
  this.reachabilityTimeout = undefined;
1200
1001
  this.log.debug('Matterbridge reachability timeout cleared');
1201
1002
  }
1202
- // Calling the shutdown method of each plugin and clear the reachability timeout
1203
1003
  for (const plugin of this.plugins) {
1204
1004
  if (!plugin.enabled || plugin.error)
1205
1005
  continue;
@@ -1210,86 +1010,42 @@ export class Matterbridge extends EventEmitter {
1210
1010
  this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
1211
1011
  }
1212
1012
  }
1213
- // Convert the matter storage to the new format
1214
- if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
1215
- if (this.bridgeMode === 'bridge') {
1216
- await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
1217
- }
1218
- else if (this.bridgeMode === 'childbridge') {
1219
- for (const plugin of this.plugins) {
1220
- if (plugin.storageContext) {
1221
- await this.convertStorage(plugin.storageContext, plugin.name);
1222
- }
1223
- }
1013
+ this.frontend.stop();
1014
+ this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
1015
+ if (this.bridgeMode === 'bridge') {
1016
+ if (this.serverNode) {
1017
+ await this.stopServerNode(this.serverNode);
1018
+ this.log.info(`Stopped matter server node for Matterbridge`);
1224
1019
  }
1225
1020
  }
1226
- // Close the http server
1227
- if (this.httpServer) {
1228
- this.httpServer.close();
1229
- this.httpServer.removeAllListeners();
1230
- this.httpServer = undefined;
1231
- this.log.debug('Frontend http server closed successfully');
1232
- }
1233
- // Close the https server
1234
- if (this.httpsServer) {
1235
- this.httpsServer.close();
1236
- this.httpsServer.removeAllListeners();
1237
- this.httpsServer = undefined;
1238
- this.log.debug('Frontend https server closed successfully');
1239
- }
1240
- // Remove listeners from the express app
1241
- if (this.expressApp) {
1242
- this.expressApp.removeAllListeners();
1243
- this.expressApp = undefined;
1244
- this.log.debug('Frontend app closed successfully');
1245
- }
1246
- // Close the WebSocket server
1247
- if (this.webSocketServer) {
1248
- // Close all active connections
1249
- this.webSocketServer.clients.forEach((client) => {
1250
- if (client.readyState === WebSocket.OPEN) {
1251
- client.close();
1252
- }
1253
- });
1254
- this.webSocketServer.close((error) => {
1255
- if (error) {
1256
- this.log.error(`Error closing WebSocket server: ${error}`);
1257
- }
1258
- else {
1259
- this.log.debug('WebSocket server closed successfully');
1021
+ if (this.bridgeMode === 'childbridge') {
1022
+ for (const plugin of this.plugins.array()) {
1023
+ if (plugin.serverNode) {
1024
+ await this.stopServerNode(plugin.serverNode);
1025
+ this.log.info(`Stopped matter server node for ${plugin.name}`);
1260
1026
  }
1261
- });
1262
- this.webSocketServer = undefined;
1027
+ }
1263
1028
  }
1264
- // Closing matter
1265
- await this.stopMatterServer();
1266
- // Closing matter storage
1029
+ this.log.info('Stopped matter server nodes');
1267
1030
  await this.stopMatterStorage();
1268
- // Remove the matterfilelogger
1269
1031
  try {
1270
1032
  Logger.removeLogger('matterfilelogger');
1271
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1272
1033
  }
1273
1034
  catch (error) {
1274
- // this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
1275
1035
  }
1276
- // Serialize registeredDevices
1277
1036
  if (this.nodeStorage && this.nodeContext) {
1278
1037
  this.log.info('Saving registered devices...');
1279
1038
  const serializedRegisteredDevices = [];
1280
1039
  this.devices.forEach(async (device) => {
1281
1040
  const serializedMatterbridgeDevice = device.serialize();
1282
- // this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
1283
1041
  if (serializedMatterbridgeDevice)
1284
1042
  serializedRegisteredDevices.push(serializedMatterbridgeDevice);
1285
1043
  });
1286
1044
  await this.nodeContext.set('devices', serializedRegisteredDevices);
1287
1045
  this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
1288
- // Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
1289
1046
  this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
1290
1047
  await this.nodeContext.close();
1291
1048
  this.nodeContext = undefined;
1292
- // Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
1293
1049
  for (const plugin of this.plugins) {
1294
1050
  if (plugin.nodeContext) {
1295
1051
  this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
@@ -1306,6 +1062,7 @@ export class Matterbridge extends EventEmitter {
1306
1062
  }
1307
1063
  this.plugins.clear();
1308
1064
  this.devices.clear();
1065
+ this.deregisterProcesslHandlers();
1309
1066
  if (restart) {
1310
1067
  if (message === 'updating...') {
1311
1068
  this.log.info('Cleanup completed. Updating...');
@@ -1319,38 +1076,6 @@ export class Matterbridge extends EventEmitter {
1319
1076
  }
1320
1077
  }
1321
1078
  else {
1322
- if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
1323
- try {
1324
- // Delete old matter storage file
1325
- const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
1326
- this.log.info(`Unlinking old matter storage file: ${file}`);
1327
- await fs.unlink(file);
1328
- }
1329
- catch (error) {
1330
- this.log.debug(`Error resetting old matter storage file: ${error}`);
1331
- }
1332
- try {
1333
- // Delete matter node storage directory with its subdirectories
1334
- const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
1335
- this.log.info(`Removing matter node storage directory: ${dir}`);
1336
- await fs.rm(dir, { recursive: true });
1337
- }
1338
- catch (error) {
1339
- this.log.debug(`Error resetting matter node storage file: ${error}`);
1340
- }
1341
- this.log.info('Reset done! Remove all paired fabrics from the controllers.');
1342
- }
1343
- if (message === 'shutting down with factory reset...') {
1344
- try {
1345
- // Delete node storage directory with its subdirectories
1346
- this.log.info('Resetting Matterbridge storage...');
1347
- await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
1348
- }
1349
- catch (error) {
1350
- this.log.debug(`Error resetting Matterbridge storage: ${error}`);
1351
- }
1352
- this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
1353
- }
1354
1079
  this.log.notice('Cleanup completed. Shutting down...');
1355
1080
  Matterbridge.instance = undefined;
1356
1081
  this.emit('shutdown');
@@ -1359,190 +1084,41 @@ export class Matterbridge extends EventEmitter {
1359
1084
  this.initialized = false;
1360
1085
  }
1361
1086
  }
1362
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1363
- async addBridgedEndpoint(pluginName, device) {
1364
- // Nothing to do here
1365
- }
1366
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1367
- async removeBridgedEndpoint(pluginName, device) {
1368
- // Nothing to do here
1369
- }
1370
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1371
- async removeAllBridgedEndpoints(pluginName) {
1372
- // Nothing to do here
1373
- }
1374
- /**
1375
- * Adds a bridged device to the Matterbridge.
1376
- * @param pluginName - The name of the plugin.
1377
- * @param device - The bridged device to add.
1378
- * @returns {Promise<void>} - A promise that resolves when the device is added.
1379
- */
1380
- async addBridgedDevice(pluginName, device) {
1381
- this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1382
- // Check if the plugin is registered
1383
- const plugin = this.plugins.get(pluginName);
1384
- if (!plugin) {
1385
- this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
1386
- return;
1387
- }
1388
- // Register and add the device to matterbridge aggregator in bridge mode
1389
- if (this.bridgeMode === 'bridge') {
1390
- if (!this.matterAggregator) {
1391
- this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
1392
- return;
1393
- }
1394
- this.matterAggregator.addBridgedDevice(device);
1395
- }
1396
- // The first time create the commissioning server and the aggregator for DynamicPlatform
1397
- // Register and add the device in childbridge mode
1398
- if (this.bridgeMode === 'childbridge') {
1399
- if (plugin.type === 'AccessoryPlatform') {
1400
- // Check if the plugin is locked with the commissioning server
1401
- if (!plugin.locked) {
1402
- plugin.locked = true;
1403
- plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
1404
- this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1405
- plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1406
- this.log.debug(`Adding device ${dev}${device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
1407
- plugin.commissioningServer.addDevice(device);
1408
- plugin.device = device;
1409
- this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1410
- await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1411
- }
1412
- }
1413
- if (plugin.type === 'DynamicPlatform') {
1414
- // Check if the plugin is locked with the commissioning server and the aggregator
1415
- if (!plugin.locked) {
1416
- plugin.locked = true;
1417
- this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
1418
- plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, plugin.description);
1419
- this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
1420
- plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1421
- this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
1422
- plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
1423
- this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
1424
- plugin.commissioningServer.addDevice(plugin.aggregator);
1425
- this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1426
- await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
1427
- }
1428
- plugin.aggregator?.addBridgedDevice(device);
1429
- }
1087
+ async createAccessoryPlugin(plugin, device, start = false) {
1088
+ if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
1089
+ plugin.locked = true;
1090
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
1091
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1092
+ this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
1093
+ await plugin.serverNode.add(device);
1094
+ if (start)
1095
+ await this.startServerNode(plugin.serverNode);
1430
1096
  }
1431
- if (plugin.registeredDevices !== undefined)
1432
- plugin.registeredDevices++;
1433
- if (plugin.addedDevices !== undefined)
1434
- plugin.addedDevices++;
1435
- // Add the device to the DeviceManager
1436
- this.devices.set(device);
1437
- this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1438
1097
  }
1439
- /**
1440
- * Removes a bridged device from the Matterbridge.
1441
- * @param pluginName - The name of the plugin.
1442
- * @param device - The device to be removed.
1443
- * @returns A Promise that resolves when the device is successfully removed.
1444
- */
1445
- async removeBridgedDevice(pluginName, device) {
1446
- this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1447
- // Check if the plugin is registered
1448
- const plugin = this.plugins.get(pluginName);
1449
- if (!plugin) {
1450
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1451
- return;
1452
- }
1453
- // Remove the device from matterbridge aggregator in bridge mode
1454
- if (this.bridgeMode === 'bridge') {
1455
- if (!this.matterAggregator) {
1456
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
1457
- return;
1458
- }
1459
- if (device.number !== undefined) {
1460
- device.setBridgedDeviceReachability(false);
1461
- device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1462
- }
1463
- // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
1464
- // device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
1465
- this.matterAggregator?.removeBridgedDevice(device);
1466
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1467
- if (plugin.registeredDevices !== undefined)
1468
- plugin.registeredDevices--;
1469
- if (plugin.addedDevices !== undefined)
1470
- plugin.addedDevices--;
1471
- }
1472
- // Remove the device in childbridge mode
1473
- if (this.bridgeMode === 'childbridge') {
1474
- if (plugin.type === 'AccessoryPlatform') {
1475
- if (!plugin.commissioningServer) {
1476
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
1477
- return;
1478
- }
1479
- }
1480
- else if (plugin.type === 'DynamicPlatform') {
1481
- if (!plugin.aggregator) {
1482
- this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
1483
- return;
1484
- }
1485
- if (device.number !== undefined) {
1486
- device.setBridgedDeviceReachability(false);
1487
- device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
1488
- }
1489
- plugin.aggregator.removeBridgedDevice(device);
1490
- }
1491
- this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1492
- if (plugin.registeredDevices !== undefined)
1493
- plugin.registeredDevices--;
1494
- if (plugin.addedDevices !== undefined)
1495
- plugin.addedDevices--;
1496
- // Remove the commissioning server
1497
- if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
1498
- this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
1499
- plugin.commissioningServer = undefined;
1500
- this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
1501
- }
1098
+ async createDynamicPlugin(plugin, start = false) {
1099
+ if (!plugin.locked) {
1100
+ plugin.locked = true;
1101
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
1102
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1103
+ plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
1104
+ await plugin.serverNode.add(plugin.aggregatorNode);
1105
+ if (start)
1106
+ await this.startServerNode(plugin.serverNode);
1502
1107
  }
1503
- // Remove the device from the DeviceManager
1504
- this.devices.remove(device);
1505
- }
1506
- /**
1507
- * Removes all bridged devices associated with a specific plugin.
1508
- *
1509
- * @param pluginName - The name of the plugin.
1510
- * @returns A promise that resolves when all devices have been removed.
1511
- */
1512
- async removeAllBridgedDevices(pluginName) {
1513
- this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
1514
- this.devices.forEach(async (device) => {
1515
- if (device.plugin === pluginName) {
1516
- await this.removeBridgedDevice(pluginName, device);
1517
- }
1518
- });
1519
1108
  }
1520
- /**
1521
- * Starts the Matterbridge in bridge mode.
1522
- * @private
1523
- * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1524
- */
1525
1109
  async startBridge() {
1526
- // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1527
- if (!this.storageManager)
1110
+ if (!this.matterStorageManager)
1528
1111
  throw new Error('No storage manager initialized');
1529
1112
  if (!this.matterbridgeContext)
1530
1113
  throw new Error('No storage context initialized');
1531
- this.matterServer = await this.createMatterServer(this.storageManager);
1532
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
1533
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
1534
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
1535
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
1536
- this.log.debug('Adding matterbridge aggregator to commissioning server');
1537
- this.commissioningServer.addDevice(this.matterAggregator);
1538
- this.log.debug('Adding matterbridge commissioning server to matter server');
1539
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1114
+ this.serverNode = await this.createServerNode(this.matterbridgeContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1115
+ this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
1116
+ await this.serverNode.add(this.aggregatorNode);
1540
1117
  await this.startPlugins();
1541
1118
  this.log.debug('Starting start matter interval in bridge mode');
1542
1119
  let failCount = 0;
1543
1120
  this.startMatterInterval = setInterval(async () => {
1544
1121
  for (const plugin of this.plugins) {
1545
- // new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1546
1122
  if (!plugin.enabled)
1547
1123
  continue;
1548
1124
  if (plugin.error) {
@@ -1567,54 +1143,51 @@ export class Matterbridge extends EventEmitter {
1567
1143
  clearInterval(this.startMatterInterval);
1568
1144
  this.startMatterInterval = undefined;
1569
1145
  this.log.debug('Cleared startMatterInterval interval for Matterbridge');
1570
- // Start the Matter server
1571
- await this.startMatterServer();
1572
- this.log.notice('Matter server started');
1573
- // Show the QR code for commissioning or log the already commissioned message
1574
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1575
- // Configure the plugins
1146
+ this.startServerNode(this.serverNode);
1576
1147
  this.configureTimeout = setTimeout(async () => {
1577
1148
  for (const plugin of this.plugins) {
1578
1149
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1579
1150
  continue;
1580
1151
  try {
1581
- await this.plugins.configure(plugin); // TODO No await do it in parallel
1152
+ await this.plugins.configure(plugin);
1582
1153
  }
1583
1154
  catch (error) {
1584
1155
  plugin.error = true;
1585
1156
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1586
1157
  }
1587
1158
  }
1588
- this.wssSendRefreshRequired();
1159
+ this.frontend.wssSendRefreshRequired();
1589
1160
  }, 30 * 1000);
1590
- // Setting reachability to true
1591
1161
  this.reachabilityTimeout = setTimeout(() => {
1592
1162
  this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
1593
- if (this.commissioningServer)
1594
- this.setCommissioningServerReachability(this.commissioningServer, true);
1595
- if (this.matterAggregator)
1596
- this.setAggregatorReachability(this.matterAggregator, true);
1163
+ if (this.serverNode)
1164
+ this.setServerNodeReachability(this.serverNode, true);
1165
+ if (this.aggregatorNode)
1166
+ this.setAggregatorReachability(this.aggregatorNode, true);
1167
+ this.frontend.wssSendRefreshRequired();
1597
1168
  }, 60 * 1000);
1598
1169
  }, 1000);
1599
1170
  }
1600
- /**
1601
- * Starts the Matterbridge in childbridge mode.
1602
- * @private
1603
- * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1604
- */
1605
1171
  async startChildbridge() {
1606
- // Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
1607
- // Plugins are configured by a timer when matter server is started and plugin.configured is set to true
1608
- if (!this.storageManager)
1172
+ if (!this.matterStorageManager)
1609
1173
  throw new Error('No storage manager initialized');
1610
- this.matterServer = await this.createMatterServer(this.storageManager);
1174
+ for (const plugin of this.plugins) {
1175
+ if (!plugin.enabled)
1176
+ continue;
1177
+ if (plugin.type === 'DynamicPlatform') {
1178
+ plugin.locked = true;
1179
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
1180
+ plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
1181
+ plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
1182
+ await plugin.serverNode.add(plugin.aggregatorNode);
1183
+ }
1184
+ }
1611
1185
  await this.startPlugins();
1612
1186
  this.log.debug('Starting start matter interval in childbridge mode...');
1613
1187
  let failCount = 0;
1614
1188
  this.startMatterInterval = setInterval(async () => {
1615
1189
  let allStarted = true;
1616
1190
  for (const plugin of this.plugins) {
1617
- // Prevents to start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
1618
1191
  if (!plugin.enabled)
1619
1192
  continue;
1620
1193
  if (plugin.error) {
@@ -1642,23 +1215,19 @@ export class Matterbridge extends EventEmitter {
1642
1215
  clearInterval(this.startMatterInterval);
1643
1216
  this.startMatterInterval = undefined;
1644
1217
  this.log.debug('Cleared startMatterInterval interval in childbridge mode');
1645
- // Start the Matter server
1646
- await this.startMatterServer();
1647
- this.log.notice('Matter server started');
1648
- // Configure the plugins
1649
1218
  this.configureTimeout = setTimeout(async () => {
1650
1219
  for (const plugin of this.plugins) {
1651
1220
  if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
1652
1221
  continue;
1653
1222
  try {
1654
- await this.plugins.configure(plugin); // TODO No await do it in parallel
1223
+ await this.plugins.configure(plugin);
1655
1224
  }
1656
1225
  catch (error) {
1657
1226
  plugin.error = true;
1658
1227
  this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
1659
1228
  }
1660
1229
  }
1661
- this.wssSendRefreshRequired();
1230
+ this.frontend.wssSendRefreshRequired();
1662
1231
  }, 30 * 1000);
1663
1232
  for (const plugin of this.plugins) {
1664
1233
  if (!plugin.enabled || plugin.error)
@@ -1667,8 +1236,8 @@ export class Matterbridge extends EventEmitter {
1667
1236
  this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
1668
1237
  continue;
1669
1238
  }
1670
- if (!plugin.commissioningServer) {
1671
- this.log.error(`Commissioning server not found for plugin ${plg}${plugin.name}${er}`);
1239
+ if (!plugin.serverNode) {
1240
+ this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
1672
1241
  continue;
1673
1242
  }
1674
1243
  if (!plugin.storageContext) {
@@ -1679,919 +1248,333 @@ export class Matterbridge extends EventEmitter {
1679
1248
  this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
1680
1249
  continue;
1681
1250
  }
1682
- await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1683
- // Setting reachability to true
1251
+ this.startServerNode(plugin.serverNode);
1684
1252
  plugin.reachabilityTimeout = setTimeout(() => {
1685
1253
  this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1686
- if (plugin.commissioningServer)
1687
- this.setCommissioningServerReachability(plugin.commissioningServer, true);
1254
+ if (plugin.serverNode)
1255
+ this.setServerNodeReachability(plugin.serverNode, true);
1688
1256
  if (plugin.type === 'AccessoryPlatform' && plugin.device)
1689
1257
  this.setDeviceReachability(plugin.device, true);
1690
- if (plugin.type === 'DynamicPlatform' && plugin.aggregator)
1691
- this.setAggregatorReachability(plugin.aggregator, true);
1258
+ if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
1259
+ this.setAggregatorReachability(plugin.aggregatorNode, true);
1260
+ this.frontend.wssSendRefreshRequired();
1692
1261
  }, 60 * 1000);
1693
1262
  }
1694
1263
  }, 1000);
1695
1264
  }
1696
- /**
1697
- * Starts the Matterbridge controller.
1698
- * @private
1699
- * @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
1700
- */
1701
1265
  async startController() {
1702
- if (!this.storageManager) {
1703
- this.log.error('No storage manager initialized');
1704
- await this.cleanup('No storage manager initialized');
1705
- return;
1706
- }
1707
- this.log.info('Creating context: mattercontrollerContext');
1708
- this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
1709
- if (!this.mattercontrollerContext) {
1710
- this.log.error('No storage context mattercontrollerContext initialized');
1711
- await this.cleanup('No storage context mattercontrollerContext initialized');
1712
- return;
1713
- }
1714
- this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1715
- this.matterServer = await this.createMatterServer(this.storageManager);
1716
- this.log.info('Creating matter commissioning controller');
1717
- this.commissioningController = new CommissioningController({
1718
- autoConnect: false,
1266
+ }
1267
+ async startMatterStorage() {
1268
+ this.log.info(`Starting matter node storage...`);
1269
+ this.matterStorageService = this.environment.get(StorageService);
1270
+ this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
1271
+ this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
1272
+ this.log.info('Matter node storage manager "Matterbridge" created');
1273
+ this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
1274
+ this.log.info('Matter node storage started');
1275
+ await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
1276
+ }
1277
+ async backupMatterStorage(storageName, backupName) {
1278
+ this.log.info('Creating matter node storage backup...');
1279
+ await copyDirectory(storageName, backupName);
1280
+ this.log.info('Created matter node storage backup');
1281
+ }
1282
+ async stopMatterStorage() {
1283
+ this.log.info('Closing matter node storage...');
1284
+ this.matterStorageManager?.close();
1285
+ this.matterStorageService = undefined;
1286
+ this.matterStorageManager = undefined;
1287
+ this.matterbridgeContext = undefined;
1288
+ this.log.info('Matter node storage closed');
1289
+ }
1290
+ async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
1291
+ if (!this.matterStorageService)
1292
+ throw new Error('No storage service initialized');
1293
+ this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
1294
+ const storageManager = await this.matterStorageService.open(pluginName);
1295
+ const storageContext = storageManager.createContext('persist');
1296
+ const random = randomBytes(8).toString('hex');
1297
+ await storageContext.set('storeId', pluginName);
1298
+ await storageContext.set('deviceName', deviceName);
1299
+ await storageContext.set('deviceType', deviceType);
1300
+ await storageContext.set('vendorId', vendorId);
1301
+ await storageContext.set('vendorName', vendorName.slice(0, 32));
1302
+ await storageContext.set('productId', productId);
1303
+ await storageContext.set('productName', productName.slice(0, 32));
1304
+ await storageContext.set('nodeLabel', productName.slice(0, 32));
1305
+ await storageContext.set('productLabel', productName.slice(0, 32));
1306
+ await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
1307
+ await storageContext.set('uniqueId', await storageContext.get('uniqueId', 'UI' + random));
1308
+ await storageContext.set('softwareVersion', this.matterbridgeVersion !== '' && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
1309
+ await storageContext.set('softwareVersionString', this.matterbridgeVersion !== '' ? this.matterbridgeVersion : '1.0.0');
1310
+ await storageContext.set('hardwareVersion', this.systemInformation.osRelease !== '' && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
1311
+ await storageContext.set('hardwareVersionString', this.systemInformation.osRelease !== '' ? this.systemInformation.osRelease : '1.0.0');
1312
+ this.log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`);
1313
+ this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
1314
+ this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
1315
+ this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
1316
+ this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1317
+ this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1318
+ this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1319
+ this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1320
+ return storageContext;
1321
+ }
1322
+ async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
1323
+ const storeId = await storageContext.get('storeId');
1324
+ this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
1325
+ this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
1326
+ this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
1327
+ this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
1328
+ this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
1329
+ this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
1330
+ this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
1331
+ const serverNode = await ServerNode.create({
1332
+ id: storeId,
1333
+ network: {
1334
+ listeningAddressIpv4: this.ipv4address,
1335
+ listeningAddressIpv6: this.ipv6address,
1336
+ port,
1337
+ },
1338
+ commissioning: {
1339
+ passcode,
1340
+ discriminator,
1341
+ },
1342
+ productDescription: {
1343
+ name: await storageContext.get('deviceName'),
1344
+ deviceType: DeviceTypeId(await storageContext.get('deviceType')),
1345
+ },
1346
+ basicInformation: {
1347
+ vendorId: VendorId(await storageContext.get('vendorId')),
1348
+ vendorName: await storageContext.get('vendorName'),
1349
+ productId: await storageContext.get('productId'),
1350
+ productName: await storageContext.get('productName'),
1351
+ productLabel: await storageContext.get('productName'),
1352
+ nodeLabel: await storageContext.get('productName'),
1353
+ serialNumber: await storageContext.get('serialNumber'),
1354
+ uniqueId: await storageContext.get('uniqueId'),
1355
+ softwareVersion: await storageContext.get('softwareVersion'),
1356
+ softwareVersionString: await storageContext.get('softwareVersionString'),
1357
+ hardwareVersion: await storageContext.get('hardwareVersion'),
1358
+ hardwareVersionString: await storageContext.get('hardwareVersionString'),
1359
+ },
1719
1360
  });
1720
- this.log.info('Adding matter commissioning controller to matter server');
1721
- await this.matterServer.addCommissioningController(this.commissioningController);
1722
- this.log.info('Starting matter server');
1723
- await this.matterServer.start();
1724
- this.log.info('Matter server started');
1725
- if (hasParameter('pairingcode')) {
1726
- this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
1727
- const pairingCode = getParameter('pairingcode');
1728
- const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get('ip') : undefined;
1729
- const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get('port') : undefined;
1730
- let longDiscriminator, setupPin, shortDiscriminator;
1731
- if (pairingCode !== undefined) {
1732
- const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
1733
- shortDiscriminator = pairingCodeCodec.shortDiscriminator;
1734
- longDiscriminator = undefined;
1735
- setupPin = pairingCodeCodec.passcode;
1736
- this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
1361
+ const sanitizeFabrics = (fabrics) => {
1362
+ const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
1363
+ this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
1364
+ if (this.bridgeMode === 'bridge') {
1365
+ this.matterbridgeFabricInformations = sanitizedFabrics;
1366
+ this.matterbridgeSessionInformations = [];
1367
+ this.matterbridgePaired = true;
1368
+ }
1369
+ if (this.bridgeMode === 'childbridge') {
1370
+ const plugin = this.plugins.get(storeId);
1371
+ if (plugin) {
1372
+ plugin.fabricInformations = sanitizedFabrics;
1373
+ plugin.sessionInformations = [];
1374
+ plugin.paired = true;
1375
+ }
1376
+ }
1377
+ };
1378
+ serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
1379
+ serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
1380
+ serverNode.lifecycle.online.on(() => {
1381
+ this.log.notice(`Server node for ${storeId} is online`);
1382
+ if (!serverNode.lifecycle.isCommissioned) {
1383
+ this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
1384
+ const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
1385
+ if (this.bridgeMode === 'bridge') {
1386
+ this.matterbridgeQrPairingCode = qrPairingCode;
1387
+ this.matterbridgeManualPairingCode = manualPairingCode;
1388
+ this.matterbridgeFabricInformations = [];
1389
+ this.matterbridgeSessionInformations = [];
1390
+ this.matterbridgePaired = false;
1391
+ this.matterbridgeConnected = false;
1392
+ this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1393
+ this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1394
+ }
1395
+ if (this.bridgeMode === 'childbridge') {
1396
+ const plugin = this.plugins.get(storeId);
1397
+ if (plugin) {
1398
+ plugin.qrPairingCode = qrPairingCode;
1399
+ plugin.manualPairingCode = manualPairingCode;
1400
+ plugin.fabricInformations = [];
1401
+ plugin.sessionInformations = [];
1402
+ plugin.paired = false;
1403
+ plugin.connected = false;
1404
+ this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
1405
+ this.log.notice(`Manual pairing code: ${manualPairingCode}`);
1406
+ }
1407
+ }
1737
1408
  }
1738
1409
  else {
1739
- longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
1740
- if (longDiscriminator > 4095)
1741
- throw new Error('Discriminator value must be less than 4096');
1742
- setupPin = this.mattercontrollerContext.get('pin', 20202021);
1410
+ this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
1411
+ sanitizeFabrics(serverNode.state.commissioning.fabrics);
1743
1412
  }
1744
- if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
1745
- throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
1413
+ this.frontend.wssSendRefreshRequired();
1414
+ });
1415
+ serverNode.lifecycle.offline.on(() => {
1416
+ this.log.notice(`Server node for ${storeId} is offline`);
1417
+ if (this.bridgeMode === 'bridge') {
1418
+ this.matterbridgeQrPairingCode = undefined;
1419
+ this.matterbridgeManualPairingCode = undefined;
1420
+ this.matterbridgeFabricInformations = [];
1421
+ this.matterbridgeSessionInformations = [];
1422
+ this.matterbridgePaired = false;
1423
+ this.matterbridgeConnected = false;
1746
1424
  }
1747
- const commissioningOptions = {
1748
- regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
1749
- regulatoryCountryCode: 'XX',
1750
- };
1751
- const options = {
1752
- commissioning: commissioningOptions,
1753
- discovery: {
1754
- knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
1755
- identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
1756
- },
1757
- passcode: setupPin,
1758
- };
1759
- this.log.info('Commissioning with options:', options);
1760
- const nodeId = await this.commissioningController.commissionNode(options);
1761
- this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1762
- this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
1763
- } // (hasParameter('pairingcode'))
1764
- if (hasParameter('unpairall')) {
1765
- this.log.info('***Commissioning controller unpairing all nodes...');
1766
- const nodeIds = this.commissioningController.getCommissionedNodes();
1767
- for (const nodeId of nodeIds) {
1768
- this.log.info('***Commissioning controller unpairing node:', nodeId);
1769
- await this.commissioningController.removeNode(nodeId);
1425
+ this.frontend.wssSendRefreshRequired();
1426
+ });
1427
+ serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
1428
+ let action = '';
1429
+ switch (fabricAction) {
1430
+ case FabricAction.Added:
1431
+ action = 'added';
1432
+ break;
1433
+ case FabricAction.Removed:
1434
+ action = 'removed';
1435
+ break;
1436
+ case FabricAction.Updated:
1437
+ action = 'updated';
1438
+ break;
1439
+ }
1440
+ this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
1441
+ sanitizeFabrics(serverNode.state.commissioning.fabrics);
1442
+ this.frontend.wssSendRefreshRequired();
1443
+ });
1444
+ const sanitizeSessions = (sessions) => {
1445
+ const sanitizedSessions = this.sanitizeSessionInformation(sessions.map((session) => ({
1446
+ ...session,
1447
+ secure: session.name.startsWith('secure'),
1448
+ })));
1449
+ this.log.debug(`Sessions: ${debugStringify(sanitizedSessions)}`);
1450
+ if (this.bridgeMode === 'bridge') {
1451
+ this.matterbridgeSessionInformations = sanitizedSessions;
1452
+ }
1453
+ if (this.bridgeMode === 'childbridge') {
1454
+ const plugin = this.plugins.get(storeId);
1455
+ if (plugin) {
1456
+ plugin.sessionInformations = sanitizedSessions;
1457
+ }
1770
1458
  }
1459
+ };
1460
+ serverNode.events.sessions.opened.on((session) => {
1461
+ this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
1462
+ sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1463
+ this.frontend.wssSendRefreshRequired();
1464
+ });
1465
+ serverNode.events.sessions.closed.on((session) => {
1466
+ this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
1467
+ sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1468
+ this.frontend.wssSendRefreshRequired();
1469
+ });
1470
+ serverNode.events.sessions.subscriptionsChanged.on((session) => {
1471
+ this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
1472
+ sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
1473
+ this.frontend.wssSendRefreshRequired();
1474
+ });
1475
+ this.log.info(`Created server node for ${storeId}`);
1476
+ return serverNode;
1477
+ }
1478
+ async startServerNode(matterServerNode) {
1479
+ if (!matterServerNode)
1771
1480
  return;
1772
- }
1773
- if (hasParameter('discover')) {
1774
- // const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
1775
- // console.log(discover);
1776
- }
1777
- if (!this.commissioningController.isCommissioned()) {
1778
- this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
1481
+ this.log.notice(`Starting ${matterServerNode.id} server node`);
1482
+ await matterServerNode.start();
1483
+ }
1484
+ async stopServerNode(matterServerNode) {
1485
+ if (!matterServerNode)
1779
1486
  return;
1780
- }
1781
- const nodeIds = this.commissioningController.getCommissionedNodes();
1782
- this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
1783
- for (const nodeId of nodeIds) {
1784
- this.log.info(`***Connecting to commissioned node: ${nodeId}`);
1785
- const node = await this.commissioningController.connectNode(nodeId, {
1786
- autoSubscribe: false,
1787
- attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) => this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
1788
- eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) => this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
1789
- stateInformationCallback: (peerNodeId, info) => {
1790
- switch (info) {
1791
- case NodeStateInformation.Connected:
1792
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
1793
- break;
1794
- case NodeStateInformation.Disconnected:
1795
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
1796
- break;
1797
- case NodeStateInformation.Reconnecting:
1798
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
1799
- break;
1800
- case NodeStateInformation.WaitingForDeviceDiscovery:
1801
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
1802
- break;
1803
- case NodeStateInformation.StructureChanged:
1804
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
1805
- break;
1806
- case NodeStateInformation.Decommissioned:
1807
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
1808
- break;
1809
- default:
1810
- this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
1811
- break;
1812
- }
1813
- },
1814
- });
1815
- node.logStructure();
1816
- // Get the interaction client
1817
- this.log.info('Getting the interaction client');
1818
- const interactionClient = await node.getInteractionClient();
1819
- let cluster;
1820
- let attributes;
1821
- // Log BasicInformationCluster
1822
- cluster = BasicInformationCluster;
1823
- attributes = await interactionClient.getMultipleAttributes({
1824
- attributes: [{ clusterId: cluster.id }],
1825
- });
1826
- if (attributes.length > 0)
1827
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1828
- attributes.forEach((attribute) => {
1829
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1830
- });
1831
- // Log PowerSourceCluster
1832
- cluster = PowerSourceCluster;
1833
- attributes = await interactionClient.getMultipleAttributes({
1834
- attributes: [{ clusterId: cluster.id }],
1835
- });
1836
- if (attributes.length > 0)
1837
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1838
- attributes.forEach((attribute) => {
1839
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1840
- });
1841
- // Log ThreadNetworkDiagnostics
1842
- cluster = ThreadNetworkDiagnosticsCluster;
1843
- attributes = await interactionClient.getMultipleAttributes({
1844
- attributes: [{ clusterId: cluster.id }],
1845
- });
1846
- if (attributes.length > 0)
1847
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1848
- attributes.forEach((attribute) => {
1849
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1850
- });
1851
- // Log SwitchCluster
1852
- cluster = SwitchCluster;
1853
- attributes = await interactionClient.getMultipleAttributes({
1854
- attributes: [{ clusterId: cluster.id }],
1855
- });
1856
- if (attributes.length > 0)
1857
- this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
1858
- attributes.forEach((attribute) => {
1859
- this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
1860
- });
1861
- this.log.info('Subscribing to all attributes and events');
1862
- await node.subscribeAllAttributesAndEvents({
1863
- ignoreInitialTriggers: false,
1864
- attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) => this.log.info(`***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`),
1865
- eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
1866
- this.log.info(`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`);
1867
- },
1868
- });
1869
- this.log.info('Subscribed to all attributes and events');
1870
- }
1487
+ this.log.notice(`Closing ${matterServerNode.id} server node`);
1488
+ await matterServerNode.close();
1871
1489
  }
1872
- /** ***********************************************************************************************************************************/
1873
- /** Matter.js methods */
1874
- /** ***********************************************************************************************************************************/
1875
- /**
1876
- * Starts the matter storage process based on the specified storage type and name.
1877
- * @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
1878
- * @param {string} storageName - The name of the storage file.
1879
- * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1880
- */
1881
- async startMatterStorage(storageType, storageName) {
1882
- this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
1883
- if (storageType === 'disk') {
1884
- const storageDisk = new StorageBackendDisk(storageName);
1885
- this.storageManager = new StorageManager(storageDisk);
1886
- }
1887
- else if (storageType === 'json') {
1888
- if (!storageName.endsWith('.json'))
1889
- storageName += '.json';
1890
- const storageJson = new StorageBackendJsonFile(storageName);
1891
- this.storageManager = new StorageManager(storageJson);
1892
- }
1893
- else {
1894
- this.log.error(`Unsupported matter storage type ${storageType}`);
1895
- await this.cleanup('Unsupported matter storage type');
1490
+ async createAggregatorNode(storageContext) {
1491
+ this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
1492
+ const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
1493
+ return aggregatorNode;
1494
+ }
1495
+ async addBridgedEndpoint(pluginName, device) {
1496
+ const plugin = this.plugins.get(pluginName);
1497
+ if (!plugin) {
1498
+ this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
1896
1499
  return;
1897
1500
  }
1898
- try {
1899
- await this.storageManager.initialize();
1900
- this.log.debug('Matter storage initialized');
1901
- if (storageType === 'json') {
1902
- await this.backupMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
1903
- }
1501
+ if (this.bridgeMode === 'bridge') {
1502
+ this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
1503
+ if (!this.aggregatorNode)
1504
+ this.log.error('Aggregator node not found for Matterbridge');
1505
+ await this.aggregatorNode?.add(device);
1904
1506
  }
1905
- catch (error) {
1906
- this.log.error(`Matter storage initialize error! The file .matterbridge/${storageName} may be corrupted: ${error instanceof Error ? error.message : error}`);
1907
- if (hasParameter('norestore')) {
1908
- this.log.fatal(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
1909
- await this.cleanup('Matter storage initialize error and -norestore parameter found!');
1910
- return;
1911
- }
1912
- await this.restoreMatterStorage(storageName.replace('.json', '') + '.backup.json', storageName);
1913
- try {
1914
- await this.storageManager.initialize();
1915
- this.log.notice('Matter storage initialized from the backup file');
1507
+ else if (this.bridgeMode === 'childbridge') {
1508
+ if (plugin.type === 'AccessoryPlatform') {
1509
+ this.createAccessoryPlugin(plugin, device);
1916
1510
  }
1917
- catch (error) {
1918
- this.log.error(`Matter storage initialize error! The backup file for .matterbridge/${storageName} may be corrupted too:`, error instanceof Error ? error.message : error);
1919
- await this.cleanup('Matter storage initialize error from backup!');
1920
- return;
1511
+ if (plugin.type === 'DynamicPlatform') {
1512
+ plugin.locked = true;
1513
+ this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} aggregator node`);
1514
+ if (!plugin.aggregatorNode)
1515
+ this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${db}`);
1516
+ await plugin.aggregatorNode?.add(device);
1921
1517
  }
1922
1518
  }
1923
- this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
1924
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', aggregator.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
1925
- await this.matterbridgeContext.set('port', this.port);
1926
- await this.matterbridgeContext.set('passcode', this.passcode);
1927
- await this.matterbridgeContext.set('discriminator', this.discriminator);
1519
+ if (plugin.registeredDevices !== undefined)
1520
+ plugin.registeredDevices++;
1521
+ if (plugin.addedDevices !== undefined)
1522
+ plugin.addedDevices++;
1523
+ this.devices.set(device);
1524
+ this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
1928
1525
  }
1929
- /**
1930
- * Convert the old API matter storage to the new API format.
1931
- * @param {StorageContext} context - The context of Matterbridge or of the plugin.
1932
- * @param {string} pluginName - The name of the plugin or Matterbridge.
1933
- * @returns {Promise<void>} - A promise that resolves when the storage process is started.
1934
- */
1935
- async convertStorage(context, pluginName) {
1936
- if (this.edge !== false)
1526
+ async removeBridgedEndpoint(pluginName, device) {
1527
+ this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
1528
+ const plugin = this.plugins.get(pluginName);
1529
+ if (!plugin) {
1530
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
1937
1531
  return;
1938
- try {
1939
- const storageService = Environment.default.get(StorageService);
1940
- Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
1941
- const nodeStorage = await storageService.open(pluginName);
1942
- if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
1943
- this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
1944
- return;
1945
- }
1946
- else {
1947
- this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
1948
- }
1949
- // Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
1950
- const fabricManagerContext = context.createContext('FabricManager');
1951
- const fabrics = (await fabricManagerContext.get('fabrics', []));
1952
- const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
1953
- // Read EventHandler from the old storage
1954
- const eventHandlerContext = context.createContext('EventHandler');
1955
- // Read SessionManager from the old storage
1956
- const sessionManagerContext = context.createContext('SessionManager');
1957
- // Read EndpointStructure from the old storage
1958
- const endpointStructureContext = context.createContext('EndpointStructure');
1959
- // Read generalCommissioning from the old storage
1960
- const generalCommissioningContext = context.createContext('Cluster-0-48');
1961
- // Read basicInformation from the old storage
1962
- const basicInformationContext = context.createContext('Cluster-0-40');
1963
- const fabricInfo = {};
1964
- const fabricInfoArray = [];
1965
- const nocArray = [];
1966
- const trcArray = [];
1967
- const aclArray = [];
1968
- this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
1969
- if (fabrics.length === 0 || nextFabricIndex === 0) {
1970
- this.log.notice(`If you want to try out matterbridge edge add -edge to the command line and pair it to your controller(s).`);
1532
+ }
1533
+ if (this.bridgeMode === 'bridge') {
1534
+ if (!this.aggregatorNode) {
1535
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1971
1536
  return;
1972
1537
  }
1973
- for (const fabric of fabrics) {
1974
- this.log.info(`- fabricIndex ${CYAN}${fabric.fabricIndex}${nf} fabricId ${CYAN}${fabric.fabricId}${nf} nodeId ${CYAN}${fabric.nodeId}${nf} rootNodeId ${CYAN}${fabric.rootNodeId}${nf} rootVendorId ${CYAN}${fabric.rootVendorId}${nf} label ${CYAN}${fabric.label}${nf}`);
1975
- fabricInfo[fabric.fabricIndex] = {
1976
- fabricIndex: fabric.fabricIndex,
1977
- fabricId: fabric.fabricId,
1978
- nodeId: fabric.nodeId,
1979
- rootNodeId: fabric.rootNodeId,
1980
- rootVendorId: fabric.rootVendorId,
1981
- label: fabric.label,
1982
- };
1983
- fabricInfoArray.push({
1984
- fabricIndex: fabric.fabricIndex,
1985
- fabricId: fabric.fabricId,
1986
- nodeId: fabric.nodeId,
1987
- vendorId: fabric.rootVendorId,
1988
- rootPublicKey: fabric.rootPublicKey,
1989
- label: fabric.label,
1990
- });
1991
- nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
1992
- trcArray.push(fabric.rootCert);
1993
- this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
1994
- const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
1995
- if (acl && acl.value.length > 0) {
1996
- aclArray.push(acl.value[0]);
1997
- this.log.info(`- ACL updated to ${debugStringify(acl.value)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
1998
- }
1999
- else {
2000
- const defaultAcl = { fabricIndex: fabric.fabricIndex, privilege: 5, authMode: 2, subjects: [fabric.rootNodeId], targets: null };
2001
- aclArray.push(defaultAcl);
2002
- this.log.info(`- ACL updated to default ${debugStringify(defaultAcl)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
2003
- }
2004
- }
2005
- await nodeStorage.createContext('fabrics').set('fabrics', fabrics);
2006
- await nodeStorage.createContext('fabrics').set('nextFabricIndex', nextFabricIndex);
2007
- await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
2008
- await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
2009
- await nodeStorage.createContext('root').set('__number__', 0);
2010
- await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
2011
- await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
2012
- await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
2013
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
2014
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
2015
- // operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
2016
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
2017
- // operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
2018
- await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
2019
- // ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
2020
- // From fabric.rootNodeId if fabric.scopedClusterData.get(0x1f).get('acl') is empty
2021
- // [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
2022
- await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
2023
- await nodeStorage
2024
- .createContext('root')
2025
- .createContext('generalCommissioning')
2026
- .set('breadcrumb', await generalCommissioningContext.get('breadcrumb', BigInt(0)));
2027
- await nodeStorage
2028
- .createContext('root')
2029
- .createContext('basicInformation')
2030
- .set('location', await basicInformationContext.get('location', 'XX'));
2031
- await nodeStorage.createContext('root').createContext('network').set('ble', false);
2032
- await nodeStorage
2033
- .createContext('root')
2034
- .createContext('network')
2035
- .set('operationalPort', await context.get('port', 5540));
2036
- await nodeStorage
2037
- .createContext('root')
2038
- .createContext('productDescription')
2039
- .set('productId', await context.get('productId', 0x8000));
2040
- await nodeStorage
2041
- .createContext('root')
2042
- .createContext('productDescription')
2043
- .set('vendorId', await context.get('vendorId', 0xfff1));
2044
- /*
2045
- "Matterbridge.EndpointStructure": {
2046
- "unique_d60ca095a002f160-index_0": 1,
2047
- "unique_d60ca095a002f160-index_0-custom_Switch0": 2,
2048
- "unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
2049
- "unique_d60ca095a002f160-index_0-custom_Light0": 4,
2050
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
2051
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
2052
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
2053
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
2054
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
2055
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
2056
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
2057
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
2058
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
2059
- "unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
2060
- "nextEndpointId": 5
2061
- },
2062
- */
2063
- const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
2064
- this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
2065
- for (const key of await endpointStructureContext.keys()) {
2066
- if (key === 'nextEndpointId') {
2067
- await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
2068
- continue;
2069
- }
2070
- const parts = key.split('-');
2071
- const number = await endpointStructureContext.get(key);
2072
- if (parts.length === 2) {
2073
- this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.__number__:${CYAN}${number}${db}`);
2074
- await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).set('__number__', number);
2075
- }
2076
- else if (parts.length === 3 && parts[2].startsWith('unique_')) {
2077
- const device = this.devices.get(parts[2].replace('unique_', ''));
2078
- if (device && device.deviceName && device.maybeNumber) {
2079
- this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${CYAN}${device.maybeNumber}${db}`);
2080
- await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
2081
- }
2082
- }
2083
- else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
2084
- const device = this.devices.get(parts[2].replace('unique_', ''));
2085
- if (device && device.deviceName && device.maybeNumber) {
2086
- const childEndpointName = parts[3].replace('custom_', '');
2087
- const childEndpoint = device.getChildEndpointByName(childEndpointName);
2088
- this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${childEndpoint?.number}${db}`);
2089
- await nodeStorage
2090
- .createContext('root')
2091
- .createContext('parts')
2092
- .createContext(rootDeviceName)
2093
- .createContext('parts')
2094
- .createContext(device.deviceName.replace(/[ .]/g, ''))
2095
- .createContext('parts')
2096
- .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
2097
- .set('__number__', childEndpoint?.number);
2098
- }
2099
- }
2100
- else if (parts.length === 3 && parts[2].startsWith('custom_')) {
2101
- this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
2102
- await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
2103
- }
2104
- else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
2105
- this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
2106
- await nodeStorage
2107
- .createContext('root')
2108
- .createContext('parts')
2109
- .createContext(rootDeviceName)
2110
- .createContext('parts')
2111
- .createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
2112
- .createContext('parts')
2113
- .createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
2114
- .set('__number__', number);
2115
- }
2116
- }
2117
- await nodeStorage.createContext('persist').set('converted', true);
2118
- await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
2119
- await nodeStorage.createContext('persist').set('deviceType', await context.get('deviceType'));
2120
- await nodeStorage.createContext('persist').set('vendorId', await context.get('vendorId'));
2121
- await nodeStorage.createContext('persist').set('vendorName', await context.get('vendorName'));
2122
- await nodeStorage.createContext('persist').set('productId', await context.get('productId'));
2123
- await nodeStorage.createContext('persist').set('productName', await context.get('productName'));
2124
- await nodeStorage.createContext('persist').set('nodeLabel', await context.get('nodeLabel'));
2125
- await nodeStorage.createContext('persist').set('productLabel', await context.get('productLabel'));
2126
- await nodeStorage.createContext('persist').set('serialNumber', 'SN' + (await context.get('serialNumber')));
2127
- await nodeStorage.createContext('persist').set('uniqueId', await context.get('uniqueId'));
2128
- await nodeStorage.createContext('persist').set('softwareVersion', await context.get('softwareVersion'));
2129
- await nodeStorage.createContext('persist').set('softwareVersionString', await context.get('softwareVersionString'));
2130
- await nodeStorage.createContext('persist').set('hardwareVersion', await context.get('hardwareVersion'));
2131
- await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
2132
- await context.set('converted', true);
2133
- this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
2134
- this.log.notice(`If you want to try out matterbridge edge add -edge to the command line.`);
2135
- this.log.notice(`All fabrics have been converted to the new storage format.`);
2136
- }
2137
- catch (error) {
2138
- this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
2139
- }
2140
- }
2141
- /**
2142
- * Makes a backup copy of the specified matter JSON storage file.
2143
- *
2144
- * @param storageName - The name of the JSON storage file to be backed up.
2145
- * @param backupName - The name of the backup file to be created.
2146
- */
2147
- async backupMatterStorage(storageName, backupName) {
2148
- try {
2149
- this.log.debug(`Making backup copy of ${storageName}`);
2150
- await fs.copyFile(storageName, backupName);
2151
- this.log.debug(`Successfully backed up ${storageName} to ${backupName}`);
1538
+ await device.delete();
1539
+ this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1540
+ if (plugin.registeredDevices !== undefined)
1541
+ plugin.registeredDevices--;
1542
+ if (plugin.addedDevices !== undefined)
1543
+ plugin.addedDevices--;
2152
1544
  }
2153
- catch (err) {
2154
- if (err instanceof Error && 'code' in err) {
2155
- if (err.code === 'ENOENT') {
2156
- this.log.debug(`No existing file to back up for ${storageName}. This is expected on the first run.`);
2157
- }
2158
- else {
2159
- this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
2160
- }
2161
- }
2162
- else {
2163
- this.log.error(`An unexpected error occurred during the backup of ${storageName}: ${String(err)}`);
1545
+ else if (this.bridgeMode === 'childbridge') {
1546
+ if (plugin.type === 'AccessoryPlatform') {
2164
1547
  }
2165
- }
2166
- }
2167
- /**
2168
- * Restore the specified matter JSON storage file.
2169
- *
2170
- * @param backupName - The name of the backup file to restore from.
2171
- * @param storageName - The name of the JSON storage file to restored.
2172
- */
2173
- async restoreMatterStorage(backupName, storageName) {
2174
- try {
2175
- this.log.notice(`Restoring the backup copy of ${storageName}`);
2176
- await fs.copyFile(backupName, storageName);
2177
- this.log.notice(`Successfully restored ${backupName} to ${storageName}`);
2178
- }
2179
- catch (err) {
2180
- if (err instanceof Error && 'code' in err) {
2181
- if (err.code === 'ENOENT') {
2182
- this.log.info(`No existing file to restore: ${backupName}.`);
2183
- }
2184
- else {
2185
- this.log.error(`Error restoring ${backupName}: ${err.message}`);
1548
+ else if (plugin.type === 'DynamicPlatform') {
1549
+ if (!plugin.aggregatorNode) {
1550
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
1551
+ return;
2186
1552
  }
1553
+ await device.delete();
2187
1554
  }
2188
- else {
2189
- this.log.error(`An unexpected error occurred during the restore of ${backupName}: ${String(err)}`);
2190
- }
2191
- }
2192
- }
2193
- /**
2194
- * Stops the matter storage.
2195
- * @returns {Promise<void>} A promise that resolves when the storage is stopped.
2196
- */
2197
- async stopMatterStorage() {
2198
- this.log.debug('Stopping storage');
2199
- await this.storageManager?.close();
2200
- this.log.debug('Storage closed');
2201
- this.storageManager = undefined;
2202
- this.matterbridgeContext = undefined;
2203
- this.mattercontrollerContext = undefined;
2204
- }
2205
- /**
2206
- * Creates a Matter server using the provided storage manager and the provided mdnsInterface.
2207
- * @param storageManager The storage manager to be used by the Matter server.
2208
- *
2209
- */
2210
- async createMatterServer(storageManager) {
2211
- this.log.debug('Creating matter server');
2212
- // Validate mdnsInterface
2213
- if (this.mdnsInterface) {
2214
- const networkInterfaces = os.networkInterfaces();
2215
- const availableInterfaces = Object.keys(networkInterfaces);
2216
- if (!availableInterfaces.includes(this.mdnsInterface)) {
2217
- this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
2218
- this.mdnsInterface = undefined;
2219
- }
2220
- else {
2221
- this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
2222
- }
2223
- }
2224
- const matterServer = new MatterServer(storageManager, { mdnsInterface: this.mdnsInterface });
2225
- this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
2226
- return matterServer;
2227
- }
2228
- /**
2229
- * Starts the Matter server.
2230
- * If the Matter server is not initialized, it logs an error and performs cleanup.
2231
- */
2232
- async startMatterServer() {
2233
- if (!this.matterServer) {
2234
- this.log.error('No matter server initialized');
2235
- await this.cleanup('No matter server initialized');
2236
- return;
2237
- }
2238
- this.log.debug('Starting matter server...');
2239
- await this.matterServer.start();
2240
- this.log.debug('Started matter server');
2241
- // this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
2242
- }
2243
- /**
2244
- * Stops the Matter server, commissioningServer and commissioningController.
2245
- */
2246
- async stopMatterServer() {
2247
- this.log.debug('Stopping matter commissioningServer');
2248
- await this.commissioningServer?.close();
2249
- this.log.debug('Stopping matter commissioningController');
2250
- await this.commissioningController?.close();
2251
- this.log.debug('Stopping matter server');
2252
- await this.matterServer?.close();
2253
- this.log.debug('Matter server closed');
2254
- this.commissioningController = undefined;
2255
- this.commissioningServer = undefined;
2256
- this.matterAggregator = undefined;
2257
- this.matterServer = undefined;
2258
- }
2259
- /**
2260
- * Creates a Matter Aggregator.
2261
- * @param {StorageContext} context - The storage context.
2262
- * @returns {Aggregator} - The created Matter Aggregator.
2263
- */
2264
- async createMatterAggregator(context, pluginName) {
2265
- this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
2266
- const matterAggregator = new Aggregator();
2267
- return matterAggregator;
2268
- }
2269
- /**
2270
- * Creates a matter commissioning server.
2271
- *
2272
- * @param {StorageContext} context - The storage context.
2273
- * @param {string} pluginName - The name of the commissioning server.
2274
- * @returns {CommissioningServer} The created commissioning server.
2275
- */
2276
- async createCommisioningServer(context, pluginName) {
2277
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
2278
- const deviceName = await context.get('deviceName');
2279
- const deviceType = await context.get('deviceType');
2280
- const vendorId = await context.get('vendorId');
2281
- const vendorName = await context.get('vendorName'); // Home app = Manufacturer
2282
- const productId = await context.get('productId');
2283
- const productName = await context.get('productName'); // Home app = Model
2284
- const serialNumber = await context.get('serialNumber');
2285
- const uniqueId = await context.get('uniqueId');
2286
- const softwareVersion = await context.get('softwareVersion', 1);
2287
- const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
2288
- const hardwareVersion = await context.get('hardwareVersion', 1);
2289
- const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
2290
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
2291
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
2292
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
2293
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
2294
- this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
2295
- // Validate ipv4address
2296
- if (this.ipv4address) {
2297
- const networkInterfaces = os.networkInterfaces();
2298
- const availableAddresses = Object.values(networkInterfaces)
2299
- .flat()
2300
- .filter((iface) => iface !== undefined && iface.family === 'IPv4' && !iface.internal)
2301
- .map((iface) => iface.address);
2302
- if (!availableAddresses.includes(this.ipv4address)) {
2303
- this.log.error(`Invalid ipv4address: ${this.ipv4address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
2304
- this.mdnsInterface = undefined;
2305
- }
2306
- else {
2307
- this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
2308
- }
2309
- }
2310
- // Validate ipv6address
2311
- if (this.ipv6address) {
2312
- const networkInterfaces = os.networkInterfaces();
2313
- const availableAddresses = Object.values(networkInterfaces)
2314
- .flat()
2315
- .filter((iface) => iface !== undefined && iface.family === 'IPv6' && !iface.internal)
2316
- .map((iface) => iface.address);
2317
- if (!availableAddresses.includes(this.ipv6address)) {
2318
- this.log.error(`Invalid ipv6address: ${this.ipv6address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
2319
- this.mdnsInterface = undefined;
2320
- }
2321
- else {
2322
- this.log.info(`Using ipv6address '${this.ipv6address}' for the Matter commissioning server.`);
2323
- }
2324
- }
2325
- const commissioningServer = new CommissioningServer({
2326
- port: this.port++,
2327
- listeningAddressIpv4: this.ipv4address,
2328
- listeningAddressIpv6: this.ipv6address,
2329
- passcode: this.passcode,
2330
- discriminator: this.discriminator,
2331
- deviceName,
2332
- deviceType,
2333
- basicInformation: {
2334
- vendorId: VendorId(vendorId),
2335
- vendorName,
2336
- productId,
2337
- productName,
2338
- nodeLabel: productName,
2339
- productLabel: productName,
2340
- softwareVersion,
2341
- softwareVersionString, // Home app = Firmware Revision
2342
- hardwareVersion,
2343
- hardwareVersionString,
2344
- uniqueId,
2345
- serialNumber,
2346
- reachable: true,
2347
- },
2348
- activeSessionsChangedCallback: (fabricIndex) => {
2349
- const sessionInformations = commissioningServer.getActiveSessionInformation(fabricIndex);
2350
- let connected = false;
2351
- sessionInformations.forEach((session) => {
2352
- this.log.info(`Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
2353
- if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
2354
- this.log.notice(`Controller ${zb}${session.fabric?.rootVendorId}${nt} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nt} on session ${session.name}`);
2355
- connected = true;
2356
- }
2357
- });
2358
- if (connected) {
2359
- if (this.bridgeMode === 'bridge') {
2360
- this.matterbridgePaired = true;
2361
- this.matterbridgeConnected = true;
2362
- this.matterbridgeSessionInformations = this.sanitizeSessionInformation(sessionInformations);
2363
- }
2364
- if (this.bridgeMode === 'childbridge') {
2365
- const plugin = this.plugins.get(pluginName);
2366
- if (plugin) {
2367
- plugin.paired = true;
2368
- plugin.connected = true;
2369
- plugin.sessionInformations = this.sanitizeSessionInformation(sessionInformations);
2370
- }
2371
- }
2372
- }
2373
- else {
2374
- if (this.bridgeMode === 'bridge') {
2375
- this.matterbridgeSessionInformations = [];
2376
- }
2377
- if (this.bridgeMode === 'childbridge') {
2378
- const plugin = this.plugins.get(pluginName);
2379
- if (plugin) {
2380
- plugin.sessionInformations = [];
2381
- }
2382
- }
2383
- }
2384
- this.wssSendRefreshRequired();
2385
- },
2386
- commissioningChangedCallback: async (fabricIndex) => {
2387
- const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
2388
- this.log.debug(`Commissioning changed on fabric ${zb}${fabricIndex}${db} for ${plg}${pluginName}${db}`, debugStringify(fabricInfo));
2389
- if (commissioningServer.getCommissionedFabricInformation().length === 0) {
2390
- this.log.warn(`Commissioning removed from fabric ${zb}${fabricIndex}${wr} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
2391
- await commissioningServer.factoryReset();
2392
- if (pluginName === 'Matterbridge') {
2393
- await this.matterbridgeContext?.clearAll();
2394
- this.matterbridgeFabricInformations = [];
2395
- this.matterbridgeSessionInformations = [];
2396
- this.matterbridgePaired = false;
2397
- this.matterbridgeConnected = false;
2398
- }
2399
- else {
2400
- for (const plugin of this.plugins) {
2401
- if (plugin.name === pluginName) {
2402
- await plugin.platform?.onShutdown('Commissioning removed by the controller');
2403
- plugin.fabricInformations = [];
2404
- plugin.sessionInformations = [];
2405
- plugin.paired = false;
2406
- plugin.connected = false;
2407
- await plugin.storageContext?.clearAll();
2408
- }
2409
- }
2410
- }
2411
- this.log.warn(`Restart to activate the pairing for ${plg}${pluginName}${wr}.`);
2412
- }
2413
- else {
2414
- const fabricInfo = commissioningServer.getCommissionedFabricInformation();
2415
- if (pluginName === 'Matterbridge') {
2416
- this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
2417
- this.matterbridgePaired = true;
2418
- }
2419
- else {
2420
- const plugin = this.plugins.get(pluginName);
2421
- if (plugin) {
2422
- plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
2423
- plugin.paired = true;
2424
- }
2425
- }
2426
- }
2427
- this.wssSendRefreshRequired();
2428
- },
2429
- });
2430
- if (this.passcode !== undefined)
2431
- this.passcode++;
2432
- if (this.discriminator !== undefined)
2433
- this.discriminator++;
2434
- commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
2435
- return commissioningServer;
2436
- }
2437
- /**
2438
- * Creates a commissioning server storage context.
2439
- *
2440
- * @param pluginName - The name of the plugin.
2441
- * @param deviceName - The name of the device.
2442
- * @param deviceType - The type of the device.
2443
- * @param vendorId - The vendor ID.
2444
- * @param vendorName - The vendor name.
2445
- * @param productId - The product ID.
2446
- * @param productName - The product name.
2447
- * @param serialNumber - The serial number of the device (optional).
2448
- * @param uniqueId - The unique ID of the device (optional).
2449
- * @param softwareVersion - The software version of the device (optional).
2450
- * @param softwareVersionString - The software version string of the device (optional).
2451
- * @param hardwareVersion - The hardware version of the device (optional).
2452
- * @param hardwareVersionString - The hardware version string of the device (optional).
2453
- * @returns The storage context for the commissioning server.
2454
- */
2455
- async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
2456
- if (!this.storageManager)
2457
- throw new Error('No storage manager initialized');
2458
- this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
2459
- const random = randomBytes(8).toString('hex');
2460
- const storageContext = this.storageManager.createContext(pluginName);
2461
- await storageContext.set('deviceName', deviceName);
2462
- await storageContext.set('deviceType', deviceType);
2463
- await storageContext.set('vendorId', vendorId);
2464
- await storageContext.set('vendorName', vendorName.slice(0, 32));
2465
- await storageContext.set('productId', productId);
2466
- await storageContext.set('productName', productName.slice(0, 32));
2467
- await storageContext.set('nodeLabel', productName.slice(0, 32));
2468
- await storageContext.set('productLabel', productName.slice(0, 32));
2469
- await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
2470
- await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
2471
- await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
2472
- await storageContext.set('softwareVersionString', this.matterbridgeVersion ?? '1.0.0');
2473
- await storageContext.set('hardwareVersion', this.systemInformation.osRelease && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
2474
- await storageContext.set('hardwareVersionString', this.systemInformation.osRelease ?? '1.0.0');
2475
- this.log.debug(`Created commissioning server storage context for ${plg}${pluginName}${db}`);
2476
- this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
2477
- this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
2478
- this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
2479
- this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2480
- return storageContext;
2481
- }
2482
- /**
2483
- * Imports the commissioning server context for a specific plugin and device.
2484
- * @param pluginName - The name of the plugin.
2485
- * @param device - The MatterbridgeDevice object representing the device.
2486
- * @returns The commissioning server context.
2487
- * @throws Error if the BasicInformationCluster is not found.
2488
- */
2489
- async importCommissioningServerContext(pluginName, device) {
2490
- this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
2491
- const basic = device.getClusterServer(BasicInformationCluster);
2492
- if (!basic) {
2493
- this.log.error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
2494
- process.exit(1);
2495
- }
2496
- if (!this.storageManager) {
2497
- this.log.error('importCommissioningServerContext error: no storage manager initialized');
2498
- process.exit(1);
2499
- }
2500
- this.log.debug(`Importing commissioning server storage context for ${plg}${pluginName}${db}`);
2501
- const storageContext = this.storageManager.createContext(pluginName);
2502
- await storageContext.set('deviceName', basic.getNodeLabelAttribute());
2503
- await storageContext.set('deviceType', DeviceTypeId(device.deviceType));
2504
- await storageContext.set('vendorId', basic.getVendorIdAttribute());
2505
- await storageContext.set('vendorName', basic.getVendorNameAttribute());
2506
- await storageContext.set('productId', basic.getProductIdAttribute());
2507
- await storageContext.set('productName', basic.getProductNameAttribute());
2508
- await storageContext.set('nodeLabel', basic.getNodeLabelAttribute());
2509
- await storageContext.set('productLabel', basic.getNodeLabelAttribute());
2510
- await storageContext.set('serialNumber', basic.attributes.serialNumber?.getLocal());
2511
- await storageContext.set('uniqueId', basic.attributes.uniqueId?.getLocal());
2512
- await storageContext.set('softwareVersion', basic.getSoftwareVersionAttribute());
2513
- await storageContext.set('softwareVersionString', basic.getSoftwareVersionStringAttribute());
2514
- await storageContext.set('hardwareVersion', basic.getHardwareVersionAttribute());
2515
- await storageContext.set('hardwareVersionString', basic.getHardwareVersionStringAttribute());
2516
- this.log.debug(`Imported commissioning server storage context for ${plg}${pluginName}${db}`);
2517
- this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
2518
- this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
2519
- this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
2520
- this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
2521
- return storageContext;
2522
- }
2523
- /**
2524
- * Shows the commissioning server QR code for a given plugin.
2525
- * @param {CommissioningServer} commissioningServer - The commissioning server instance.
2526
- * @param {StorageContext} storageContext - The storage context instance.
2527
- * @param {NodeStorage} nodeContext - The node storage instance.
2528
- * @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
2529
- * @returns {Promise<void>} - A promise that resolves when the QR code is shown.
2530
- */
2531
- async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
2532
- if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
2533
- this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
2534
- await this.cleanup('No storage initialized in showCommissioningQRCode');
2535
- return;
2536
- }
2537
- if (!commissioningServer.isCommissioned()) {
2538
- const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
2539
- const QrCode = new QrCodeSchema();
2540
- this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
2541
- // eslint-disable-next-line no-console
2542
- if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
2543
- console.log(`${QrCode.encode(qrPairingCode)}\n`);
2544
- this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
2545
- if (pluginName === 'Matterbridge') {
2546
- this.matterbridgeQrPairingCode = qrPairingCode;
2547
- this.matterbridgeManualPairingCode = manualPairingCode;
2548
- this.matterbridgeFabricInformations = [];
2549
- this.matterbridgeSessionInformations = [];
2550
- this.matterbridgePaired = false;
2551
- this.matterbridgeConnected = false;
2552
- }
2553
- if (pluginName !== 'Matterbridge') {
2554
- const plugin = this.plugins.get(pluginName);
2555
- if (plugin) {
2556
- plugin.qrPairingCode = qrPairingCode;
2557
- plugin.manualPairingCode = manualPairingCode;
2558
- plugin.fabricInformations = [];
2559
- plugin.sessionInformations = [];
2560
- plugin.paired = false;
2561
- plugin.connected = false;
1555
+ this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
1556
+ if (plugin.registeredDevices !== undefined)
1557
+ plugin.registeredDevices--;
1558
+ if (plugin.addedDevices !== undefined)
1559
+ plugin.addedDevices--;
1560
+ if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
1561
+ if (plugin.serverNode) {
1562
+ await this.stopServerNode(plugin.serverNode);
1563
+ plugin.locked = false;
1564
+ plugin.aggregatorNode = undefined;
1565
+ plugin.serverNode = undefined;
1566
+ this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
2562
1567
  }
2563
1568
  }
2564
1569
  }
2565
- else {
2566
- this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is already commissioned. Waiting for controllers to connect ...`);
2567
- const fabricInfo = commissioningServer.getCommissionedFabricInformation();
2568
- if (fabricInfo.length > 0)
2569
- this.log.info('Commissioned fabric information:');
2570
- fabricInfo?.forEach((info) => {
2571
- this.log.info(`- fabric index ${zb}${info.fabricIndex}${nf} id ${zb}${info.fabricId}${nf} vendor ${zb}${info.rootVendorId}${nf} ${this.getVendorIdName(info.rootVendorId)} ${info.label}`);
2572
- });
2573
- if (pluginName === 'Matterbridge') {
2574
- this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
2575
- this.matterbridgeSessionInformations = [];
2576
- this.matterbridgePaired = true;
2577
- }
2578
- if (pluginName !== 'Matterbridge') {
2579
- const plugin = this.plugins.get(pluginName);
2580
- if (plugin) {
2581
- plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
2582
- plugin.sessionInformations = [];
2583
- plugin.paired = true;
2584
- }
2585
- }
1570
+ this.devices.remove(device);
1571
+ }
1572
+ async removeAllBridgedEndpoints(pluginName) {
1573
+ this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
1574
+ for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
1575
+ await this.removeBridgedEndpoint(pluginName, device);
2586
1576
  }
2587
- this.wssSendRefreshRequired();
2588
1577
  }
2589
- /**
2590
- * Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
2591
- *
2592
- * @param fabricInfo - The array of exposed fabric information objects.
2593
- * @returns An array of sanitized exposed fabric information objects.
2594
- */
2595
1578
  sanitizeFabricInformations(fabricInfo) {
2596
1579
  return fabricInfo.map((info) => {
2597
1580
  return {
@@ -2605,12 +1588,6 @@ export class Matterbridge extends EventEmitter {
2605
1588
  };
2606
1589
  });
2607
1590
  }
2608
- /**
2609
- * Sanitizes the session information by converting bigint properties to string.
2610
- *
2611
- * @param sessionInfo - The array of session information objects.
2612
- * @returns An array of sanitized session information objects.
2613
- */
2614
1591
  sanitizeSessionInformation(sessionInfo) {
2615
1592
  return sessionInfo
2616
1593
  .filter((session) => session.isPeerActive)
@@ -2638,48 +1615,11 @@ export class Matterbridge extends EventEmitter {
2638
1615
  };
2639
1616
  });
2640
1617
  }
2641
- /**
2642
- * Sets the reachability of a commissioning server and trigger.
2643
- *
2644
- * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
2645
- * @param {boolean} reachable - The new reachability status.
2646
- */
2647
- setCommissioningServerReachability(commissioningServer, reachable) {
2648
- const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
2649
- if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
2650
- basicInformationCluster.setReachableAttribute(reachable);
2651
- if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2652
- basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1618
+ setServerNodeReachability(serverNode, reachable) {
2653
1619
  }
2654
- /**
2655
- * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
2656
- * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
2657
- * @param {boolean} reachable - A boolean indicating the reachability status to set.
2658
- */
2659
- setAggregatorReachability(matterAggregator, reachable) {
2660
- const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
2661
- if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
2662
- basicInformationCluster.setReachableAttribute(reachable);
2663
- if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2664
- basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2665
- matterAggregator.getBridgedDevices().forEach((device) => {
2666
- this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
2667
- device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
2668
- device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
2669
- });
1620
+ setAggregatorReachability(aggregatorNode, reachable) {
2670
1621
  }
2671
- /**
2672
- * Sets the reachability of a device and trigger.
2673
- *
2674
- * @param {MatterbridgeDevice} device - The device to set the reachability for.
2675
- * @param {boolean} reachable - The new reachability status of the device.
2676
- */
2677
1622
  setDeviceReachability(device, reachable) {
2678
- const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
2679
- if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
2680
- basicInformationCluster.setReachableAttribute(reachable);
2681
- if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
2682
- basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
2683
1623
  }
2684
1624
  getVendorIdName = (vendorId) => {
2685
1625
  if (!vendorId)
@@ -2722,71 +1662,13 @@ export class Matterbridge extends EventEmitter {
2722
1662
  }
2723
1663
  return vendorName;
2724
1664
  };
2725
- /**
2726
- * Retrieves the base registered plugins sanitized for res.json().
2727
- * @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
2728
- */
2729
- async getBaseRegisteredPlugins() {
2730
- const baseRegisteredPlugins = [];
2731
- for (const plugin of this.plugins) {
2732
- baseRegisteredPlugins.push({
2733
- path: plugin.path,
2734
- type: plugin.type,
2735
- name: plugin.name,
2736
- version: plugin.version,
2737
- description: plugin.description,
2738
- author: plugin.author,
2739
- latestVersion: plugin.latestVersion,
2740
- locked: plugin.locked,
2741
- error: plugin.error,
2742
- enabled: plugin.enabled,
2743
- loaded: plugin.loaded,
2744
- started: plugin.started,
2745
- configured: plugin.configured,
2746
- paired: plugin.paired,
2747
- connected: plugin.connected,
2748
- fabricInformations: plugin.fabricInformations,
2749
- sessionInformations: plugin.sessionInformations,
2750
- registeredDevices: plugin.registeredDevices,
2751
- addedDevices: plugin.addedDevices,
2752
- qrPairingCode: plugin.qrPairingCode,
2753
- manualPairingCode: plugin.manualPairingCode,
2754
- configJson: plugin.configJson,
2755
- schemaJson: plugin.schemaJson,
2756
- });
2757
- }
2758
- return baseRegisteredPlugins;
2759
- }
2760
- /**
2761
- * Spawns a child process with the given command and arguments.
2762
- * @param {string} command - The command to execute.
2763
- * @param {string[]} args - The arguments to pass to the command (default: []).
2764
- * @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
2765
- */
2766
1665
  async spawnCommand(command, args = []) {
2767
- /*
2768
- npm > npm.cmd on windows
2769
- cmd.exe ['dir'] on windows
2770
- await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
2771
- process.on('unhandledRejection', (reason, promise) => {
2772
- this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
2773
- });
2774
-
2775
- spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
2776
- spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
2777
- debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
2778
- debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
2779
- */
2780
1666
  const cmdLine = command + ' ' + args.join(' ');
2781
1667
  if (process.platform === 'win32' && command === 'npm') {
2782
- // Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
2783
1668
  const argstring = 'npm ' + args.join(' ');
2784
1669
  args.splice(0, args.length, '/c', argstring);
2785
1670
  command = 'cmd.exe';
2786
1671
  }
2787
- // Decide when using sudo on linux
2788
- // When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
2789
- // When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
2790
1672
  if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
2791
1673
  args.unshift(command);
2792
1674
  command = 'sudo';
@@ -2801,7 +1683,7 @@ export class Matterbridge extends EventEmitter {
2801
1683
  reject(err);
2802
1684
  });
2803
1685
  childProcess.on('close', (code, signal) => {
2804
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
1686
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
2805
1687
  if (code === 0) {
2806
1688
  if (cmdLine.startsWith('npm install -g'))
2807
1689
  this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
@@ -2814,7 +1696,7 @@ export class Matterbridge extends EventEmitter {
2814
1696
  }
2815
1697
  });
2816
1698
  childProcess.on('exit', (code, signal) => {
2817
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
1699
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
2818
1700
  if (code === 0) {
2819
1701
  this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
2820
1702
  resolve(true);
@@ -2832,1060 +1714,16 @@ export class Matterbridge extends EventEmitter {
2832
1714
  childProcess.stdout.on('data', (data) => {
2833
1715
  const message = data.toString().trim();
2834
1716
  this.log.debug(`Spawn output (stdout): ${message}`);
2835
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
1717
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2836
1718
  });
2837
1719
  }
2838
1720
  if (childProcess.stderr) {
2839
1721
  childProcess.stderr.on('data', (data) => {
2840
1722
  const message = data.toString().trim();
2841
1723
  this.log.debug(`Spawn verbose (stderr): ${message}`);
2842
- this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
2843
- });
2844
- }
2845
- });
2846
- }
2847
- /**
2848
- * Sends a WebSocket message to all connected clients.
2849
- *
2850
- * @param {string} level - The logger level of the message: debug info notice warn error fatal...
2851
- * @param {string} time - The time string of the message
2852
- * @param {string} name - The logger name of the message
2853
- * @param {string} message - The content of the message.
2854
- */
2855
- wssSendMessage(level, time, name, message) {
2856
- if (!level || !time || !name || !message)
2857
- return;
2858
- // Remove ANSI escape codes from the message
2859
- // eslint-disable-next-line no-control-regex
2860
- message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
2861
- // Remove leading asterisks from the message
2862
- message = message.replace(/^\*+/, '');
2863
- // Replace all occurrences of \t and \n
2864
- message = message.replace(/[\t\n]/g, '');
2865
- // Remove non-printable characters
2866
- // eslint-disable-next-line no-control-regex
2867
- message = message.replace(/[\x00-\x1F\x7F]/g, '');
2868
- // Replace all occurrences of \" with "
2869
- message = message.replace(/\\"/g, '"');
2870
- // Define the maximum allowed length for continuous characters without a space
2871
- const maxContinuousLength = 100;
2872
- const keepStartLength = 20;
2873
- const keepEndLength = 20;
2874
- // Split the message into words
2875
- message = message
2876
- .split(' ')
2877
- .map((word) => {
2878
- // If the word length exceeds the max continuous length, insert spaces and truncate
2879
- if (word.length > maxContinuousLength) {
2880
- return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
2881
- }
2882
- return word;
2883
- })
2884
- .join(' ');
2885
- // Send the message to all connected clients
2886
- this.webSocketServer?.clients.forEach((client) => {
2887
- if (client.readyState === WebSocket.OPEN) {
2888
- client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
2889
- }
2890
- });
2891
- }
2892
- /**
2893
- * Sends a need to refresh WebSocket message to all connected clients.
2894
- *
2895
- */
2896
- wssSendRefreshRequired() {
2897
- this.matterbridgeInformation.refreshRequired = true;
2898
- // Send the message to all connected clients
2899
- this.webSocketServer?.clients.forEach((client) => {
2900
- if (client.readyState === WebSocket.OPEN) {
2901
- client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
2902
- }
2903
- });
2904
- }
2905
- /**
2906
- * Sends a need to restart WebSocket message to all connected clients.
2907
- *
2908
- */
2909
- wssSendRestartRequired() {
2910
- this.matterbridgeInformation.restartRequired = true;
2911
- // Send the message to all connected clients
2912
- this.webSocketServer?.clients.forEach((client) => {
2913
- if (client.readyState === WebSocket.OPEN) {
2914
- client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
2915
- }
2916
- });
2917
- }
2918
- /**
2919
- * Initializes the frontend of Matterbridge.
2920
- *
2921
- * @param port The port number to run the frontend server on. Default is 8283.
2922
- */
2923
- async initializeFrontend(port = 8283) {
2924
- let initializeError = false;
2925
- this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
2926
- // Create the express app that serves the frontend
2927
- this.expressApp = express();
2928
- // Log all requests to the server for debugging
2929
- /*
2930
- if (hasParameter('homedir')) {
2931
- this.expressApp.use((req, res, next) => {
2932
- this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
2933
- next();
2934
- });
2935
- }
2936
- */
2937
- // Serve static files from '/static' endpoint
2938
- this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2939
- if (!hasParameter('ssl')) {
2940
- // Create an HTTP server and attach the express app
2941
- this.httpServer = createServer(this.expressApp);
2942
- // Listen on the specified port
2943
- if (hasParameter('ingress')) {
2944
- this.httpServer.listen(port, '0.0.0.0', () => {
2945
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
2946
- });
2947
- }
2948
- else {
2949
- this.httpServer.listen(port, () => {
2950
- if (this.systemInformation.ipv4Address !== '')
2951
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
2952
- if (this.systemInformation.ipv6Address !== '')
2953
- this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
2954
- });
2955
- }
2956
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2957
- this.httpServer.on('error', (error) => {
2958
- this.log.error(`Frontend http server error listening on ${port}`);
2959
- switch (error.code) {
2960
- case 'EACCES':
2961
- this.log.error(`Port ${port} requires elevated privileges`);
2962
- break;
2963
- case 'EADDRINUSE':
2964
- this.log.error(`Port ${port} is already in use`);
2965
- break;
2966
- }
2967
- initializeError = true;
2968
- return;
2969
- });
2970
- }
2971
- else {
2972
- // Load the SSL certificate, the private key and optionally the CA certificate
2973
- let cert;
2974
- try {
2975
- cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
2976
- this.log.info(`Loaded certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}`);
2977
- }
2978
- catch (error) {
2979
- this.log.error(`Error reading certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
2980
- return;
2981
- }
2982
- let key;
2983
- try {
2984
- key = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
2985
- this.log.info(`Loaded key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}`);
2986
- }
2987
- catch (error) {
2988
- this.log.error(`Error reading key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
2989
- return;
2990
- }
2991
- let ca;
2992
- try {
2993
- ca = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
2994
- this.log.info(`Loaded CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')}`);
2995
- }
2996
- catch (error) {
2997
- this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
2998
- }
2999
- const serverOptions = { cert, key, ca };
3000
- // Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
3001
- this.httpsServer = https.createServer(serverOptions, this.expressApp);
3002
- // Listen on the specified port
3003
- if (hasParameter('ingress')) {
3004
- this.httpsServer.listen(port, '0.0.0.0', () => {
3005
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
3006
- });
3007
- }
3008
- else {
3009
- this.httpsServer.listen(port, () => {
3010
- if (this.systemInformation.ipv4Address !== '')
3011
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
3012
- if (this.systemInformation.ipv6Address !== '')
3013
- this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
3014
- });
3015
- }
3016
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3017
- this.httpsServer.on('error', (error) => {
3018
- this.log.error(`Frontend https server error listening on ${port}`);
3019
- switch (error.code) {
3020
- case 'EACCES':
3021
- this.log.error(`Port ${port} requires elevated privileges`);
3022
- break;
3023
- case 'EADDRINUSE':
3024
- this.log.error(`Port ${port} is already in use`);
3025
- break;
3026
- }
3027
- initializeError = true;
3028
- return;
3029
- });
3030
- }
3031
- if (initializeError)
3032
- return;
3033
- // Createe a WebSocket server and attach it to the http or https server
3034
- const wssPort = port;
3035
- const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
3036
- this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
3037
- this.webSocketServer.on('connection', (ws, request) => {
3038
- const clientIp = request.socket.remoteAddress;
3039
- AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
3040
- this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
3041
- ws.on('message', (message) => {
3042
- this.log.debug(`WebSocket client message: ${message}`);
3043
- this.matterbridgeMessageHandler(ws, message);
3044
- });
3045
- ws.on('ping', () => {
3046
- this.log.debug('WebSocket client ping');
3047
- ws.pong();
3048
- });
3049
- ws.on('pong', () => {
3050
- this.log.debug('WebSocket client pong');
3051
- });
3052
- ws.on('close', () => {
3053
- this.log.info('WebSocket client disconnected');
3054
- if (this.webSocketServer?.clients.size === 0) {
3055
- AnsiLogger.setGlobalCallback(undefined);
3056
- this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
3057
- }
3058
- });
3059
- ws.on('error', (error) => {
3060
- this.log.error(`WebSocket client error: ${error}`);
3061
- });
3062
- });
3063
- this.webSocketServer.on('close', () => {
3064
- this.log.debug(`WebSocketServer closed`);
3065
- });
3066
- this.webSocketServer.on('listening', () => {
3067
- this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
3068
- });
3069
- this.webSocketServer.on('error', (ws, error) => {
3070
- this.log.error(`WebSocketServer error: ${error}`);
3071
- });
3072
- // Endpoint to validate login code
3073
- this.expressApp.post('/api/login', express.json(), async (req, res) => {
3074
- const { password } = req.body;
3075
- this.log.debug('The frontend sent /api/login', password);
3076
- if (!this.nodeContext) {
3077
- this.log.error('/api/login nodeContext not found');
3078
- res.json({ valid: false });
3079
- return;
3080
- }
3081
- try {
3082
- const storedPassword = await this.nodeContext.get('password', '');
3083
- if (storedPassword === '' || password === storedPassword) {
3084
- this.log.debug('/api/login password valid');
3085
- res.json({ valid: true });
3086
- }
3087
- else {
3088
- this.log.warn('/api/login error wrong password');
3089
- res.json({ valid: false });
3090
- }
3091
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3092
- }
3093
- catch (error) {
3094
- this.log.error('/api/login error getting password');
3095
- res.json({ valid: false });
3096
- }
3097
- });
3098
- // Endpoint to provide health check
3099
- this.expressApp.get('/health', (req, res) => {
3100
- this.log.debug('Express received /health');
3101
- const healthStatus = {
3102
- status: 'ok', // Indicate service is healthy
3103
- uptime: process.uptime(), // Server uptime in seconds
3104
- timestamp: new Date().toISOString(), // Current timestamp
3105
- };
3106
- res.status(200).json(healthStatus);
3107
- });
3108
- // Endpoint to provide settings
3109
- this.expressApp.get('/api/settings', express.json(), async (req, res) => {
3110
- this.log.debug('The frontend sent /api/settings');
3111
- this.matterbridgeInformation.bridgeMode = this.bridgeMode;
3112
- this.matterbridgeInformation.restartMode = this.restartMode;
3113
- this.matterbridgeInformation.loggerLevel = this.log.logLevel;
3114
- this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
3115
- this.matterbridgeInformation.mattermdnsinterface = (await this.nodeContext?.get('mattermdnsinterface', '')) || '';
3116
- this.matterbridgeInformation.matteripv4address = (await this.nodeContext?.get('matteripv4address', '')) || '';
3117
- this.matterbridgeInformation.matteripv6address = (await this.nodeContext?.get('matteripv6address', '')) || '';
3118
- this.matterbridgeInformation.matterPort = (await this.nodeContext?.get('matterport', 5540)) ?? 5540;
3119
- this.matterbridgeInformation.matterDiscriminator = await this.nodeContext?.get('matterdiscriminator');
3120
- this.matterbridgeInformation.matterPasscode = await this.nodeContext?.get('matterpasscode');
3121
- this.matterbridgeInformation.matterbridgePaired = this.matterbridgePaired;
3122
- this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
3123
- this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
3124
- this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
3125
- this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
3126
- this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
3127
- this.matterbridgeInformation.profile = this.profile;
3128
- const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
3129
- // this.log.debug('Response:', debugStringify(response));
3130
- res.json(response);
3131
- });
3132
- // Endpoint to provide plugins
3133
- this.expressApp.get('/api/plugins', async (req, res) => {
3134
- this.log.debug('The frontend sent /api/plugins');
3135
- const response = await this.getBaseRegisteredPlugins();
3136
- // this.log.debug('Response:', debugStringify(response));
3137
- res.json(response);
3138
- });
3139
- // Endpoint to provide devices
3140
- this.expressApp.get('/api/devices', (req, res) => {
3141
- this.log.debug('The frontend sent /api/devices');
3142
- const devices = [];
3143
- this.devices.forEach(async (device) => {
3144
- const pluginName = device.plugin ?? 'Unknown';
3145
- if (this.edge)
3146
- device = EndpointServer.forEndpoint(device);
3147
- let name = device.getClusterServer(BasicInformationCluster)?.attributes.nodeLabel?.getLocal();
3148
- if (!name)
3149
- name = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.nodeLabel?.getLocal() ?? 'Unknown';
3150
- let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
3151
- if (!serial)
3152
- serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
3153
- let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
3154
- if (!productUrl)
3155
- productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
3156
- let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
3157
- if (!uniqueId)
3158
- uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
3159
- const cluster = this.getClusterTextFromDevice(device);
3160
- devices.push({
3161
- pluginName,
3162
- type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
3163
- endpoint: device.number,
3164
- name,
3165
- serial,
3166
- productUrl,
3167
- configUrl: device.configUrl,
3168
- uniqueId,
3169
- cluster: cluster,
1724
+ this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
3170
1725
  });
3171
- });
3172
- // this.log.debug('Response:', debugStringify(data));
3173
- res.json(devices);
3174
- });
3175
- // Endpoint to provide the cluster servers of the devices
3176
- this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
3177
- const selectedPluginName = req.params.selectedPluginName;
3178
- const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
3179
- this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
3180
- if (selectedPluginName === 'none') {
3181
- res.json([]);
3182
- return;
3183
- }
3184
- const data = [];
3185
- this.devices.forEach(async (device) => {
3186
- const pluginName = device.plugin;
3187
- if (this.edge)
3188
- device = EndpointServer.forEndpoint(device);
3189
- if (pluginName === selectedPluginName && device.number === selectedDeviceEndpoint) {
3190
- const clusterServers = device.getAllClusterServers();
3191
- clusterServers.forEach((clusterServer) => {
3192
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
3193
- if (clusterServer.name === 'EveHistory')
3194
- return;
3195
- // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
3196
- let attributeValue;
3197
- try {
3198
- if (typeof value.getLocal() === 'object')
3199
- attributeValue = stringify(value.getLocal());
3200
- else
3201
- attributeValue = value.getLocal().toString();
3202
- }
3203
- catch (error) {
3204
- attributeValue = 'Fabric-Scoped';
3205
- this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3206
- // console.log(error);
3207
- }
3208
- data.push({
3209
- endpoint: device.number ? device.number.toString() : '...',
3210
- clusterName: clusterServer.name,
3211
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
3212
- attributeName: key,
3213
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
3214
- attributeValue,
3215
- });
3216
- });
3217
- });
3218
- device.getChildEndpoints().forEach((childEndpoint) => {
3219
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3220
- const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
3221
- const clusterServers = childEndpoint.getAllClusterServers();
3222
- clusterServers.forEach((clusterServer) => {
3223
- Object.entries(clusterServer.attributes).forEach(([key, value]) => {
3224
- if (clusterServer.name === 'EveHistory')
3225
- return;
3226
- // this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
3227
- let attributeValue;
3228
- try {
3229
- if (typeof value.getLocal() === 'object')
3230
- attributeValue = stringify(value.getLocal());
3231
- else
3232
- attributeValue = value.getLocal().toString();
3233
- }
3234
- catch (error) {
3235
- attributeValue = 'Unavailable';
3236
- this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
3237
- // console.log(error);
3238
- }
3239
- data.push({
3240
- endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
3241
- clusterName: clusterServer.name,
3242
- clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
3243
- attributeName: key,
3244
- attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
3245
- attributeValue,
3246
- });
3247
- });
3248
- });
3249
- });
3250
- }
3251
- });
3252
- res.json(data);
3253
- });
3254
- // Endpoint to view the log
3255
- this.expressApp.get('/api/view-log', async (req, res) => {
3256
- this.log.debug('The frontend sent /api/log');
3257
- try {
3258
- const data = await fs.readFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'utf8');
3259
- res.type('text/plain');
3260
- res.send(data);
3261
- }
3262
- catch (error) {
3263
- this.log.error(`Error reading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
3264
- res.status(500).send('Error reading log file');
3265
- }
3266
- });
3267
- // Endpoint to download the matterbridge log
3268
- this.expressApp.get('/api/download-mblog', async (req, res) => {
3269
- this.log.debug('The frontend sent /api/download-mblog');
3270
- try {
3271
- await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
3272
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3273
1726
  }
3274
- catch (error) {
3275
- fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
3276
- }
3277
- res.download(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'matterbridge.log', (error) => {
3278
- if (error) {
3279
- this.log.error(`Error downloading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
3280
- res.status(500).send('Error downloading the matterbridge log file');
3281
- }
3282
- });
3283
- });
3284
- // Endpoint to download the matter log
3285
- this.expressApp.get('/api/download-mjlog', async (req, res) => {
3286
- this.log.debug('The frontend sent /api/download-mjlog');
3287
- try {
3288
- await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
3289
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3290
- }
3291
- catch (error) {
3292
- fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
3293
- }
3294
- res.download(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'matter.log', (error) => {
3295
- if (error) {
3296
- this.log.error(`Error downloading log file ${this.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
3297
- res.status(500).send('Error downloading the matter log file');
3298
- }
3299
- });
3300
- });
3301
- // Endpoint to download the matter storage file
3302
- this.expressApp.get('/api/download-mjstorage', (req, res) => {
3303
- this.log.debug('The frontend sent /api/download-mjstorage');
3304
- res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
3305
- if (error) {
3306
- this.log.error(`Error downloading log file ${this.matterStorageName}: ${error instanceof Error ? error.message : error}`);
3307
- res.status(500).send('Error downloading the matter storage file');
3308
- }
3309
- });
3310
- });
3311
- // Endpoint to download the matterbridge storage directory
3312
- this.expressApp.get('/api/download-mbstorage', async (req, res) => {
3313
- this.log.debug('The frontend sent /api/download-mbstorage');
3314
- await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
3315
- res.download(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), `matterbridge.${this.nodeStorageName}.zip`, (error) => {
3316
- if (error) {
3317
- this.log.error(`Error downloading file ${`matterbridge.${this.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
3318
- res.status(500).send('Error downloading the matterbridge storage file');
3319
- }
3320
- });
3321
- });
3322
- // Endpoint to download the matterbridge plugin directory
3323
- this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
3324
- this.log.debug('The frontend sent /api/download-pluginstorage');
3325
- await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
3326
- res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
3327
- if (error) {
3328
- this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
3329
- res.status(500).send('Error downloading the matterbridge plugin storage file');
3330
- }
3331
- });
3332
1727
  });
3333
- // Endpoint to download the matterbridge plugin config files
3334
- this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
3335
- this.log.debug('The frontend sent /api/download-pluginconfig');
3336
- await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3337
- // await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
3338
- res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
3339
- if (error) {
3340
- this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
3341
- res.status(500).send('Error downloading the matterbridge plugin storage file');
3342
- }
3343
- });
3344
- });
3345
- // Endpoint to download the matterbridge plugin config files
3346
- this.expressApp.get('/api/download-backup', async (req, res) => {
3347
- this.log.debug('The frontend sent /api/download-backup');
3348
- res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
3349
- if (error) {
3350
- this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
3351
- res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
3352
- }
3353
- });
3354
- });
3355
- // Endpoint to receive commands
3356
- this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
3357
- const command = req.params.command;
3358
- let param = req.params.param;
3359
- this.log.debug(`The frontend sent /api/command/${command}/${param}`);
3360
- if (!command) {
3361
- res.status(400).json({ error: 'No command provided' });
3362
- return;
3363
- }
3364
- this.log.debug(`Received frontend command: ${command}:${param}`);
3365
- // Handle the command setpassword from Settings
3366
- if (command === 'setpassword') {
3367
- const password = param.slice(1, -1); // Remove the first and last characters
3368
- this.log.debug('setpassword', param, password);
3369
- await this.nodeContext?.set('password', password);
3370
- res.json({ message: 'Command received' });
3371
- return;
3372
- }
3373
- // Handle the command setbridgemode from Settings
3374
- if (command === 'setbridgemode') {
3375
- this.log.debug(`setbridgemode: ${param}`);
3376
- this.wssSendRestartRequired();
3377
- await this.nodeContext?.set('bridgeMode', param);
3378
- res.json({ message: 'Command received' });
3379
- return;
3380
- }
3381
- // Handle the command backup from Settings
3382
- if (command === 'backup') {
3383
- this.log.notice(`Prepairing the backup...`);
3384
- await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
3385
- this.log.notice(`Backup ready to be downloaded.`);
3386
- res.json({ message: 'Command received' });
3387
- return;
3388
- }
3389
- // Handle the command setmbloglevel from Settings
3390
- if (command === 'setmbloglevel') {
3391
- this.log.debug('Matterbridge log level:', param);
3392
- if (param === 'Debug') {
3393
- this.log.logLevel = "debug" /* LogLevel.DEBUG */;
3394
- }
3395
- else if (param === 'Info') {
3396
- this.log.logLevel = "info" /* LogLevel.INFO */;
3397
- }
3398
- else if (param === 'Notice') {
3399
- this.log.logLevel = "notice" /* LogLevel.NOTICE */;
3400
- }
3401
- else if (param === 'Warn') {
3402
- this.log.logLevel = "warn" /* LogLevel.WARN */;
3403
- }
3404
- else if (param === 'Error') {
3405
- this.log.logLevel = "error" /* LogLevel.ERROR */;
3406
- }
3407
- else if (param === 'Fatal') {
3408
- this.log.logLevel = "fatal" /* LogLevel.FATAL */;
3409
- }
3410
- await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
3411
- MatterbridgeDevice.logLevel = this.log.logLevel;
3412
- this.plugins.logLevel = this.log.logLevel;
3413
- for (const plugin of this.plugins) {
3414
- if (!plugin.platform || !plugin.platform.config)
3415
- continue;
3416
- plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
3417
- await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
3418
- }
3419
- res.json({ message: 'Command received' });
3420
- return;
3421
- }
3422
- // Handle the command setmbloglevel from Settings
3423
- if (command === 'setmjloglevel') {
3424
- this.log.debug('Matter.js log level:', param);
3425
- if (param === 'Debug') {
3426
- Logger.defaultLogLevel = MatterLogLevel.DEBUG;
3427
- }
3428
- else if (param === 'Info') {
3429
- Logger.defaultLogLevel = MatterLogLevel.INFO;
3430
- }
3431
- else if (param === 'Notice') {
3432
- Logger.defaultLogLevel = MatterLogLevel.NOTICE;
3433
- }
3434
- else if (param === 'Warn') {
3435
- Logger.defaultLogLevel = MatterLogLevel.WARN;
3436
- }
3437
- else if (param === 'Error') {
3438
- Logger.defaultLogLevel = MatterLogLevel.ERROR;
3439
- }
3440
- else if (param === 'Fatal') {
3441
- Logger.defaultLogLevel = MatterLogLevel.FATAL;
3442
- }
3443
- await this.nodeContext?.set('matterLogLevel', Logger.defaultLogLevel);
3444
- res.json({ message: 'Command received' });
3445
- return;
3446
- }
3447
- // Handle the command setmdnsinterface from Settings
3448
- if (command === 'setmdnsinterface') {
3449
- param = param.slice(1, -1); // Remove the first and last characters *mdns*
3450
- this.matterbridgeInformation.mattermdnsinterface = param;
3451
- this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
3452
- await this.nodeContext?.set('mattermdnsinterface', param);
3453
- res.json({ message: 'Command received' });
3454
- return;
3455
- }
3456
- // Handle the command setipv4address from Settings
3457
- if (command === 'setipv4address') {
3458
- param = param.slice(1, -1); // Remove the first and last characters *ip*
3459
- this.matterbridgeInformation.matteripv4address = param;
3460
- this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
3461
- await this.nodeContext?.set('matteripv4address', param);
3462
- res.json({ message: 'Command received' });
3463
- return;
3464
- }
3465
- // Handle the command setipv6address from Settings
3466
- if (command === 'setipv6address') {
3467
- param = param.slice(1, -1); // Remove the first and last characters *ip*
3468
- this.matterbridgeInformation.matteripv6address = param;
3469
- this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
3470
- await this.nodeContext?.set('matteripv6address', param);
3471
- res.json({ message: 'Command received' });
3472
- return;
3473
- }
3474
- // Handle the command setmatterport from Settings
3475
- if (command === 'setmatterport') {
3476
- const port = Math.min(Math.max(parseInt(param), 5540), 5560);
3477
- this.matterbridgeInformation.matterPort = port;
3478
- this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
3479
- await this.nodeContext?.set('matterport', port);
3480
- res.json({ message: 'Command received' });
3481
- return;
3482
- }
3483
- // Handle the command setmatterdiscriminator from Settings
3484
- if (command === 'setmatterdiscriminator') {
3485
- const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
3486
- this.matterbridgeInformation.matterDiscriminator = discriminator;
3487
- this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
3488
- await this.nodeContext?.set('matterdiscriminator', discriminator);
3489
- res.json({ message: 'Command received' });
3490
- return;
3491
- }
3492
- // Handle the command setmatterpasscode from Settings
3493
- if (command === 'setmatterpasscode') {
3494
- const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
3495
- this.matterbridgeInformation.matterPasscode = passcode;
3496
- this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
3497
- await this.nodeContext?.set('matterpasscode', passcode);
3498
- res.json({ message: 'Command received' });
3499
- return;
3500
- }
3501
- // Handle the command setmbloglevel from Settings
3502
- if (command === 'setmblogfile') {
3503
- this.log.debug('Matterbridge file log:', param);
3504
- this.matterbridgeInformation.fileLogger = param === 'true';
3505
- await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
3506
- // Create the file logger for matterbridge
3507
- if (param === 'true')
3508
- AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
3509
- else
3510
- AnsiLogger.setGlobalLogfile(undefined);
3511
- res.json({ message: 'Command received' });
3512
- return;
3513
- }
3514
- // Handle the command setmbloglevel from Settings
3515
- if (command === 'setmjlogfile') {
3516
- this.log.debug('Matter file log:', param);
3517
- this.matterbridgeInformation.matterFileLogger = param === 'true';
3518
- await this.nodeContext?.set('matterFileLog', param === 'true');
3519
- if (param === 'true') {
3520
- try {
3521
- Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
3522
- defaultLogLevel: MatterLogLevel.DEBUG,
3523
- logFormat: MatterLogFormat.PLAIN,
3524
- });
3525
- }
3526
- catch (error) {
3527
- this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
3528
- }
3529
- }
3530
- else {
3531
- try {
3532
- Logger.removeLogger('matterfilelogger');
3533
- }
3534
- catch (error) {
3535
- this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
3536
- }
3537
- }
3538
- res.json({ message: 'Command received' });
3539
- return;
3540
- }
3541
- // Handle the command unregister from Settings
3542
- if (command === 'unregister') {
3543
- await this.unregisterAndShutdownProcess();
3544
- res.json({ message: 'Command received' });
3545
- return;
3546
- }
3547
- // Handle the command reset from Settings
3548
- if (command === 'reset') {
3549
- await this.shutdownProcessAndReset();
3550
- res.json({ message: 'Command received' });
3551
- return;
3552
- }
3553
- // Handle the command factoryreset from Settings
3554
- if (command === 'factoryreset') {
3555
- await this.shutdownProcessAndFactoryReset();
3556
- res.json({ message: 'Command received' });
3557
- return;
3558
- }
3559
- // Handle the command shutdown from Header
3560
- if (command === 'shutdown') {
3561
- await this.shutdownProcess();
3562
- res.json({ message: 'Command received' });
3563
- return;
3564
- }
3565
- // Handle the command restart from Header
3566
- if (command === 'restart') {
3567
- await this.restartProcess();
3568
- res.json({ message: 'Command received' });
3569
- return;
3570
- }
3571
- // Handle the command update from Header
3572
- if (command === 'update') {
3573
- this.log.info('Updating matterbridge...');
3574
- try {
3575
- await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
3576
- this.log.info('Matterbridge has been updated. Full restart required.');
3577
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3578
- }
3579
- catch (error) {
3580
- this.log.error('Error updating matterbridge');
3581
- }
3582
- await this.updateProcess();
3583
- this.wssSendRestartRequired();
3584
- res.json({ message: 'Command received' });
3585
- return;
3586
- }
3587
- // Handle the command saveconfig from Home
3588
- if (command === 'saveconfig') {
3589
- param = param.replace(/\*/g, '\\');
3590
- this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
3591
- // console.log('Req.body:', JSON.stringify(req.body, null, 2));
3592
- if (!this.plugins.has(param)) {
3593
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3594
- }
3595
- else {
3596
- const plugin = this.plugins.get(param);
3597
- if (!plugin)
3598
- return;
3599
- this.plugins.saveConfigFromJson(plugin, req.body);
3600
- }
3601
- this.wssSendRestartRequired();
3602
- res.json({ message: 'Command received' });
3603
- return;
3604
- }
3605
- // Handle the command installplugin from Home
3606
- if (command === 'installplugin') {
3607
- param = param.replace(/\*/g, '\\');
3608
- this.log.info(`Installing plugin ${plg}${param}${nf}...`);
3609
- try {
3610
- await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
3611
- this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
3612
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3613
- }
3614
- catch (error) {
3615
- this.log.error(`Error installing plugin ${plg}${param}${er}`);
3616
- }
3617
- this.wssSendRestartRequired();
3618
- param = param.split('@')[0];
3619
- // Also add the plugin to matterbridge so no return!
3620
- if (param === 'matterbridge') {
3621
- // If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
3622
- res.json({ message: 'Command received' });
3623
- return;
3624
- }
3625
- }
3626
- // Handle the command addplugin from Home
3627
- if (command === 'addplugin' || command === 'installplugin') {
3628
- param = param.replace(/\*/g, '\\');
3629
- const plugin = await this.plugins.add(param);
3630
- if (plugin) {
3631
- this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
3632
- }
3633
- res.json({ message: 'Command received' });
3634
- this.wssSendRefreshRequired();
3635
- return;
3636
- }
3637
- // Handle the command removeplugin from Home
3638
- if (command === 'removeplugin') {
3639
- if (!this.plugins.has(param)) {
3640
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3641
- }
3642
- else {
3643
- const plugin = this.plugins.get(param);
3644
- await this.plugins.shutdown(plugin, 'The plugin has been removed.', true);
3645
- await this.plugins.remove(param);
3646
- }
3647
- res.json({ message: 'Command received' });
3648
- this.wssSendRefreshRequired();
3649
- return;
3650
- }
3651
- // Handle the command enableplugin from Home
3652
- if (command === 'enableplugin') {
3653
- if (!this.plugins.has(param)) {
3654
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3655
- }
3656
- else {
3657
- const plugin = this.plugins.get(param);
3658
- if (plugin && !plugin.enabled) {
3659
- plugin.locked = undefined;
3660
- plugin.error = undefined;
3661
- plugin.loaded = undefined;
3662
- plugin.started = undefined;
3663
- plugin.configured = undefined;
3664
- plugin.connected = undefined;
3665
- plugin.platform = undefined;
3666
- plugin.registeredDevices = undefined;
3667
- plugin.addedDevices = undefined;
3668
- await this.plugins.enable(param);
3669
- this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
3670
- }
3671
- }
3672
- res.json({ message: 'Command received' });
3673
- this.wssSendRefreshRequired();
3674
- return;
3675
- }
3676
- // Handle the command disableplugin from Home
3677
- if (command === 'disableplugin') {
3678
- if (!this.plugins.has(param)) {
3679
- this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
3680
- }
3681
- else {
3682
- const plugin = this.plugins.get(param);
3683
- if (plugin && plugin.enabled) {
3684
- await this.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
3685
- await this.plugins.disable(param);
3686
- }
3687
- }
3688
- res.json({ message: 'Command received' });
3689
- this.wssSendRefreshRequired();
3690
- return;
3691
- }
3692
- });
3693
- // Fallback for routing (must be the last route)
3694
- this.expressApp.get('*', (req, res) => {
3695
- this.log.debug('The frontend sent:', req.url);
3696
- this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
3697
- res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
3698
- });
3699
- this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
3700
- }
3701
- /**
3702
- * Retrieves the cluster text description from a given device.
3703
- * @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
3704
- * @returns {string} The attributes description of the cluster servers in the device.
3705
- */
3706
- getClusterTextFromDevice(device) {
3707
- const stringifyUserLabel = (endpoint) => {
3708
- const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
3709
- if (!labelList)
3710
- return;
3711
- const composed = labelList.find((entry) => entry.label === 'composed');
3712
- if (composed)
3713
- return 'Composed: ' + composed.value;
3714
- else
3715
- return '';
3716
- };
3717
- const stringifyFixedLabel = (endpoint) => {
3718
- const labelList = endpoint.getClusterServer(FixedLabelCluster)?.attributes.labelList.getLocal();
3719
- if (!labelList)
3720
- return;
3721
- const composed = labelList.find((entry) => entry.label === 'composed');
3722
- if (composed)
3723
- return 'Composed: ' + composed.value;
3724
- else
3725
- return '';
3726
- };
3727
- let attributes = '';
3728
- // this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3729
- const clusterServers = device.getAllClusterServers();
3730
- clusterServers.forEach((clusterServer) => {
3731
- try {
3732
- // this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3733
- if (clusterServer.name === 'OnOff')
3734
- attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
3735
- if (clusterServer.name === 'Switch')
3736
- attributes += `Position: ${clusterServer.attributes.currentPosition.getLocal()} `;
3737
- if (clusterServer.name === 'WindowCovering')
3738
- attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
3739
- if (clusterServer.name === 'DoorLock')
3740
- attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
3741
- if (clusterServer.name === 'Thermostat')
3742
- attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
3743
- if (clusterServer.name === 'LevelControl')
3744
- attributes += `Level: ${clusterServer.attributes.currentLevel.getLocal()}% `;
3745
- if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentX)
3746
- attributes += `X: ${Math.round(clusterServer.attributes.currentX.getLocal())} Y: ${Math.round(clusterServer.attributes.currentY.getLocal())} `;
3747
- if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentHue)
3748
- attributes += `Hue: ${Math.round(clusterServer.attributes.currentHue.getLocal())} Saturation: ${Math.round(clusterServer.attributes.currentSaturation.getLocal())}% `;
3749
- if (clusterServer.name === 'ColorControl' && clusterServer.attributes.colorTemperatureMireds)
3750
- attributes += `ColorTemp: ${Math.round(clusterServer.attributes.colorTemperatureMireds.getLocal())} `;
3751
- if (clusterServer.name === 'BooleanState')
3752
- attributes += `Contact: ${clusterServer.attributes.stateValue.getLocal()} `;
3753
- if (clusterServer.name === 'BooleanStateConfiguration' && clusterServer.attributes.alarmsActive)
3754
- attributes += `Active alarms: ${stringify(clusterServer.attributes.alarmsActive.getLocal())} `;
3755
- if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.smokeState)
3756
- attributes += `Smoke: ${clusterServer.attributes.smokeState.getLocal()} `;
3757
- if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.coState)
3758
- attributes += `Co: ${clusterServer.attributes.coState.getLocal()} `;
3759
- if (clusterServer.name === 'FanControl')
3760
- attributes += `Mode: ${clusterServer.attributes.fanMode.getLocal()} Speed: ${clusterServer.attributes.percentCurrent.getLocal()} `;
3761
- if (clusterServer.name === 'FanControl' && clusterServer.attributes.speedCurrent)
3762
- attributes += `MultiSpeed: ${clusterServer.attributes.speedCurrent.getLocal()} `;
3763
- if (clusterServer.name === 'OccupancySensing')
3764
- attributes += `Occupancy: ${clusterServer.attributes.occupancy.getLocal().occupied} `;
3765
- if (clusterServer.name === 'IlluminanceMeasurement')
3766
- attributes += `Illuminance: ${clusterServer.attributes.measuredValue.getLocal()} `;
3767
- if (clusterServer.name === 'AirQuality')
3768
- attributes += `Air quality: ${clusterServer.attributes.airQuality.getLocal()} `;
3769
- if (clusterServer.name === 'TvocMeasurement')
3770
- attributes += `Voc: ${clusterServer.attributes.measuredValue.getLocal()} `;
3771
- if (clusterServer.name === 'TemperatureMeasurement')
3772
- attributes += `Temperature: ${clusterServer.attributes.measuredValue.getLocal() / 100}°C `;
3773
- if (clusterServer.name === 'RelativeHumidityMeasurement')
3774
- attributes += `Humidity: ${clusterServer.attributes.measuredValue.getLocal() / 100}% `;
3775
- if (clusterServer.name === 'PressureMeasurement')
3776
- attributes += `Pressure: ${clusterServer.attributes.measuredValue.getLocal()} `;
3777
- if (clusterServer.name === 'FlowMeasurement')
3778
- attributes += `Flow: ${clusterServer.attributes.measuredValue.getLocal()} `;
3779
- if (clusterServer.name === 'FixedLabel')
3780
- attributes += `${stringifyFixedLabel(device)} `;
3781
- if (clusterServer.name === 'UserLabel')
3782
- attributes += `${stringifyUserLabel(device)} `;
3783
- // this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
3784
- }
3785
- catch (error) {
3786
- this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
3787
- }
3788
- });
3789
- // this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
3790
- return attributes;
3791
- }
3792
- /**
3793
- * Initializes the Matterbridge instance as extension for zigbee2mqtt.
3794
- * @deprecated This method is deprecated and will be removed in a future version.
3795
- *
3796
- * @returns A Promise that resolves when the initialization is complete.
3797
- */
3798
- async startExtension(dataPath, extensionVersion, port = 5540) {
3799
- // Set the bridge mode
3800
- this.bridgeMode = 'bridge';
3801
- // Set the first port to use
3802
- this.port = port;
3803
- // Set Matterbridge logger
3804
- this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
3805
- this.log.debug('Matterbridge extension is starting...');
3806
- // Initialize NodeStorage
3807
- this.matterbridgeDirectory = dataPath;
3808
- this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
3809
- this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
3810
- this.log.debug('Creating node storage context for matterbridge: matterbridge');
3811
- this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
3812
- const plugin = {
3813
- path: '',
3814
- type: 'DynamicPlatform',
3815
- name: 'MatterbridgeExtension',
3816
- version: '1.0.0',
3817
- description: 'Matterbridge extension',
3818
- author: 'https://github.com/Luligu',
3819
- enabled: false,
3820
- registeredDevices: 0,
3821
- addedDevices: 0,
3822
- };
3823
- this.plugins.set(plugin);
3824
- this.plugins.saveToStorage();
3825
- // Log system info and create .matterbridge directory
3826
- await this.logNodeAndSystemInfo();
3827
- this.matterbridgeDirectory = dataPath;
3828
- // Set matter.js logger level and format
3829
- Logger.defaultLogLevel = MatterLogLevel.INFO;
3830
- Logger.format = MatterLogFormat.ANSI;
3831
- // Start the storage and create matterbridgeContext
3832
- await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
3833
- if (!this.storageManager)
3834
- return false;
3835
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge zigbee2MQTT', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'zigbee2MQTT Matter extension');
3836
- if (!this.matterbridgeContext)
3837
- return false;
3838
- await this.matterbridgeContext.set('softwareVersion', 1);
3839
- await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
3840
- await this.matterbridgeContext.set('hardwareVersion', 1);
3841
- await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
3842
- this.matterServer = await this.createMatterServer(this.storageManager);
3843
- this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
3844
- this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
3845
- this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
3846
- this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
3847
- this.log.debug('Adding matterbridge aggregator to commissioning server');
3848
- this.commissioningServer.addDevice(this.matterAggregator);
3849
- this.log.debug('Adding matterbridge commissioning server to matter server');
3850
- await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
3851
- await this.startMatterServer();
3852
- this.log.info('Matter server started');
3853
- await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
3854
- // Set reachability to true and trigger event after 60 seconds
3855
- setTimeout(() => {
3856
- this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
3857
- if (this.commissioningServer)
3858
- this.setCommissioningServerReachability(this.commissioningServer, true);
3859
- if (this.matterAggregator)
3860
- this.setAggregatorReachability(this.matterAggregator, true);
3861
- }, 60 * 1000);
3862
- return this.commissioningServer.isCommissioned();
3863
- }
3864
- /**
3865
- * Close the Matterbridge instance as extension for zigbee2mqtt.
3866
- * @deprecated This method is deprecated and will be removed in a future version.
3867
- *
3868
- * @returns A Promise that resolves when the initialization is complete.
3869
- */
3870
- async stopExtension() {
3871
- // Closing matter
3872
- await this.stopMatterServer();
3873
- // Clearing the session manager
3874
- // this.matterbridgeContext?.createContext('SessionManager').clear();
3875
- // Closing storage
3876
- await this.stopMatterStorage();
3877
- this.log.info('Matter server stopped');
3878
- }
3879
- /**
3880
- * Checks if the extension is commissioned.
3881
- * @deprecated This method is deprecated and will be removed in a future version.
3882
- *
3883
- * @returns {boolean} Returns true if the extension is commissioned, false otherwise.
3884
- */
3885
- isExtensionCommissioned() {
3886
- if (!this.commissioningServer)
3887
- return false;
3888
- return this.commissioningServer.isCommissioned();
3889
1728
  }
3890
1729
  }
3891
- //# sourceMappingURL=matterbridge.js.map