matterbridge 1.1.8 → 1.1.10
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 +18 -0
- package/README.md +20 -4
- package/dist/cli.d.ts +22 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +45 -5
- 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 +38 -3
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +458 -72
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +9 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
- package/dist/matterbridgeAccessoryPlatform.js +13 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
- package/dist/matterbridgeDevice.d.ts +3 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +137 -6
- package/dist/matterbridgeDevice.js.map +1 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +9 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
- package/dist/matterbridgeDynamicPlatform.js +13 -0
- package/dist/matterbridgeDynamicPlatform.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.3804969f.css +2 -0
- package/frontend/build/static/css/main.3804969f.css.map +1 -0
- package/frontend/build/static/js/main.82822a11.js +3 -0
- package/frontend/build/static/js/main.82822a11.js.map +1 -0
- package/package.json +2 -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.82822a11.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -25,7 +25,8 @@ 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 {
|
|
28
|
+
import { exec, spawn } from 'child_process';
|
|
29
|
+
import EventEmitter from 'events';
|
|
29
30
|
import express from 'express';
|
|
30
31
|
import os from 'os';
|
|
31
32
|
import path from 'path';
|
|
@@ -44,7 +45,7 @@ const typ = '\u001B[38;5;207m';
|
|
|
44
45
|
/**
|
|
45
46
|
* Represents the Matterbridge application.
|
|
46
47
|
*/
|
|
47
|
-
export class Matterbridge {
|
|
48
|
+
export class Matterbridge extends EventEmitter {
|
|
48
49
|
systemInformation = {
|
|
49
50
|
ipv4Address: '',
|
|
50
51
|
ipv6Address: '',
|
|
@@ -61,7 +62,9 @@ export class Matterbridge {
|
|
|
61
62
|
homeDirectory = '';
|
|
62
63
|
rootDirectory = '';
|
|
63
64
|
matterbridgeDirectory = '';
|
|
65
|
+
matterbridgePluginDirectory = '';
|
|
64
66
|
matterbridgeVersion = '';
|
|
67
|
+
matterbridgeLatestVersion = '';
|
|
65
68
|
globalModulesDir = '';
|
|
66
69
|
bridgeMode = '';
|
|
67
70
|
debugEnabled = false;
|
|
@@ -71,7 +74,8 @@ export class Matterbridge {
|
|
|
71
74
|
registeredDevices = [];
|
|
72
75
|
nodeStorage;
|
|
73
76
|
nodeContext;
|
|
74
|
-
|
|
77
|
+
expressApp;
|
|
78
|
+
expressServer;
|
|
75
79
|
storageManager;
|
|
76
80
|
matterbridgeContext;
|
|
77
81
|
mattercontrollerContext;
|
|
@@ -81,26 +85,25 @@ export class Matterbridge {
|
|
|
81
85
|
commissioningController;
|
|
82
86
|
static instance;
|
|
83
87
|
constructor() {
|
|
84
|
-
|
|
88
|
+
super();
|
|
89
|
+
// We load asyncronously
|
|
85
90
|
}
|
|
86
91
|
/**
|
|
87
92
|
* Loads an instance of the Matterbridge class.
|
|
88
93
|
* If an instance already exists, return that instance.
|
|
89
94
|
* @returns The loaded instance of the Matterbridge class.
|
|
90
95
|
*/
|
|
91
|
-
static async loadInstance(
|
|
92
|
-
// eslint-disable-next-line no-console
|
|
93
|
-
console.error('loadInstance cli:', cli);
|
|
96
|
+
static async loadInstance(initialize = false) {
|
|
94
97
|
if (!Matterbridge.instance) {
|
|
95
98
|
// eslint-disable-next-line no-console
|
|
96
|
-
console.
|
|
99
|
+
console.log('Matterbridge instance does not exists');
|
|
97
100
|
Matterbridge.instance = new Matterbridge();
|
|
98
|
-
if (
|
|
101
|
+
if (initialize)
|
|
99
102
|
await Matterbridge.instance.initialize();
|
|
100
103
|
}
|
|
101
104
|
else {
|
|
102
105
|
// eslint-disable-next-line no-console
|
|
103
|
-
console.
|
|
106
|
+
console.log('Matterbridge instance already exists');
|
|
104
107
|
}
|
|
105
108
|
return Matterbridge.instance;
|
|
106
109
|
}
|
|
@@ -115,6 +118,10 @@ export class Matterbridge {
|
|
|
115
118
|
* @returns A Promise that resolves when the initialization is complete.
|
|
116
119
|
*/
|
|
117
120
|
async initialize() {
|
|
121
|
+
/*
|
|
122
|
+
const wtf = await import('wtfnode');
|
|
123
|
+
wtf.dump();
|
|
124
|
+
*/
|
|
118
125
|
// Display the help
|
|
119
126
|
if (hasParameter('help')) {
|
|
120
127
|
// eslint-disable-next-line no-console
|
|
@@ -161,17 +168,54 @@ export class Matterbridge {
|
|
|
161
168
|
this.registeredPlugins = await this.nodeContext.get('plugins', []);
|
|
162
169
|
for (const plugin of this.registeredPlugins) {
|
|
163
170
|
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
|
|
171
|
+
plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
|
|
172
|
+
await plugin.nodeContext.set('name', plugin.name);
|
|
173
|
+
await plugin.nodeContext.set('type', plugin.type);
|
|
174
|
+
await plugin.nodeContext.set('path', plugin.path);
|
|
175
|
+
await plugin.nodeContext.set('version', plugin.version);
|
|
176
|
+
await plugin.nodeContext.set('description', plugin.description);
|
|
177
|
+
await plugin.nodeContext.set('author', plugin.author);
|
|
171
178
|
}
|
|
172
179
|
// Parse command line
|
|
173
180
|
this.parseCommandLine();
|
|
174
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Spawns a child process with the given command and arguments.
|
|
184
|
+
* @param command - The command to execute.
|
|
185
|
+
* @param args - The arguments to pass to the command (default: []).
|
|
186
|
+
* @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
187
|
+
*/
|
|
188
|
+
async spawnCommand(command, args = []) {
|
|
189
|
+
/*
|
|
190
|
+
npm > npm.cmd on windows
|
|
191
|
+
*/
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
const childProcess = spawn(command, args, {
|
|
194
|
+
stdio: 'inherit',
|
|
195
|
+
});
|
|
196
|
+
childProcess.on('error', (err) => {
|
|
197
|
+
this.log.error(`Failed to start child process: ${err.message}`);
|
|
198
|
+
reject(err); // Reject the promise on error
|
|
199
|
+
});
|
|
200
|
+
childProcess.on('close', (code) => {
|
|
201
|
+
if (code === 0) {
|
|
202
|
+
this.log.info(`Child process stdio streams have closed with code ${code}`);
|
|
203
|
+
resolve();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.log.error(`Child process stdio streams have closed with code ${code}`);
|
|
207
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// The 'exit' event might be redundant here since 'close' is also being handled
|
|
211
|
+
childProcess.on('exit', (code, signal) => {
|
|
212
|
+
this.log.info(`Child process exited with code ${code} and signal ${signal}`);
|
|
213
|
+
});
|
|
214
|
+
childProcess.on('disconnect', () => {
|
|
215
|
+
this.log.info('Child process has been disconnected from the parent');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
175
219
|
/**
|
|
176
220
|
* Parses the command line arguments and performs the corresponding actions.
|
|
177
221
|
* @private
|
|
@@ -190,26 +234,31 @@ export class Matterbridge {
|
|
|
190
234
|
this.log.info(`│ └─ ${db}${plugin.path}${db}`);
|
|
191
235
|
}
|
|
192
236
|
});
|
|
237
|
+
this.emit('shutdown');
|
|
193
238
|
process.exit(0);
|
|
194
239
|
}
|
|
195
240
|
if (getParameter('add')) {
|
|
196
241
|
this.log.debug(`Registering plugin ${getParameter('add')}`);
|
|
197
242
|
await this.executeCommandLine(getParameter('add'), 'add');
|
|
243
|
+
this.emit('shutdown');
|
|
198
244
|
process.exit(0);
|
|
199
245
|
}
|
|
200
246
|
if (getParameter('remove')) {
|
|
201
247
|
this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
|
|
202
248
|
await this.executeCommandLine(getParameter('remove'), 'remove');
|
|
249
|
+
this.emit('shutdown');
|
|
203
250
|
process.exit(0);
|
|
204
251
|
}
|
|
205
252
|
if (getParameter('enable')) {
|
|
206
253
|
this.log.debug(`Enable plugin ${getParameter('enable')}`);
|
|
207
254
|
await this.executeCommandLine(getParameter('enable'), 'enable');
|
|
255
|
+
this.emit('shutdown');
|
|
208
256
|
process.exit(0);
|
|
209
257
|
}
|
|
210
258
|
if (getParameter('disable')) {
|
|
211
259
|
this.log.debug(`Disable plugin ${getParameter('disable')}`);
|
|
212
260
|
await this.executeCommandLine(getParameter('disable'), 'disable');
|
|
261
|
+
this.emit('shutdown');
|
|
213
262
|
process.exit(0);
|
|
214
263
|
}
|
|
215
264
|
// Start the storage (we need it now for frontend and later for matterbridge)
|
|
@@ -227,8 +276,10 @@ export class Matterbridge {
|
|
|
227
276
|
this.bridgeMode = 'bridge';
|
|
228
277
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
229
278
|
for (const plugin of this.registeredPlugins) {
|
|
230
|
-
if (!plugin.enabled)
|
|
279
|
+
if (!plugin.enabled) {
|
|
280
|
+
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
231
281
|
continue;
|
|
282
|
+
}
|
|
232
283
|
plugin.loaded = false;
|
|
233
284
|
plugin.started = false;
|
|
234
285
|
plugin.configured = false;
|
|
@@ -243,8 +294,10 @@ export class Matterbridge {
|
|
|
243
294
|
this.bridgeMode = 'childbridge';
|
|
244
295
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
245
296
|
for (const plugin of this.registeredPlugins) {
|
|
246
|
-
if (!plugin.enabled)
|
|
297
|
+
if (!plugin.enabled) {
|
|
298
|
+
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
247
299
|
continue;
|
|
300
|
+
}
|
|
248
301
|
plugin.loaded = false;
|
|
249
302
|
plugin.started = false;
|
|
250
303
|
plugin.configured = false;
|
|
@@ -275,7 +328,7 @@ export class Matterbridge {
|
|
|
275
328
|
this.log.debug(`Package.json not found at ${packageJsonPath}`);
|
|
276
329
|
this.log.debug(`Trying at ${this.globalModulesDir}`);
|
|
277
330
|
packageJsonPath = path.join(this.globalModulesDir, pluginPath);
|
|
278
|
-
this.log.debug(`Got ${packageJsonPath}`);
|
|
331
|
+
//this.log.debug(`Got ${packageJsonPath}`);
|
|
279
332
|
}
|
|
280
333
|
try {
|
|
281
334
|
// Load the package.json of the plugin
|
|
@@ -360,7 +413,6 @@ export class Matterbridge {
|
|
|
360
413
|
this.log.warn(`Plugin ${plg}${packageJsonPath}${wr} not registerd in matterbridge`);
|
|
361
414
|
}
|
|
362
415
|
}
|
|
363
|
-
//}
|
|
364
416
|
}
|
|
365
417
|
catch (err) {
|
|
366
418
|
this.log.error(`Failed to load plugin from ${plg}${packageJsonPath}${er}: ${err}`);
|
|
@@ -371,21 +423,32 @@ export class Matterbridge {
|
|
|
371
423
|
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
372
424
|
*/
|
|
373
425
|
async registerSignalHandlers() {
|
|
374
|
-
process.
|
|
426
|
+
process.once('SIGINT', async () => {
|
|
375
427
|
await this.cleanup('SIGINT received, cleaning up...');
|
|
376
428
|
});
|
|
377
|
-
process.
|
|
429
|
+
process.once('SIGTERM', async () => {
|
|
378
430
|
await this.cleanup('SIGTERM received, cleaning up...');
|
|
379
431
|
});
|
|
380
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
435
|
+
*/
|
|
436
|
+
async restartProcess() {
|
|
437
|
+
//this.log.info('Restarting still not implemented');
|
|
438
|
+
//return;
|
|
439
|
+
await this.cleanup('restarting...', true);
|
|
440
|
+
this.hasCleanupStarted = false;
|
|
441
|
+
}
|
|
381
442
|
/**
|
|
382
443
|
* Performs cleanup operations before shutting down Matterbridge.
|
|
383
444
|
* @param message - The reason for the cleanup.
|
|
384
445
|
*/
|
|
385
|
-
async cleanup(message) {
|
|
446
|
+
async cleanup(message, restart = false) {
|
|
386
447
|
if (!this.hasCleanupStarted) {
|
|
387
448
|
this.hasCleanupStarted = true;
|
|
388
449
|
this.log.info(message);
|
|
450
|
+
process.removeAllListeners('SIGINT');
|
|
451
|
+
process.removeAllListeners('SIGTERM');
|
|
389
452
|
// Callint the shutdown functions with a reason
|
|
390
453
|
for (const plugin of this.registeredPlugins) {
|
|
391
454
|
if (plugin.platform)
|
|
@@ -408,23 +471,55 @@ export class Matterbridge {
|
|
|
408
471
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'DynamicPlatform') registeredDevice.device.setBridgedDeviceReachability(false);
|
|
409
472
|
});
|
|
410
473
|
*/
|
|
411
|
-
|
|
474
|
+
// Close the express server
|
|
475
|
+
if (this.expressServer) {
|
|
476
|
+
this.expressServer.close();
|
|
477
|
+
this.expressServer = undefined;
|
|
478
|
+
}
|
|
479
|
+
// Remove listeners
|
|
480
|
+
if (this.expressApp) {
|
|
481
|
+
this.expressApp.removeAllListeners();
|
|
482
|
+
this.expressApp = undefined;
|
|
483
|
+
}
|
|
484
|
+
const cleanupTimeout1 = setTimeout(async () => {
|
|
412
485
|
// Closing matter
|
|
413
486
|
await this.stopMatter();
|
|
414
487
|
// Closing storage
|
|
415
488
|
await this.stopStorage();
|
|
416
489
|
// Serialize registeredDevices
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
serializedRegisteredDevices
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
|
|
490
|
+
if (this.nodeContext) {
|
|
491
|
+
this.log.info('Saving registered devices...');
|
|
492
|
+
const serializedRegisteredDevices = [];
|
|
493
|
+
this.registeredDevices.forEach((registeredDevice) => {
|
|
494
|
+
serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
|
|
495
|
+
});
|
|
496
|
+
//console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
|
|
497
|
+
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
498
|
+
this.log.info('Saved registered devices');
|
|
499
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
500
|
+
this.nodeContext = undefined;
|
|
501
|
+
this.nodeStorage = undefined;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
this.log.error('Error saving registered devices: nodeContext not found!');
|
|
505
|
+
}
|
|
506
|
+
this.registeredPlugins = [];
|
|
507
|
+
this.registeredDevices = [];
|
|
508
|
+
const cleanupTimeout2 = setTimeout(async () => {
|
|
509
|
+
if (restart) {
|
|
510
|
+
this.log.info('Cleanup completed. Restarting...');
|
|
511
|
+
Matterbridge.instance = undefined;
|
|
512
|
+
this.emit('restart');
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
this.log.info('Cleanup completed. Shutting down...');
|
|
516
|
+
Matterbridge.instance = undefined;
|
|
517
|
+
this.emit('shutdown');
|
|
518
|
+
}
|
|
426
519
|
}, 2 * 1000);
|
|
520
|
+
cleanupTimeout2.unref();
|
|
427
521
|
}, 3 * 1000);
|
|
522
|
+
cleanupTimeout1.unref();
|
|
428
523
|
}
|
|
429
524
|
}
|
|
430
525
|
/**
|
|
@@ -448,29 +543,33 @@ export class Matterbridge {
|
|
|
448
543
|
* @returns A Promise that resolves when the device is added successfully.
|
|
449
544
|
*/
|
|
450
545
|
async addDevice(pluginName, device) {
|
|
451
|
-
this.
|
|
546
|
+
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
547
|
+
this.log.error(`Adding device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this.log.debug(`Adding device ${dev}${device.name}-${device.deviceName}${db} for plugin ${plg}${pluginName}${db}`);
|
|
452
551
|
// Check if the plugin is registered
|
|
453
552
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
454
553
|
if (!plugin) {
|
|
455
|
-
this.log.error(`
|
|
554
|
+
this.log.error(`Error adding device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
456
555
|
return;
|
|
457
556
|
}
|
|
458
557
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
459
558
|
if (this.bridgeMode === 'bridge') {
|
|
460
|
-
this.matterAggregator
|
|
559
|
+
this.matterAggregator?.addBridgedDevice(device);
|
|
461
560
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
462
561
|
if (plugin.registeredDevices !== undefined)
|
|
463
562
|
plugin.registeredDevices++;
|
|
464
563
|
if (plugin.addedDevices !== undefined)
|
|
465
564
|
plugin.addedDevices++;
|
|
466
|
-
this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
565
|
+
this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
467
566
|
}
|
|
468
567
|
// Only register the device in childbridge mode
|
|
469
568
|
if (this.bridgeMode === 'childbridge') {
|
|
470
569
|
this.registeredDevices.push({ plugin: pluginName, device, added: false });
|
|
471
570
|
if (plugin.registeredDevices !== undefined)
|
|
472
571
|
plugin.registeredDevices++;
|
|
473
|
-
this.log.info(`Registered device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
572
|
+
this.log.info(`Registered device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
474
573
|
}
|
|
475
574
|
}
|
|
476
575
|
/**
|
|
@@ -480,29 +579,111 @@ export class Matterbridge {
|
|
|
480
579
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
481
580
|
*/
|
|
482
581
|
async addBridgedDevice(pluginName, device) {
|
|
483
|
-
this.
|
|
582
|
+
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
583
|
+
this.log.error(`Adding bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
this.log.debug(`Adding bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
484
587
|
// Check if the plugin is registered
|
|
485
588
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
486
589
|
if (!plugin) {
|
|
487
|
-
this.log.error(`
|
|
590
|
+
this.log.error(`Error adding bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
488
591
|
return;
|
|
489
592
|
}
|
|
490
593
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
491
594
|
if (this.bridgeMode === 'bridge') {
|
|
492
|
-
this.matterAggregator
|
|
595
|
+
this.matterAggregator?.addBridgedDevice(device);
|
|
493
596
|
this.registeredDevices.push({ plugin: pluginName, device, added: true });
|
|
494
597
|
if (plugin.registeredDevices !== undefined)
|
|
495
598
|
plugin.registeredDevices++;
|
|
496
599
|
if (plugin.addedDevices !== undefined)
|
|
497
600
|
plugin.addedDevices++;
|
|
498
|
-
this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
601
|
+
this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
499
602
|
}
|
|
500
603
|
// Only register the device in childbridge mode
|
|
501
604
|
if (this.bridgeMode === 'childbridge') {
|
|
502
605
|
this.registeredDevices.push({ plugin: pluginName, device, added: false });
|
|
503
606
|
if (plugin.registeredDevices !== undefined)
|
|
504
607
|
plugin.registeredDevices++;
|
|
505
|
-
this.log.info(`Registered bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
608
|
+
this.log.info(`Registered bridged device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
async removeBridgedDevice(pluginName, device) {
|
|
612
|
+
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
613
|
+
this.log.error(`Removing bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
this.log.debug(`Removing bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
617
|
+
// Check if the plugin is registered
|
|
618
|
+
const plugin = this.findPlugin(pluginName);
|
|
619
|
+
if (!plugin) {
|
|
620
|
+
this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (this.bridgeMode === 'childbridge' && !plugin.aggregator) {
|
|
624
|
+
this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} aggregator not found`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (this.bridgeMode === 'childbridge' && !plugin.connected) {
|
|
628
|
+
this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not connected`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
632
|
+
if (this.bridgeMode === 'bridge') {
|
|
633
|
+
this.matterAggregator.removeBridgedDevice(device);
|
|
634
|
+
this.registeredDevices.forEach((registeredDevice, index) => {
|
|
635
|
+
if (registeredDevice.device === device) {
|
|
636
|
+
this.registeredDevices.splice(index, 1);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
if (plugin.registeredDevices !== undefined)
|
|
641
|
+
plugin.registeredDevices--;
|
|
642
|
+
if (plugin.addedDevices !== undefined)
|
|
643
|
+
plugin.addedDevices--;
|
|
644
|
+
this.log.info(`Rmoved bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
645
|
+
}
|
|
646
|
+
// Only register the device in childbridge mode
|
|
647
|
+
if (this.bridgeMode === 'childbridge') {
|
|
648
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
649
|
+
this.log.warn(`Removing bridged device ${dev}${device.name}-${device.deviceName}${wr} for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
|
|
650
|
+
}
|
|
651
|
+
else if (plugin.type === 'DynamicPlatform') {
|
|
652
|
+
this.registeredDevices.forEach((registeredDevice, index) => {
|
|
653
|
+
if (registeredDevice.device === device) {
|
|
654
|
+
this.registeredDevices.splice(index, 1);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
plugin.aggregator.removeBridgedDevice(device);
|
|
659
|
+
}
|
|
660
|
+
if (plugin.registeredDevices !== undefined)
|
|
661
|
+
plugin.registeredDevices--;
|
|
662
|
+
if (plugin.addedDevices !== undefined)
|
|
663
|
+
plugin.addedDevices--;
|
|
664
|
+
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
669
|
+
*
|
|
670
|
+
* @param pluginName - The name of the plugin.
|
|
671
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
672
|
+
*/
|
|
673
|
+
async removeAllBridgedDevices(pluginName) {
|
|
674
|
+
const plugin = this.findPlugin(pluginName);
|
|
675
|
+
if (this.bridgeMode === 'childbridge' && plugin?.type === 'AccessoryPlatform') {
|
|
676
|
+
this.log.warn(`Removing devices for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const devicesToRemove = [];
|
|
680
|
+
for (const registeredDevice of this.registeredDevices) {
|
|
681
|
+
if (registeredDevice.plugin === pluginName) {
|
|
682
|
+
devicesToRemove.push(registeredDevice);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for (const registeredDevice of devicesToRemove) {
|
|
686
|
+
this.removeBridgedDevice(pluginName, registeredDevice.device);
|
|
506
687
|
}
|
|
507
688
|
}
|
|
508
689
|
/**
|
|
@@ -512,18 +693,22 @@ export class Matterbridge {
|
|
|
512
693
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
513
694
|
*/
|
|
514
695
|
async startStorage(storageType, storageName) {
|
|
515
|
-
if (!storageName.endsWith('.json')) {
|
|
516
|
-
storageName += '.json';
|
|
517
|
-
}
|
|
518
696
|
this.log.debug(`Starting storage ${storageType} ${storageName}`);
|
|
519
697
|
if (storageType === 'disk') {
|
|
520
698
|
const storageDisk = new StorageBackendDisk(storageName);
|
|
521
699
|
this.storageManager = new StorageManager(storageDisk);
|
|
522
700
|
}
|
|
523
|
-
if (storageType === 'json') {
|
|
701
|
+
else if (storageType === 'json') {
|
|
702
|
+
if (!storageName.endsWith('.json'))
|
|
703
|
+
storageName += '.json';
|
|
524
704
|
const storageJson = new StorageBackendJsonFile(storageName);
|
|
525
705
|
this.storageManager = new StorageManager(storageJson);
|
|
526
706
|
}
|
|
707
|
+
else {
|
|
708
|
+
this.log.error(`Unsupported storage type ${storageType}`);
|
|
709
|
+
await this.cleanup('Unsupported storage type');
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
527
712
|
try {
|
|
528
713
|
await this.storageManager.initialize();
|
|
529
714
|
this.log.debug('Storage initialized');
|
|
@@ -533,7 +718,7 @@ export class Matterbridge {
|
|
|
533
718
|
}
|
|
534
719
|
catch (error) {
|
|
535
720
|
this.log.error('Storage initialize() error!');
|
|
536
|
-
|
|
721
|
+
await this.cleanup('Storage initialize() error!');
|
|
537
722
|
}
|
|
538
723
|
}
|
|
539
724
|
/**
|
|
@@ -568,8 +753,11 @@ export class Matterbridge {
|
|
|
568
753
|
*/
|
|
569
754
|
async stopStorage() {
|
|
570
755
|
this.log.debug('Stopping storage');
|
|
571
|
-
await this.storageManager
|
|
756
|
+
await this.storageManager?.close();
|
|
572
757
|
this.log.debug('Storage closed');
|
|
758
|
+
this.storageManager = undefined;
|
|
759
|
+
this.matterbridgeContext = undefined;
|
|
760
|
+
this.mattercontrollerContext = undefined;
|
|
573
761
|
}
|
|
574
762
|
async testStartMatterBridge() {
|
|
575
763
|
/*
|
|
@@ -734,13 +922,27 @@ export class Matterbridge {
|
|
|
734
922
|
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
735
923
|
*/
|
|
736
924
|
async startMatterBridge() {
|
|
925
|
+
if (!this.storageManager) {
|
|
926
|
+
this.log.error('No storage manager initialized');
|
|
927
|
+
await this.cleanup('No storage manager initialized');
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
737
930
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
738
931
|
this.createMatterServer(this.storageManager);
|
|
932
|
+
if (!this.matterServer) {
|
|
933
|
+
this.log.error('No matter server initialized');
|
|
934
|
+
await this.cleanup('No matter server initialized');
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
739
937
|
if (this.bridgeMode === 'bridge') {
|
|
740
938
|
// 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
|
|
939
|
+
// Plugins are started and configured by callback when Matterbridge is commissioned
|
|
742
940
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
743
941
|
this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
942
|
+
if (!this.matterbridgeContext) {
|
|
943
|
+
this.log.error(`Error creating storage context${er}`);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
744
946
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
745
947
|
this.commissioningServer = this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
746
948
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -754,17 +956,22 @@ export class Matterbridge {
|
|
|
754
956
|
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
755
957
|
}
|
|
756
958
|
if (this.bridgeMode === 'childbridge') {
|
|
757
|
-
// Plugins are loaded and started by loadPlugin on startup
|
|
959
|
+
// Plugins are loaded and started by loadPlugin on startup
|
|
758
960
|
// addDevice and addBridgedDeevice just register the devices that are added here to the plugin commissioning server for Accessory Platform
|
|
759
961
|
// or to the plugin aggregator for Dynamic Platform after the commissioning is done
|
|
760
962
|
// Plugins are configured by callback when the plugin is commissioned
|
|
761
|
-
this.registeredPlugins.forEach(
|
|
963
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
762
964
|
if (!plugin.enabled)
|
|
763
965
|
return;
|
|
764
966
|
// Start the interval to check if the plugins is started
|
|
765
967
|
// TODO set a counter or a timeout
|
|
766
968
|
this.log.debug(`*Starting startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
767
969
|
const startInterval = setInterval(async () => {
|
|
970
|
+
if (!this.matterServer) {
|
|
971
|
+
this.log.error('No matter server initialized');
|
|
972
|
+
await this.cleanup('No matter server initialized');
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
768
975
|
if (!plugin.loaded || !plugin.started) {
|
|
769
976
|
this.log.info(`**Waiting in startMatterBridge interval for plugin ${plg}${plugin.name}${db} loaded: ${plugin.loaded} started: ${plugin.started}...`);
|
|
770
977
|
return;
|
|
@@ -774,7 +981,7 @@ export class Matterbridge {
|
|
|
774
981
|
.filter((registeredDevice) => registeredDevice.plugin === plugin.name)
|
|
775
982
|
.forEach((registeredDevice) => {
|
|
776
983
|
if (!plugin.storageContext)
|
|
777
|
-
plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
|
|
984
|
+
plugin.storageContext = this.importCommissioningServerContext(plugin.name, registeredDevice.device);
|
|
778
985
|
if (!plugin.commissioningServer)
|
|
779
986
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
780
987
|
this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -784,9 +991,7 @@ export class Matterbridge {
|
|
|
784
991
|
await this.matterServer.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
785
992
|
}
|
|
786
993
|
if (plugin.type === 'DynamicPlatform') {
|
|
787
|
-
plugin.storageContext = this.createCommissioningServerContext(
|
|
788
|
-
// Generate serialNumber and uniqueId
|
|
789
|
-
plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
|
|
994
|
+
plugin.storageContext = this.createCommissioningServerContext(plugin.name, 'Matterbridge Dynamic Platform', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Dynamic Platform');
|
|
790
995
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
791
996
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
792
997
|
plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
|
|
@@ -840,6 +1045,11 @@ export class Matterbridge {
|
|
|
840
1045
|
}
|
|
841
1046
|
}
|
|
842
1047
|
async startMatterServer() {
|
|
1048
|
+
if (!this.matterServer) {
|
|
1049
|
+
this.log.error('No matter server initialized');
|
|
1050
|
+
await this.cleanup('No matter server initialized');
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
843
1053
|
this.log.debug('Starting matter server');
|
|
844
1054
|
await this.matterServer.start();
|
|
845
1055
|
this.log.debug('Started matter server');
|
|
@@ -879,6 +1089,10 @@ export class Matterbridge {
|
|
|
879
1089
|
* @returns The storage context for the commissioning server.
|
|
880
1090
|
*/
|
|
881
1091
|
createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) {
|
|
1092
|
+
if (!this.storageManager) {
|
|
1093
|
+
this.log.error('No storage manager initialized');
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
}
|
|
882
1096
|
this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
883
1097
|
const random = 'CS' + CryptoNode.getRandomData(8).toHex();
|
|
884
1098
|
const storageContext = this.storageManager.createContext(pluginName);
|
|
@@ -1044,7 +1258,7 @@ export class Matterbridge {
|
|
|
1044
1258
|
if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
|
|
1045
1259
|
for (const registeredDevice of this.registeredDevices) {
|
|
1046
1260
|
if (registeredDevice.plugin === name) {
|
|
1047
|
-
this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1261
|
+
this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1048
1262
|
if (!plugin.aggregator) {
|
|
1049
1263
|
this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
|
|
1050
1264
|
continue;
|
|
@@ -1052,7 +1266,9 @@ export class Matterbridge {
|
|
|
1052
1266
|
plugin.aggregator.addBridgedDevice(registeredDevice.device);
|
|
1053
1267
|
if (plugin.addedDevices !== undefined)
|
|
1054
1268
|
plugin.addedDevices++;
|
|
1055
|
-
this.log.info(
|
|
1269
|
+
this.log.info(
|
|
1270
|
+
// eslint-disable-next-line max-len
|
|
1271
|
+
`Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} for plugin ${plg}${plugin.name}${nf}`);
|
|
1056
1272
|
registeredDevice.added = true;
|
|
1057
1273
|
}
|
|
1058
1274
|
}
|
|
@@ -1088,6 +1304,7 @@ export class Matterbridge {
|
|
|
1088
1304
|
createMatterServer(storageManager) {
|
|
1089
1305
|
this.log.debug('Creating matter server');
|
|
1090
1306
|
this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
|
|
1307
|
+
this.log.debug('Created matter server');
|
|
1091
1308
|
}
|
|
1092
1309
|
/**
|
|
1093
1310
|
* Creates a Matter Aggregator.
|
|
@@ -1135,6 +1352,43 @@ export class Matterbridge {
|
|
|
1135
1352
|
this.log.debug('Stopping matter server');
|
|
1136
1353
|
await this.matterServer?.close();
|
|
1137
1354
|
this.log.debug('Matter server closed');
|
|
1355
|
+
this.commissioningController = undefined;
|
|
1356
|
+
this.commissioningServer = undefined;
|
|
1357
|
+
this.matterAggregator = undefined;
|
|
1358
|
+
this.matterServer = undefined;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
1362
|
+
* @param packageName - The name of the package.
|
|
1363
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
1364
|
+
*/
|
|
1365
|
+
async getLatestVersion(packageName) {
|
|
1366
|
+
return new Promise((resolve, reject) => {
|
|
1367
|
+
exec(`npm view ${packageName} version`, (error, stdout) => {
|
|
1368
|
+
if (error) {
|
|
1369
|
+
reject(error);
|
|
1370
|
+
}
|
|
1371
|
+
else {
|
|
1372
|
+
resolve(stdout.trim());
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
1379
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
1380
|
+
*/
|
|
1381
|
+
async getGlobalNodeModules() {
|
|
1382
|
+
return new Promise((resolve, reject) => {
|
|
1383
|
+
exec('npm root -g', (error, stdout) => {
|
|
1384
|
+
if (error) {
|
|
1385
|
+
reject(error);
|
|
1386
|
+
}
|
|
1387
|
+
else {
|
|
1388
|
+
resolve(stdout.trim());
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1138
1392
|
}
|
|
1139
1393
|
/**
|
|
1140
1394
|
* Logs the node and system information.
|
|
@@ -1196,7 +1450,7 @@ export class Matterbridge {
|
|
|
1196
1450
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
1197
1451
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1198
1452
|
// Global node_modules directory
|
|
1199
|
-
this.globalModulesDir =
|
|
1453
|
+
this.globalModulesDir = await this.getGlobalNodeModules();
|
|
1200
1454
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDir}`);
|
|
1201
1455
|
// Create the data directory .matterbridge in the home directory
|
|
1202
1456
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
@@ -1204,13 +1458,55 @@ export class Matterbridge {
|
|
|
1204
1458
|
await fs.access(this.matterbridgeDirectory);
|
|
1205
1459
|
}
|
|
1206
1460
|
catch (err) {
|
|
1207
|
-
|
|
1461
|
+
if (err instanceof Error) {
|
|
1462
|
+
const nodeErr = err;
|
|
1463
|
+
if (nodeErr.code === 'ENOENT') {
|
|
1464
|
+
try {
|
|
1465
|
+
await fs.mkdir(this.matterbridgeDirectory, { recursive: true });
|
|
1466
|
+
this.log.info(`Created Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1467
|
+
}
|
|
1468
|
+
catch (err) {
|
|
1469
|
+
this.log.error(`Error creating directory: ${err}`);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
this.log.error(`Error accessing directory: ${err}`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1208
1476
|
}
|
|
1209
1477
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1478
|
+
// Create the data directory .matterbridge in the home directory
|
|
1479
|
+
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
1480
|
+
try {
|
|
1481
|
+
await fs.access(this.matterbridgePluginDirectory);
|
|
1482
|
+
}
|
|
1483
|
+
catch (err) {
|
|
1484
|
+
if (err instanceof Error) {
|
|
1485
|
+
const nodeErr = err;
|
|
1486
|
+
if (nodeErr.code === 'ENOENT') {
|
|
1487
|
+
try {
|
|
1488
|
+
await fs.mkdir(this.matterbridgePluginDirectory, { recursive: true });
|
|
1489
|
+
this.log.info(`Created Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1490
|
+
}
|
|
1491
|
+
catch (err) {
|
|
1492
|
+
this.log.error(`Error creating directory: ${err}`);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
else {
|
|
1496
|
+
this.log.error(`Error accessing directory: ${err}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1210
1501
|
// Matterbridge version
|
|
1211
1502
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1212
1503
|
this.matterbridgeVersion = packageJson.version;
|
|
1213
1504
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1505
|
+
this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
|
|
1506
|
+
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1507
|
+
if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
|
|
1508
|
+
this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
|
|
1509
|
+
}
|
|
1214
1510
|
// Current working directory
|
|
1215
1511
|
const currentDir = process.cwd();
|
|
1216
1512
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
@@ -1262,14 +1558,16 @@ export class Matterbridge {
|
|
|
1262
1558
|
*/
|
|
1263
1559
|
async initializeFrontend(port = 3000) {
|
|
1264
1560
|
this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
1265
|
-
this.
|
|
1561
|
+
this.expressApp = express();
|
|
1266
1562
|
// Serve React build directory
|
|
1267
|
-
this.
|
|
1563
|
+
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
1268
1564
|
// Endpoint to provide QR pairing code
|
|
1269
|
-
this.
|
|
1565
|
+
this.expressApp.get('/api/qr-code', (req, res) => {
|
|
1270
1566
|
this.log.debug('The frontend sent /api/qr-code');
|
|
1271
|
-
if (!this.matterbridgeContext)
|
|
1272
|
-
|
|
1567
|
+
if (!this.matterbridgeContext) {
|
|
1568
|
+
res.json([]);
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1273
1571
|
try {
|
|
1274
1572
|
const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
|
|
1275
1573
|
res.json(qrData);
|
|
@@ -1281,17 +1579,17 @@ export class Matterbridge {
|
|
|
1281
1579
|
}
|
|
1282
1580
|
});
|
|
1283
1581
|
// Endpoint to provide system information
|
|
1284
|
-
this.
|
|
1582
|
+
this.expressApp.get('/api/system-info', (req, res) => {
|
|
1285
1583
|
this.log.debug('The frontend sent /api/system-info');
|
|
1286
1584
|
res.json(this.systemInformation);
|
|
1287
1585
|
});
|
|
1288
1586
|
// Endpoint to provide plugins
|
|
1289
|
-
this.
|
|
1587
|
+
this.expressApp.get('/api/plugins', (req, res) => {
|
|
1290
1588
|
this.log.debug('The frontend sent /api/plugins');
|
|
1291
1589
|
res.json(this.getBaseRegisteredPlugins());
|
|
1292
1590
|
});
|
|
1293
1591
|
// Endpoint to provide devices
|
|
1294
|
-
this.
|
|
1592
|
+
this.expressApp.get('/api/devices', (req, res) => {
|
|
1295
1593
|
this.log.debug('The frontend sent /api/devices');
|
|
1296
1594
|
const data = [];
|
|
1297
1595
|
this.registeredDevices.forEach((registeredDevice) => {
|
|
@@ -1318,7 +1616,7 @@ export class Matterbridge {
|
|
|
1318
1616
|
res.json(data);
|
|
1319
1617
|
});
|
|
1320
1618
|
// Endpoint to provide the cluster servers of the devices
|
|
1321
|
-
this.
|
|
1619
|
+
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
1322
1620
|
const selectedPluginName = req.params.selectedPluginName;
|
|
1323
1621
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
1324
1622
|
this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
|
|
@@ -1360,12 +1658,83 @@ export class Matterbridge {
|
|
|
1360
1658
|
});
|
|
1361
1659
|
res.json(data);
|
|
1362
1660
|
});
|
|
1661
|
+
// Endpoint to receive commands
|
|
1662
|
+
this.expressApp.post('/api/command/:command/:param', async (req, res) => {
|
|
1663
|
+
const command = req.params.command;
|
|
1664
|
+
const param = req.params.param;
|
|
1665
|
+
this.log.debug(`The frontend sent /api/command/${command}/${param}`);
|
|
1666
|
+
if (!command) {
|
|
1667
|
+
res.status(400).json({ error: 'No command provided' });
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
this.log.info(`***Received command: ${command}:${param}`);
|
|
1671
|
+
// Handle the command debugLevel from Settings
|
|
1672
|
+
if (command === 'setloglevel') {
|
|
1673
|
+
if (param === 'Debug') {
|
|
1674
|
+
this.log.setLogDebug(true);
|
|
1675
|
+
this.debugEnabled = true;
|
|
1676
|
+
Logger.defaultLogLevel = Level.DEBUG;
|
|
1677
|
+
}
|
|
1678
|
+
else if (param === 'Info') {
|
|
1679
|
+
this.log.setLogDebug(false);
|
|
1680
|
+
this.debugEnabled = false;
|
|
1681
|
+
Logger.defaultLogLevel = Level.INFO;
|
|
1682
|
+
}
|
|
1683
|
+
else if (param === 'Warn') {
|
|
1684
|
+
this.log.setLogDebug(false);
|
|
1685
|
+
this.debugEnabled = false;
|
|
1686
|
+
Logger.defaultLogLevel = Level.WARN;
|
|
1687
|
+
}
|
|
1688
|
+
this.registeredPlugins.forEach((plugin) => {
|
|
1689
|
+
plugin.platform?.log.setLogDebug(this.debugEnabled);
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
// Handle the command debugLevel from Header
|
|
1693
|
+
if (command === 'restart') {
|
|
1694
|
+
this.restartProcess();
|
|
1695
|
+
}
|
|
1696
|
+
// Handle the command update from Header
|
|
1697
|
+
if (command === 'update') {
|
|
1698
|
+
this.log.warn(`The /api/command/${command} is not yet implemented`);
|
|
1699
|
+
}
|
|
1700
|
+
// Handle the command addplugin from Header
|
|
1701
|
+
if (command === 'addplugin') {
|
|
1702
|
+
this.log.warn(`The /api/command/${command}/${param} is not yet implemented`);
|
|
1703
|
+
}
|
|
1704
|
+
// Handle the command enableplugin from Home
|
|
1705
|
+
if (command === 'enableplugin') {
|
|
1706
|
+
const plugin = this.findPlugin(param);
|
|
1707
|
+
if (plugin) {
|
|
1708
|
+
plugin.enabled = true;
|
|
1709
|
+
plugin.loaded = undefined;
|
|
1710
|
+
plugin.started = undefined;
|
|
1711
|
+
plugin.configured = undefined;
|
|
1712
|
+
plugin.connected = undefined;
|
|
1713
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
1714
|
+
this.log.info(`Enabled plugin ${plg}${param}${nf}`);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
// Handle the command disableplugin from Home
|
|
1718
|
+
if (command === 'disableplugin') {
|
|
1719
|
+
const plugin = this.findPlugin(param);
|
|
1720
|
+
if (plugin) {
|
|
1721
|
+
plugin.enabled = false;
|
|
1722
|
+
plugin.loaded = undefined;
|
|
1723
|
+
plugin.started = undefined;
|
|
1724
|
+
plugin.configured = undefined;
|
|
1725
|
+
plugin.connected = undefined;
|
|
1726
|
+
await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
|
|
1727
|
+
this.log.info(`Disabled plugin ${plg}${param}${nf}`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
res.json({ message: 'Command received' });
|
|
1731
|
+
});
|
|
1363
1732
|
// Fallback for routing
|
|
1364
|
-
this.
|
|
1733
|
+
this.expressApp.get('*', (req, res) => {
|
|
1365
1734
|
this.log.warn('The frontend sent *', req.url);
|
|
1366
1735
|
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
1367
1736
|
});
|
|
1368
|
-
this.
|
|
1737
|
+
this.expressServer = this.expressApp.listen(port, () => {
|
|
1369
1738
|
this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
|
|
1370
1739
|
});
|
|
1371
1740
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
@@ -1413,6 +1782,23 @@ export class Matterbridge {
|
|
|
1413
1782
|
}
|
|
1414
1783
|
/*
|
|
1415
1784
|
TO IMPLEMENT
|
|
1785
|
+
|
|
1786
|
+
import { spawn } from 'child_process';
|
|
1787
|
+
|
|
1788
|
+
function restartProcess() {
|
|
1789
|
+
// Spawn a new process
|
|
1790
|
+
const newProcess = spawn(process.argv[0], process.argv.slice(1), {
|
|
1791
|
+
detached: true,
|
|
1792
|
+
stdio: 'inherit',
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
// Unreference the new process so that the current process can exit
|
|
1796
|
+
newProcess.unref();
|
|
1797
|
+
|
|
1798
|
+
// Exit the current process
|
|
1799
|
+
process.exit();
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1416
1802
|
import * as WebSocket from 'ws';
|
|
1417
1803
|
const globalModulesDir = require('global-modules');
|
|
1418
1804
|
|