matterbridge 1.1.7 → 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 +24 -0
- package/README.md +1 -1
- 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 +17 -2
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +387 -102
- 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 +8 -8
- 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/453.d855a71b.chunk.js +2 -0
- package/frontend/build/static/js/{453.8ab44547.chunk.js.map → 453.d855a71b.chunk.js.map} +1 -1
- package/frontend/build/static/js/main.cc840fb3.js +3 -0
- package/frontend/build/static/js/{main.e5888ebb.js.LICENSE.txt → main.cc840fb3.js.LICENSE.txt} +19 -0
- package/frontend/build/static/js/main.cc840fb3.js.map +1 -0
- package/package.json +2 -2
- package/frontend/build/static/css/main.6d93e0db.css +0 -2
- package/frontend/build/static/css/main.6d93e0db.css.map +0 -1
- package/frontend/build/static/js/453.8ab44547.chunk.js +0 -2
- package/frontend/build/static/js/main.e5888ebb.js +0 -3
- package/frontend/build/static/js/main.e5888ebb.js.map +0 -1
package/dist/matterbridge.js
CHANGED
|
@@ -25,6 +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 { exec, execSync } from 'child_process';
|
|
28
29
|
import express from 'express';
|
|
29
30
|
import os from 'os';
|
|
30
31
|
import path from 'path';
|
|
@@ -60,7 +61,10 @@ export class Matterbridge {
|
|
|
60
61
|
homeDirectory = '';
|
|
61
62
|
rootDirectory = '';
|
|
62
63
|
matterbridgeDirectory = '';
|
|
64
|
+
matterbridgePluginDirectory = '';
|
|
63
65
|
matterbridgeVersion = '';
|
|
66
|
+
matterbridgeLatestVersion = '';
|
|
67
|
+
globalModulesDir = '';
|
|
64
68
|
bridgeMode = '';
|
|
65
69
|
debugEnabled = false;
|
|
66
70
|
log;
|
|
@@ -69,7 +73,8 @@ export class Matterbridge {
|
|
|
69
73
|
registeredDevices = [];
|
|
70
74
|
nodeStorage;
|
|
71
75
|
nodeContext;
|
|
72
|
-
|
|
76
|
+
expressApp;
|
|
77
|
+
expressServer;
|
|
73
78
|
storageManager;
|
|
74
79
|
matterbridgeContext;
|
|
75
80
|
mattercontrollerContext;
|
|
@@ -79,26 +84,24 @@ export class Matterbridge {
|
|
|
79
84
|
commissioningController;
|
|
80
85
|
static instance;
|
|
81
86
|
constructor() {
|
|
82
|
-
//
|
|
87
|
+
// We load asyncronously
|
|
83
88
|
}
|
|
84
89
|
/**
|
|
85
90
|
* Loads an instance of the Matterbridge class.
|
|
86
91
|
* If an instance already exists, return that instance.
|
|
87
92
|
* @returns The loaded instance of the Matterbridge class.
|
|
88
93
|
*/
|
|
89
|
-
static async loadInstance(
|
|
90
|
-
// eslint-disable-next-line no-console
|
|
91
|
-
console.error('loadInstance cli:', cli);
|
|
94
|
+
static async loadInstance(initialize = false) {
|
|
92
95
|
if (!Matterbridge.instance) {
|
|
93
96
|
// eslint-disable-next-line no-console
|
|
94
|
-
console.
|
|
97
|
+
console.log('Matterbridge instance does not exists');
|
|
95
98
|
Matterbridge.instance = new Matterbridge();
|
|
96
|
-
if (
|
|
99
|
+
if (initialize)
|
|
97
100
|
await Matterbridge.instance.initialize();
|
|
98
101
|
}
|
|
99
102
|
else {
|
|
100
103
|
// eslint-disable-next-line no-console
|
|
101
|
-
console.
|
|
104
|
+
console.log('Matterbridge instance already exists');
|
|
102
105
|
}
|
|
103
106
|
return Matterbridge.instance;
|
|
104
107
|
}
|
|
@@ -124,10 +127,14 @@ export class Matterbridge {
|
|
|
124
127
|
- frontend [port]: start the frontend on the given port (default 3000)
|
|
125
128
|
- debug: enable debug mode (default false)
|
|
126
129
|
- list: list the registered plugins
|
|
127
|
-
- add [plugin path]: register the plugin
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
130
|
+
- add [plugin path]: register the plugin from the given absolute or relative path
|
|
131
|
+
- add [plugin name]: register the globally installed plugin with the given name
|
|
132
|
+
- remove [plugin path]: remove the plugin from the given absolute or relative path
|
|
133
|
+
- remove [plugin name]: remove the globally installed plugin with the given name
|
|
134
|
+
- enable [plugin path]: enable the plugin from the given absolute or relative path
|
|
135
|
+
- enable [plugin name]: enable the globally installed plugin with the given name
|
|
136
|
+
- disable [plugin path]: disable the plugin from the given absolute or relative path
|
|
137
|
+
- disable [plugin name]: disable the globally installed plugin with the given name\n`);
|
|
131
138
|
process.exit(0);
|
|
132
139
|
}
|
|
133
140
|
// set Matterbridge logger
|
|
@@ -155,13 +162,13 @@ export class Matterbridge {
|
|
|
155
162
|
this.registeredPlugins = await this.nodeContext.get('plugins', []);
|
|
156
163
|
for (const plugin of this.registeredPlugins) {
|
|
157
164
|
this.log.debug(`Creating node storage context for plugin ${plugin.name}`);
|
|
158
|
-
plugin.nodeContext = await this.nodeStorage
|
|
159
|
-
await plugin.nodeContext
|
|
160
|
-
await plugin.nodeContext
|
|
161
|
-
await plugin.nodeContext
|
|
162
|
-
await plugin.nodeContext
|
|
163
|
-
await plugin.nodeContext
|
|
164
|
-
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);
|
|
165
172
|
}
|
|
166
173
|
// Parse command line
|
|
167
174
|
this.parseCommandLine();
|
|
@@ -215,7 +222,7 @@ export class Matterbridge {
|
|
|
215
222
|
if (hasParameter('test')) {
|
|
216
223
|
this.bridgeMode = 'childbridge';
|
|
217
224
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
218
|
-
this.testStartMatterBridge(); // No await do it asyncronously
|
|
225
|
+
await this.testStartMatterBridge(); // No await do it asyncronously
|
|
219
226
|
}
|
|
220
227
|
if (hasParameter('bridge')) {
|
|
221
228
|
this.bridgeMode = 'bridge';
|
|
@@ -227,6 +234,8 @@ export class Matterbridge {
|
|
|
227
234
|
plugin.started = false;
|
|
228
235
|
plugin.configured = false;
|
|
229
236
|
plugin.connected = undefined;
|
|
237
|
+
plugin.qrPairingCode = undefined;
|
|
238
|
+
plugin.manualPairingCode = undefined;
|
|
230
239
|
this.loadPlugin(plugin); // No await do it asyncronously
|
|
231
240
|
}
|
|
232
241
|
await this.startMatterBridge();
|
|
@@ -241,11 +250,49 @@ export class Matterbridge {
|
|
|
241
250
|
plugin.started = false;
|
|
242
251
|
plugin.configured = false;
|
|
243
252
|
plugin.connected = false;
|
|
253
|
+
plugin.qrPairingCode = (await plugin.nodeContext?.get('qrPairingCode', undefined)) ?? undefined;
|
|
254
|
+
plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
|
|
244
255
|
this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
245
256
|
}
|
|
246
257
|
await this.startMatterBridge();
|
|
247
258
|
}
|
|
248
259
|
}
|
|
260
|
+
async resolvePluginName(pluginPath) {
|
|
261
|
+
if (!pluginPath.endsWith('package.json'))
|
|
262
|
+
pluginPath = path.join(pluginPath, 'package.json');
|
|
263
|
+
// Resolve the package.json of the plugin
|
|
264
|
+
let packageJsonPath = path.resolve(pluginPath);
|
|
265
|
+
this.log.debug(`Loading plugin from ${plg}${packageJsonPath}${db}`);
|
|
266
|
+
// Check if the package.json file exists
|
|
267
|
+
let packageJsonExists = false;
|
|
268
|
+
try {
|
|
269
|
+
await fs.access(packageJsonPath);
|
|
270
|
+
packageJsonExists = true;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
packageJsonExists = false;
|
|
274
|
+
}
|
|
275
|
+
if (!packageJsonExists) {
|
|
276
|
+
this.log.debug(`Package.json not found at ${packageJsonPath}`);
|
|
277
|
+
this.log.debug(`Trying at ${this.globalModulesDir}`);
|
|
278
|
+
packageJsonPath = path.join(this.globalModulesDir, pluginPath);
|
|
279
|
+
//this.log.debug(`Got ${packageJsonPath}`);
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
// Load the package.json of the plugin
|
|
283
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
284
|
+
if (!packageJson.name) {
|
|
285
|
+
this.log.debug(`Package.json name not found at ${packageJsonPath}`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
this.log.debug(`Package.json name: ${plg}${packageJson.name}${db} description: "${nf}${packageJson.description}${db}" found at "${nf}${packageJsonPath}${db}"`);
|
|
289
|
+
return packageJsonPath;
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
this.log.debug(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
249
296
|
/**
|
|
250
297
|
* Loads a plugin from the specified package.json file path.
|
|
251
298
|
* @param packageJsonPath - The path to the package.json file of the plugin.
|
|
@@ -253,10 +300,7 @@ export class Matterbridge {
|
|
|
253
300
|
* @returns A Promise that resolves when the plugin is loaded successfully, or rejects with an error if loading fails.
|
|
254
301
|
*/
|
|
255
302
|
async executeCommandLine(packageJsonPath, mode) {
|
|
256
|
-
|
|
257
|
-
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
258
|
-
// Resolve the package.json of the plugin
|
|
259
|
-
packageJsonPath = path.resolve(packageJsonPath);
|
|
303
|
+
packageJsonPath = (await this.resolvePluginName(packageJsonPath)) ?? packageJsonPath;
|
|
260
304
|
this.log.debug(`Loading plugin from ${plg}${packageJsonPath}${db}`);
|
|
261
305
|
try {
|
|
262
306
|
// Load the package.json of the plugin
|
|
@@ -317,7 +361,6 @@ export class Matterbridge {
|
|
|
317
361
|
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
318
362
|
}
|
|
319
363
|
}
|
|
320
|
-
//}
|
|
321
364
|
}
|
|
322
365
|
catch (err) {
|
|
323
366
|
this.log.error(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
|
|
@@ -328,21 +371,32 @@ export class Matterbridge {
|
|
|
328
371
|
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
329
372
|
*/
|
|
330
373
|
async registerSignalHandlers() {
|
|
331
|
-
process.
|
|
374
|
+
process.once('SIGINT', async () => {
|
|
332
375
|
await this.cleanup('SIGINT received, cleaning up...');
|
|
333
376
|
});
|
|
334
|
-
process.
|
|
377
|
+
process.once('SIGTERM', async () => {
|
|
335
378
|
await this.cleanup('SIGTERM received, cleaning up...');
|
|
336
379
|
});
|
|
337
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
|
+
}
|
|
338
390
|
/**
|
|
339
391
|
* Performs cleanup operations before shutting down Matterbridge.
|
|
340
392
|
* @param message - The reason for the cleanup.
|
|
341
393
|
*/
|
|
342
|
-
async cleanup(message) {
|
|
394
|
+
async cleanup(message, restart = false) {
|
|
343
395
|
if (!this.hasCleanupStarted) {
|
|
344
396
|
this.hasCleanupStarted = true;
|
|
345
397
|
this.log.info(message);
|
|
398
|
+
process.removeAllListeners('SIGINT');
|
|
399
|
+
process.removeAllListeners('SIGTERM');
|
|
346
400
|
// Callint the shutdown functions with a reason
|
|
347
401
|
for (const plugin of this.registeredPlugins) {
|
|
348
402
|
if (plugin.platform)
|
|
@@ -365,21 +419,47 @@ export class Matterbridge {
|
|
|
365
419
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') registeredDevice.device.setBridgedDeviceReachability(false);
|
|
366
420
|
});
|
|
367
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
|
+
}
|
|
368
432
|
setTimeout(async () => {
|
|
369
433
|
// Closing matter
|
|
370
434
|
await this.stopMatter();
|
|
371
435
|
// Closing storage
|
|
372
436
|
await this.stopStorage();
|
|
373
437
|
// Serialize registeredDevices
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
serializedRegisteredDevices
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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 () => {
|
|
381
457
|
this.log.info('Cleanup completed.');
|
|
382
|
-
|
|
458
|
+
//if (restart) console.log(this);
|
|
459
|
+
if (restart)
|
|
460
|
+
await this.initialize();
|
|
461
|
+
else
|
|
462
|
+
process.exit(0);
|
|
383
463
|
}, 2 * 1000);
|
|
384
464
|
}, 3 * 1000);
|
|
385
465
|
}
|
|
@@ -405,16 +485,20 @@ export class Matterbridge {
|
|
|
405
485
|
* @returns A Promise that resolves when the device is added successfully.
|
|
406
486
|
*/
|
|
407
487
|
async addDevice(pluginName, device) {
|
|
408
|
-
this.
|
|
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
|
+
}
|
|
492
|
+
this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
|
|
409
493
|
// Check if the plugin is registered
|
|
410
494
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
411
495
|
if (!plugin) {
|
|
412
|
-
this.log.error(`
|
|
496
|
+
this.log.error(`Error adding device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
413
497
|
return;
|
|
414
498
|
}
|
|
415
|
-
//
|
|
499
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
416
500
|
if (this.bridgeMode === 'bridge') {
|
|
417
|
-
this.matterAggregator
|
|
501
|
+
this.matterAggregator?.addBridgedDevice(device);
|
|
418
502
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
419
503
|
if (plugin.registeredDevices !== undefined)
|
|
420
504
|
plugin.registeredDevices++;
|
|
@@ -437,16 +521,20 @@ export class Matterbridge {
|
|
|
437
521
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
438
522
|
*/
|
|
439
523
|
async addBridgedDevice(pluginName, device) {
|
|
440
|
-
this.
|
|
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
|
+
}
|
|
528
|
+
this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
441
529
|
// Check if the plugin is registered
|
|
442
530
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
443
531
|
if (!plugin) {
|
|
444
|
-
this.log.error(`
|
|
532
|
+
this.log.error(`Error adding bridged device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
445
533
|
return;
|
|
446
534
|
}
|
|
447
|
-
//
|
|
535
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
448
536
|
if (this.bridgeMode === 'bridge') {
|
|
449
|
-
this.matterAggregator
|
|
537
|
+
this.matterAggregator?.addBridgedDevice(device);
|
|
450
538
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
451
539
|
if (plugin.registeredDevices !== undefined)
|
|
452
540
|
plugin.registeredDevices++;
|
|
@@ -469,18 +557,22 @@ export class Matterbridge {
|
|
|
469
557
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
470
558
|
*/
|
|
471
559
|
async startStorage(storageType, storageName) {
|
|
472
|
-
if (!storageName.endsWith('.json')) {
|
|
473
|
-
storageName += '.json';
|
|
474
|
-
}
|
|
475
560
|
this.log.debug(`Starting storage ${storageType} ${storageName}`);
|
|
476
561
|
if (storageType === 'disk') {
|
|
477
562
|
const storageDisk = new StorageBackendDisk(storageName);
|
|
478
563
|
this.storageManager = new StorageManager(storageDisk);
|
|
479
564
|
}
|
|
480
|
-
if (storageType === 'json') {
|
|
565
|
+
else if (storageType === 'json') {
|
|
566
|
+
if (!storageName.endsWith('.json'))
|
|
567
|
+
storageName += '.json';
|
|
481
568
|
const storageJson = new StorageBackendJsonFile(storageName);
|
|
482
569
|
this.storageManager = new StorageManager(storageJson);
|
|
483
570
|
}
|
|
571
|
+
else {
|
|
572
|
+
this.log.error(`Unsupported storage type ${storageType}`);
|
|
573
|
+
await this.cleanup('Unsupported storage type');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
484
576
|
try {
|
|
485
577
|
await this.storageManager.initialize();
|
|
486
578
|
this.log.debug('Storage initialized');
|
|
@@ -490,7 +582,7 @@ export class Matterbridge {
|
|
|
490
582
|
}
|
|
491
583
|
catch (error) {
|
|
492
584
|
this.log.error('Storage initialize() error!');
|
|
493
|
-
|
|
585
|
+
await this.cleanup('Storage initialize() error!');
|
|
494
586
|
}
|
|
495
587
|
}
|
|
496
588
|
/**
|
|
@@ -525,41 +617,57 @@ export class Matterbridge {
|
|
|
525
617
|
*/
|
|
526
618
|
async stopStorage() {
|
|
527
619
|
this.log.debug('Stopping storage');
|
|
528
|
-
await this.storageManager
|
|
620
|
+
await this.storageManager?.close();
|
|
529
621
|
this.log.debug('Storage closed');
|
|
622
|
+
this.storageManager = undefined;
|
|
623
|
+
this.matterbridgeContext = undefined;
|
|
624
|
+
this.mattercontrollerContext = undefined;
|
|
530
625
|
}
|
|
531
626
|
async testStartMatterBridge() {
|
|
627
|
+
/*
|
|
628
|
+
this.log.error('****Start forEach registeredPlugin');
|
|
629
|
+
this.registeredPlugins
|
|
630
|
+
.filter((plugin) => plugin.enabled === true)
|
|
631
|
+
.forEach(async (plugin) => {
|
|
632
|
+
plugin.loaded = false;
|
|
633
|
+
plugin.started = false;
|
|
634
|
+
plugin.configured = false;
|
|
635
|
+
plugin.connected = undefined;
|
|
636
|
+
this.log.error(`****Starting registeredPlugin ${plugin.name}`);
|
|
637
|
+
this.loadPlugin(plugin, true, 'Matterbridge is starting');
|
|
638
|
+
});
|
|
639
|
+
this.log.error('****Stop forEach registeredPlugin');
|
|
640
|
+
*/
|
|
641
|
+
/*
|
|
532
642
|
for (const plugin of this.registeredPlugins) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
643
|
+
if (!plugin.enabled) continue;
|
|
644
|
+
// No await do it asyncronously
|
|
645
|
+
this.loadPlugin(plugin)
|
|
646
|
+
.then(() => {
|
|
647
|
+
// No await do it asyncronously
|
|
648
|
+
this.startPlugin(plugin)
|
|
649
|
+
.then(() => {})
|
|
650
|
+
.catch((err) => {
|
|
651
|
+
this.log.error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
543
652
|
});
|
|
544
653
|
})
|
|
545
|
-
|
|
546
|
-
|
|
654
|
+
.catch((err) => {
|
|
655
|
+
this.log.error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`);
|
|
547
656
|
});
|
|
548
657
|
}
|
|
549
658
|
for (const plugin of this.registeredPlugins) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
clearInterval(interval);
|
|
561
|
-
}, 1000);
|
|
659
|
+
if (!plugin.enabled) continue;
|
|
660
|
+
// Start the interval to check if the plugin is loaded and started
|
|
661
|
+
let times = 0;
|
|
662
|
+
const interval = setInterval(() => {
|
|
663
|
+
times++;
|
|
664
|
+
this.log.info(`Waiting ${times} secs for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) and send devices ...`);
|
|
665
|
+
if (!plugin.loaded || !plugin.started) return;
|
|
666
|
+
this.log.info(`Plugin ${plg}${plugin.name}${db} sent ${plugin.registeredDevices} devices`);
|
|
667
|
+
clearInterval(interval);
|
|
668
|
+
}, 1000);
|
|
562
669
|
}
|
|
670
|
+
*/
|
|
563
671
|
}
|
|
564
672
|
async startPlugin(plugin, message, configure = false) {
|
|
565
673
|
if (!plugin.loaded || !plugin.platform) {
|
|
@@ -678,13 +786,27 @@ export class Matterbridge {
|
|
|
678
786
|
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
679
787
|
*/
|
|
680
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
|
+
}
|
|
681
794
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
682
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
|
+
}
|
|
683
801
|
if (this.bridgeMode === 'bridge') {
|
|
684
802
|
// Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
|
|
685
|
-
// Plugins are started and configured by callback when Matterbridge is commissioned
|
|
803
|
+
// Plugins are started and configured by callback when Matterbridge is commissioned
|
|
686
804
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
687
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
|
+
}
|
|
688
810
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
689
811
|
this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
690
812
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -698,19 +820,24 @@ export class Matterbridge {
|
|
|
698
820
|
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
699
821
|
}
|
|
700
822
|
if (this.bridgeMode === 'childbridge') {
|
|
701
|
-
// Plugins are loaded and started by loadPlugin on startup
|
|
823
|
+
// Plugins are loaded and started by loadPlugin on startup
|
|
702
824
|
// addDevice and addBridgedDeevice just register the devices that are added here to the plugin commissioning server for Accessory Platform
|
|
703
825
|
// or to the plugin aggregator for Dynamic Platform after the commissioning is done
|
|
704
826
|
// Plugins are configured by callback when the plugin is commissioned
|
|
705
|
-
this.registeredPlugins.forEach(
|
|
827
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
706
828
|
if (!plugin.enabled)
|
|
707
829
|
return;
|
|
708
830
|
// Start the interval to check if the plugins is started
|
|
709
831
|
// TODO set a counter or a timeout
|
|
710
|
-
this.log.
|
|
832
|
+
this.log.debug(`*Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
711
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
|
+
}
|
|
712
839
|
if (!plugin.loaded || !plugin.started) {
|
|
713
|
-
this.log.info(
|
|
840
|
+
this.log.info(`**Waiting in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
714
841
|
return;
|
|
715
842
|
}
|
|
716
843
|
if (plugin.type === 'AccessoryPlatform') {
|
|
@@ -718,7 +845,11 @@ export class Matterbridge {
|
|
|
718
845
|
.filter((registeredDevice) => registeredDevice.plugin === plugin.name)
|
|
719
846
|
.forEach((registeredDevice) => {
|
|
720
847
|
if (!plugin.storageContext)
|
|
721
|
-
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
|
+
}
|
|
722
853
|
if (!plugin.commissioningServer)
|
|
723
854
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
724
855
|
this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -728,9 +859,11 @@ export class Matterbridge {
|
|
|
728
859
|
await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
729
860
|
}
|
|
730
861
|
if (plugin.type === 'DynamicPlatform') {
|
|
731
|
-
plugin.storageContext = this.createCommissioningServerContext(
|
|
732
|
-
|
|
733
|
-
|
|
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
|
+
}
|
|
734
867
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
735
868
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
736
869
|
plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
|
|
@@ -744,19 +877,20 @@ export class Matterbridge {
|
|
|
744
877
|
});
|
|
745
878
|
// Start the interval to check if all plugins are loaded and started and so start the matter server
|
|
746
879
|
// TODO set a counter or a timeout
|
|
747
|
-
this.log.
|
|
880
|
+
this.log.debug('*Starting start matter interval...');
|
|
748
881
|
const startMatterInterval = setInterval(async () => {
|
|
749
882
|
let allStarted = true;
|
|
750
883
|
this.registeredPlugins.forEach((plugin) => {
|
|
751
884
|
if (!plugin.enabled)
|
|
752
885
|
return;
|
|
753
|
-
this.log.info(`**Waiting in start matter server interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
|
|
754
886
|
if (plugin.enabled && (!plugin.loaded || !plugin.started))
|
|
755
887
|
allStarted = false;
|
|
888
|
+
if (!allStarted)
|
|
889
|
+
this.log.info(`**Waiting in start matter server interval for plugin ${plg}${plugin.name}${db} to load (${plugin.loaded}) and start (${plugin.started}) ...`);
|
|
756
890
|
});
|
|
757
891
|
if (!allStarted)
|
|
758
892
|
return;
|
|
759
|
-
this.log.info('
|
|
893
|
+
this.log.info('Starting matter server');
|
|
760
894
|
// Setting reachability to true
|
|
761
895
|
this.registeredPlugins.forEach((plugin) => {
|
|
762
896
|
if (!plugin.enabled)
|
|
@@ -783,6 +917,11 @@ export class Matterbridge {
|
|
|
783
917
|
}
|
|
784
918
|
}
|
|
785
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
|
+
}
|
|
786
925
|
this.log.debug('Starting matter server');
|
|
787
926
|
await this.matterServer.start();
|
|
788
927
|
this.log.debug('Started matter server');
|
|
@@ -822,6 +961,10 @@ export class Matterbridge {
|
|
|
822
961
|
* @returns The storage context for the commissioning server.
|
|
823
962
|
*/
|
|
824
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
|
+
}
|
|
825
968
|
this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
826
969
|
const random = 'CS' + CryptoNode.getRandomData(8).toHex();
|
|
827
970
|
const storageContext = this.storageManager.createContext(pluginName);
|
|
@@ -862,9 +1005,15 @@ export class Matterbridge {
|
|
|
862
1005
|
storageContext.set('manualPairingCode', manualPairingCode);
|
|
863
1006
|
const QrCode = new QrCodeSchema();
|
|
864
1007
|
this.log.info(`Pairing code:\n\n${QrCode.encode(qrPairingCode)}\nManual pairing code: ${manualPairingCode}\n`);
|
|
1008
|
+
if (this.bridgeMode === 'bridge') {
|
|
1009
|
+
await this.nodeContext?.set('qrPairingCode', qrPairingCode);
|
|
1010
|
+
await this.nodeContext?.set('manualPairingCode', manualPairingCode);
|
|
1011
|
+
}
|
|
865
1012
|
if (this.bridgeMode === 'childbridge') {
|
|
866
1013
|
const plugin = this.findPlugin(pluginName);
|
|
867
1014
|
if (plugin) {
|
|
1015
|
+
await plugin.nodeContext?.set('qrPairingCode', qrPairingCode);
|
|
1016
|
+
await plugin.nodeContext?.set('manualPairingCode', manualPairingCode);
|
|
868
1017
|
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
869
1018
|
plugin.paired = false;
|
|
870
1019
|
}
|
|
@@ -872,9 +1021,19 @@ export class Matterbridge {
|
|
|
872
1021
|
}
|
|
873
1022
|
else {
|
|
874
1023
|
this.log.info(`***The commissioning server for ${plg}${pluginName}${nf} is already commissioned. Waiting for controllers to connect ...`);
|
|
1024
|
+
if (this.bridgeMode === 'bridge') {
|
|
1025
|
+
const qrPairingCode = storageContext.get('qrPairingCode', '');
|
|
1026
|
+
const manualPairingCode = storageContext.get('manualPairingCode', '');
|
|
1027
|
+
await this.nodeContext?.set('qrPairingCode', qrPairingCode);
|
|
1028
|
+
await this.nodeContext?.set('manualPairingCode', manualPairingCode);
|
|
1029
|
+
}
|
|
875
1030
|
if (this.bridgeMode === 'childbridge') {
|
|
876
1031
|
const plugin = this.findPlugin(pluginName);
|
|
877
|
-
if (plugin) {
|
|
1032
|
+
if (plugin && plugin.storageContext && plugin.nodeContext) {
|
|
1033
|
+
plugin.qrPairingCode = plugin.storageContext.get('qrPairingCode', '');
|
|
1034
|
+
plugin.manualPairingCode = plugin.storageContext.get('manualPairingCode', '');
|
|
1035
|
+
await plugin.nodeContext.set('qrPairingCode', plugin.qrPairingCode);
|
|
1036
|
+
await plugin.nodeContext.set('manualPairingCode', plugin.manualPairingCode);
|
|
878
1037
|
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
879
1038
|
plugin.paired = true;
|
|
880
1039
|
}
|
|
@@ -960,7 +1119,7 @@ export class Matterbridge {
|
|
|
960
1119
|
for (const plugin of this.registeredPlugins) {
|
|
961
1120
|
if (!plugin.enabled)
|
|
962
1121
|
continue;
|
|
963
|
-
this.startPlugin(plugin, 'Matterbridge is commissioned and controllers are connected', true); // No await do it asyncronously
|
|
1122
|
+
this.startPlugin(plugin, 'Matterbridge is commissioned and controllers are connected', true); // No await do it asyncronously with also configurePlugin
|
|
964
1123
|
//this.configurePlugin(plugin); // No await do it asyncronously
|
|
965
1124
|
}
|
|
966
1125
|
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
@@ -968,7 +1127,7 @@ export class Matterbridge {
|
|
|
968
1127
|
if (this.bridgeMode === 'childbridge') {
|
|
969
1128
|
//Logger.defaultLogLevel = Level.INFO;
|
|
970
1129
|
const plugin = this.findPlugin(name);
|
|
971
|
-
if (plugin && plugin.type === 'DynamicPlatform') {
|
|
1130
|
+
if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
|
|
972
1131
|
for (const registeredDevice of this.registeredDevices) {
|
|
973
1132
|
if (registeredDevice.plugin === name) {
|
|
974
1133
|
this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -985,7 +1144,7 @@ export class Matterbridge {
|
|
|
985
1144
|
}
|
|
986
1145
|
}
|
|
987
1146
|
for (const plugin of this.registeredPlugins) {
|
|
988
|
-
if (plugin.name === name && plugin.platform) {
|
|
1147
|
+
if (plugin.name === name && plugin.platform && plugin.configured !== true) {
|
|
989
1148
|
this.configurePlugin(plugin); // No await do it asyncronously
|
|
990
1149
|
}
|
|
991
1150
|
}
|
|
@@ -1015,6 +1174,7 @@ export class Matterbridge {
|
|
|
1015
1174
|
createMatterServer(storageManager) {
|
|
1016
1175
|
this.log.debug('Creating matter server');
|
|
1017
1176
|
this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
|
|
1177
|
+
this.log.debug('Created matter server');
|
|
1018
1178
|
}
|
|
1019
1179
|
/**
|
|
1020
1180
|
* Creates a Matter Aggregator.
|
|
@@ -1062,6 +1222,27 @@ export class Matterbridge {
|
|
|
1062
1222
|
this.log.debug('Stopping matter server');
|
|
1063
1223
|
await this.matterServer?.close();
|
|
1064
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
|
+
});
|
|
1065
1246
|
}
|
|
1066
1247
|
/**
|
|
1067
1248
|
* Logs the node and system information.
|
|
@@ -1122,19 +1303,64 @@ export class Matterbridge {
|
|
|
1122
1303
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
1123
1304
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
1124
1305
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1306
|
+
// Global node_modules directory
|
|
1307
|
+
this.globalModulesDir = execSync('npm root -g').toString().trim();
|
|
1308
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDir}`);
|
|
1125
1309
|
// Create the data directory .matterbridge in the home directory
|
|
1126
1310
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
1127
1311
|
try {
|
|
1128
1312
|
await fs.access(this.matterbridgeDirectory);
|
|
1129
1313
|
}
|
|
1130
1314
|
catch (err) {
|
|
1131
|
-
|
|
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
|
+
}
|
|
1132
1330
|
}
|
|
1133
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}`);
|
|
1134
1355
|
// Matterbridge version
|
|
1135
1356
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1136
1357
|
this.matterbridgeVersion = packageJson.version;
|
|
1137
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
|
+
}
|
|
1138
1364
|
// Current working directory
|
|
1139
1365
|
const currentDir = process.cwd();
|
|
1140
1366
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
@@ -1157,6 +1383,8 @@ export class Matterbridge {
|
|
|
1157
1383
|
paired: plugin.paired,
|
|
1158
1384
|
connected: plugin.connected,
|
|
1159
1385
|
registeredDevices: plugin.registeredDevices,
|
|
1386
|
+
qrPairingCode: plugin.qrPairingCode,
|
|
1387
|
+
manualPairingCode: plugin.manualPairingCode,
|
|
1160
1388
|
}));
|
|
1161
1389
|
return baseRegisteredPlugins;
|
|
1162
1390
|
}
|
|
@@ -1184,14 +1412,16 @@ export class Matterbridge {
|
|
|
1184
1412
|
*/
|
|
1185
1413
|
async initializeFrontend(port = 3000) {
|
|
1186
1414
|
this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
1187
|
-
this.
|
|
1415
|
+
this.expressApp = express();
|
|
1188
1416
|
// Serve React build directory
|
|
1189
|
-
this.
|
|
1417
|
+
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
1190
1418
|
// Endpoint to provide QR pairing code
|
|
1191
|
-
this.
|
|
1419
|
+
this.expressApp.get('/api/qr-code', (req, res) => {
|
|
1192
1420
|
this.log.debug('The frontend sent /api/qr-code');
|
|
1193
|
-
if (!this.matterbridgeContext)
|
|
1194
|
-
|
|
1421
|
+
if (!this.matterbridgeContext) {
|
|
1422
|
+
res.json([]);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1195
1425
|
try {
|
|
1196
1426
|
const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
|
|
1197
1427
|
res.json(qrData);
|
|
@@ -1203,17 +1433,17 @@ export class Matterbridge {
|
|
|
1203
1433
|
}
|
|
1204
1434
|
});
|
|
1205
1435
|
// Endpoint to provide system information
|
|
1206
|
-
this.
|
|
1436
|
+
this.expressApp.get('/api/system-info', (req, res) => {
|
|
1207
1437
|
this.log.debug('The frontend sent /api/system-info');
|
|
1208
1438
|
res.json(this.systemInformation);
|
|
1209
1439
|
});
|
|
1210
1440
|
// Endpoint to provide plugins
|
|
1211
|
-
this.
|
|
1441
|
+
this.expressApp.get('/api/plugins', (req, res) => {
|
|
1212
1442
|
this.log.debug('The frontend sent /api/plugins');
|
|
1213
1443
|
res.json(this.getBaseRegisteredPlugins());
|
|
1214
1444
|
});
|
|
1215
1445
|
// Endpoint to provide devices
|
|
1216
|
-
this.
|
|
1446
|
+
this.expressApp.get('/api/devices', (req, res) => {
|
|
1217
1447
|
this.log.debug('The frontend sent /api/devices');
|
|
1218
1448
|
const data = [];
|
|
1219
1449
|
this.registeredDevices.forEach((registeredDevice) => {
|
|
@@ -1240,7 +1470,7 @@ export class Matterbridge {
|
|
|
1240
1470
|
res.json(data);
|
|
1241
1471
|
});
|
|
1242
1472
|
// Endpoint to provide the cluster servers of the devices
|
|
1243
|
-
this.
|
|
1473
|
+
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
1244
1474
|
const selectedPluginName = req.params.selectedPluginName;
|
|
1245
1475
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
1246
1476
|
this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
|
|
@@ -1282,12 +1512,49 @@ export class Matterbridge {
|
|
|
1282
1512
|
});
|
|
1283
1513
|
res.json(data);
|
|
1284
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
|
+
});
|
|
1285
1552
|
// Fallback for routing
|
|
1286
|
-
this.
|
|
1553
|
+
this.expressApp.get('*', (req, res) => {
|
|
1287
1554
|
this.log.warn('The frontend sent *', req.url);
|
|
1288
1555
|
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
1289
1556
|
});
|
|
1290
|
-
this.
|
|
1557
|
+
this.expressServer = this.expressApp.listen(port, () => {
|
|
1291
1558
|
this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
|
|
1292
1559
|
});
|
|
1293
1560
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
@@ -1335,7 +1602,25 @@ export class Matterbridge {
|
|
|
1335
1602
|
}
|
|
1336
1603
|
/*
|
|
1337
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
|
+
|
|
1338
1622
|
import * as WebSocket from 'ws';
|
|
1623
|
+
const globalModulesDir = require('global-modules');
|
|
1339
1624
|
|
|
1340
1625
|
const wss = new WebSocket.Server({ port: 8080 });
|
|
1341
1626
|
|