matterbridge 1.1.8 → 1.1.9
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 +7 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +26 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/matterbridge.d.ts +15 -2
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +264 -58
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeDevice.d.ts +2 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +74 -1
- package/dist/matterbridgeDevice.js.map +1 -1
- package/frontend/build/asset-manifest.json +6 -6
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/css/main.ce1ee9e7.css +2 -0
- package/frontend/build/static/css/main.ce1ee9e7.css.map +1 -0
- package/frontend/build/static/js/main.cc840fb3.js +3 -0
- package/frontend/build/static/js/main.cc840fb3.js.map +1 -0
- package/package.json +1 -1
- package/frontend/build/static/css/main.ff2b240c.css +0 -2
- package/frontend/build/static/css/main.ff2b240c.css.map +0 -1
- package/frontend/build/static/js/main.157c9cd1.js +0 -3
- package/frontend/build/static/js/main.157c9cd1.js.map +0 -1
- /package/frontend/build/static/js/{main.157c9cd1.js.LICENSE.txt → main.cc840fb3.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -25,7 +25,7 @@ import { NodeStorageManager } from 'node-persist-manager';
|
|
|
25
25
|
import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr } from 'node-ansi-logger';
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
27
27
|
import { promises as fs } from 'fs';
|
|
28
|
-
import { execSync } from 'child_process';
|
|
28
|
+
import { exec, execSync } from 'child_process';
|
|
29
29
|
import express from 'express';
|
|
30
30
|
import os from 'os';
|
|
31
31
|
import path from 'path';
|
|
@@ -61,7 +61,9 @@ export class Matterbridge {
|
|
|
61
61
|
homeDirectory = '';
|
|
62
62
|
rootDirectory = '';
|
|
63
63
|
matterbridgeDirectory = '';
|
|
64
|
+
matterbridgePluginDirectory = '';
|
|
64
65
|
matterbridgeVersion = '';
|
|
66
|
+
matterbridgeLatestVersion = '';
|
|
65
67
|
globalModulesDir = '';
|
|
66
68
|
bridgeMode = '';
|
|
67
69
|
debugEnabled = false;
|
|
@@ -71,7 +73,8 @@ export class Matterbridge {
|
|
|
71
73
|
registeredDevices = [];
|
|
72
74
|
nodeStorage;
|
|
73
75
|
nodeContext;
|
|
74
|
-
|
|
76
|
+
expressApp;
|
|
77
|
+
expressServer;
|
|
75
78
|
storageManager;
|
|
76
79
|
matterbridgeContext;
|
|
77
80
|
mattercontrollerContext;
|
|
@@ -81,26 +84,24 @@ export class Matterbridge {
|
|
|
81
84
|
commissioningController;
|
|
82
85
|
static instance;
|
|
83
86
|
constructor() {
|
|
84
|
-
//
|
|
87
|
+
// We load asyncronously
|
|
85
88
|
}
|
|
86
89
|
/**
|
|
87
90
|
* Loads an instance of the Matterbridge class.
|
|
88
91
|
* If an instance already exists, return that instance.
|
|
89
92
|
* @returns The loaded instance of the Matterbridge class.
|
|
90
93
|
*/
|
|
91
|
-
static async loadInstance(
|
|
92
|
-
// eslint-disable-next-line no-console
|
|
93
|
-
console.error('loadInstance cli:', cli);
|
|
94
|
+
static async loadInstance(initialize = false) {
|
|
94
95
|
if (!Matterbridge.instance) {
|
|
95
96
|
// eslint-disable-next-line no-console
|
|
96
|
-
console.
|
|
97
|
+
console.log('Matterbridge instance does not exists');
|
|
97
98
|
Matterbridge.instance = new Matterbridge();
|
|
98
|
-
if (
|
|
99
|
+
if (initialize)
|
|
99
100
|
await Matterbridge.instance.initialize();
|
|
100
101
|
}
|
|
101
102
|
else {
|
|
102
103
|
// eslint-disable-next-line no-console
|
|
103
|
-
console.
|
|
104
|
+
console.log('Matterbridge instance already exists');
|
|
104
105
|
}
|
|
105
106
|
return Matterbridge.instance;
|
|
106
107
|
}
|
|
@@ -161,13 +162,13 @@ export class Matterbridge {
|
|
|
161
162
|
this.registeredPlugins = await this.nodeContext.get('plugins', []);
|
|
162
163
|
for (const plugin of this.registeredPlugins) {
|
|
163
164
|
this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
|
|
164
|
-
plugin.nodeContext = await this.nodeStorage
|
|
165
|
-
await plugin.nodeContext
|
|
166
|
-
await plugin.nodeContext
|
|
167
|
-
await plugin.nodeContext
|
|
168
|
-
await plugin.nodeContext
|
|
169
|
-
await plugin.nodeContext
|
|
170
|
-
await plugin.nodeContext
|
|
165
|
+
plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
|
|
166
|
+
await plugin.nodeContext.set('name', plugin.name);
|
|
167
|
+
await plugin.nodeContext.set('type', plugin.type);
|
|
168
|
+
await plugin.nodeContext.set('path', plugin.path);
|
|
169
|
+
await plugin.nodeContext.set('version', plugin.version);
|
|
170
|
+
await plugin.nodeContext.set('description', plugin.description);
|
|
171
|
+
await plugin.nodeContext.set('author', plugin.author);
|
|
171
172
|
}
|
|
172
173
|
// Parse command line
|
|
173
174
|
this.parseCommandLine();
|
|
@@ -275,7 +276,7 @@ export class Matterbridge {
|
|
|
275
276
|
this.log.debug(`Package.json not found at ${packageJsonPath}`);
|
|
276
277
|
this.log.debug(`Trying at ${this.globalModulesDir}`);
|
|
277
278
|
packageJsonPath = path.join(this.globalModulesDir, pluginPath);
|
|
278
|
-
this.log.debug(`Got ${packageJsonPath}`);
|
|
279
|
+
//this.log.debug(`Got ${packageJsonPath}`);
|
|
279
280
|
}
|
|
280
281
|
try {
|
|
281
282
|
// Load the package.json of the plugin
|
|
@@ -360,7 +361,6 @@ export class Matterbridge {
|
|
|
360
361
|
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
361
362
|
}
|
|
362
363
|
}
|
|
363
|
-
//}
|
|
364
364
|
}
|
|
365
365
|
catch (err) {
|
|
366
366
|
this.log.error(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
|
|
@@ -371,21 +371,32 @@ export class Matterbridge {
|
|
|
371
371
|
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
372
372
|
*/
|
|
373
373
|
async registerSignalHandlers() {
|
|
374
|
-
process.
|
|
374
|
+
process.once('SIGINT', async () => {
|
|
375
375
|
await this.cleanup('SIGINT received, cleaning up...');
|
|
376
376
|
});
|
|
377
|
-
process.
|
|
377
|
+
process.once('SIGTERM', async () => {
|
|
378
378
|
await this.cleanup('SIGTERM received, cleaning up...');
|
|
379
379
|
});
|
|
380
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
383
|
+
*/
|
|
384
|
+
async restartProcess() {
|
|
385
|
+
//this.log.info('Restarting still not implemented');
|
|
386
|
+
//return;
|
|
387
|
+
await this.cleanup('Matterbridge is restarting...', true);
|
|
388
|
+
this.hasCleanupStarted = false;
|
|
389
|
+
}
|
|
381
390
|
/**
|
|
382
391
|
* Performs cleanup operations before shutting down Matterbridge.
|
|
383
392
|
* @param message - The reason for the cleanup.
|
|
384
393
|
*/
|
|
385
|
-
async cleanup(message) {
|
|
394
|
+
async cleanup(message, restart = false) {
|
|
386
395
|
if (!this.hasCleanupStarted) {
|
|
387
396
|
this.hasCleanupStarted = true;
|
|
388
397
|
this.log.info(message);
|
|
398
|
+
process.removeAllListeners('SIGINT');
|
|
399
|
+
process.removeAllListeners('SIGTERM');
|
|
389
400
|
// Callint the shutdown functions with a reason
|
|
390
401
|
for (const plugin of this.registeredPlugins) {
|
|
391
402
|
if (plugin.platform)
|
|
@@ -408,21 +419,47 @@ export class Matterbridge {
|
|
|
408
419
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') registeredDevice.device.setBridgedDeviceReachability(false);
|
|
409
420
|
});
|
|
410
421
|
*/
|
|
422
|
+
// Close the express server
|
|
423
|
+
if (this.expressServer) {
|
|
424
|
+
this.expressServer.close();
|
|
425
|
+
this.expressServer = undefined;
|
|
426
|
+
}
|
|
427
|
+
// Remove listeners
|
|
428
|
+
if (this.expressApp) {
|
|
429
|
+
this.expressApp.removeAllListeners();
|
|
430
|
+
this.expressApp = undefined;
|
|
431
|
+
}
|
|
411
432
|
setTimeout(async () => {
|
|
412
433
|
// Closing matter
|
|
413
434
|
await this.stopMatter();
|
|
414
435
|
// Closing storage
|
|
415
436
|
await this.stopStorage();
|
|
416
437
|
// Serialize registeredDevices
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
serializedRegisteredDevices
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
438
|
+
if (this.nodeContext) {
|
|
439
|
+
this.log.info('Saving registered devices...');
|
|
440
|
+
const serializedRegisteredDevices = [];
|
|
441
|
+
this.registeredDevices.forEach((registeredDevice) => {
|
|
442
|
+
serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
|
|
443
|
+
});
|
|
444
|
+
//console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
|
|
445
|
+
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
446
|
+
this.log.info('Saved registered devices');
|
|
447
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
448
|
+
this.nodeContext = undefined;
|
|
449
|
+
this.nodeStorage = undefined;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
this.log.error('Error saving registered devices: nodeContext not found!');
|
|
453
|
+
}
|
|
454
|
+
this.registeredPlugins = [];
|
|
455
|
+
this.registeredDevices = [];
|
|
456
|
+
setTimeout(async () => {
|
|
424
457
|
this.log.info('Cleanup completed.');
|
|
425
|
-
|
|
458
|
+
//if (restart) console.log(this);
|
|
459
|
+
if (restart)
|
|
460
|
+
await this.initialize();
|
|
461
|
+
else
|
|
462
|
+
process.exit(0);
|
|
426
463
|
}, 2 * 1000);
|
|
427
464
|
}, 3 * 1000);
|
|
428
465
|
}
|
|
@@ -448,16 +485,20 @@ export class Matterbridge {
|
|
|
448
485
|
* @returns A Promise that resolves when the device is added successfully.
|
|
449
486
|
*/
|
|
450
487
|
async addDevice(pluginName, device) {
|
|
488
|
+
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
489
|
+
this.log.error(`Adding device ${dev}${device.name}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
451
492
|
this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
|
|
452
493
|
// Check if the plugin is registered
|
|
453
494
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
454
495
|
if (!plugin) {
|
|
455
|
-
this.log.error(`
|
|
496
|
+
this.log.error(`Error adding device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
456
497
|
return;
|
|
457
498
|
}
|
|
458
499
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
459
500
|
if (this.bridgeMode === 'bridge') {
|
|
460
|
-
this.matterAggregator
|
|
501
|
+
this.matterAggregator?.addBridgedDevice(device);
|
|
461
502
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
462
503
|
if (plugin.registeredDevices !== undefined)
|
|
463
504
|
plugin.registeredDevices++;
|
|
@@ -480,16 +521,20 @@ export class Matterbridge {
|
|
|
480
521
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
481
522
|
*/
|
|
482
523
|
async addBridgedDevice(pluginName, device) {
|
|
524
|
+
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
525
|
+
this.log.error(`Adding bridged device ${dev}${device.name}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
483
528
|
this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
484
529
|
// Check if the plugin is registered
|
|
485
530
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
486
531
|
if (!plugin) {
|
|
487
|
-
this.log.error(`
|
|
532
|
+
this.log.error(`Error adding bridged device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
488
533
|
return;
|
|
489
534
|
}
|
|
490
535
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
491
536
|
if (this.bridgeMode === 'bridge') {
|
|
492
|
-
this.matterAggregator
|
|
537
|
+
this.matterAggregator?.addBridgedDevice(device);
|
|
493
538
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
494
539
|
if (plugin.registeredDevices !== undefined)
|
|
495
540
|
plugin.registeredDevices++;
|
|
@@ -512,18 +557,22 @@ export class Matterbridge {
|
|
|
512
557
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
513
558
|
*/
|
|
514
559
|
async startStorage(storageType, storageName) {
|
|
515
|
-
if (!storageName.endsWith('.json')) {
|
|
516
|
-
storageName += '.json';
|
|
517
|
-
}
|
|
518
560
|
this.log.debug(`Starting storage ${storageType} ${storageName}`);
|
|
519
561
|
if (storageType === 'disk') {
|
|
520
562
|
const storageDisk = new StorageBackendDisk(storageName);
|
|
521
563
|
this.storageManager = new StorageManager(storageDisk);
|
|
522
564
|
}
|
|
523
|
-
if (storageType === 'json') {
|
|
565
|
+
else if (storageType === 'json') {
|
|
566
|
+
if (!storageName.endsWith('.json'))
|
|
567
|
+
storageName += '.json';
|
|
524
568
|
const storageJson = new StorageBackendJsonFile(storageName);
|
|
525
569
|
this.storageManager = new StorageManager(storageJson);
|
|
526
570
|
}
|
|
571
|
+
else {
|
|
572
|
+
this.log.error(`Unsupported storage type ${storageType}`);
|
|
573
|
+
await this.cleanup('Unsupported storage type');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
527
576
|
try {
|
|
528
577
|
await this.storageManager.initialize();
|
|
529
578
|
this.log.debug('Storage initialized');
|
|
@@ -533,7 +582,7 @@ export class Matterbridge {
|
|
|
533
582
|
}
|
|
534
583
|
catch (error) {
|
|
535
584
|
this.log.error('Storage initialize() error!');
|
|
536
|
-
|
|
585
|
+
await this.cleanup('Storage initialize() error!');
|
|
537
586
|
}
|
|
538
587
|
}
|
|
539
588
|
/**
|
|
@@ -568,8 +617,11 @@ export class Matterbridge {
|
|
|
568
617
|
*/
|
|
569
618
|
async stopStorage() {
|
|
570
619
|
this.log.debug('Stopping storage');
|
|
571
|
-
await this.storageManager
|
|
620
|
+
await this.storageManager?.close();
|
|
572
621
|
this.log.debug('Storage closed');
|
|
622
|
+
this.storageManager = undefined;
|
|
623
|
+
this.matterbridgeContext = undefined;
|
|
624
|
+
this.mattercontrollerContext = undefined;
|
|
573
625
|
}
|
|
574
626
|
async testStartMatterBridge() {
|
|
575
627
|
/*
|
|
@@ -734,13 +786,27 @@ export class Matterbridge {
|
|
|
734
786
|
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
735
787
|
*/
|
|
736
788
|
async startMatterBridge() {
|
|
789
|
+
if (!this.storageManager) {
|
|
790
|
+
this.log.error('No storage manager initialized');
|
|
791
|
+
await this.cleanup('No storage manager initialized');
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
737
794
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
738
795
|
this.createMatterServer(this.storageManager);
|
|
796
|
+
if (!this.matterServer) {
|
|
797
|
+
this.log.error('No matter server initialized');
|
|
798
|
+
await this.cleanup('No matter server initialized');
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
739
801
|
if (this.bridgeMode === 'bridge') {
|
|
740
802
|
// Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
|
|
741
|
-
// Plugins are started and configured by callback when Matterbridge is commissioned
|
|
803
|
+
// Plugins are started and configured by callback when Matterbridge is commissioned
|
|
742
804
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
743
805
|
this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
806
|
+
if (!this.matterbridgeContext) {
|
|
807
|
+
this.log.error(`Error creating storage context${er}`);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
744
810
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
745
811
|
this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
746
812
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -754,17 +820,22 @@ export class Matterbridge {
|
|
|
754
820
|
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
755
821
|
}
|
|
756
822
|
if (this.bridgeMode === 'childbridge') {
|
|
757
|
-
// Plugins are loaded and started by loadPlugin on startup
|
|
823
|
+
// Plugins are loaded and started by loadPlugin on startup
|
|
758
824
|
// addDevice and addBridgedDeevice just register the devices that are added here to the plugin commissioning server for Accessory Platform
|
|
759
825
|
// or to the plugin aggregator for Dynamic Platform after the commissioning is done
|
|
760
826
|
// Plugins are configured by callback when the plugin is commissioned
|
|
761
|
-
this.registeredPlugins.forEach(
|
|
827
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
762
828
|
if (!plugin.enabled)
|
|
763
829
|
return;
|
|
764
830
|
// Start the interval to check if the plugins is started
|
|
765
831
|
// TODO set a counter or a timeout
|
|
766
832
|
this.log.debug(`*Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
767
833
|
const startInterval = setInterval(async () => {
|
|
834
|
+
if (!this.matterServer) {
|
|
835
|
+
this.log.error('No matter server initialized');
|
|
836
|
+
await this.cleanup('No matter server initialized');
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
768
839
|
if (!plugin.loaded || !plugin.started) {
|
|
769
840
|
this.log.info(`**Waiting in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
770
841
|
return;
|
|
@@ -774,7 +845,11 @@ export class Matterbridge {
|
|
|
774
845
|
.filter((registeredDevice) => registeredDevice.plugin === plugin.name)
|
|
775
846
|
.forEach((registeredDevice) => {
|
|
776
847
|
if (!plugin.storageContext)
|
|
777
|
-
plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
|
|
848
|
+
plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
|
|
849
|
+
if (!plugin.storageContext) {
|
|
850
|
+
this.log.error(`Error importing storage context for plugin ${plg}${plugin.name}${er}`);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
778
853
|
if (!plugin.commissioningServer)
|
|
779
854
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
780
855
|
this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -784,9 +859,11 @@ export class Matterbridge {
|
|
|
784
859
|
await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
785
860
|
}
|
|
786
861
|
if (plugin.type === 'DynamicPlatform') {
|
|
787
|
-
plugin.storageContext = this.createCommissioningServerContext(
|
|
788
|
-
|
|
789
|
-
|
|
862
|
+
plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
|
|
863
|
+
if (!plugin.storageContext) {
|
|
864
|
+
this.log.error(`Error creating storage context for plugin ${plg}${plugin.name}${er}`);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
790
867
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
791
868
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
792
869
|
plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
|
|
@@ -840,6 +917,11 @@ export class Matterbridge {
|
|
|
840
917
|
}
|
|
841
918
|
}
|
|
842
919
|
async startMatterServer() {
|
|
920
|
+
if (!this.matterServer) {
|
|
921
|
+
this.log.error('No matter server initialized');
|
|
922
|
+
await this.cleanup('No matter server initialized');
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
843
925
|
this.log.debug('Starting matter server');
|
|
844
926
|
await this.matterServer.start();
|
|
845
927
|
this.log.debug('Started matter server');
|
|
@@ -879,6 +961,10 @@ export class Matterbridge {
|
|
|
879
961
|
* @returns The storage context for the commissioning server.
|
|
880
962
|
*/
|
|
881
963
|
createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
|
|
964
|
+
if (!this.storageManager) {
|
|
965
|
+
this.log.error('No storage manager initialized');
|
|
966
|
+
process.exit(1);
|
|
967
|
+
}
|
|
882
968
|
this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
883
969
|
const random = 'CS' + CryptoNode.getRandomData(8).toHex();
|
|
884
970
|
const storageContext = this.storageManager.createContext(pluginName);
|
|
@@ -1088,6 +1174,7 @@ export class Matterbridge {
|
|
|
1088
1174
|
createMatterServer(storageManager) {
|
|
1089
1175
|
this.log.debug('Creating matter server');
|
|
1090
1176
|
this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
|
|
1177
|
+
this.log.debug('Created matter server');
|
|
1091
1178
|
}
|
|
1092
1179
|
/**
|
|
1093
1180
|
* Creates a Matter Aggregator.
|
|
@@ -1135,6 +1222,27 @@ export class Matterbridge {
|
|
|
1135
1222
|
this.log.debug('Stopping matter server');
|
|
1136
1223
|
await this.matterServer?.close();
|
|
1137
1224
|
this.log.debug('Matter server closed');
|
|
1225
|
+
this.commissioningController = undefined;
|
|
1226
|
+
this.commissioningServer = undefined;
|
|
1227
|
+
this.matterAggregator = undefined;
|
|
1228
|
+
this.matterServer = undefined;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
1232
|
+
* @param packageName - The name of the package.
|
|
1233
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
1234
|
+
*/
|
|
1235
|
+
async getLatestVersion(packageName) {
|
|
1236
|
+
return new Promise((resolve, reject) => {
|
|
1237
|
+
exec(`npm view ${packageName} version`, (error, stdout) => {
|
|
1238
|
+
if (error) {
|
|
1239
|
+
reject(error);
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
resolve(stdout.trim());
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1138
1246
|
}
|
|
1139
1247
|
/**
|
|
1140
1248
|
* Logs the node and system information.
|
|
@@ -1204,13 +1312,55 @@ export class Matterbridge {
|
|
|
1204
1312
|
await fs.access(this.matterbridgeDirectory);
|
|
1205
1313
|
}
|
|
1206
1314
|
catch (err) {
|
|
1207
|
-
|
|
1315
|
+
if (err instanceof Error) {
|
|
1316
|
+
const nodeErr = err;
|
|
1317
|
+
if (nodeErr.code === 'ENOENT') {
|
|
1318
|
+
try {
|
|
1319
|
+
await fs.mkdir(this.matterbridgeDirectory, { recursive: true });
|
|
1320
|
+
this.log.info(`Created Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1321
|
+
}
|
|
1322
|
+
catch (err) {
|
|
1323
|
+
this.log.error(`Error creating directory: ${err}`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
this.log.error(`Error accessing directory: ${err}`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1208
1330
|
}
|
|
1209
1331
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1332
|
+
// Create the data directory .matterbridge in the home directory
|
|
1333
|
+
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
1334
|
+
try {
|
|
1335
|
+
await fs.access(this.matterbridgePluginDirectory);
|
|
1336
|
+
}
|
|
1337
|
+
catch (err) {
|
|
1338
|
+
if (err instanceof Error) {
|
|
1339
|
+
const nodeErr = err;
|
|
1340
|
+
if (nodeErr.code === 'ENOENT') {
|
|
1341
|
+
try {
|
|
1342
|
+
await fs.mkdir(this.matterbridgePluginDirectory, { recursive: true });
|
|
1343
|
+
this.log.info(`Created Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1344
|
+
}
|
|
1345
|
+
catch (err) {
|
|
1346
|
+
this.log.error(`Error creating directory: ${err}`);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
else {
|
|
1350
|
+
this.log.error(`Error accessing directory: ${err}`);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1210
1355
|
// Matterbridge version
|
|
1211
1356
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1212
1357
|
this.matterbridgeVersion = packageJson.version;
|
|
1213
1358
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1359
|
+
this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
|
|
1360
|
+
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1361
|
+
if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
|
|
1362
|
+
this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
|
|
1363
|
+
}
|
|
1214
1364
|
// Current working directory
|
|
1215
1365
|
const currentDir = process.cwd();
|
|
1216
1366
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
@@ -1262,14 +1412,16 @@ export class Matterbridge {
|
|
|
1262
1412
|
*/
|
|
1263
1413
|
async initializeFrontend(port = 3000) {
|
|
1264
1414
|
this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
1265
|
-
this.
|
|
1415
|
+
this.expressApp = express();
|
|
1266
1416
|
// Serve React build directory
|
|
1267
|
-
this.
|
|
1417
|
+
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
1268
1418
|
// Endpoint to provide QR pairing code
|
|
1269
|
-
this.
|
|
1419
|
+
this.expressApp.get('/api/qr-code', (req, res) => {
|
|
1270
1420
|
this.log.debug('The frontend sent /api/qr-code');
|
|
1271
|
-
if (!this.matterbridgeContext)
|
|
1272
|
-
|
|
1421
|
+
if (!this.matterbridgeContext) {
|
|
1422
|
+
res.json([]);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1273
1425
|
try {
|
|
1274
1426
|
const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
|
|
1275
1427
|
res.json(qrData);
|
|
@@ -1281,17 +1433,17 @@ export class Matterbridge {
|
|
|
1281
1433
|
}
|
|
1282
1434
|
});
|
|
1283
1435
|
// Endpoint to provide system information
|
|
1284
|
-
this.
|
|
1436
|
+
this.expressApp.get('/api/system-info', (req, res) => {
|
|
1285
1437
|
this.log.debug('The frontend sent /api/system-info');
|
|
1286
1438
|
res.json(this.systemInformation);
|
|
1287
1439
|
});
|
|
1288
1440
|
// Endpoint to provide plugins
|
|
1289
|
-
this.
|
|
1441
|
+
this.expressApp.get('/api/plugins', (req, res) => {
|
|
1290
1442
|
this.log.debug('The frontend sent /api/plugins');
|
|
1291
1443
|
res.json(this.getBaseRegisteredPlugins());
|
|
1292
1444
|
});
|
|
1293
1445
|
// Endpoint to provide devices
|
|
1294
|
-
this.
|
|
1446
|
+
this.expressApp.get('/api/devices', (req, res) => {
|
|
1295
1447
|
this.log.debug('The frontend sent /api/devices');
|
|
1296
1448
|
const data = [];
|
|
1297
1449
|
this.registeredDevices.forEach((registeredDevice) => {
|
|
@@ -1318,7 +1470,7 @@ export class Matterbridge {
|
|
|
1318
1470
|
res.json(data);
|
|
1319
1471
|
});
|
|
1320
1472
|
// Endpoint to provide the cluster servers of the devices
|
|
1321
|
-
this.
|
|
1473
|
+
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
1322
1474
|
const selectedPluginName = req.params.selectedPluginName;
|
|
1323
1475
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
1324
1476
|
this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
|
|
@@ -1360,12 +1512,49 @@ export class Matterbridge {
|
|
|
1360
1512
|
});
|
|
1361
1513
|
res.json(data);
|
|
1362
1514
|
});
|
|
1515
|
+
// Endpoint to receive commands
|
|
1516
|
+
this.expressApp.post('/api/command/:command/:param', (req, res) => {
|
|
1517
|
+
const command = req.params.command;
|
|
1518
|
+
const param = req.params.param;
|
|
1519
|
+
this.log.debug(`The frontend sent /api/command/${command}/${param}`);
|
|
1520
|
+
if (!command) {
|
|
1521
|
+
res.status(400).json({ error: 'No command provided' });
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
this.log.info(`***Received command: ${command}:${param}`);
|
|
1525
|
+
// Handle the command debugLevel
|
|
1526
|
+
if (command === 'setloglevel') {
|
|
1527
|
+
if (param === 'Debug') {
|
|
1528
|
+
this.log.setLogDebug(true);
|
|
1529
|
+
this.debugEnabled = true;
|
|
1530
|
+
Logger.defaultLogLevel = Level.DEBUG;
|
|
1531
|
+
}
|
|
1532
|
+
else if (param === 'Info') {
|
|
1533
|
+
this.log.setLogDebug(false);
|
|
1534
|
+
this.debugEnabled = false;
|
|
1535
|
+
Logger.defaultLogLevel = Level.INFO;
|
|
1536
|
+
}
|
|
1537
|
+
else if (param === 'Warn') {
|
|
1538
|
+
this.log.setLogDebug(false);
|
|
1539
|
+
this.debugEnabled = false;
|
|
1540
|
+
Logger.defaultLogLevel = Level.WARN;
|
|
1541
|
+
}
|
|
1542
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
1543
|
+
plugin.platform?.log.setLogDebug(this.debugEnabled);
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
// Handle the command debugLevel
|
|
1547
|
+
if (command === 'restart') {
|
|
1548
|
+
this.restartProcess();
|
|
1549
|
+
}
|
|
1550
|
+
res.json({ message: 'Command received' });
|
|
1551
|
+
});
|
|
1363
1552
|
// Fallback for routing
|
|
1364
|
-
this.
|
|
1553
|
+
this.expressApp.get('*', (req, res) => {
|
|
1365
1554
|
this.log.warn('The frontend sent *', req.url);
|
|
1366
1555
|
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
1367
1556
|
});
|
|
1368
|
-
this.
|
|
1557
|
+
this.expressServer = this.expressApp.listen(port, () => {
|
|
1369
1558
|
this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
|
|
1370
1559
|
});
|
|
1371
1560
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
@@ -1413,6 +1602,23 @@ export class Matterbridge {
|
|
|
1413
1602
|
}
|
|
1414
1603
|
/*
|
|
1415
1604
|
TO IMPLEMENT
|
|
1605
|
+
|
|
1606
|
+
import { spawn } from 'child_process';
|
|
1607
|
+
|
|
1608
|
+
function restartProcess() {
|
|
1609
|
+
// Spawn a new process
|
|
1610
|
+
const newProcess = spawn(process.argv[0], process.argv.slice(1), {
|
|
1611
|
+
detached: true,
|
|
1612
|
+
stdio: 'inherit',
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
// Unreference the new process so that the current process can exit
|
|
1616
|
+
newProcess.unref();
|
|
1617
|
+
|
|
1618
|
+
// Exit the current process
|
|
1619
|
+
process.exit();
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1416
1622
|
import * as WebSocket from 'ws';
|
|
1417
1623
|
const globalModulesDir = require('global-modules');
|
|
1418
1624
|
|