matterbridge 1.5.10 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -4
- package/README-SERVICE.md +1 -1
- package/README.md +11 -7
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/cluster/export.js +1 -1
- package/dist/cluster/export.js.map +1 -1
- package/dist/defaultConfigSchema.d.ts +1 -1
- package/dist/defaultConfigSchema.d.ts.map +1 -1
- package/dist/defaultConfigSchema.js +10 -2
- package/dist/defaultConfigSchema.js.map +1 -1
- package/dist/deviceManager.d.ts +1 -1
- package/dist/deviceManager.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/matterbridge.d.ts +92 -56
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +290 -155
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +1 -1
- package/dist/matterbridgeAccessoryPlatform.js +1 -1
- package/dist/matterbridgeController.d.ts +1 -1
- package/dist/matterbridgeController.js +1 -1
- package/dist/matterbridgeDevice.d.ts +137 -1029
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +55 -361
- package/dist/matterbridgeDevice.js.map +1 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +1 -1
- package/dist/matterbridgeDynamicPlatform.js +1 -1
- package/dist/matterbridgeEdge.d.ts +90 -0
- package/dist/matterbridgeEdge.d.ts.map +1 -0
- package/dist/matterbridgeEdge.js +555 -0
- package/dist/matterbridgeEdge.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +5164 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +2196 -0
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +1 -1
- package/dist/matterbridgePlatform.js +1 -1
- package/dist/matterbridgeTypes.d.ts +5 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -1
- package/dist/matterbridgeTypes.js.map +1 -1
- package/dist/matterbridgeWebsocket.d.ts +49 -0
- package/dist/matterbridgeWebsocket.d.ts.map +1 -0
- package/dist/matterbridgeWebsocket.js +181 -0
- package/dist/matterbridgeWebsocket.js.map +1 -0
- package/dist/pluginManager.d.ts +135 -2
- package/dist/pluginManager.d.ts.map +1 -1
- package/dist/pluginManager.js +146 -13
- package/dist/pluginManager.js.map +1 -1
- package/dist/utils/colorUtils.d.ts +1 -1
- package/dist/utils/colorUtils.js +1 -1
- package/dist/utils/utils.d.ts +21 -1
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +57 -1
- package/dist/utils/utils.js.map +1 -1
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.96d6324b.js → main.045d08f7.js} +3 -3
- package/frontend/build/static/js/main.045d08f7.js.map +1 -0
- package/npm-shrinkwrap.json +816 -6210
- package/package.json +2 -78
- package/dist/history/export.d.ts +0 -2
- package/dist/history/export.d.ts.map +0 -1
- package/dist/history/export.js +0 -2
- package/dist/history/export.js.map +0 -1
- package/frontend/build/static/js/main.96d6324b.js.map +0 -1
- /package/frontend/build/static/js/{main.96d6324b.js.LICENSE.txt → main.045d08f7.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -36,7 +36,7 @@ import { NodeStorageManager } from 'node-persist-manager';
|
|
|
36
36
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, hk, or, idn, BLUE, CYAN, nt } from 'node-ansi-logger';
|
|
37
37
|
// Matterbridge
|
|
38
38
|
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
39
|
-
import { logInterfaces, wait, waiter, createZip } from './utils/utils.js';
|
|
39
|
+
import { logInterfaces, wait, waiter, createZip, copyDirectory } from './utils/utils.js';
|
|
40
40
|
import { PluginManager } from './pluginManager.js';
|
|
41
41
|
import { DeviceManager } from './deviceManager.js';
|
|
42
42
|
// @project-chip/matter-node.js
|
|
@@ -50,6 +50,7 @@ import { StorageBackendDisk, StorageBackendJsonFile, StorageManager } from '@pro
|
|
|
50
50
|
import { getParameter, getIntParameter, hasParameter } from '@project-chip/matter-node.js/util';
|
|
51
51
|
import { CryptoNode } from '@project-chip/matter-node.js/crypto';
|
|
52
52
|
import { Specification } from '@project-chip/matter-node.js/model';
|
|
53
|
+
import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
|
|
53
54
|
// Default colors
|
|
54
55
|
const plg = '\u001B[38;5;33m';
|
|
55
56
|
const dev = '\u001B[38;5;79m';
|
|
@@ -130,13 +131,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
130
131
|
initialized = false;
|
|
131
132
|
execRunningCount = 0;
|
|
132
133
|
startMatterInterval;
|
|
133
|
-
cleanupTimeout1;
|
|
134
|
-
cleanupTimeout2;
|
|
135
134
|
checkUpdateInterval;
|
|
136
135
|
configureTimeout;
|
|
137
136
|
reachabilityTimeout;
|
|
138
137
|
sigintHandler;
|
|
139
138
|
sigtermHandler;
|
|
139
|
+
exceptionHandler;
|
|
140
|
+
rejectionHandler;
|
|
140
141
|
// Frontend
|
|
141
142
|
expressApp;
|
|
142
143
|
httpServer;
|
|
@@ -156,11 +157,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
156
157
|
matterAggregator;
|
|
157
158
|
commissioningServer;
|
|
158
159
|
commissioningController;
|
|
160
|
+
aggregatorVendorId = VendorId(0xfff1);
|
|
161
|
+
aggregatorProductId = 0x8000;
|
|
159
162
|
static instance;
|
|
160
163
|
// We load asyncronously so is private
|
|
161
164
|
constructor() {
|
|
162
165
|
super();
|
|
166
|
+
// Bind the handler to the instance
|
|
167
|
+
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
163
168
|
}
|
|
169
|
+
matterbridgeMessageHandler;
|
|
164
170
|
/** ***********************************************************************************************************************************/
|
|
165
171
|
/** loadInstance() and cleanup() methods */
|
|
166
172
|
/** ***********************************************************************************************************************************/
|
|
@@ -219,19 +225,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
219
225
|
// Set the matterbridge directory
|
|
220
226
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
221
227
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
222
|
-
// Initialize nodeStorage and nodeContext
|
|
223
|
-
// this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
224
|
-
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
225
|
-
// this.log.debug('Creating node storage context for matterbridge');
|
|
226
|
-
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
227
|
-
// Check if the storage is corrupted and remove it
|
|
228
|
-
// TODO: Check if the storage is corrupted and remove it
|
|
229
228
|
// Create matterbridge logger
|
|
230
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.
|
|
229
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
230
|
+
// Initialize nodeStorage and nodeContext
|
|
231
|
+
try {
|
|
232
|
+
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
233
|
+
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
234
|
+
this.log.debug('Creating node storage context for matterbridge');
|
|
235
|
+
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
236
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
+
const keys = (await this.nodeStorage?.storage.keys());
|
|
239
|
+
for (const key of keys) {
|
|
240
|
+
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
|
+
await this.nodeStorage?.storage.get(key);
|
|
243
|
+
}
|
|
244
|
+
const storages = await this.nodeStorage.getStorageNames();
|
|
245
|
+
for (const storage of storages) {
|
|
246
|
+
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
247
|
+
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
248
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
250
|
+
const keys = (await nodeContext?.storage.keys());
|
|
251
|
+
keys.forEach(async (key) => {
|
|
252
|
+
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
253
|
+
await nodeContext?.get(key);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
257
|
+
this.log.debug('Creating node storage backup...');
|
|
258
|
+
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
259
|
+
this.log.debug('Created node storage backup');
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
263
|
+
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
264
|
+
if (hasParameter('norestore')) {
|
|
265
|
+
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
266
|
+
await this.cleanup('Fatal error creating node storage manager and context for matterbridge');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.log.notice(`The matterbridge storage is corrupted. Restoring it with backup...`);
|
|
270
|
+
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
271
|
+
this.log.notice(`The matterbridge storage has been restored with backup`);
|
|
272
|
+
}
|
|
273
|
+
if (!this.nodeStorage || !this.nodeContext) {
|
|
274
|
+
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
275
|
+
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
235
276
|
}
|
|
236
277
|
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
237
278
|
if (hasParameter('logger')) {
|
|
@@ -263,6 +304,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
263
304
|
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
264
305
|
}
|
|
265
306
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
307
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
308
|
+
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
309
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
310
|
+
this.matterbridgeInformation.fileLogger = true;
|
|
311
|
+
}
|
|
266
312
|
this.log.notice('Matterbridge is starting...');
|
|
267
313
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
268
314
|
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
@@ -300,7 +346,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
300
346
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
301
347
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
302
348
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
303
|
-
defaultLogLevel:
|
|
349
|
+
defaultLogLevel: Logger.defaultLogLevel,
|
|
304
350
|
logFormat: Format.PLAIN,
|
|
305
351
|
});
|
|
306
352
|
}
|
|
@@ -345,7 +391,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
345
391
|
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
346
392
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
347
393
|
try {
|
|
348
|
-
await this.spawnCommand('npm', ['install', '-g', '--omit=dev',
|
|
394
|
+
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
349
395
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
350
396
|
plugin.error = false;
|
|
351
397
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -381,8 +427,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
381
427
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
382
428
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
383
429
|
}
|
|
384
|
-
// Register
|
|
385
|
-
this.
|
|
430
|
+
// Register process handlers
|
|
431
|
+
this.registerProcessHandlers();
|
|
386
432
|
// Parse command line
|
|
387
433
|
await this.parseCommandLine();
|
|
388
434
|
this.initialized = true;
|
|
@@ -413,6 +459,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
413
459
|
- logstorage: log the node storage
|
|
414
460
|
- sudo: force the use of sudo to install or update packages if the internal logic fails
|
|
415
461
|
- nosudo: force not to use sudo to install or update packages if the internal logic fails
|
|
462
|
+
- norestore: force not to automatically restore the matterbridge node storage and the matter storage from backup if it is corrupted
|
|
416
463
|
- ssl: enable SSL for the frontend and WebSockerServer (certificates in .matterbridge/certs directory cert.pem, key.pem and ca.pem (optional))
|
|
417
464
|
- add [plugin path]: register the plugin from the given absolute or relative path
|
|
418
465
|
- add [plugin name]: register the globally installed plugin with the given name
|
|
@@ -520,9 +567,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
520
567
|
return;
|
|
521
568
|
}
|
|
522
569
|
// Start the matter storage and create the matterbridge context
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
570
|
+
try {
|
|
571
|
+
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
575
|
+
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
576
|
+
}
|
|
526
577
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
527
578
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
528
579
|
await this.matterbridgeContext?.clearAll();
|
|
@@ -560,12 +611,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
560
611
|
this.getPluginLatestVersion(plugin);
|
|
561
612
|
}
|
|
562
613
|
}, 60 * 60 * 1000);
|
|
614
|
+
// Start the matterbridge in mode test
|
|
563
615
|
if (hasParameter('test')) {
|
|
564
616
|
this.bridgeMode = 'bridge';
|
|
565
617
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
566
|
-
await this.startTest();
|
|
567
618
|
return;
|
|
568
619
|
}
|
|
620
|
+
// Start the matterbridge in mode controller
|
|
569
621
|
if (hasParameter('controller')) {
|
|
570
622
|
this.bridgeMode = 'controller';
|
|
571
623
|
await this.startController();
|
|
@@ -576,96 +628,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
576
628
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
577
629
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
578
630
|
}
|
|
631
|
+
// Start matterbridge in bridge mode
|
|
579
632
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
580
633
|
this.bridgeMode = 'bridge';
|
|
581
634
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
582
|
-
|
|
583
|
-
throw new Error('No storage manager initialized');
|
|
584
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
585
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
586
|
-
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
587
|
-
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
588
|
-
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
589
|
-
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
|
|
590
|
-
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
591
|
-
this.commissioningServer.addDevice(this.matterAggregator);
|
|
592
|
-
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
593
|
-
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
594
|
-
for (const plugin of this.plugins) {
|
|
595
|
-
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
596
|
-
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
597
|
-
// Check if the plugin is available
|
|
598
|
-
if (!(await this.plugins.resolve(plugin.path))) {
|
|
599
|
-
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
600
|
-
plugin.enabled = false;
|
|
601
|
-
plugin.error = true;
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
// Check if the plugin has a new version
|
|
605
|
-
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
606
|
-
if (!plugin.enabled) {
|
|
607
|
-
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
plugin.error = false;
|
|
611
|
-
plugin.locked = false;
|
|
612
|
-
plugin.loaded = false;
|
|
613
|
-
plugin.started = false;
|
|
614
|
-
plugin.configured = false;
|
|
615
|
-
plugin.connected = undefined;
|
|
616
|
-
plugin.registeredDevices = undefined;
|
|
617
|
-
plugin.addedDevices = undefined;
|
|
618
|
-
plugin.qrPairingCode = undefined;
|
|
619
|
-
plugin.manualPairingCode = undefined;
|
|
620
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
621
|
-
}
|
|
635
|
+
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
622
636
|
await this.startBridge();
|
|
623
637
|
return;
|
|
624
638
|
}
|
|
639
|
+
// Start matterbridge in childbridge mode
|
|
625
640
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
626
641
|
this.bridgeMode = 'childbridge';
|
|
627
642
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
628
|
-
|
|
629
|
-
throw new Error('No storage manager initialized');
|
|
630
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
631
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
632
|
-
for (const plugin of this.plugins) {
|
|
633
|
-
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
634
|
-
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
635
|
-
// Check if the plugin is available
|
|
636
|
-
if (!(await this.plugins.resolve(plugin.path))) {
|
|
637
|
-
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
638
|
-
plugin.enabled = false;
|
|
639
|
-
plugin.error = true;
|
|
640
|
-
continue;
|
|
641
|
-
}
|
|
642
|
-
// Check if the plugin has a new version
|
|
643
|
-
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
644
|
-
if (!plugin.enabled) {
|
|
645
|
-
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
plugin.error = false;
|
|
649
|
-
plugin.locked = false;
|
|
650
|
-
plugin.loaded = false;
|
|
651
|
-
plugin.started = false;
|
|
652
|
-
plugin.configured = false;
|
|
653
|
-
plugin.connected = false;
|
|
654
|
-
plugin.registeredDevices = undefined;
|
|
655
|
-
plugin.addedDevices = undefined;
|
|
656
|
-
plugin.qrPairingCode = undefined;
|
|
657
|
-
plugin.manualPairingCode = undefined;
|
|
658
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
659
|
-
}
|
|
643
|
+
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
660
644
|
await this.startChildbridge();
|
|
661
645
|
return;
|
|
662
646
|
}
|
|
663
647
|
}
|
|
664
648
|
/**
|
|
665
|
-
*
|
|
649
|
+
* Asynchronously loads and starts the registered plugins.
|
|
650
|
+
*
|
|
651
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
652
|
+
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
653
|
+
*
|
|
654
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
655
|
+
*/
|
|
656
|
+
async startPlugins() {
|
|
657
|
+
// Check, load and start the plugins
|
|
658
|
+
for (const plugin of this.plugins) {
|
|
659
|
+
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
660
|
+
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
661
|
+
// Check if the plugin is available
|
|
662
|
+
if (!(await this.plugins.resolve(plugin.path))) {
|
|
663
|
+
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
664
|
+
plugin.enabled = false;
|
|
665
|
+
plugin.error = true;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
// Check if the plugin has a new version
|
|
669
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
670
|
+
if (!plugin.enabled) {
|
|
671
|
+
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
plugin.error = false;
|
|
675
|
+
plugin.locked = false;
|
|
676
|
+
plugin.loaded = false;
|
|
677
|
+
plugin.started = false;
|
|
678
|
+
plugin.configured = false;
|
|
679
|
+
plugin.connected = undefined;
|
|
680
|
+
plugin.registeredDevices = undefined;
|
|
681
|
+
plugin.addedDevices = undefined;
|
|
682
|
+
plugin.qrPairingCode = undefined;
|
|
683
|
+
plugin.manualPairingCode = undefined;
|
|
684
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
685
|
+
}
|
|
686
|
+
this.wssSendRefreshRequired();
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
666
690
|
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
667
691
|
*/
|
|
668
|
-
|
|
692
|
+
registerProcessHandlers() {
|
|
693
|
+
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
694
|
+
process.removeAllListeners('uncaughtException');
|
|
695
|
+
process.removeAllListeners('unhandledRejection');
|
|
696
|
+
this.exceptionHandler = async (error) => {
|
|
697
|
+
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
698
|
+
await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
699
|
+
};
|
|
700
|
+
process.on('uncaughtException', this.exceptionHandler);
|
|
701
|
+
this.rejectionHandler = async (reason, promise) => {
|
|
702
|
+
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
703
|
+
await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
704
|
+
};
|
|
705
|
+
process.on('unhandledRejection', this.rejectionHandler);
|
|
669
706
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
670
707
|
this.sigintHandler = async () => {
|
|
671
708
|
await this.cleanup('SIGINT received, cleaning up...');
|
|
@@ -677,9 +714,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
677
714
|
process.on('SIGTERM', this.sigtermHandler);
|
|
678
715
|
}
|
|
679
716
|
/**
|
|
680
|
-
* Deregisters the SIGINT and SIGTERM signal handlers.
|
|
717
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
681
718
|
*/
|
|
682
|
-
|
|
719
|
+
deregisterProcesslHandlers() {
|
|
720
|
+
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
721
|
+
if (this.exceptionHandler)
|
|
722
|
+
process.off('uncaughtException', this.exceptionHandler);
|
|
723
|
+
this.exceptionHandler = undefined;
|
|
724
|
+
if (this.rejectionHandler)
|
|
725
|
+
process.off('unhandledRejection', this.rejectionHandler);
|
|
726
|
+
this.rejectionHandler = undefined;
|
|
683
727
|
this.log.debug(`Deregistering SIGINT and SIGTERM signal handlers...`);
|
|
684
728
|
if (this.sigintHandler)
|
|
685
729
|
process.off('SIGINT', this.sigintHandler);
|
|
@@ -1080,8 +1124,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1080
1124
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1081
1125
|
this.hasCleanupStarted = true;
|
|
1082
1126
|
this.log.info(message);
|
|
1083
|
-
// Deregisters the
|
|
1084
|
-
this.
|
|
1127
|
+
// Deregisters the process handlers
|
|
1128
|
+
this.deregisterProcesslHandlers();
|
|
1085
1129
|
// Clear the start matter interval
|
|
1086
1130
|
if (this.startMatterInterval) {
|
|
1087
1131
|
clearInterval(this.startMatterInterval);
|
|
@@ -1155,7 +1199,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1155
1199
|
});
|
|
1156
1200
|
this.webSocketServer = undefined;
|
|
1157
1201
|
}
|
|
1158
|
-
// this.cleanupTimeout1 = setTimeout(async () => {
|
|
1159
1202
|
// Closing matter
|
|
1160
1203
|
await this.stopMatterServer();
|
|
1161
1204
|
// Closing matter storage
|
|
@@ -1201,9 +1244,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1201
1244
|
}
|
|
1202
1245
|
this.plugins.clear();
|
|
1203
1246
|
this.devices.clear();
|
|
1204
|
-
// this.registeredDevices = [];
|
|
1205
|
-
// this.log.info('Waiting for matter to deliver last messages...');
|
|
1206
|
-
// this.cleanupTimeout2 = setTimeout(async () => {
|
|
1207
1247
|
if (restart) {
|
|
1208
1248
|
if (message === 'updating...') {
|
|
1209
1249
|
this.log.info('Cleanup completed. Updating...');
|
|
@@ -1238,8 +1278,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1238
1278
|
}
|
|
1239
1279
|
this.hasCleanupStarted = false;
|
|
1240
1280
|
this.initialized = false;
|
|
1241
|
-
// }, 2 * 1000);
|
|
1242
|
-
// }, 3 * 1000);
|
|
1243
1281
|
}
|
|
1244
1282
|
}
|
|
1245
1283
|
/**
|
|
@@ -1249,17 +1287,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1249
1287
|
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1250
1288
|
*/
|
|
1251
1289
|
async addBridgedDevice(pluginName, device) {
|
|
1252
|
-
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${
|
|
1290
|
+
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1253
1291
|
// Check if the plugin is registered
|
|
1254
1292
|
const plugin = this.plugins.get(pluginName);
|
|
1255
1293
|
if (!plugin) {
|
|
1256
|
-
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${
|
|
1294
|
+
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1257
1295
|
return;
|
|
1258
1296
|
}
|
|
1259
1297
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1260
1298
|
if (this.bridgeMode === 'bridge') {
|
|
1261
1299
|
if (!this.matterAggregator) {
|
|
1262
|
-
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${
|
|
1300
|
+
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
1263
1301
|
return;
|
|
1264
1302
|
}
|
|
1265
1303
|
this.matterAggregator.addBridgedDevice(device);
|
|
@@ -1314,17 +1352,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1314
1352
|
* @returns A Promise that resolves when the device is successfully removed.
|
|
1315
1353
|
*/
|
|
1316
1354
|
async removeBridgedDevice(pluginName, device) {
|
|
1317
|
-
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${
|
|
1355
|
+
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1318
1356
|
// Check if the plugin is registered
|
|
1319
1357
|
const plugin = this.plugins.get(pluginName);
|
|
1320
1358
|
if (!plugin) {
|
|
1321
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${
|
|
1359
|
+
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1322
1360
|
return;
|
|
1323
1361
|
}
|
|
1324
1362
|
// Remove the device from matterbridge aggregator in bridge mode
|
|
1325
1363
|
if (this.bridgeMode === 'bridge') {
|
|
1326
1364
|
if (!this.matterAggregator) {
|
|
1327
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${
|
|
1365
|
+
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
1328
1366
|
return;
|
|
1329
1367
|
}
|
|
1330
1368
|
if (device.number !== undefined) {
|
|
@@ -1334,7 +1372,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1334
1372
|
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1335
1373
|
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1336
1374
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1337
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${
|
|
1375
|
+
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1338
1376
|
if (plugin.registeredDevices !== undefined)
|
|
1339
1377
|
plugin.registeredDevices--;
|
|
1340
1378
|
if (plugin.addedDevices !== undefined)
|
|
@@ -1344,13 +1382,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1344
1382
|
if (this.bridgeMode === 'childbridge') {
|
|
1345
1383
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1346
1384
|
if (!plugin.commissioningServer) {
|
|
1347
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${
|
|
1385
|
+
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
|
|
1348
1386
|
return;
|
|
1349
1387
|
}
|
|
1350
1388
|
}
|
|
1351
1389
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1352
1390
|
if (!plugin.aggregator) {
|
|
1353
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${
|
|
1391
|
+
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
|
|
1354
1392
|
return;
|
|
1355
1393
|
}
|
|
1356
1394
|
if (device.number !== undefined) {
|
|
@@ -1359,7 +1397,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1359
1397
|
}
|
|
1360
1398
|
plugin.aggregator.removeBridgedDevice(device);
|
|
1361
1399
|
}
|
|
1362
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${
|
|
1400
|
+
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1363
1401
|
if (plugin.registeredDevices !== undefined)
|
|
1364
1402
|
plugin.registeredDevices--;
|
|
1365
1403
|
if (plugin.addedDevices !== undefined)
|
|
@@ -1388,18 +1426,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1388
1426
|
}
|
|
1389
1427
|
});
|
|
1390
1428
|
}
|
|
1391
|
-
async startTest() {
|
|
1392
|
-
// Start the Matterbridge test
|
|
1393
|
-
}
|
|
1394
1429
|
/**
|
|
1395
1430
|
* Starts the Matterbridge in bridge mode.
|
|
1396
1431
|
* @private
|
|
1397
1432
|
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1398
1433
|
*/
|
|
1399
1434
|
async startBridge() {
|
|
1400
|
-
// Plugins are loaded and started by loadPlugin on startup and plugin.loaded and plugin.started are set to true
|
|
1401
1435
|
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1402
|
-
this.
|
|
1436
|
+
if (!this.storageManager)
|
|
1437
|
+
throw new Error('No storage manager initialized');
|
|
1438
|
+
if (!this.matterbridgeContext)
|
|
1439
|
+
throw new Error('No storage context initialized');
|
|
1440
|
+
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1441
|
+
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
1442
|
+
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
1443
|
+
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
1444
|
+
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
|
|
1445
|
+
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
1446
|
+
this.commissioningServer.addDevice(this.matterAggregator);
|
|
1447
|
+
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
1448
|
+
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
1449
|
+
await this.startPlugins();
|
|
1450
|
+
this.log.debug('Starting start matter interval in bridge mode');
|
|
1403
1451
|
let failCount = 0;
|
|
1404
1452
|
this.startMatterInterval = setInterval(async () => {
|
|
1405
1453
|
for (const plugin of this.plugins) {
|
|
@@ -1428,8 +1476,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1428
1476
|
clearInterval(this.startMatterInterval);
|
|
1429
1477
|
this.startMatterInterval = undefined;
|
|
1430
1478
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1479
|
+
// Start the Matter server
|
|
1431
1480
|
await this.startMatterServer();
|
|
1432
1481
|
this.log.notice('Matter server started');
|
|
1482
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1483
|
+
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1433
1484
|
// Configure the plugins
|
|
1434
1485
|
this.configureTimeout = setTimeout(async () => {
|
|
1435
1486
|
for (const plugin of this.plugins) {
|
|
@@ -1443,9 +1494,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1443
1494
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1444
1495
|
}
|
|
1445
1496
|
}
|
|
1497
|
+
this.wssSendRefreshRequired();
|
|
1446
1498
|
}, 30 * 1000);
|
|
1447
|
-
// Show the QR code for commissioning or log the already commissioned message
|
|
1448
|
-
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1449
1499
|
// Setting reachability to true
|
|
1450
1500
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1451
1501
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
@@ -1462,9 +1512,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1462
1512
|
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1463
1513
|
*/
|
|
1464
1514
|
async startChildbridge() {
|
|
1465
|
-
//
|
|
1466
|
-
// addDevice and addBridgedDeevice create the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1515
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1467
1516
|
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1517
|
+
if (!this.storageManager)
|
|
1518
|
+
throw new Error('No storage manager initialized');
|
|
1519
|
+
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1520
|
+
await this.startPlugins();
|
|
1468
1521
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1469
1522
|
let failCount = 0;
|
|
1470
1523
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1476,7 +1529,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
1529
|
if (plugin.error) {
|
|
1477
1530
|
clearInterval(this.startMatterInterval);
|
|
1478
1531
|
this.startMatterInterval = undefined;
|
|
1479
|
-
this.log.debug('Cleared startMatterInterval interval for
|
|
1532
|
+
this.log.debug('Cleared startMatterInterval interval for a plugin in error state');
|
|
1480
1533
|
this.log.error(`The plugin ${plg}${plugin.name}${er} is in error state.`);
|
|
1481
1534
|
this.log.error('The bridge will not start until the problem is solved to prevent the controllers from deleting all registered devices.');
|
|
1482
1535
|
this.log.error('If you want to start the bridge disable the plugin in error state and restart.');
|
|
@@ -1498,6 +1551,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1498
1551
|
clearInterval(this.startMatterInterval);
|
|
1499
1552
|
this.startMatterInterval = undefined;
|
|
1500
1553
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1554
|
+
// Start the Matter server
|
|
1501
1555
|
await this.startMatterServer();
|
|
1502
1556
|
this.log.notice('Matter server started');
|
|
1503
1557
|
// Configure the plugins
|
|
@@ -1513,6 +1567,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1513
1567
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1514
1568
|
}
|
|
1515
1569
|
}
|
|
1570
|
+
this.wssSendRefreshRequired();
|
|
1516
1571
|
}, 30 * 1000);
|
|
1517
1572
|
for (const plugin of this.plugins) {
|
|
1518
1573
|
if (!plugin.enabled || plugin.error)
|
|
@@ -1733,7 +1788,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1733
1788
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1734
1789
|
*/
|
|
1735
1790
|
async startMatterStorage(storageType, storageName) {
|
|
1736
|
-
this.log.debug(`Starting ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1791
|
+
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1737
1792
|
if (storageType === 'disk') {
|
|
1738
1793
|
const storageDisk = new StorageBackendDisk(storageName);
|
|
1739
1794
|
this.storageManager = new StorageManager(storageDisk);
|
|
@@ -1745,23 +1800,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1745
1800
|
this.storageManager = new StorageManager(storageJson);
|
|
1746
1801
|
}
|
|
1747
1802
|
else {
|
|
1748
|
-
this.log.error(`Unsupported storage type ${storageType}`);
|
|
1749
|
-
await this.cleanup('Unsupported storage type');
|
|
1803
|
+
this.log.error(`Unsupported matter storage type ${storageType}`);
|
|
1804
|
+
await this.cleanup('Unsupported matter storage type');
|
|
1750
1805
|
return;
|
|
1751
1806
|
}
|
|
1752
1807
|
try {
|
|
1753
1808
|
await this.storageManager.initialize();
|
|
1754
|
-
this.log.debug('
|
|
1809
|
+
this.log.debug('Matter storage initialized');
|
|
1755
1810
|
if (storageType === 'json') {
|
|
1756
|
-
await this.
|
|
1811
|
+
await this.backupMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
|
|
1757
1812
|
}
|
|
1758
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1759
1813
|
}
|
|
1760
1814
|
catch (error) {
|
|
1761
|
-
this.log.error(`
|
|
1762
|
-
|
|
1763
|
-
|
|
1815
|
+
this.log.error(`Matter storage initialize error! The file .matterbridge/${storageName} may be corrupted: ${error instanceof Error ? error.message : error}`);
|
|
1816
|
+
if (hasParameter('norestore')) {
|
|
1817
|
+
this.log.fatal(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
|
|
1818
|
+
await this.cleanup('Matter storage initialize error and -norestore parameter found!');
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
await this.restoreMatterStorage(storageName.replace('.json', '') + '.backup.json', storageName);
|
|
1822
|
+
try {
|
|
1823
|
+
await this.storageManager.initialize();
|
|
1824
|
+
this.log.notice('Matter storage initialized from the backup file');
|
|
1825
|
+
}
|
|
1826
|
+
catch (error) {
|
|
1827
|
+
this.log.error(`Matter storage initialize error! The backup file for .matterbridge/${storageName} may be corrupted too:`, error instanceof Error ? error.message : error);
|
|
1828
|
+
await this.cleanup('Matter storage initialize error from backup!');
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1764
1831
|
}
|
|
1832
|
+
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
1833
|
+
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
1765
1834
|
}
|
|
1766
1835
|
/**
|
|
1767
1836
|
* Makes a backup copy of the specified matter JSON storage file.
|
|
@@ -1769,7 +1838,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1769
1838
|
* @param storageName - The name of the JSON storage file to be backed up.
|
|
1770
1839
|
* @param backupName - The name of the backup file to be created.
|
|
1771
1840
|
*/
|
|
1772
|
-
async
|
|
1841
|
+
async backupMatterStorage(storageName, backupName) {
|
|
1773
1842
|
try {
|
|
1774
1843
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
1775
1844
|
await fs.copyFile(storageName, backupName);
|
|
@@ -1778,7 +1847,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1778
1847
|
catch (err) {
|
|
1779
1848
|
if (err instanceof Error && 'code' in err) {
|
|
1780
1849
|
if (err.code === 'ENOENT') {
|
|
1781
|
-
this.log.
|
|
1850
|
+
this.log.debug(`No existing file to back up for ${storageName}. This is expected on the first run.`);
|
|
1782
1851
|
}
|
|
1783
1852
|
else {
|
|
1784
1853
|
this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
|
|
@@ -1789,6 +1858,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1789
1858
|
}
|
|
1790
1859
|
}
|
|
1791
1860
|
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Restore the specified matter JSON storage file.
|
|
1863
|
+
*
|
|
1864
|
+
* @param backupName - The name of the backup file to restore from.
|
|
1865
|
+
* @param storageName - The name of the JSON storage file to restored.
|
|
1866
|
+
*/
|
|
1867
|
+
async restoreMatterStorage(backupName, storageName) {
|
|
1868
|
+
try {
|
|
1869
|
+
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
1870
|
+
await fs.copyFile(backupName, storageName);
|
|
1871
|
+
this.log.notice(`Successfully restored ${backupName} to ${storageName}`);
|
|
1872
|
+
}
|
|
1873
|
+
catch (err) {
|
|
1874
|
+
if (err instanceof Error && 'code' in err) {
|
|
1875
|
+
if (err.code === 'ENOENT') {
|
|
1876
|
+
this.log.info(`No existing file to restore: ${backupName}.`);
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
this.log.error(`Error restoring ${backupName}: ${err.message}`);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
this.log.error(`An unexpected error occurred during the restore of ${backupName}: ${String(err)}`);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1792
1887
|
/**
|
|
1793
1888
|
* Stops the matter storage.
|
|
1794
1889
|
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
@@ -2010,6 +2105,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2010
2105
|
}
|
|
2011
2106
|
}
|
|
2012
2107
|
}
|
|
2108
|
+
this.wssSendRefreshRequired();
|
|
2013
2109
|
},
|
|
2014
2110
|
commissioningChangedCallback: async (fabricIndex) => {
|
|
2015
2111
|
const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
|
|
@@ -2052,6 +2148,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2052
2148
|
}
|
|
2053
2149
|
}
|
|
2054
2150
|
}
|
|
2151
|
+
this.wssSendRefreshRequired();
|
|
2055
2152
|
},
|
|
2056
2153
|
});
|
|
2057
2154
|
if (this.passcode !== undefined)
|
|
@@ -2211,6 +2308,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2211
2308
|
}
|
|
2212
2309
|
}
|
|
2213
2310
|
}
|
|
2311
|
+
this.wssSendRefreshRequired();
|
|
2214
2312
|
}
|
|
2215
2313
|
/**
|
|
2216
2314
|
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
@@ -2333,6 +2431,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2333
2431
|
case 4701:
|
|
2334
2432
|
vendorName = '(Tuya)';
|
|
2335
2433
|
break;
|
|
2434
|
+
case 4718:
|
|
2435
|
+
vendorName = '(Xiaomi)';
|
|
2436
|
+
break;
|
|
2336
2437
|
case 4742:
|
|
2337
2438
|
vendorName = '(eWeLink)';
|
|
2338
2439
|
break;
|
|
@@ -2384,7 +2485,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2384
2485
|
* Spawns a child process with the given command and arguments.
|
|
2385
2486
|
* @param {string} command - The command to execute.
|
|
2386
2487
|
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2387
|
-
* @returns {Promise<
|
|
2488
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2388
2489
|
*/
|
|
2389
2490
|
async spawnCommand(command, args = []) {
|
|
2390
2491
|
/*
|
|
@@ -2429,7 +2530,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2429
2530
|
if (cmdLine.startsWith('npm install -g'))
|
|
2430
2531
|
this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
|
|
2431
2532
|
this.log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
|
|
2432
|
-
resolve();
|
|
2533
|
+
resolve(true);
|
|
2433
2534
|
}
|
|
2434
2535
|
else {
|
|
2435
2536
|
this.log.error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
|
|
@@ -2440,7 +2541,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2440
2541
|
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
2441
2542
|
if (code === 0) {
|
|
2442
2543
|
this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
|
|
2443
|
-
resolve();
|
|
2544
|
+
resolve(true);
|
|
2444
2545
|
}
|
|
2445
2546
|
else {
|
|
2446
2547
|
this.log.error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
|
|
@@ -2449,7 +2550,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2449
2550
|
});
|
|
2450
2551
|
childProcess.on('disconnect', () => {
|
|
2451
2552
|
this.log.debug(`Child process "${cmdLine}" has been disconnected from the parent`);
|
|
2452
|
-
resolve();
|
|
2553
|
+
resolve(true);
|
|
2453
2554
|
});
|
|
2454
2555
|
if (childProcess.stdout) {
|
|
2455
2556
|
childProcess.stdout.on('data', (data) => {
|
|
@@ -2508,7 +2609,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
2508
2609
|
// Send the message to all connected clients
|
|
2509
2610
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2510
2611
|
if (client.readyState === WebSocket.OPEN) {
|
|
2511
|
-
client.send(JSON.stringify({ level, time, name, message }));
|
|
2612
|
+
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2618
|
+
*
|
|
2619
|
+
*/
|
|
2620
|
+
wssSendRefreshRequired() {
|
|
2621
|
+
this.matterbridgeInformation.refreshRequired = true;
|
|
2622
|
+
// Send the message to all connected clients
|
|
2623
|
+
this.webSocketServer?.clients.forEach((client) => {
|
|
2624
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2625
|
+
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2631
|
+
*
|
|
2632
|
+
*/
|
|
2633
|
+
wssSendRestartRequired() {
|
|
2634
|
+
this.matterbridgeInformation.restartRequired = true;
|
|
2635
|
+
// Send the message to all connected clients
|
|
2636
|
+
this.webSocketServer?.clients.forEach((client) => {
|
|
2637
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2638
|
+
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
|
|
2512
2639
|
}
|
|
2513
2640
|
});
|
|
2514
2641
|
}
|
|
@@ -2627,7 +2754,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2627
2754
|
}
|
|
2628
2755
|
if (initializeError)
|
|
2629
2756
|
return;
|
|
2630
|
-
// Createe a WebSocket server and attach it to the http server
|
|
2757
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2631
2758
|
const wssPort = port;
|
|
2632
2759
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2633
2760
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
@@ -2637,6 +2764,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2637
2764
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2638
2765
|
ws.on('message', (message) => {
|
|
2639
2766
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
2767
|
+
this.matterbridgeMessageHandler(ws, message);
|
|
2768
|
+
});
|
|
2769
|
+
ws.on('ping', () => {
|
|
2770
|
+
this.log.debug('WebSocket client ping');
|
|
2771
|
+
ws.pong();
|
|
2772
|
+
});
|
|
2773
|
+
ws.on('pong', () => {
|
|
2774
|
+
this.log.debug('WebSocket client pong');
|
|
2640
2775
|
});
|
|
2641
2776
|
ws.on('close', () => {
|
|
2642
2777
|
this.log.info('WebSocket client disconnected');
|
|
@@ -2699,12 +2834,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2699
2834
|
this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
|
|
2700
2835
|
this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
|
|
2701
2836
|
this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
|
|
2702
|
-
// this.matterbridgeInformation.matterbridgeSessionInformations = this.matterbridgeSessionInformations;
|
|
2703
2837
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2704
|
-
|
|
2705
|
-
if (this.profile)
|
|
2706
|
-
this.matterbridgeInformation.profile = this.profile;
|
|
2707
|
-
// const response = { wssHost, ssl: hasParameter('ssl'), qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2838
|
+
this.matterbridgeInformation.profile = this.profile;
|
|
2708
2839
|
const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2709
2840
|
// this.log.debug('Response:', debugStringify(response));
|
|
2710
2841
|
res.json(response);
|
|
@@ -2785,7 +2916,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2785
2916
|
});
|
|
2786
2917
|
});
|
|
2787
2918
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
2788
|
-
const name =
|
|
2919
|
+
const name = childEndpoint.uniqueStorageKey;
|
|
2789
2920
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2790
2921
|
clusterServers.forEach((clusterServer) => {
|
|
2791
2922
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
@@ -2819,7 +2950,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2819
2950
|
});
|
|
2820
2951
|
res.json(data);
|
|
2821
2952
|
});
|
|
2822
|
-
// Endpoint to
|
|
2953
|
+
// Endpoint to view the log
|
|
2823
2954
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2824
2955
|
this.log.debug('The frontend sent /api/log');
|
|
2825
2956
|
try {
|
|
@@ -2941,7 +3072,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2941
3072
|
// Handle the command setbridgemode from Settings
|
|
2942
3073
|
if (command === 'setbridgemode') {
|
|
2943
3074
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2944
|
-
this.
|
|
3075
|
+
this.wssSendRestartRequired();
|
|
2945
3076
|
await this.nodeContext?.set('bridgeMode', param);
|
|
2946
3077
|
res.json({ message: 'Command received' });
|
|
2947
3078
|
return;
|
|
@@ -3121,7 +3252,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3121
3252
|
this.log.error('Error updating matterbridge');
|
|
3122
3253
|
}
|
|
3123
3254
|
await this.updateProcess();
|
|
3124
|
-
this.
|
|
3255
|
+
this.wssSendRestartRequired();
|
|
3125
3256
|
res.json({ message: 'Command received' });
|
|
3126
3257
|
return;
|
|
3127
3258
|
}
|
|
@@ -3139,7 +3270,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3139
3270
|
return;
|
|
3140
3271
|
this.plugins.saveConfigFromJson(plugin, req.body);
|
|
3141
3272
|
}
|
|
3142
|
-
this.
|
|
3273
|
+
this.wssSendRestartRequired();
|
|
3143
3274
|
res.json({ message: 'Command received' });
|
|
3144
3275
|
return;
|
|
3145
3276
|
}
|
|
@@ -3155,7 +3286,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3155
3286
|
catch (error) {
|
|
3156
3287
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
3157
3288
|
}
|
|
3158
|
-
this.
|
|
3289
|
+
this.wssSendRestartRequired();
|
|
3159
3290
|
// Also add the plugin to matterbridge so no return!
|
|
3160
3291
|
// res.json({ message: 'Command received' });
|
|
3161
3292
|
// return;
|
|
@@ -3168,6 +3299,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3168
3299
|
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
3169
3300
|
}
|
|
3170
3301
|
res.json({ message: 'Command received' });
|
|
3302
|
+
this.wssSendRefreshRequired();
|
|
3171
3303
|
return;
|
|
3172
3304
|
}
|
|
3173
3305
|
// Handle the command removeplugin from Home
|
|
@@ -3181,6 +3313,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3181
3313
|
await this.plugins.remove(param);
|
|
3182
3314
|
}
|
|
3183
3315
|
res.json({ message: 'Command received' });
|
|
3316
|
+
this.wssSendRefreshRequired();
|
|
3184
3317
|
return;
|
|
3185
3318
|
}
|
|
3186
3319
|
// Handle the command enableplugin from Home
|
|
@@ -3205,6 +3338,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3205
3338
|
}
|
|
3206
3339
|
}
|
|
3207
3340
|
res.json({ message: 'Command received' });
|
|
3341
|
+
this.wssSendRefreshRequired();
|
|
3208
3342
|
return;
|
|
3209
3343
|
}
|
|
3210
3344
|
// Handle the command disableplugin from Home
|
|
@@ -3220,6 +3354,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3220
3354
|
}
|
|
3221
3355
|
}
|
|
3222
3356
|
res.json({ message: 'Command received' });
|
|
3357
|
+
this.wssSendRefreshRequired();
|
|
3223
3358
|
return;
|
|
3224
3359
|
}
|
|
3225
3360
|
});
|