matterbridge 1.3.4 → 1.3.6
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 +615 -551
- package/README.md +643 -594
- package/dist/cluster/ElectricalEnergyMeasurementCluster.js +1 -1
- package/dist/cluster/ElectricalEnergyMeasurementCluster.js.map +1 -1
- package/dist/cluster/ElectricalPowerMeasurementCluster.js +10 -10
- package/dist/cluster/ElectricalPowerMeasurementCluster.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +2 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matterbridge.d.ts +7 -0
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +248 -320
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeController.d.ts.map +1 -1
- package/dist/matterbridgeController.js +0 -3
- package/dist/matterbridgeController.js.map +1 -1
- package/dist/matterbridgeDevice.d.ts +213 -4
- package/dist/matterbridgeDevice.d.ts.map +1 -1
- package/dist/matterbridgeDevice.js +133 -46
- package/dist/matterbridgeDevice.js.map +1 -1
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +2 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +313 -0
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/utils.d.ts +73 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +266 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -1
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.cbfc6c9b.js → main.c3b5dfce.js} +3 -3
- package/frontend/build/static/js/{main.cbfc6c9b.js.map → main.c3b5dfce.js.map} +1 -1
- package/package.json +57 -21
- /package/frontend/build/static/js/{main.cbfc6c9b.js.LICENSE.txt → main.c3b5dfce.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -25,6 +25,7 @@ import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugSt
|
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
26
26
|
import { promises as fs } from 'fs';
|
|
27
27
|
import { exec, spawn } from 'child_process';
|
|
28
|
+
import { createServer } from 'http';
|
|
28
29
|
import https from 'https';
|
|
29
30
|
import EventEmitter from 'events';
|
|
30
31
|
import express from 'express';
|
|
@@ -35,6 +36,7 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
35
36
|
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
36
37
|
import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js';
|
|
37
38
|
import { BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster } from './cluster/BridgedDeviceBasicInformationCluster.js';
|
|
39
|
+
import { logInterfaces } from './utils/utils.js';
|
|
38
40
|
// @project-chip/matter-node.js
|
|
39
41
|
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter-node.js';
|
|
40
42
|
import { BasicInformationCluster, ClusterServer, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, ThreadNetworkDiagnosticsCluster, getClusterNameById } from '@project-chip/matter-node.js/cluster';
|
|
@@ -54,6 +56,7 @@ const typ = '\u001B[38;5;207m';
|
|
|
54
56
|
*/
|
|
55
57
|
export class Matterbridge extends EventEmitter {
|
|
56
58
|
systemInformation = {
|
|
59
|
+
interfaceName: '',
|
|
57
60
|
macAddress: '',
|
|
58
61
|
ipv4Address: '',
|
|
59
62
|
ipv6Address: '',
|
|
@@ -100,12 +103,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
100
103
|
port = 5540;
|
|
101
104
|
log;
|
|
102
105
|
hasCleanupStarted = false;
|
|
106
|
+
plugins = new Map();
|
|
107
|
+
devices = new Map();
|
|
103
108
|
registeredPlugins = [];
|
|
104
109
|
registeredDevices = [];
|
|
105
110
|
nodeStorage;
|
|
106
111
|
nodeContext;
|
|
107
112
|
expressApp;
|
|
108
113
|
expressServer;
|
|
114
|
+
httpServer;
|
|
115
|
+
httpsServer;
|
|
109
116
|
webSocketServer;
|
|
110
117
|
storageManager;
|
|
111
118
|
matterbridgeContext;
|
|
@@ -256,10 +263,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
256
263
|
- bridge: start Matterbridge in bridge mode
|
|
257
264
|
- childbridge: start Matterbridge in childbridge mode
|
|
258
265
|
- frontend [port]: start the frontend on the given port (default 8283)
|
|
259
|
-
- debug: enable debug mode (default false)
|
|
266
|
+
- debug: enable the Matterbridge debug mode (default false)
|
|
267
|
+
- matterlogger: set the matter.js logger level: debug | info | notice | warn | error | fatal (default info)
|
|
260
268
|
- reset: remove the commissioning for Matterbridge (bridge mode). Shutdown Matterbridge before using it!
|
|
261
269
|
- factoryreset: remove all commissioning information and reset all internal storages. Shutdown Matterbridge before using it!
|
|
262
270
|
- list: list the registered plugins
|
|
271
|
+
- loginterfaces: log the network interfaces
|
|
272
|
+
- logstorage: log the node storage
|
|
273
|
+
- ssl: enable SSL for the frontend and WebSockerServer (certificates in .matterbridge/certs directory cert.pem, key.pem and ca.pem (optional))
|
|
263
274
|
- add [plugin path]: register the plugin from the given absolute or relative path
|
|
264
275
|
- add [plugin name]: register the globally installed plugin with the given name
|
|
265
276
|
- remove [plugin path]: remove the plugin from the given absolute or relative path
|
|
@@ -305,9 +316,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
305
316
|
}
|
|
306
317
|
// Log system info and create .matterbridge directory
|
|
307
318
|
await this.logNodeAndSystemInfo();
|
|
308
|
-
this.log.info(
|
|
309
|
-
// eslint-disable-next-line max-len
|
|
310
|
-
`Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} ` +
|
|
319
|
+
this.log.info(`Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} ` +
|
|
311
320
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
|
|
312
321
|
// Check node version and throw error
|
|
313
322
|
requireMinNodeVersion(18);
|
|
@@ -380,7 +389,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
380
389
|
process.exit(0);
|
|
381
390
|
}
|
|
382
391
|
if (hasParameter('logstorage')) {
|
|
383
|
-
this.log.info(`${plg}
|
|
392
|
+
this.log.info(`${plg}Matterbridge${nf} storage log`);
|
|
384
393
|
await this.nodeContext?.logStorage();
|
|
385
394
|
for (const plugin of this.registeredPlugins) {
|
|
386
395
|
this.log.info(`${plg}${plugin.name}${nf} storage log`);
|
|
@@ -389,6 +398,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
389
398
|
this.emit('shutdown');
|
|
390
399
|
process.exit(0);
|
|
391
400
|
}
|
|
401
|
+
if (hasParameter('loginterfaces')) {
|
|
402
|
+
this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
|
|
403
|
+
logInterfaces();
|
|
404
|
+
this.emit('shutdown');
|
|
405
|
+
process.exit(0);
|
|
406
|
+
}
|
|
392
407
|
if (getParameter('add')) {
|
|
393
408
|
this.log.debug(`Registering plugin ${getParameter('add')}`);
|
|
394
409
|
await this.executeCommandLine(getParameter('add'), 'add');
|
|
@@ -507,10 +522,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
507
522
|
continue;
|
|
508
523
|
}
|
|
509
524
|
plugin.error = false;
|
|
525
|
+
plugin.locked = false;
|
|
510
526
|
plugin.loaded = false;
|
|
511
527
|
plugin.started = false;
|
|
512
528
|
plugin.configured = false;
|
|
513
529
|
plugin.connected = undefined;
|
|
530
|
+
plugin.registeredDevices = undefined;
|
|
531
|
+
plugin.addedDevices = undefined;
|
|
514
532
|
plugin.qrPairingCode = undefined;
|
|
515
533
|
plugin.manualPairingCode = undefined;
|
|
516
534
|
this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
@@ -537,10 +555,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
537
555
|
continue;
|
|
538
556
|
}
|
|
539
557
|
plugin.error = false;
|
|
558
|
+
plugin.locked = false;
|
|
540
559
|
plugin.loaded = false;
|
|
541
560
|
plugin.started = false;
|
|
542
561
|
plugin.configured = false;
|
|
543
562
|
plugin.connected = false;
|
|
563
|
+
plugin.registeredDevices = undefined;
|
|
564
|
+
plugin.addedDevices = undefined;
|
|
544
565
|
plugin.qrPairingCode = (await plugin.nodeContext?.get('qrPairingCode', undefined)) ?? undefined;
|
|
545
566
|
plugin.manualPairingCode = (await plugin.nodeContext?.get('manualPairingCode', undefined)) ?? undefined;
|
|
546
567
|
this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
@@ -549,6 +570,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
549
570
|
return;
|
|
550
571
|
}
|
|
551
572
|
}
|
|
573
|
+
async savePluginsToStorage() {
|
|
574
|
+
if (!this.nodeContext) {
|
|
575
|
+
this.log.error('loadPluginsFromStorage() error: the node context is not initialized');
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// Convert the map to an array
|
|
579
|
+
// const pluginArray = Array.from(this.plugins.values());
|
|
580
|
+
// await this.nodeContext.set('plugins', pluginArray);
|
|
581
|
+
// TODO remove after migration done
|
|
582
|
+
await this.nodeContext.set('plugins', await this.getBaseRegisteredPlugins());
|
|
583
|
+
}
|
|
584
|
+
async loadPluginsFromStorage() {
|
|
585
|
+
if (!this.nodeContext) {
|
|
586
|
+
this.log.error('loadPluginsFromStorage() error: the node context is not initialized');
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Load the array from storage and convert it back to a map
|
|
590
|
+
// const pluginArray = await this.nodeContext.get<RegisteredPlugin[]>('plugins', []);
|
|
591
|
+
// for (const plugin of pluginArray) this.plugins.set(plugin.name, plugin);
|
|
592
|
+
// TODO remove after migration done
|
|
593
|
+
this.registeredPlugins = await this.nodeContext.get('plugins', []);
|
|
594
|
+
}
|
|
552
595
|
/**
|
|
553
596
|
* Resolves the name of a plugin by loading and parsing its package.json file.
|
|
554
597
|
* @param pluginPath - The path to the plugin or the path to the plugin's package.json file.
|
|
@@ -573,7 +616,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
573
616
|
this.log.debug(`Package.json not found at ${packageJsonPath}`);
|
|
574
617
|
this.log.debug(`Trying at ${this.globalModulesDirectory}`);
|
|
575
618
|
packageJsonPath = path.join(this.globalModulesDirectory, pluginPath);
|
|
576
|
-
// this.log.debug(`Got ${packageJsonPath}`);
|
|
577
619
|
}
|
|
578
620
|
try {
|
|
579
621
|
// Load the package.json of the plugin
|
|
@@ -758,7 +800,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
758
800
|
this.log.debug('All listeners removed');
|
|
759
801
|
this.checkUpdateInterval && clearInterval(this.checkUpdateInterval);
|
|
760
802
|
this.checkUpdateInterval = undefined;
|
|
761
|
-
// Calling the shutdown
|
|
803
|
+
// Calling the shutdown method of each plugin
|
|
762
804
|
for (const plugin of this.registeredPlugins) {
|
|
763
805
|
if (!plugin.enabled || plugin.error)
|
|
764
806
|
continue;
|
|
@@ -817,14 +859,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
817
859
|
// Close the express server
|
|
818
860
|
if (this.expressServer) {
|
|
819
861
|
this.expressServer.close();
|
|
862
|
+
this.expressServer.removeAllListeners();
|
|
820
863
|
this.expressServer = undefined;
|
|
821
864
|
this.log.debug('Express server closed successfully');
|
|
822
865
|
}
|
|
823
|
-
//
|
|
866
|
+
// Close the http server
|
|
867
|
+
if (this.httpServer) {
|
|
868
|
+
this.httpServer.close();
|
|
869
|
+
this.httpServer.removeAllListeners();
|
|
870
|
+
this.httpServer = undefined;
|
|
871
|
+
this.log.debug('Frontend http server closed successfully');
|
|
872
|
+
}
|
|
873
|
+
// Close the https server
|
|
874
|
+
if (this.httpsServer) {
|
|
875
|
+
this.httpsServer.close();
|
|
876
|
+
this.httpsServer.removeAllListeners();
|
|
877
|
+
this.httpsServer = undefined;
|
|
878
|
+
this.log.debug('Frontend https server closed successfully');
|
|
879
|
+
}
|
|
880
|
+
// Remove listeners from the express app
|
|
824
881
|
if (this.expressApp) {
|
|
825
882
|
this.expressApp.removeAllListeners();
|
|
826
883
|
this.expressApp = undefined;
|
|
827
|
-
this.log.debug('Frontend closed successfully');
|
|
884
|
+
this.log.debug('Frontend app closed successfully');
|
|
828
885
|
}
|
|
829
886
|
// Close the WebSocket server
|
|
830
887
|
if (this.webSocketServer) {
|
|
@@ -1236,8 +1293,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1236
1293
|
await fs.access(configFile);
|
|
1237
1294
|
const data = await fs.readFile(configFile, 'utf8');
|
|
1238
1295
|
const config = JSON.parse(data);
|
|
1239
|
-
// this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
|
|
1240
1296
|
this.log.debug(`Config file found: ${configFile}.`);
|
|
1297
|
+
// this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
|
|
1241
1298
|
/* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/
|
|
1242
1299
|
config.name = plugin.name;
|
|
1243
1300
|
config.type = plugin.type;
|
|
@@ -1547,7 +1604,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1547
1604
|
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1548
1605
|
for (const nodeId of nodeIds) {
|
|
1549
1606
|
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1550
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1551
1607
|
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1552
1608
|
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) => this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1553
1609
|
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) => this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
@@ -1590,9 +1646,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1590
1646
|
});
|
|
1591
1647
|
this.log.warn(`Cluster: ${cluster.name} attributes:`);
|
|
1592
1648
|
attributes.forEach((attribute) => {
|
|
1593
|
-
this.log.info(
|
|
1594
|
-
// eslint-disable-next-line max-len
|
|
1595
|
-
`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1649
|
+
this.log.info(`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1596
1650
|
});
|
|
1597
1651
|
// Log PowerSourceCluster
|
|
1598
1652
|
cluster = PowerSourceCluster;
|
|
@@ -1601,9 +1655,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1601
1655
|
});
|
|
1602
1656
|
this.log.warn(`Cluster: ${cluster.name} attributes:`);
|
|
1603
1657
|
attributes.forEach((attribute) => {
|
|
1604
|
-
this.log.info(
|
|
1605
|
-
// eslint-disable-next-line max-len
|
|
1606
|
-
`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1658
|
+
this.log.info(`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1607
1659
|
});
|
|
1608
1660
|
// Log ThreadNetworkDiagnostics
|
|
1609
1661
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
@@ -1612,9 +1664,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1612
1664
|
});
|
|
1613
1665
|
this.log.warn(`Cluster: ${cluster.name} attributes:`);
|
|
1614
1666
|
attributes.forEach((attribute) => {
|
|
1615
|
-
this.log.info(
|
|
1616
|
-
// eslint-disable-next-line max-len
|
|
1617
|
-
`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1667
|
+
this.log.info(`- endpoint ${attribute.path.endpointId} cluster ${getClusterNameById(attribute.path.clusterId)} (${attribute.path.clusterId}) attribute ${attribute.path.attributeName} (${attribute.path.attributeId}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1618
1668
|
});
|
|
1619
1669
|
}
|
|
1620
1670
|
}
|
|
@@ -1635,7 +1685,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1635
1685
|
let failCount = 0;
|
|
1636
1686
|
const startMatterInterval = setInterval(async () => {
|
|
1637
1687
|
for (const plugin of this.registeredPlugins) {
|
|
1638
|
-
// if (!plugin.enabled || plugin.error) continue;
|
|
1639
1688
|
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1640
1689
|
if (!plugin.enabled)
|
|
1641
1690
|
continue;
|
|
@@ -1661,53 +1710,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1661
1710
|
this.log.debug('***Cleared startMatterInterval interval for Matterbridge');
|
|
1662
1711
|
await this.startMatterServer();
|
|
1663
1712
|
this.log.info('Matter server started');
|
|
1664
|
-
|
|
1713
|
+
// Configure the plugins
|
|
1665
1714
|
/*
|
|
1666
|
-
const
|
|
1667
|
-
|
|
1668
|
-
console.log('GeneralDiagnosticsCluster', gdcCluster);
|
|
1669
|
-
const net = gdcCluster.getNetworkInterfacesAttribute();
|
|
1670
|
-
console.log('NetworkInterfaces', net);
|
|
1671
|
-
|
|
1672
|
-
// We have like "30:f6:ef:69:2b:c5" in this.systemInformation.macAddress
|
|
1673
|
-
const macArray = this.systemInformation.macAddress.split(':').map((hex) => parseInt(hex, 16));
|
|
1674
|
-
let hardwareAddress = new Uint8Array(macArray);
|
|
1675
|
-
if (hardwareAddress.length === 6) hardwareAddress = Uint8Array.from([0, 0, ...hardwareAddress]);
|
|
1676
|
-
// We have like "192.168.1.189" in this.systemInformation.ipv4Address
|
|
1677
|
-
const ipv4Array = this.systemInformation.ipv4Address.split('.').map((num) => parseInt(num));
|
|
1678
|
-
const iPv4Address = new Uint8Array(ipv4Array);
|
|
1679
|
-
// We have like "fd78:cbf8:4939:746:d555:85a9:74f6:9c6" in this.systemInformation.ipv6Address
|
|
1680
|
-
const ipv6Groups = this.systemInformation.ipv6Address.split(':');
|
|
1681
|
-
const ipv6Array = [];
|
|
1682
|
-
for (const group of ipv6Groups) {
|
|
1683
|
-
const decimal = parseInt(group, 16);
|
|
1684
|
-
ipv6Array.push(decimal >> 8); // High byte
|
|
1685
|
-
ipv6Array.push(decimal & 0xff); // Low byte
|
|
1686
|
-
}
|
|
1687
|
-
const iPv6Address = new Uint8Array(ipv6Array);
|
|
1688
|
-
this.log.warn(`GeneralDiagnosticsCluster for hardwareAddress ${this.systemInformation.macAddress} => ${debugStringify(hardwareAddress)}`);
|
|
1689
|
-
this.log.warn(`GeneralDiagnosticsCluster for iPv4Address ${this.systemInformation.ipv4Address} => ${debugStringify(iPv4Address)}`);
|
|
1690
|
-
this.log.warn(`GeneralDiagnosticsCluster for iPv6Address ${this.systemInformation.ipv6Address} => ${debugStringify(iPv6Address)}`);
|
|
1715
|
+
for (const plugin of this.registeredPlugins) {
|
|
1716
|
+
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error) continue;
|
|
1691
1717
|
try {
|
|
1692
|
-
|
|
1693
|
-
{
|
|
1694
|
-
name: 'eth0',
|
|
1695
|
-
isOperational: true,
|
|
1696
|
-
offPremiseServicesReachableIPv4: null,
|
|
1697
|
-
offPremiseServicesReachableIPv6: null,
|
|
1698
|
-
hardwareAddress,
|
|
1699
|
-
iPv4Addresses: [iPv4Address],
|
|
1700
|
-
iPv6Addresses: [iPv6Address],
|
|
1701
|
-
type: GeneralDiagnostics.InterfaceType.Ethernet,
|
|
1702
|
-
},
|
|
1703
|
-
]);
|
|
1704
|
-
const net = gdcCluster.getNetworkInterfacesAttribute();
|
|
1705
|
-
console.log('NetworkInterfaces', net);
|
|
1718
|
+
this.configurePlugin(plugin); // No await do it asyncronously
|
|
1706
1719
|
} catch (error) {
|
|
1707
|
-
|
|
1720
|
+
plugin.error = true;
|
|
1721
|
+
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1708
1722
|
}
|
|
1709
1723
|
}
|
|
1710
1724
|
*/
|
|
1725
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1726
|
+
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1711
1727
|
// Setting reachability to true
|
|
1712
1728
|
setTimeout(() => {
|
|
1713
1729
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
@@ -1723,14 +1739,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1723
1739
|
// addDevice and addBridgedDeevice create the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1724
1740
|
// Plugins are configured by callback when the plugin is commissioned
|
|
1725
1741
|
// Start the interval to check if all plugins are loaded and started and so start the matter server
|
|
1726
|
-
// TODO set a counter or a timeout
|
|
1727
1742
|
this.log.debug('***Starting start matter interval in childbridge mode...');
|
|
1728
1743
|
let failCount = 0;
|
|
1729
1744
|
const startMatterInterval = setInterval(async () => {
|
|
1730
1745
|
let allStarted = true;
|
|
1731
|
-
// this.registeredPlugins.forEach((plugin) => {
|
|
1732
1746
|
for (const plugin of this.registeredPlugins) {
|
|
1733
|
-
// if (!plugin.enabled || plugin.error) return;
|
|
1734
1747
|
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1735
1748
|
if (!plugin.enabled)
|
|
1736
1749
|
continue;
|
|
@@ -2093,9 +2106,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2093
2106
|
const info = commissioningServer.getActiveSessionInformation(fabricIndex);
|
|
2094
2107
|
let connected = false;
|
|
2095
2108
|
info.forEach((session) => {
|
|
2096
|
-
this.log.info(
|
|
2097
|
-
// eslint-disable-next-line max-len
|
|
2098
|
-
`*Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
|
|
2109
|
+
this.log.info(`*Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
|
|
2099
2110
|
if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
|
|
2100
2111
|
this.log.info(`*Controller ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nf} on session ${session.name}`);
|
|
2101
2112
|
connected = true;
|
|
@@ -2172,48 +2183,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2172
2183
|
}
|
|
2173
2184
|
},
|
|
2174
2185
|
});
|
|
2175
|
-
/*
|
|
2176
|
-
const gdcCluster = commissioningServer.getRootClusterServer(GeneralDiagnosticsCluster);
|
|
2177
|
-
if (gdcCluster) {
|
|
2178
|
-
// console.log('GeneralDiagnosticsCluster found for', plg, pluginName, db);
|
|
2179
|
-
// console.log('GeneralDiagnosticsCluster', gdcCluster);
|
|
2180
|
-
// We have like "30:f6:ef:69:2b:c5" in this.systemInformation.macAddress
|
|
2181
|
-
const macArray = this.systemInformation.macAddress.split(':').map((hex) => parseInt(hex, 16));
|
|
2182
|
-
let hardwareAddress = new Uint8Array(macArray);
|
|
2183
|
-
if (hardwareAddress.length === 6) hardwareAddress = Uint8Array.from([0, 0, ...hardwareAddress]);
|
|
2184
|
-
// We have like "192.168.1.189" in this.systemInformation.ipv4Address
|
|
2185
|
-
const ipv4Array = this.systemInformation.ipv4Address.split('.').map((num) => parseInt(num));
|
|
2186
|
-
const iPv4Address = new Uint8Array(ipv4Array);
|
|
2187
|
-
// We have like "fd78:cbf8:4939:746:d555:85a9:74f6:9c6" in this.systemInformation.ipv6Address
|
|
2188
|
-
const ipv6Groups = this.systemInformation.ipv6Address.split(':');
|
|
2189
|
-
const ipv6Array = [];
|
|
2190
|
-
for (const group of ipv6Groups) {
|
|
2191
|
-
const decimal = parseInt(group, 16);
|
|
2192
|
-
ipv6Array.push(decimal >> 8); // High byte
|
|
2193
|
-
ipv6Array.push(decimal & 0xff); // Low byte
|
|
2194
|
-
}
|
|
2195
|
-
const iPv6Address = new Uint8Array(ipv6Array);
|
|
2196
|
-
this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} hardwareAddress ${this.systemInformation.macAddress} => ${debugStringify(hardwareAddress)}`);
|
|
2197
|
-
this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} iPv4Address ${this.systemInformation.ipv4Address} => ${debugStringify(iPv4Address)}`);
|
|
2198
|
-
this.log.debug(`GeneralDiagnosticsCluster for ${plg}${pluginName}${db} iPv6Address ${this.systemInformation.ipv6Address} => ${debugStringify(iPv6Address)}`);
|
|
2199
|
-
try {
|
|
2200
|
-
gdcCluster.setNetworkInterfacesAttribute([
|
|
2201
|
-
{
|
|
2202
|
-
name: 'eth0',
|
|
2203
|
-
isOperational: true,
|
|
2204
|
-
offPremiseServicesReachableIPv4: null,
|
|
2205
|
-
offPremiseServicesReachableIPv6: null,
|
|
2206
|
-
hardwareAddress,
|
|
2207
|
-
iPv4Addresses: [iPv4Address],
|
|
2208
|
-
iPv6Addresses: [iPv6Address],
|
|
2209
|
-
type: GeneralDiagnostics.InterfaceType.Ethernet,
|
|
2210
|
-
},
|
|
2211
|
-
]);
|
|
2212
|
-
} catch (error) {
|
|
2213
|
-
this.log.error(`GeneralDiagnosticsCluster.setNetworkInterfacesAttribute for ${plg}${pluginName}${er} error:`, error);
|
|
2214
|
-
}
|
|
2215
|
-
} else this.log.warn(`*GeneralDiagnosticsCluster not found for ${plg}${pluginName}${wr}`);
|
|
2216
|
-
*/
|
|
2217
2186
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
2218
2187
|
return commissioningServer;
|
|
2219
2188
|
}
|
|
@@ -2321,24 +2290,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
2321
2290
|
async logNodeAndSystemInfo() {
|
|
2322
2291
|
// IP address information
|
|
2323
2292
|
const networkInterfaces = os.networkInterfaces();
|
|
2324
|
-
this.systemInformation.ipv4Address = '
|
|
2325
|
-
this.systemInformation.ipv6Address = '
|
|
2326
|
-
for (const interfaceDetails of Object.
|
|
2293
|
+
this.systemInformation.ipv4Address = '';
|
|
2294
|
+
this.systemInformation.ipv6Address = '';
|
|
2295
|
+
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
2327
2296
|
if (!interfaceDetails) {
|
|
2328
2297
|
break;
|
|
2329
2298
|
}
|
|
2330
2299
|
for (const detail of interfaceDetails) {
|
|
2331
|
-
if (detail.family === 'IPv4' && !detail.internal && this.systemInformation.ipv4Address === '
|
|
2300
|
+
if (detail.family === 'IPv4' && !detail.internal && this.systemInformation.ipv4Address === '') {
|
|
2301
|
+
this.systemInformation.interfaceName = interfaceName;
|
|
2332
2302
|
this.systemInformation.ipv4Address = detail.address;
|
|
2333
2303
|
this.systemInformation.macAddress = detail.mac;
|
|
2334
2304
|
}
|
|
2335
|
-
else if (detail.family === 'IPv6' && !detail.internal && this.systemInformation.ipv6Address === '
|
|
2305
|
+
else if (detail.family === 'IPv6' && !detail.internal && this.systemInformation.ipv6Address === '') {
|
|
2306
|
+
this.systemInformation.interfaceName = interfaceName;
|
|
2336
2307
|
this.systemInformation.ipv6Address = detail.address;
|
|
2337
2308
|
this.systemInformation.macAddress = detail.mac;
|
|
2338
2309
|
}
|
|
2339
2310
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
2311
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
2312
|
+
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
2313
|
+
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
2314
|
+
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
2315
|
+
this.log.debug(`- with IPv6 address: '${this.systemInformation.ipv6Address}'`);
|
|
2342
2316
|
break;
|
|
2343
2317
|
}
|
|
2344
2318
|
}
|
|
@@ -2361,6 +2335,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2361
2335
|
this.log.debug('Host System Information:');
|
|
2362
2336
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
2363
2337
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
2338
|
+
this.log.debug(`- Interface: ${this.systemInformation.interfaceName}`);
|
|
2364
2339
|
this.log.debug(`- MAC Address: ${this.systemInformation.macAddress}`);
|
|
2365
2340
|
this.log.debug(`- IPv4 Address: ${this.systemInformation.ipv4Address}`);
|
|
2366
2341
|
this.log.debug(`- IPv6 Address: ${this.systemInformation.ipv6Address}`);
|
|
@@ -2532,9 +2507,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2532
2507
|
type: plugin.type,
|
|
2533
2508
|
name: plugin.name,
|
|
2534
2509
|
version: plugin.version,
|
|
2535
|
-
latestVersion: plugin.latestVersion,
|
|
2536
2510
|
description: plugin.description,
|
|
2537
2511
|
author: plugin.author,
|
|
2512
|
+
latestVersion: plugin.latestVersion,
|
|
2513
|
+
locked: plugin.locked,
|
|
2538
2514
|
error: plugin.error,
|
|
2539
2515
|
enabled: plugin.enabled,
|
|
2540
2516
|
loaded: plugin.loaded,
|
|
@@ -2542,11 +2518,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2542
2518
|
configured: plugin.configured,
|
|
2543
2519
|
paired: plugin.paired,
|
|
2544
2520
|
connected: plugin.connected,
|
|
2521
|
+
fabricInfo: plugin.fabricInfo,
|
|
2545
2522
|
registeredDevices: plugin.registeredDevices,
|
|
2523
|
+
addedDevices: plugin.addedDevices,
|
|
2546
2524
|
qrPairingCode: plugin.qrPairingCode,
|
|
2547
2525
|
manualPairingCode: plugin.manualPairingCode,
|
|
2548
|
-
// configJson: includeConfigSchema ? await this.loadPluginConfig(plugin) : {},
|
|
2549
|
-
// schemaJson: includeConfigSchema ? await this.loadPluginSchema(plugin) : {},
|
|
2550
2526
|
configJson: includeConfigSchema ? plugin.configJson : {},
|
|
2551
2527
|
schemaJson: includeConfigSchema ? plugin.schemaJson : {},
|
|
2552
2528
|
});
|
|
@@ -2662,6 +2638,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2662
2638
|
const cleanMessage = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2663
2639
|
// Remove leading asterisks from the message
|
|
2664
2640
|
const finalMessage = cleanMessage.replace(/^\*+/, '');
|
|
2641
|
+
// Send the message to all connected clients
|
|
2665
2642
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2666
2643
|
if (client.readyState === WebSocket.OPEN) {
|
|
2667
2644
|
client.send(JSON.stringify({ type, subType, message: finalMessage }));
|
|
@@ -2674,65 +2651,156 @@ export class Matterbridge extends EventEmitter {
|
|
|
2674
2651
|
* @param port The port number to run the frontend server on. Default is 3000.
|
|
2675
2652
|
*/
|
|
2676
2653
|
async initializeFrontend(port = 8283) {
|
|
2677
|
-
this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2654
|
+
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2655
|
+
// Create the express app that serves the frontend
|
|
2656
|
+
this.expressApp = express();
|
|
2657
|
+
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2658
|
+
if (!hasParameter('ssl')) {
|
|
2659
|
+
// Create an HTTP server and attach the express app
|
|
2660
|
+
this.httpServer = createServer(this.expressApp);
|
|
2661
|
+
// Listen on the specified port
|
|
2662
|
+
this.httpServer.listen(port, () => {
|
|
2663
|
+
if (this.systemInformation.ipv4Address !== '')
|
|
2664
|
+
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
2665
|
+
if (this.systemInformation.ipv6Address !== '')
|
|
2666
|
+
this.log.debug(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2667
|
+
});
|
|
2668
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2669
|
+
this.httpServer.on('error', (error) => {
|
|
2670
|
+
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2671
|
+
switch (error.code) {
|
|
2672
|
+
case 'EACCES':
|
|
2673
|
+
this.log.error(`Port ${port} requires elevated privileges`);
|
|
2674
|
+
break;
|
|
2675
|
+
case 'EADDRINUSE':
|
|
2676
|
+
this.log.error(`Port ${port} is already in use`);
|
|
2677
|
+
break;
|
|
2678
|
+
}
|
|
2679
|
+
process.exit(1);
|
|
2680
|
+
});
|
|
2686
2681
|
}
|
|
2687
2682
|
else {
|
|
2688
|
-
//
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2683
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2684
|
+
let cert;
|
|
2685
|
+
try {
|
|
2686
|
+
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
2687
|
+
this.log.info(`Loaded certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
2688
|
+
}
|
|
2689
|
+
catch (error) {
|
|
2690
|
+
this.log.error(`Error reading certificate file: ${error}`);
|
|
2691
|
+
process.exit(1);
|
|
2692
|
+
}
|
|
2693
|
+
let key;
|
|
2694
|
+
try {
|
|
2695
|
+
key = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
2696
|
+
this.log.info(`Loaded key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}`);
|
|
2697
|
+
}
|
|
2698
|
+
catch (error) {
|
|
2699
|
+
this.log.error(`Error reading key file: ${error}`);
|
|
2700
|
+
process.exit(1);
|
|
2701
|
+
}
|
|
2702
|
+
let ca;
|
|
2703
|
+
try {
|
|
2704
|
+
ca = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
|
|
2705
|
+
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')}`);
|
|
2706
|
+
}
|
|
2707
|
+
catch (error) {
|
|
2708
|
+
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2709
|
+
}
|
|
2710
|
+
const serverOptions = { cert, key, ca };
|
|
2711
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
2712
|
+
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
2713
|
+
// Listen on the specified port
|
|
2714
|
+
this.httpsServer.listen(port, () => {
|
|
2715
|
+
if (this.systemInformation.ipv4Address !== '')
|
|
2716
|
+
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
2717
|
+
if (this.systemInformation.ipv6Address !== '')
|
|
2718
|
+
this.log.debug(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2719
|
+
});
|
|
2720
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2721
|
+
this.httpsServer.on('error', (error) => {
|
|
2722
|
+
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2723
|
+
switch (error.code) {
|
|
2724
|
+
case 'EACCES':
|
|
2725
|
+
this.log.error(`Port ${port} requires elevated privileges`);
|
|
2726
|
+
break;
|
|
2727
|
+
case 'EADDRINUSE':
|
|
2728
|
+
this.log.error(`Port ${port} is already in use`);
|
|
2729
|
+
break;
|
|
2730
|
+
}
|
|
2731
|
+
process.exit(1);
|
|
2704
2732
|
});
|
|
2705
2733
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2734
|
+
// Createe a WebSocket server and attach it to the http server
|
|
2735
|
+
const wssPort = port;
|
|
2736
|
+
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2737
|
+
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2738
|
+
this.webSocketServer.on('connection', (ws, request) => {
|
|
2739
|
+
const clientIp = request.socket.remoteAddress;
|
|
2740
|
+
this.log.info(`WebSocketServer client ${clientIp} connected`);
|
|
2708
2741
|
this.log.setGlobalCallback(this.wssSendMessage.bind(this));
|
|
2709
|
-
this.log.debug('WebSocketServer
|
|
2742
|
+
this.log.debug('WebSocketServer logger callback added');
|
|
2710
2743
|
this.wssSendMessage('Matterbridge', 'info', 'WebSocketServer client connected to Matterbridge');
|
|
2711
2744
|
ws.on('message', (message) => {
|
|
2712
|
-
this.log.
|
|
2745
|
+
this.log.debug(`WebSocket client message: ${message}`);
|
|
2713
2746
|
});
|
|
2714
2747
|
ws.on('close', () => {
|
|
2715
2748
|
this.log.info('WebSocket client disconnected');
|
|
2716
2749
|
if (this.webSocketServer?.clients.size === 0) {
|
|
2717
2750
|
this.log.setGlobalCallback(undefined);
|
|
2718
|
-
this.log.debug('WebSocket
|
|
2751
|
+
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger callback removed');
|
|
2719
2752
|
}
|
|
2720
2753
|
});
|
|
2721
2754
|
ws.on('error', (error) => {
|
|
2722
|
-
this.log.error(`WebSocket error: ${error}`);
|
|
2755
|
+
this.log.error(`WebSocket client error: ${error}`);
|
|
2723
2756
|
});
|
|
2724
2757
|
});
|
|
2758
|
+
this.webSocketServer.on('close', () => {
|
|
2759
|
+
this.log.debug(`WebSocketServer closed`);
|
|
2760
|
+
});
|
|
2761
|
+
this.webSocketServer.on('listening', () => {
|
|
2762
|
+
this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
|
|
2763
|
+
});
|
|
2725
2764
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2726
2765
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2727
|
-
return;
|
|
2728
2766
|
});
|
|
2767
|
+
/*
|
|
2768
|
+
// Create a WebSocket server
|
|
2769
|
+
const wssPort = 8284;
|
|
2770
|
+
const wssHost = `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2771
|
+
this.webSocketServer = new WebSocketServer({ port: wssPort, host: this.systemInformation.ipv4Address });
|
|
2772
|
+
this.log.debug(`WebSocket server created on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
|
|
2773
|
+
|
|
2729
2774
|
this.webSocketServer.on('listening', () => {
|
|
2730
|
-
|
|
2731
|
-
|
|
2775
|
+
this.log.info(`WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
|
|
2776
|
+
return;
|
|
2732
2777
|
});
|
|
2778
|
+
*/
|
|
2779
|
+
/*
|
|
2733
2780
|
// Serve React build directory
|
|
2734
2781
|
this.expressApp = express();
|
|
2735
2782
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2783
|
+
|
|
2784
|
+
// Listen on HTTP
|
|
2785
|
+
this.expressServer = this.expressApp.listen(port, () => {
|
|
2786
|
+
this.log.info(`The frontend is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
2787
|
+
this.log.debug(`The frontend is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2791
|
+
this.expressServer.on('error', (error: any) => {
|
|
2792
|
+
this.log.error(`Frontend error listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
2793
|
+
switch (error.code) {
|
|
2794
|
+
case 'EACCES':
|
|
2795
|
+
this.log.error(`Port ${port} requires elevated privileges`);
|
|
2796
|
+
break;
|
|
2797
|
+
case 'EADDRINUSE':
|
|
2798
|
+
this.log.error(`Port ${port} is already in use`);
|
|
2799
|
+
break;
|
|
2800
|
+
}
|
|
2801
|
+
process.exit(1);
|
|
2802
|
+
});
|
|
2803
|
+
*/
|
|
2736
2804
|
// Endpoint to validate login code
|
|
2737
2805
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2738
2806
|
const { password } = req.body;
|
|
@@ -2783,13 +2851,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2783
2851
|
this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
|
|
2784
2852
|
// this.matterbridgeInformation.matterbridgeFabricInfo = this.matterbridgeFabricInfo;
|
|
2785
2853
|
const response = { wssHost, qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2786
|
-
this.log.debug('Response:', debugStringify(response));
|
|
2854
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2787
2855
|
res.json(response);
|
|
2788
2856
|
});
|
|
2789
2857
|
// Endpoint to provide plugins
|
|
2790
2858
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2791
2859
|
this.log.debug('The frontend sent /api/plugins');
|
|
2792
|
-
|
|
2860
|
+
const response = await this.getBaseRegisteredPlugins(true);
|
|
2861
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2862
|
+
res.json(response);
|
|
2793
2863
|
});
|
|
2794
2864
|
// Endpoint to provide devices
|
|
2795
2865
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
@@ -2816,6 +2886,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2816
2886
|
cluster: cluster,
|
|
2817
2887
|
});
|
|
2818
2888
|
});
|
|
2889
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2819
2890
|
res.json(data);
|
|
2820
2891
|
});
|
|
2821
2892
|
// Endpoint to provide the cluster servers of the devices
|
|
@@ -2946,7 +3017,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2946
3017
|
plugin.platform?.log.setLogDebug(this.debugEnabled);
|
|
2947
3018
|
});
|
|
2948
3019
|
}
|
|
2949
|
-
// Handle the command
|
|
3020
|
+
// Handle the command unregister from Settings
|
|
2950
3021
|
if (command === 'unregister') {
|
|
2951
3022
|
await this.unregisterAndShutdownProcess();
|
|
2952
3023
|
}
|
|
@@ -2958,7 +3029,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2958
3029
|
if (command === 'factoryreset') {
|
|
2959
3030
|
this.shutdownProcessAndFactoryReset(); // No await do it asyncronously
|
|
2960
3031
|
}
|
|
2961
|
-
// Handle the command
|
|
3032
|
+
// Handle the command shutdown from Header
|
|
2962
3033
|
if (command === 'shutdown') {
|
|
2963
3034
|
this.shutdownProcess(); // No await do it asyncronously
|
|
2964
3035
|
}
|
|
@@ -2980,7 +3051,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2980
3051
|
}
|
|
2981
3052
|
this.updateProcess();
|
|
2982
3053
|
}
|
|
2983
|
-
// Handle the command
|
|
3054
|
+
// Handle the command saveconfig from Home
|
|
2984
3055
|
if (command === 'saveconfig') {
|
|
2985
3056
|
param = param.replace(/\*/g, '\\');
|
|
2986
3057
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
@@ -3030,6 +3101,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3030
3101
|
this.registeredPlugins.push(plugin);
|
|
3031
3102
|
await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
|
|
3032
3103
|
this.log.info(`Plugin ${plg}${packageJsonPath}${nf} type ${plugin.type} added to matterbridge. Restart required.`);
|
|
3104
|
+
this.startPlugin(plugin, 'The plugin has been added to Matterbridge', true);
|
|
3033
3105
|
}
|
|
3034
3106
|
else {
|
|
3035
3107
|
this.log.error(`Error adding plugin ${plg}${packageJsonPath}${er}`);
|
|
@@ -3047,8 +3119,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
3047
3119
|
if (index !== -1) {
|
|
3048
3120
|
if (this.registeredPlugins[index].platform) {
|
|
3049
3121
|
await this.registeredPlugins[index].platform?.onShutdown('The plugin has been removed.');
|
|
3050
|
-
// await this.savePluginConfig(this.registeredPlugins[index]);
|
|
3051
3122
|
}
|
|
3123
|
+
// Remove all devices from the plugin
|
|
3124
|
+
await this.removeAllBridgedDevices(this.registeredPlugins[index].name);
|
|
3125
|
+
// Remove the plugin from the registered plugins
|
|
3052
3126
|
this.registeredPlugins.splice(index, 1);
|
|
3053
3127
|
await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins());
|
|
3054
3128
|
this.log.info(`Plugin ${plg}${param}${nf} removed from matterbridge`);
|
|
@@ -3065,6 +3139,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3065
3139
|
const plugin = plugins.find((plugin) => plugin.name === param);
|
|
3066
3140
|
if (plugin) {
|
|
3067
3141
|
plugin.enabled = true;
|
|
3142
|
+
plugin.locked = undefined;
|
|
3068
3143
|
plugin.error = undefined;
|
|
3069
3144
|
plugin.loaded = undefined;
|
|
3070
3145
|
plugin.started = undefined;
|
|
@@ -3072,6 +3147,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3072
3147
|
plugin.connected = undefined;
|
|
3073
3148
|
plugin.platform = undefined;
|
|
3074
3149
|
plugin.registeredDevices = undefined;
|
|
3150
|
+
plugin.addedDevices = undefined;
|
|
3075
3151
|
await this.nodeContext?.set('plugins', plugins);
|
|
3076
3152
|
this.log.info(`Enabled plugin ${plg}${param}${nf}`);
|
|
3077
3153
|
}
|
|
@@ -3096,10 +3172,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
3096
3172
|
const pluginToDisable = this.findPlugin(param);
|
|
3097
3173
|
if (pluginToDisable) {
|
|
3098
3174
|
if (pluginToDisable.platform) {
|
|
3099
|
-
await pluginToDisable.platform.onShutdown('The plugin has been
|
|
3100
|
-
// await this.savePluginConfig(pluginToDisable);
|
|
3175
|
+
await pluginToDisable.platform.onShutdown('The plugin has been disabled.');
|
|
3101
3176
|
}
|
|
3177
|
+
// Remove all devices from the plugin
|
|
3178
|
+
this.log.info(`Unregistering devices for plugin ${plg}${pluginToDisable.name}${nf}...`);
|
|
3179
|
+
await this.removeAllBridgedDevices(pluginToDisable.name);
|
|
3102
3180
|
pluginToDisable.enabled = false;
|
|
3181
|
+
pluginToDisable.locked = undefined;
|
|
3103
3182
|
pluginToDisable.error = undefined;
|
|
3104
3183
|
pluginToDisable.loaded = undefined;
|
|
3105
3184
|
pluginToDisable.started = undefined;
|
|
@@ -3107,12 +3186,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
3107
3186
|
pluginToDisable.connected = undefined;
|
|
3108
3187
|
pluginToDisable.platform = undefined;
|
|
3109
3188
|
pluginToDisable.registeredDevices = undefined;
|
|
3189
|
+
pluginToDisable.addedDevices = undefined;
|
|
3190
|
+
// Save the plugins state in node storage
|
|
3110
3191
|
const plugins = await this.nodeContext?.get('plugins');
|
|
3111
3192
|
if (!plugins)
|
|
3112
3193
|
return;
|
|
3113
3194
|
const plugin = plugins.find((plugin) => plugin.name === param);
|
|
3114
3195
|
if (plugin) {
|
|
3115
3196
|
plugin.enabled = false;
|
|
3197
|
+
plugin.locked = undefined;
|
|
3116
3198
|
plugin.error = undefined;
|
|
3117
3199
|
plugin.loaded = undefined;
|
|
3118
3200
|
plugin.started = undefined;
|
|
@@ -3120,6 +3202,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3120
3202
|
plugin.connected = undefined;
|
|
3121
3203
|
plugin.platform = undefined;
|
|
3122
3204
|
plugin.registeredDevices = undefined;
|
|
3205
|
+
plugin.addedDevices = undefined;
|
|
3123
3206
|
await this.nodeContext?.set('plugins', plugins);
|
|
3124
3207
|
this.log.info(`Disabled plugin ${plg}${param}${nf}`);
|
|
3125
3208
|
}
|
|
@@ -3127,46 +3210,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
3127
3210
|
}
|
|
3128
3211
|
res.json({ message: 'Command received' });
|
|
3129
3212
|
});
|
|
3130
|
-
// Fallback for routing
|
|
3213
|
+
// Fallback for routing (must be the last route but it should not be used because the frontend is static)
|
|
3131
3214
|
this.expressApp.get('*', (req, res) => {
|
|
3132
|
-
this.log.
|
|
3215
|
+
this.log.warn('The frontend sent:', req.url);
|
|
3216
|
+
this.log.warn('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
3133
3217
|
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
3134
3218
|
});
|
|
3135
|
-
if (!useHttps) {
|
|
3136
|
-
// Listen on HTTP
|
|
3137
|
-
this.expressServer = this.expressApp.listen(port, () => {
|
|
3138
|
-
this.log.info(`The frontend is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
3139
|
-
});
|
|
3140
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3141
|
-
this.expressServer.on('error', (error) => {
|
|
3142
|
-
this.log.error(`Frontend error listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
3143
|
-
switch (error.code) {
|
|
3144
|
-
case 'EACCES':
|
|
3145
|
-
this.log.error(`Port ${port} requires elevated privileges`);
|
|
3146
|
-
break;
|
|
3147
|
-
case 'EADDRINUSE':
|
|
3148
|
-
this.log.error(`Port ${port} is already in use`);
|
|
3149
|
-
break;
|
|
3150
|
-
default:
|
|
3151
|
-
this.log.error(`Port ${port} requires elevated privileges`);
|
|
3152
|
-
}
|
|
3153
|
-
process.exit(1);
|
|
3154
|
-
});
|
|
3155
|
-
}
|
|
3156
|
-
else {
|
|
3157
|
-
// SSL certificate and private key paths
|
|
3158
|
-
const options = {
|
|
3159
|
-
cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.pem')), // Ensure the path is correct
|
|
3160
|
-
key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.key')), // Ensure the path is correct
|
|
3161
|
-
};
|
|
3162
|
-
// Create HTTPS server
|
|
3163
|
-
const httpsServer = https.createServer(options, this.expressApp);
|
|
3164
|
-
// Specify the port to listen on, for example 443 for default HTTPS
|
|
3165
|
-
const PORT = 443;
|
|
3166
|
-
httpsServer.listen(PORT, () => {
|
|
3167
|
-
this.log.info(`The frontend is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${PORT}${UNDERLINEOFF}${rs}`);
|
|
3168
|
-
});
|
|
3169
|
-
}
|
|
3170
3219
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
3171
3220
|
}
|
|
3172
3221
|
/**
|
|
@@ -3228,125 +3277,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
3228
3277
|
return attributes;
|
|
3229
3278
|
}
|
|
3230
3279
|
}
|
|
3231
|
-
/*
|
|
3232
|
-
TO IMPLEMENT
|
|
3233
|
-
|
|
3234
|
-
import { spawn } from 'child_process';
|
|
3235
|
-
|
|
3236
|
-
function restartProcess() {
|
|
3237
|
-
// Spawn a new process
|
|
3238
|
-
const newProcess = spawn(process.argv[0], process.argv.slice(1), {
|
|
3239
|
-
detached: true,
|
|
3240
|
-
stdio: 'inherit',
|
|
3241
|
-
});
|
|
3242
|
-
|
|
3243
|
-
// Handle errors
|
|
3244
|
-
newProcess.on('error', (err) => {
|
|
3245
|
-
console.error('Failed to start new process:', err);
|
|
3246
|
-
});
|
|
3247
|
-
|
|
3248
|
-
// Unreference the new process so that the current process can exit
|
|
3249
|
-
newProcess.unref();
|
|
3250
|
-
|
|
3251
|
-
// Exit the current process
|
|
3252
|
-
cleanup();
|
|
3253
|
-
process.exit();
|
|
3254
|
-
}
|
|
3255
|
-
|
|
3256
|
-
import React from 'react';
|
|
3257
|
-
import Form from "@rjsf/core";
|
|
3258
|
-
|
|
3259
|
-
const schema = {
|
|
3260
|
-
title: "Todo",
|
|
3261
|
-
type: "object",
|
|
3262
|
-
required: ["title"],
|
|
3263
|
-
properties: {
|
|
3264
|
-
title: {type: "string", title: "Title", default: "A new task"},
|
|
3265
|
-
done: {type: "boolean", title: "Done?", default: false}
|
|
3266
|
-
}
|
|
3267
|
-
};
|
|
3268
|
-
|
|
3269
|
-
const log = (type) => console.log.bind(console, type);
|
|
3270
|
-
|
|
3271
|
-
function Todo() {
|
|
3272
|
-
return (
|
|
3273
|
-
<Form schema={schema}
|
|
3274
|
-
onChange={log("changed")}
|
|
3275
|
-
onSubmit={log("submitted")}
|
|
3276
|
-
onError={log("errors")} />
|
|
3277
|
-
);
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
export default Todo;
|
|
3281
|
-
|
|
3282
|
-
/*
|
|
3283
|
-
How frontend was created
|
|
3284
|
-
npx create-react-app matterbridge-frontend
|
|
3285
|
-
cd matterbridge-frontend
|
|
3286
|
-
npm install react-router-dom
|
|
3287
|
-
|
|
3288
|
-
Success! Created frontend at C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend
|
|
3289
|
-
Inside that directory, you can run several commands:
|
|
3290
|
-
|
|
3291
|
-
npm start
|
|
3292
|
-
Starts the development server.
|
|
3293
|
-
|
|
3294
|
-
npm run build
|
|
3295
|
-
Bundles the app into static files for production.
|
|
3296
|
-
|
|
3297
|
-
npm test
|
|
3298
|
-
Starts the test runner.
|
|
3299
|
-
|
|
3300
|
-
npm run eject
|
|
3301
|
-
Removes this tool and copies build dependencies, configuration files
|
|
3302
|
-
and scripts into the app directory. If you do this, you can’t go back!
|
|
3303
|
-
|
|
3304
|
-
We suggest that you begin by typing:
|
|
3305
|
-
|
|
3306
|
-
cd frontend
|
|
3307
|
-
npm start
|
|
3308
|
-
|
|
3309
|
-
Happy hacking!
|
|
3310
|
-
PS C:\Users\lligu\OneDrive\GitHub\matterbridge> cd frontend
|
|
3311
|
-
PS C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend> npm run build
|
|
3312
|
-
|
|
3313
|
-
> frontend@0.1.0 build
|
|
3314
|
-
> react-scripts build
|
|
3315
|
-
|
|
3316
|
-
Creating an optimized production build...
|
|
3317
|
-
One of your dependencies, babel-preset-react-app, is importing the
|
|
3318
|
-
"@babel/plugin-proposal-private-property-in-object" package without
|
|
3319
|
-
declaring it in its dependencies. This is currently working because
|
|
3320
|
-
"@babel/plugin-proposal-private-property-in-object" is already in your
|
|
3321
|
-
node_modules folder for unrelated reasons, but it may break at any time.
|
|
3322
|
-
|
|
3323
|
-
babel-preset-react-app is part of the create-react-app project, which
|
|
3324
|
-
is not maintianed anymore. It is thus unlikely that this bug will
|
|
3325
|
-
ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to
|
|
3326
|
-
your devDependencies to work around this error. This will make this message
|
|
3327
|
-
go away.
|
|
3328
|
-
|
|
3329
|
-
Compiled successfully.
|
|
3330
|
-
|
|
3331
|
-
File sizes after gzip:
|
|
3332
|
-
|
|
3333
|
-
46.65 kB build\static\js\main.9b7ec296.js
|
|
3334
|
-
1.77 kB build\static\js\453.8ab44547.chunk.js
|
|
3335
|
-
513 B build\static\css\main.f855e6bc.css
|
|
3336
|
-
|
|
3337
|
-
The project was built assuming it is hosted at /.
|
|
3338
|
-
You can control this with the homepage field in your package.json.
|
|
3339
|
-
|
|
3340
|
-
The build folder is ready to be deployed.
|
|
3341
|
-
You may serve it with a static server:
|
|
3342
|
-
|
|
3343
|
-
npm install -g serve
|
|
3344
|
-
serve -s build
|
|
3345
|
-
|
|
3346
|
-
Find out more about deployment here:
|
|
3347
|
-
|
|
3348
|
-
https://cra.link/deployment
|
|
3349
|
-
|
|
3350
|
-
PS C:\Users\lligu\OneDrive\GitHub\matterbridge\frontend>
|
|
3351
|
-
*/
|
|
3352
3280
|
//# sourceMappingURL=matterbridge.js.map
|