matterbridge 1.1.9 → 1.1.11
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 +17 -0
- package/README.md +20 -4
- package/dist/cli.js +20 -4
- package/dist/cli.js.map +1 -1
- package/dist/matterbridge.d.ts +35 -2
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +293 -51
- 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 -2
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +80 -20
- 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.70102d98.css +2 -0
- package/frontend/build/static/css/main.70102d98.css.map +1 -0
- package/frontend/build/static/js/main.5d39b100.js +3 -0
- package/frontend/build/static/js/main.5d39b100.js.map +1 -0
- package/package.json +9 -6
- package/frontend/build/static/css/main.ce1ee9e7.css +0 -2
- package/frontend/build/static/css/main.ce1ee9e7.css.map +0 -1
- package/frontend/build/static/js/main.cc840fb3.js +0 -3
- package/frontend/build/static/js/main.cc840fb3.js.map +0 -1
- /package/frontend/build/static/js/{main.cc840fb3.js.LICENSE.txt → main.5d39b100.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -25,11 +25,12 @@ 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,
|
|
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';
|
|
32
|
-
import { CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
|
|
33
|
+
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
|
|
33
34
|
import { BasicInformationCluster, BridgedDeviceBasicInformationCluster, ClusterServer } from '@project-chip/matter-node.js/cluster';
|
|
34
35
|
import { DeviceTypeId, VendorId } from '@project-chip/matter-node.js/datatype';
|
|
35
36
|
import { Aggregator, DeviceTypes } from '@project-chip/matter-node.js/device';
|
|
@@ -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: '',
|
|
@@ -58,13 +59,24 @@ export class Matterbridge {
|
|
|
58
59
|
freeMemory: '',
|
|
59
60
|
systemUptime: '',
|
|
60
61
|
};
|
|
62
|
+
matterbridgeInformation = {
|
|
63
|
+
homeDirectory: '',
|
|
64
|
+
rootDirectory: '',
|
|
65
|
+
matterbridgeDirectory: '',
|
|
66
|
+
matterbridgePluginDirectory: '',
|
|
67
|
+
globalModulesDirectory: '',
|
|
68
|
+
matterbridgeVersion: '',
|
|
69
|
+
matterbridgeLatestVersion: '',
|
|
70
|
+
bridgeMode: '',
|
|
71
|
+
debugEnabled: false,
|
|
72
|
+
};
|
|
61
73
|
homeDirectory = '';
|
|
62
74
|
rootDirectory = '';
|
|
63
75
|
matterbridgeDirectory = '';
|
|
64
76
|
matterbridgePluginDirectory = '';
|
|
77
|
+
globalModulesDirectory = '';
|
|
65
78
|
matterbridgeVersion = '';
|
|
66
79
|
matterbridgeLatestVersion = '';
|
|
67
|
-
globalModulesDir = '';
|
|
68
80
|
bridgeMode = '';
|
|
69
81
|
debugEnabled = false;
|
|
70
82
|
log;
|
|
@@ -84,6 +96,7 @@ export class Matterbridge {
|
|
|
84
96
|
commissioningController;
|
|
85
97
|
static instance;
|
|
86
98
|
constructor() {
|
|
99
|
+
super();
|
|
87
100
|
// We load asyncronously
|
|
88
101
|
}
|
|
89
102
|
/**
|
|
@@ -116,6 +129,10 @@ export class Matterbridge {
|
|
|
116
129
|
* @returns A Promise that resolves when the initialization is complete.
|
|
117
130
|
*/
|
|
118
131
|
async initialize() {
|
|
132
|
+
/*
|
|
133
|
+
const wtf = await import('wtfnode');
|
|
134
|
+
wtf.dump();
|
|
135
|
+
*/
|
|
119
136
|
// Display the help
|
|
120
137
|
if (hasParameter('help')) {
|
|
121
138
|
// eslint-disable-next-line no-console
|
|
@@ -173,6 +190,43 @@ export class Matterbridge {
|
|
|
173
190
|
// Parse command line
|
|
174
191
|
this.parseCommandLine();
|
|
175
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Spawns a child process with the given command and arguments.
|
|
195
|
+
* @param command - The command to execute.
|
|
196
|
+
* @param args - The arguments to pass to the command (default: []).
|
|
197
|
+
* @returns A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
198
|
+
*/
|
|
199
|
+
async spawnCommand(command, args = []) {
|
|
200
|
+
/*
|
|
201
|
+
npm > npm.cmd on windows
|
|
202
|
+
*/
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const childProcess = spawn(command, args, {
|
|
205
|
+
stdio: 'inherit',
|
|
206
|
+
});
|
|
207
|
+
childProcess.on('error', (err) => {
|
|
208
|
+
this.log.error(`Failed to start child process: ${err.message}`);
|
|
209
|
+
reject(err); // Reject the promise on error
|
|
210
|
+
});
|
|
211
|
+
childProcess.on('close', (code) => {
|
|
212
|
+
if (code === 0) {
|
|
213
|
+
this.log.info(`Child process stdio streams have closed with code ${code}`);
|
|
214
|
+
resolve();
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.log.error(`Child process stdio streams have closed with code ${code}`);
|
|
218
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// The 'exit' event might be redundant here since 'close' is also being handled
|
|
222
|
+
childProcess.on('exit', (code, signal) => {
|
|
223
|
+
this.log.info(`Child process exited with code ${code} and signal ${signal}`);
|
|
224
|
+
});
|
|
225
|
+
childProcess.on('disconnect', () => {
|
|
226
|
+
this.log.info('Child process has been disconnected from the parent');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
176
230
|
/**
|
|
177
231
|
* Parses the command line arguments and performs the corresponding actions.
|
|
178
232
|
* @private
|
|
@@ -191,26 +245,31 @@ export class Matterbridge {
|
|
|
191
245
|
this.log.info(`│ └─ ${db}${plugin.path}${db}`);
|
|
192
246
|
}
|
|
193
247
|
});
|
|
248
|
+
this.emit('shutdown');
|
|
194
249
|
process.exit(0);
|
|
195
250
|
}
|
|
196
251
|
if (getParameter('add')) {
|
|
197
252
|
this.log.debug(`Registering plugin ${getParameter('add')}`);
|
|
198
253
|
await this.executeCommandLine(getParameter('add'), 'add');
|
|
254
|
+
this.emit('shutdown');
|
|
199
255
|
process.exit(0);
|
|
200
256
|
}
|
|
201
257
|
if (getParameter('remove')) {
|
|
202
258
|
this.log.debug(`Unregistering plugin ${getParameter('remove')}`);
|
|
203
259
|
await this.executeCommandLine(getParameter('remove'), 'remove');
|
|
260
|
+
this.emit('shutdown');
|
|
204
261
|
process.exit(0);
|
|
205
262
|
}
|
|
206
263
|
if (getParameter('enable')) {
|
|
207
264
|
this.log.debug(`Enable plugin ${getParameter('enable')}`);
|
|
208
265
|
await this.executeCommandLine(getParameter('enable'), 'enable');
|
|
266
|
+
this.emit('shutdown');
|
|
209
267
|
process.exit(0);
|
|
210
268
|
}
|
|
211
269
|
if (getParameter('disable')) {
|
|
212
270
|
this.log.debug(`Disable plugin ${getParameter('disable')}`);
|
|
213
271
|
await this.executeCommandLine(getParameter('disable'), 'disable');
|
|
272
|
+
this.emit('shutdown');
|
|
214
273
|
process.exit(0);
|
|
215
274
|
}
|
|
216
275
|
// Start the storage (we need it now for frontend and later for matterbridge)
|
|
@@ -224,12 +283,20 @@ export class Matterbridge {
|
|
|
224
283
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
225
284
|
await this.testStartMatterBridge(); // No await do it asyncronously
|
|
226
285
|
}
|
|
286
|
+
if (hasParameter('controller')) {
|
|
287
|
+
this.bridgeMode = 'controller';
|
|
288
|
+
this.log.info('Creating mattercontrollerContext: mattercontrollerContext');
|
|
289
|
+
this.mattercontrollerContext = this.storageManager?.createContext('mattercontrollerContext');
|
|
290
|
+
await this.startMatterBridge();
|
|
291
|
+
}
|
|
227
292
|
if (hasParameter('bridge')) {
|
|
228
293
|
this.bridgeMode = 'bridge';
|
|
229
294
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
230
295
|
for (const plugin of this.registeredPlugins) {
|
|
231
|
-
if (!plugin.enabled)
|
|
296
|
+
if (!plugin.enabled) {
|
|
297
|
+
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
232
298
|
continue;
|
|
299
|
+
}
|
|
233
300
|
plugin.loaded = false;
|
|
234
301
|
plugin.started = false;
|
|
235
302
|
plugin.configured = false;
|
|
@@ -244,8 +311,10 @@ export class Matterbridge {
|
|
|
244
311
|
this.bridgeMode = 'childbridge';
|
|
245
312
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
246
313
|
for (const plugin of this.registeredPlugins) {
|
|
247
|
-
if (!plugin.enabled)
|
|
314
|
+
if (!plugin.enabled) {
|
|
315
|
+
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
248
316
|
continue;
|
|
317
|
+
}
|
|
249
318
|
plugin.loaded = false;
|
|
250
319
|
plugin.started = false;
|
|
251
320
|
plugin.configured = false;
|
|
@@ -274,8 +343,8 @@ export class Matterbridge {
|
|
|
274
343
|
}
|
|
275
344
|
if (!packageJsonExists) {
|
|
276
345
|
this.log.debug(`Package.json not found at ${packageJsonPath}`);
|
|
277
|
-
this.log.debug(`Trying at ${this.
|
|
278
|
-
packageJsonPath = path.join(this.
|
|
346
|
+
this.log.debug(`Trying at ${this.globalModulesDirectory}`);
|
|
347
|
+
packageJsonPath = path.join(this.globalModulesDirectory, pluginPath);
|
|
279
348
|
//this.log.debug(`Got ${packageJsonPath}`);
|
|
280
349
|
}
|
|
281
350
|
try {
|
|
@@ -384,7 +453,7 @@ export class Matterbridge {
|
|
|
384
453
|
async restartProcess() {
|
|
385
454
|
//this.log.info('Restarting still not implemented');
|
|
386
455
|
//return;
|
|
387
|
-
await this.cleanup('
|
|
456
|
+
await this.cleanup('restarting...', true);
|
|
388
457
|
this.hasCleanupStarted = false;
|
|
389
458
|
}
|
|
390
459
|
/**
|
|
@@ -397,7 +466,7 @@ export class Matterbridge {
|
|
|
397
466
|
this.log.info(message);
|
|
398
467
|
process.removeAllListeners('SIGINT');
|
|
399
468
|
process.removeAllListeners('SIGTERM');
|
|
400
|
-
//
|
|
469
|
+
// Calling the shutdown functions with a reason
|
|
401
470
|
for (const plugin of this.registeredPlugins) {
|
|
402
471
|
if (plugin.platform)
|
|
403
472
|
await plugin.platform.onShutdown('Matterbridge is closing: ' + message);
|
|
@@ -429,23 +498,26 @@ export class Matterbridge {
|
|
|
429
498
|
this.expressApp.removeAllListeners();
|
|
430
499
|
this.expressApp = undefined;
|
|
431
500
|
}
|
|
432
|
-
setTimeout(async () => {
|
|
501
|
+
const cleanupTimeout1 = setTimeout(async () => {
|
|
433
502
|
// Closing matter
|
|
434
503
|
await this.stopMatter();
|
|
435
504
|
// Closing storage
|
|
436
505
|
await this.stopStorage();
|
|
437
506
|
// Serialize registeredDevices
|
|
438
|
-
if (this.nodeContext) {
|
|
507
|
+
if (this.nodeStorage && this.nodeContext) {
|
|
439
508
|
this.log.info('Saving registered devices...');
|
|
440
509
|
const serializedRegisteredDevices = [];
|
|
441
510
|
this.registeredDevices.forEach((registeredDevice) => {
|
|
442
511
|
serializedRegisteredDevices.push(registeredDevice.device.serialize(registeredDevice.plugin));
|
|
443
512
|
});
|
|
444
|
-
//console.log('serializedRegisteredDevices:', serializedRegisteredDevices);
|
|
445
513
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
446
514
|
this.log.info('Saved registered devices');
|
|
447
515
|
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
516
|
+
this.log.debug('Closing node storage context...');
|
|
517
|
+
this.nodeContext.close();
|
|
448
518
|
this.nodeContext = undefined;
|
|
519
|
+
this.log.debug('Closing node storage manager...');
|
|
520
|
+
this.nodeStorage.close();
|
|
449
521
|
this.nodeStorage = undefined;
|
|
450
522
|
}
|
|
451
523
|
else {
|
|
@@ -453,15 +525,22 @@ export class Matterbridge {
|
|
|
453
525
|
}
|
|
454
526
|
this.registeredPlugins = [];
|
|
455
527
|
this.registeredDevices = [];
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
528
|
+
this.log.info('Waiting for matter to deliver last messages...');
|
|
529
|
+
const cleanupTimeout2 = setTimeout(async () => {
|
|
530
|
+
if (restart) {
|
|
531
|
+
this.log.info('Cleanup completed. Restarting...');
|
|
532
|
+
Matterbridge.instance = undefined;
|
|
533
|
+
this.emit('restart');
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
this.log.info('Cleanup completed. Shutting down...');
|
|
537
|
+
Matterbridge.instance = undefined;
|
|
538
|
+
this.emit('shutdown');
|
|
539
|
+
}
|
|
463
540
|
}, 2 * 1000);
|
|
541
|
+
cleanupTimeout2.unref();
|
|
464
542
|
}, 3 * 1000);
|
|
543
|
+
cleanupTimeout1.unref();
|
|
465
544
|
}
|
|
466
545
|
}
|
|
467
546
|
/**
|
|
@@ -486,14 +565,14 @@ export class Matterbridge {
|
|
|
486
565
|
*/
|
|
487
566
|
async addDevice(pluginName, device) {
|
|
488
567
|
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`);
|
|
568
|
+
this.log.error(`Adding device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
490
569
|
return;
|
|
491
570
|
}
|
|
492
|
-
this.log.debug(`Adding device ${dev}${device.name}${db} for plugin ${plg}${pluginName}${db}`);
|
|
571
|
+
this.log.debug(`Adding device ${dev}${device.name}-${device.deviceName}${db} for plugin ${plg}${pluginName}${db}`);
|
|
493
572
|
// Check if the plugin is registered
|
|
494
573
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
495
574
|
if (!plugin) {
|
|
496
|
-
this.log.error(`Error adding device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
575
|
+
this.log.error(`Error adding device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
497
576
|
return;
|
|
498
577
|
}
|
|
499
578
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
@@ -504,14 +583,14 @@ export class Matterbridge {
|
|
|
504
583
|
plugin.registeredDevices++;
|
|
505
584
|
if (plugin.addedDevices !== undefined)
|
|
506
585
|
plugin.addedDevices++;
|
|
507
|
-
this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
586
|
+
this.log.info(`Added and registered device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
508
587
|
}
|
|
509
588
|
// Only register the device in childbridge mode
|
|
510
589
|
if (this.bridgeMode === 'childbridge') {
|
|
511
590
|
this.registeredDevices.push({ plugin: pluginName, device, added: false });
|
|
512
591
|
if (plugin.registeredDevices !== undefined)
|
|
513
592
|
plugin.registeredDevices++;
|
|
514
|
-
this.log.info(`Registered device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
593
|
+
this.log.info(`Registered device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
515
594
|
}
|
|
516
595
|
}
|
|
517
596
|
/**
|
|
@@ -522,14 +601,14 @@ export class Matterbridge {
|
|
|
522
601
|
*/
|
|
523
602
|
async addBridgedDevice(pluginName, device) {
|
|
524
603
|
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`);
|
|
604
|
+
this.log.error(`Adding bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
526
605
|
return;
|
|
527
606
|
}
|
|
528
|
-
this.log.debug(`Adding bridged device ${db}${device.name}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
607
|
+
this.log.debug(`Adding bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
529
608
|
// Check if the plugin is registered
|
|
530
609
|
const plugin = this.registeredPlugins.find((plugin) => plugin.name === pluginName);
|
|
531
610
|
if (!plugin) {
|
|
532
|
-
this.log.error(`Error adding bridged device ${dev}${device.name}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
611
|
+
this.log.error(`Error adding bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
533
612
|
return;
|
|
534
613
|
}
|
|
535
614
|
// Register and add the device to matterbridge aggregator in bridge mode
|
|
@@ -540,14 +619,92 @@ export class Matterbridge {
|
|
|
540
619
|
plugin.registeredDevices++;
|
|
541
620
|
if (plugin.addedDevices !== undefined)
|
|
542
621
|
plugin.addedDevices++;
|
|
543
|
-
this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
622
|
+
this.log.info(`Added and registered bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
544
623
|
}
|
|
545
624
|
// Only register the device in childbridge mode
|
|
546
625
|
if (this.bridgeMode === 'childbridge') {
|
|
547
626
|
this.registeredDevices.push({ plugin: pluginName, device, added: false });
|
|
548
627
|
if (plugin.registeredDevices !== undefined)
|
|
549
628
|
plugin.registeredDevices++;
|
|
550
|
-
this.log.info(`Registered bridged device ${dev}${device.name}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
629
|
+
this.log.info(`Registered bridged device ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
async removeBridgedDevice(pluginName, device) {
|
|
633
|
+
if (this.bridgeMode === 'bridge' && !this.matterAggregator) {
|
|
634
|
+
this.log.error(`Removing bridged device ${dev}${device.name}-${device.deviceName}${er} for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
this.log.debug(`Removing bridged device ${db}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${db}`);
|
|
638
|
+
// Check if the plugin is registered
|
|
639
|
+
const plugin = this.findPlugin(pluginName);
|
|
640
|
+
if (!plugin) {
|
|
641
|
+
this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not found`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (this.bridgeMode === 'childbridge' && !plugin.aggregator) {
|
|
645
|
+
this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} aggregator not found`);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (this.bridgeMode === 'childbridge' && !plugin.connected) {
|
|
649
|
+
this.log.error(`Error removing bridged device ${dev}${device.name}-${device.deviceName}${er} plugin ${plg}${pluginName}${er} not connected`);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
653
|
+
if (this.bridgeMode === 'bridge') {
|
|
654
|
+
this.matterAggregator.removeBridgedDevice(device);
|
|
655
|
+
this.registeredDevices.forEach((registeredDevice, index) => {
|
|
656
|
+
if (registeredDevice.device === device) {
|
|
657
|
+
this.registeredDevices.splice(index, 1);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
if (plugin.registeredDevices !== undefined)
|
|
662
|
+
plugin.registeredDevices--;
|
|
663
|
+
if (plugin.addedDevices !== undefined)
|
|
664
|
+
plugin.addedDevices--;
|
|
665
|
+
this.log.info(`Rmoved bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
666
|
+
}
|
|
667
|
+
// Only register the device in childbridge mode
|
|
668
|
+
if (this.bridgeMode === 'childbridge') {
|
|
669
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
670
|
+
this.log.warn(`Removing bridged device ${dev}${device.name}-${device.deviceName}${wr} for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
|
|
671
|
+
}
|
|
672
|
+
else if (plugin.type === 'DynamicPlatform') {
|
|
673
|
+
this.registeredDevices.forEach((registeredDevice, index) => {
|
|
674
|
+
if (registeredDevice.device === device) {
|
|
675
|
+
this.registeredDevices.splice(index, 1);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
plugin.aggregator.removeBridgedDevice(device);
|
|
680
|
+
}
|
|
681
|
+
if (plugin.registeredDevices !== undefined)
|
|
682
|
+
plugin.registeredDevices--;
|
|
683
|
+
if (plugin.addedDevices !== undefined)
|
|
684
|
+
plugin.addedDevices--;
|
|
685
|
+
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.name}-${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
690
|
+
*
|
|
691
|
+
* @param pluginName - The name of the plugin.
|
|
692
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
693
|
+
*/
|
|
694
|
+
async removeAllBridgedDevices(pluginName) {
|
|
695
|
+
const plugin = this.findPlugin(pluginName);
|
|
696
|
+
if (this.bridgeMode === 'childbridge' && plugin?.type === 'AccessoryPlatform') {
|
|
697
|
+
this.log.warn(`Removing devices for plugin ${plg}${pluginName}${wr} error: AccessoryPlatform not supported in childbridge mode`);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const devicesToRemove = [];
|
|
701
|
+
for (const registeredDevice of this.registeredDevices) {
|
|
702
|
+
if (registeredDevice.plugin === pluginName) {
|
|
703
|
+
devicesToRemove.push(registeredDevice);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
for (const registeredDevice of devicesToRemove) {
|
|
707
|
+
this.removeBridgedDevice(pluginName, registeredDevice.device);
|
|
551
708
|
}
|
|
552
709
|
}
|
|
553
710
|
/**
|
|
@@ -798,13 +955,33 @@ export class Matterbridge {
|
|
|
798
955
|
await this.cleanup('No matter server initialized');
|
|
799
956
|
return;
|
|
800
957
|
}
|
|
958
|
+
if (this.bridgeMode === 'controller') {
|
|
959
|
+
this.log.info('Creating matter commissioning controller');
|
|
960
|
+
this.commissioningController = new CommissioningController({
|
|
961
|
+
autoConnect: false,
|
|
962
|
+
});
|
|
963
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
964
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
965
|
+
this.log.info('Starting matter server');
|
|
966
|
+
await this.matterServer.start();
|
|
967
|
+
this.log.info('Matter server started');
|
|
968
|
+
if (hasParameter('discover')) {
|
|
969
|
+
const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
970
|
+
console.log(discover);
|
|
971
|
+
}
|
|
972
|
+
this.log.info(`Commissioning controller is already commisioned: ${this.commissioningController.isCommissioned()}`);
|
|
973
|
+
const nodes = this.commissioningController.getCommissionedNodes();
|
|
974
|
+
nodes.forEach(async (nodeId) => {
|
|
975
|
+
this.log.warn(`Connecting to commissioned node: ${nodeId}`);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
801
978
|
if (this.bridgeMode === 'bridge') {
|
|
802
979
|
// Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
|
|
803
980
|
// Plugins are started and configured by callback when Matterbridge is commissioned
|
|
804
981
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
805
982
|
this.matterbridgeContext = this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
806
983
|
if (!this.matterbridgeContext) {
|
|
807
|
-
this.log.error(`Error creating storage context${er}`);
|
|
984
|
+
this.log.error(`Error creating storage context for ${plg}Matterbridge${er}`);
|
|
808
985
|
return;
|
|
809
986
|
}
|
|
810
987
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
@@ -815,8 +992,9 @@ export class Matterbridge {
|
|
|
815
992
|
this.commissioningServer.addDevice(this.matterAggregator);
|
|
816
993
|
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
817
994
|
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
818
|
-
this.log.debug('Starting matter server');
|
|
995
|
+
this.log.debug('Starting matter server...');
|
|
819
996
|
await this.startMatterServer();
|
|
997
|
+
this.log.info('Matter server started');
|
|
820
998
|
this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, 'Matterbridge');
|
|
821
999
|
}
|
|
822
1000
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -846,10 +1024,6 @@ export class Matterbridge {
|
|
|
846
1024
|
.forEach((registeredDevice) => {
|
|
847
1025
|
if (!plugin.storageContext)
|
|
848
1026
|
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
|
-
}
|
|
853
1027
|
if (!plugin.commissioningServer)
|
|
854
1028
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
855
1029
|
this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -860,10 +1034,6 @@ export class Matterbridge {
|
|
|
860
1034
|
}
|
|
861
1035
|
if (plugin.type === 'DynamicPlatform') {
|
|
862
1036
|
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
|
-
}
|
|
867
1037
|
plugin.commissioningServer = this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
868
1038
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
869
1039
|
plugin.aggregator = this.createMatterAggregator(plugin.storageContext); // Generate serialNumber and uniqueId
|
|
@@ -890,7 +1060,7 @@ export class Matterbridge {
|
|
|
890
1060
|
});
|
|
891
1061
|
if (!allStarted)
|
|
892
1062
|
return;
|
|
893
|
-
this.log.info('Starting matter server');
|
|
1063
|
+
this.log.info('Starting matter server...');
|
|
894
1064
|
// Setting reachability to true
|
|
895
1065
|
this.registeredPlugins.forEach((plugin) => {
|
|
896
1066
|
if (!plugin.enabled)
|
|
@@ -907,13 +1077,13 @@ export class Matterbridge {
|
|
|
907
1077
|
});
|
|
908
1078
|
});
|
|
909
1079
|
await this.startMatterServer();
|
|
1080
|
+
this.log.info('Matter server started');
|
|
910
1081
|
for (const plugin of this.registeredPlugins) {
|
|
911
1082
|
this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.name);
|
|
912
1083
|
}
|
|
913
1084
|
Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
|
|
914
1085
|
clearInterval(startMatterInterval);
|
|
915
1086
|
}, 1000);
|
|
916
|
-
return;
|
|
917
1087
|
}
|
|
918
1088
|
}
|
|
919
1089
|
async startMatterServer() {
|
|
@@ -979,9 +1149,9 @@ export class Matterbridge {
|
|
|
979
1149
|
storageContext.set('serialNumber', storageContext.get('serialNumber', random));
|
|
980
1150
|
storageContext.set('uniqueId', storageContext.get('uniqueId', random));
|
|
981
1151
|
storageContext.set('softwareVersion', softwareVersion ?? 1);
|
|
982
|
-
storageContext.set('softwareVersionString', softwareVersionString ?? '1.0
|
|
1152
|
+
storageContext.set('softwareVersionString', softwareVersionString ?? '1.0');
|
|
983
1153
|
storageContext.set('hardwareVersion', hardwareVersion ?? 1);
|
|
984
|
-
storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0
|
|
1154
|
+
storageContext.set('hardwareVersionString', hardwareVersionString ?? '1.0');
|
|
985
1155
|
return storageContext;
|
|
986
1156
|
}
|
|
987
1157
|
/**
|
|
@@ -1130,7 +1300,7 @@ export class Matterbridge {
|
|
|
1130
1300
|
if (plugin && plugin.type === 'DynamicPlatform' && plugin.configured !== true) {
|
|
1131
1301
|
for (const registeredDevice of this.registeredDevices) {
|
|
1132
1302
|
if (registeredDevice.plugin === name) {
|
|
1133
|
-
this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1303
|
+
this.log.info(`Adding bridged device ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} to aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1134
1304
|
if (!plugin.aggregator) {
|
|
1135
1305
|
this.log.error(`****Aggregator not found for plugin ${plg}${plugin.name}${er}`);
|
|
1136
1306
|
continue;
|
|
@@ -1138,7 +1308,9 @@ export class Matterbridge {
|
|
|
1138
1308
|
plugin.aggregator.addBridgedDevice(registeredDevice.device);
|
|
1139
1309
|
if (plugin.addedDevices !== undefined)
|
|
1140
1310
|
plugin.addedDevices++;
|
|
1141
|
-
this.log.info(
|
|
1311
|
+
this.log.info(
|
|
1312
|
+
// eslint-disable-next-line max-len
|
|
1313
|
+
`Added bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${registeredDevice.device.name}-${registeredDevice.device.deviceName}${nf} for plugin ${plg}${plugin.name}${nf}`);
|
|
1142
1314
|
registeredDevice.added = true;
|
|
1143
1315
|
}
|
|
1144
1316
|
}
|
|
@@ -1244,6 +1416,22 @@ export class Matterbridge {
|
|
|
1244
1416
|
});
|
|
1245
1417
|
});
|
|
1246
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
1421
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
1422
|
+
*/
|
|
1423
|
+
async getGlobalNodeModules() {
|
|
1424
|
+
return new Promise((resolve, reject) => {
|
|
1425
|
+
exec('npm root -g', (error, stdout) => {
|
|
1426
|
+
if (error) {
|
|
1427
|
+
reject(error);
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
resolve(stdout.trim());
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1247
1435
|
/**
|
|
1248
1436
|
* Logs the node and system information.
|
|
1249
1437
|
*/
|
|
@@ -1298,16 +1486,20 @@ export class Matterbridge {
|
|
|
1298
1486
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1299
1487
|
// Home directory
|
|
1300
1488
|
this.homeDirectory = os.homedir();
|
|
1489
|
+
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
1301
1490
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1302
1491
|
// Package root directory
|
|
1303
1492
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
1304
1493
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
1494
|
+
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
1305
1495
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1306
1496
|
// Global node_modules directory
|
|
1307
|
-
this.
|
|
1308
|
-
this.
|
|
1497
|
+
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
1498
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1499
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1309
1500
|
// Create the data directory .matterbridge in the home directory
|
|
1310
1501
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
1502
|
+
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
1311
1503
|
try {
|
|
1312
1504
|
await fs.access(this.matterbridgeDirectory);
|
|
1313
1505
|
}
|
|
@@ -1331,6 +1523,7 @@ export class Matterbridge {
|
|
|
1331
1523
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1332
1524
|
// Create the data directory .matterbridge in the home directory
|
|
1333
1525
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
1526
|
+
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
1334
1527
|
try {
|
|
1335
1528
|
await fs.access(this.matterbridgePluginDirectory);
|
|
1336
1529
|
}
|
|
@@ -1355,8 +1548,10 @@ export class Matterbridge {
|
|
|
1355
1548
|
// Matterbridge version
|
|
1356
1549
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1357
1550
|
this.matterbridgeVersion = packageJson.version;
|
|
1551
|
+
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
1358
1552
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1359
1553
|
this.matterbridgeLatestVersion = await this.getLatestVersion('matterbridge');
|
|
1554
|
+
this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeLatestVersion;
|
|
1360
1555
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1361
1556
|
if (this.matterbridgeVersion !== this.matterbridgeLatestVersion) {
|
|
1362
1557
|
this.log.warn(`Matterbridge is out of date. Current version: ${this.matterbridgeVersion}, Latest version: ${this.matterbridgeLatestVersion}`);
|
|
@@ -1437,6 +1632,13 @@ export class Matterbridge {
|
|
|
1437
1632
|
this.log.debug('The frontend sent /api/system-info');
|
|
1438
1633
|
res.json(this.systemInformation);
|
|
1439
1634
|
});
|
|
1635
|
+
// Endpoint to provide matterbridge information
|
|
1636
|
+
this.expressApp.get('/api/matterbridge-info', (req, res) => {
|
|
1637
|
+
this.log.debug('The frontend sent /api/matterbridge-info');
|
|
1638
|
+
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
1639
|
+
this.matterbridgeInformation.debugEnabled = this.debugEnabled;
|
|
1640
|
+
res.json(this.matterbridgeInformation);
|
|
1641
|
+
});
|
|
1440
1642
|
// Endpoint to provide plugins
|
|
1441
1643
|
this.expressApp.get('/api/plugins', (req, res) => {
|
|
1442
1644
|
this.log.debug('The frontend sent /api/plugins');
|
|
@@ -1513,7 +1715,7 @@ export class Matterbridge {
|
|
|
1513
1715
|
res.json(data);
|
|
1514
1716
|
});
|
|
1515
1717
|
// Endpoint to receive commands
|
|
1516
|
-
this.expressApp.post('/api/command/:command/:param', (req, res) => {
|
|
1718
|
+
this.expressApp.post('/api/command/:command/:param', async (req, res) => {
|
|
1517
1719
|
const command = req.params.command;
|
|
1518
1720
|
const param = req.params.param;
|
|
1519
1721
|
this.log.debug(`The frontend sent /api/command/${command}/${param}`);
|
|
@@ -1522,7 +1724,7 @@ export class Matterbridge {
|
|
|
1522
1724
|
return;
|
|
1523
1725
|
}
|
|
1524
1726
|
this.log.info(`***Received command: ${command}:${param}`);
|
|
1525
|
-
// Handle the command debugLevel
|
|
1727
|
+
// Handle the command debugLevel from Settings
|
|
1526
1728
|
if (command === 'setloglevel') {
|
|
1527
1729
|
if (param === 'Debug') {
|
|
1528
1730
|
this.log.setLogDebug(true);
|
|
@@ -1543,10 +1745,50 @@ export class Matterbridge {
|
|
|
1543
1745
|
plugin.platform?.log.setLogDebug(this.debugEnabled);
|
|
1544
1746
|
});
|
|
1545
1747
|
}
|
|
1546
|
-
// Handle the command debugLevel
|
|
1748
|
+
// Handle the command debugLevel from Header
|
|
1547
1749
|
if (command === 'restart') {
|
|
1548
1750
|
this.restartProcess();
|
|
1549
1751
|
}
|
|
1752
|
+
// Handle the command update from Header
|
|
1753
|
+
if (command === 'update') {
|
|
1754
|
+
this.log.warn(`The /api/command/${command} is not yet implemented`);
|
|
1755
|
+
}
|
|
1756
|
+
// Handle the command addplugin from Header
|
|
1757
|
+
if (command === 'addplugin') {
|
|
1758
|
+
this.log.warn(`The /api/command/${command}/${param} is not yet implemented`);
|
|
1759
|
+
}
|
|
1760
|
+
// Handle the command enableplugin from Home
|
|
1761
|
+
if (command === 'enableplugin') {
|
|
1762
|
+
const plugins = await this.nodeContext?.get('plugins');
|
|
1763
|
+
if (!plugins)
|
|
1764
|
+
return;
|
|
1765
|
+
const plugin = plugins.find((plugin) => plugin.name === param);
|
|
1766
|
+
if (plugin) {
|
|
1767
|
+
plugin.enabled = true;
|
|
1768
|
+
plugin.loaded = undefined;
|
|
1769
|
+
plugin.started = undefined;
|
|
1770
|
+
plugin.configured = undefined;
|
|
1771
|
+
plugin.connected = undefined;
|
|
1772
|
+
await this.nodeContext?.set('plugins', plugins);
|
|
1773
|
+
this.log.info(`Enabled plugin ${plg}${param}${nf}`);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
// Handle the command disableplugin from Home
|
|
1777
|
+
if (command === 'disableplugin') {
|
|
1778
|
+
const plugins = await this.nodeContext?.get('plugins');
|
|
1779
|
+
if (!plugins)
|
|
1780
|
+
return;
|
|
1781
|
+
const plugin = plugins.find((plugin) => plugin.name === param);
|
|
1782
|
+
if (plugin) {
|
|
1783
|
+
plugin.enabled = false;
|
|
1784
|
+
plugin.loaded = undefined;
|
|
1785
|
+
plugin.started = undefined;
|
|
1786
|
+
plugin.configured = undefined;
|
|
1787
|
+
plugin.connected = undefined;
|
|
1788
|
+
await this.nodeContext?.set('plugins', plugins);
|
|
1789
|
+
this.log.info(`Disabled plugin ${plg}${param}${nf}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1550
1792
|
res.json({ message: 'Command received' });
|
|
1551
1793
|
});
|
|
1552
1794
|
// Fallback for routing
|