matterbridge 1.2.8 → 1.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ import { AnsiLogger, BRIGHT, RESET, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugSt
26
26
  import { fileURLToPath, pathToFileURL } from 'url';
27
27
  import { promises as fs } from 'fs';
28
28
  import { exec, spawn } from 'child_process';
29
+ import https from 'https';
29
30
  import EventEmitter from 'events';
30
31
  import express from 'express';
31
32
  import os from 'os';
@@ -81,6 +82,7 @@ export class Matterbridge extends EventEmitter {
81
82
  matterbridgeVersion = '';
82
83
  matterbridgeLatestVersion = '';
83
84
  bridgeMode = '';
85
+ restartMode = '';
84
86
  debugEnabled = false;
85
87
  port = 5540;
86
88
  log;
@@ -127,9 +129,11 @@ export class Matterbridge extends EventEmitter {
127
129
  *
128
130
  * @returns A Promise that resolves when the initialization is complete.
129
131
  */
130
- async initializeAsExtension(dataPath, debugEnabled) {
132
+ async startExtension(dataPath, debugEnabled, extensionVersion, port = 5560) {
133
+ // Set the bridge mode
134
+ this.bridgeMode = 'bridge';
131
135
  // Set the first port to use
132
- this.port = 5560;
136
+ this.port = port;
133
137
  // Set Matterbridge logger
134
138
  this.debugEnabled = debugEnabled;
135
139
  this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: this.debugEnabled });
@@ -140,6 +144,19 @@ export class Matterbridge extends EventEmitter {
140
144
  this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
141
145
  this.log.debug('Creating node storage context for matterbridge: matterbridge');
142
146
  this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
147
+ const plugin = {
148
+ path: '',
149
+ type: 'DynamicPlatform',
150
+ name: 'MatterbridgeExtension',
151
+ version: '1.0.0',
152
+ description: 'Matterbridge extension',
153
+ author: 'https://github.com/Luligu',
154
+ enabled: false,
155
+ registeredDevices: 0,
156
+ addedDevices: 0,
157
+ };
158
+ this.registeredPlugins.push(plugin);
159
+ await this.nodeContext?.set('plugins', this.getBaseRegisteredPlugins());
143
160
  // Log system info and create .matterbridge directory
144
161
  await this.logNodeAndSystemInfo();
145
162
  this.matterbridgeDirectory = dataPath;
@@ -148,14 +165,16 @@ export class Matterbridge extends EventEmitter {
148
165
  Logger.format = Format.ANSI;
149
166
  // Start the storage and create matterbridgeContext
150
167
  await this.startStorage('json', path.join(this.matterbridgeDirectory, 'matterbridge.json'));
151
- this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
152
- if (!this.storageManager || !this.matterbridgeContext)
153
- return;
168
+ if (!this.storageManager)
169
+ return false;
170
+ this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge zigbee2MQTT', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'zigbee2MQTT Matter extension');
171
+ if (!this.matterbridgeContext)
172
+ return false;
154
173
  await this.matterbridgeContext.set('softwareVersion', 1);
155
174
  await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
156
- await this.matterbridgeContext.set('hardwareVersion', 0);
157
- await this.matterbridgeContext.set('hardwareVersionString', '0.0.1');
158
- this.createMatterServer(this.storageManager);
175
+ await this.matterbridgeContext.set('hardwareVersion', 1);
176
+ await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
177
+ this.matterServer = this.createMatterServer(this.storageManager);
159
178
  this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
160
179
  this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
161
180
  this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
@@ -163,24 +182,38 @@ export class Matterbridge extends EventEmitter {
163
182
  this.log.debug('Adding matterbridge aggregator to commissioning server');
164
183
  this.commissioningServer.addDevice(this.matterAggregator);
165
184
  this.log.debug('Adding matterbridge commissioning server to matter server');
166
- await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
167
- this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
168
- this.commissioningServer.setReachability(true);
169
- this.log.debug('Starting matter server...');
185
+ await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
170
186
  await this.startMatterServer();
171
187
  this.log.info('Matter server started');
172
188
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
189
+ // Set reachability to true and trigger event after 60 seconds
190
+ setTimeout(() => {
191
+ this.log.info(`*Setting reachability to true for ${plg}Matterbridge${db}`);
192
+ if (this.commissioningServer)
193
+ this.setCommissioningServerReachability(this.commissioningServer, true);
194
+ if (this.matterAggregator)
195
+ this.setAggregatorReachability(this.matterAggregator, true);
196
+ }, 60 * 1000);
197
+ return this.commissioningServer.isCommissioned();
173
198
  }
174
199
  /**
175
200
  * Close the Matterbridge instance as extension for zigbee2mqtt.
176
201
  *
177
202
  * @returns A Promise that resolves when the initialization is complete.
178
203
  */
179
- async closeAsExtension() {
204
+ async stopExtension() {
180
205
  // Closing matter
181
206
  await this.stopMatter();
207
+ // Clearing the session manager
208
+ // this.matterbridgeContext?.createContext('SessionManager').clear();
182
209
  // Closing storage
183
210
  await this.stopStorage();
211
+ this.log.info('Matter server stopped');
212
+ }
213
+ isExtensionCommissioned() {
214
+ if (!this.commissioningServer)
215
+ return false;
216
+ return this.commissioningServer.isCommissioned();
184
217
  }
185
218
  /**
186
219
  * Initializes the Matterbridge application.
@@ -220,6 +253,11 @@ export class Matterbridge extends EventEmitter {
220
253
  }
221
254
  // Set the first port to use
222
255
  this.port = getIntParameter('port') ?? 5540;
256
+ // Set the restart mode
257
+ if (hasParameter('service'))
258
+ this.restartMode = 'service';
259
+ if (hasParameter('docker'))
260
+ this.restartMode = 'docker';
223
261
  // Set Matterbridge logger
224
262
  if (hasParameter('debug'))
225
263
  this.debugEnabled = true;
@@ -254,7 +292,8 @@ export class Matterbridge extends EventEmitter {
254
292
  await this.logNodeAndSystemInfo();
255
293
  this.log.info(
256
294
  // eslint-disable-next-line max-len
257
- `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
295
+ `Matterbridge version ${this.matterbridgeVersion} mode ${hasParameter('bridge') ? 'bridge' : ''}${hasParameter('childbridge') ? 'childbridge' : ''}${hasParameter('controller') ? 'controller' : ''} ` +
296
+ `${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}running on ${this.systemInformation.osType} ${this.systemInformation.osRelease} ${this.systemInformation.osPlatform} ${this.systemInformation.osArch}`);
258
297
  // Check node version and throw error
259
298
  requireMinNodeVersion(18);
260
299
  // Register SIGINT SIGTERM signal handlers
@@ -582,6 +621,7 @@ export class Matterbridge extends EventEmitter {
582
621
  this.log.info(message);
583
622
  process.removeAllListeners('SIGINT');
584
623
  process.removeAllListeners('SIGTERM');
624
+ this.log.debug('All listeners removed');
585
625
  // Calling the shutdown functions with a reason
586
626
  for (const plugin of this.registeredPlugins) {
587
627
  if (!plugin.enabled)
@@ -637,11 +677,13 @@ export class Matterbridge extends EventEmitter {
637
677
  if (this.expressServer) {
638
678
  this.expressServer.close();
639
679
  this.expressServer = undefined;
680
+ this.log.debug('Express server closed successfully');
640
681
  }
641
682
  // Remove listeners
642
683
  if (this.expressApp) {
643
684
  this.expressApp.removeAllListeners();
644
685
  this.expressApp = undefined;
686
+ this.log.debug('Frontend closed successfully');
645
687
  }
646
688
  // Close the WebSocket server
647
689
  if (this.webSocketServer) {
@@ -715,20 +757,6 @@ export class Matterbridge extends EventEmitter {
715
757
  //cleanupTimeout1.unref();
716
758
  }
717
759
  }
718
- /**
719
- * Sets the reachable attribute of a device.
720
- *
721
- * @param device - The device for which to set the reachable attribute.
722
- * @param reachable - The value to set for the reachable attribute.
723
- */
724
- setReachableAttribute(device, reachable) {
725
- const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
726
- if (!basicInformationCluster) {
727
- this.log.error('setReachableAttribute BasicInformationCluster needs to be set!');
728
- return;
729
- }
730
- basicInformationCluster.setReachableAttribute(reachable);
731
- }
732
760
  /**
733
761
  * Adds a device to the Matterbridge.
734
762
  * @param pluginName - The name of the plugin.
@@ -824,7 +852,7 @@ export class Matterbridge extends EventEmitter {
824
852
  return;
825
853
  }
826
854
  if (this.bridgeMode === 'childbridge' && !plugin.connected) {
827
- this.log.warn(`Removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) plugin ${plg}${pluginName}${wr} not connected`);
855
+ this.log.info(`Removing bridged device ${dev}${device.deviceName}${wr} (${dev}${device.name}${wr}) plugin ${plg}${pluginName}${wr} not connected`);
828
856
  return;
829
857
  }
830
858
  // Remove the device from matterbridge aggregator in bridge mode
@@ -971,12 +999,7 @@ export class Matterbridge extends EventEmitter {
971
999
  return;
972
1000
  }
973
1001
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
974
- this.createMatterServer(this.storageManager);
975
- if (!this.matterServer) {
976
- this.log.error('No matter server initialized');
977
- await this.cleanup('No matter server initialized');
978
- return;
979
- }
1002
+ this.matterServer = this.createMatterServer(this.storageManager);
980
1003
  this.log.debug('***Starting startMatterbridge interval for Matterbridge');
981
1004
  let failCount = 0;
982
1005
  const startInterval = setInterval(async () => {
@@ -1152,7 +1175,7 @@ export class Matterbridge extends EventEmitter {
1152
1175
  return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`));
1153
1176
  }
1154
1177
  if (plugin.started) {
1155
- this.log.warn(`Plugin ${plg}${plugin.name}${wr} already started`);
1178
+ this.log.debug(`Plugin ${plg}${plugin.name}${db} already started`);
1156
1179
  return Promise.resolve();
1157
1180
  }
1158
1181
  this.log.info(`Starting plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
@@ -1292,12 +1315,7 @@ export class Matterbridge extends EventEmitter {
1292
1315
  return;
1293
1316
  }
1294
1317
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1295
- this.createMatterServer(this.storageManager);
1296
- if (!this.matterServer) {
1297
- this.log.error('No matter server initialized');
1298
- await this.cleanup('No matter server initialized');
1299
- return;
1300
- }
1318
+ this.matterServer = this.createMatterServer(this.storageManager);
1301
1319
  this.log.info('Creating matter commissioning controller');
1302
1320
  this.commissioningController = new CommissioningController({
1303
1321
  autoConnect: false,
@@ -1454,12 +1472,7 @@ export class Matterbridge extends EventEmitter {
1454
1472
  return;
1455
1473
  }
1456
1474
  this.log.debug('Starting matterbridge in mode', this.bridgeMode);
1457
- this.createMatterServer(this.storageManager);
1458
- if (!this.matterServer) {
1459
- this.log.error('No matter server initialized');
1460
- await this.cleanup('No matter server initialized');
1461
- return;
1462
- }
1475
+ this.matterServer = this.createMatterServer(this.storageManager);
1463
1476
  if (this.bridgeMode === 'bridge') {
1464
1477
  // Plugins are loaded by loadPlugin on startup and plugin.loaded is set to true
1465
1478
  // Plugins are started and configured by callback when Matterbridge is commissioned
@@ -1503,22 +1516,17 @@ export class Matterbridge extends EventEmitter {
1503
1516
  this.commissioningServer.addDevice(this.matterAggregator);
1504
1517
  this.log.debug('Adding matterbridge commissioning server to matter server');
1505
1518
  await this.matterServer?.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
1506
- this.log.debug(`Setting reachability to true for ${plg}Matterbridge${db}`);
1507
- this.commissioningServer.setReachability(true);
1508
- this.log.debug('Starting matter server...');
1509
1519
  await this.startMatterServer();
1510
1520
  this.log.info('Matter server started');
1511
1521
  await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
1512
1522
  //if (hasParameter('advertise')) await this.commissioningServer.advertise();
1513
- /*
1514
- setInterval(() => {
1515
- this.matterAggregator?.getBridgedDevices().forEach((device) => {
1516
- this.log.info(`Bridged device: ${dev}${device.name}${nf}`);
1517
- device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(true);
1518
- device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: true });
1519
- });
1520
- }, 30 * 1000);
1521
- */
1523
+ setTimeout(() => {
1524
+ this.log.info(`*Setting reachability to true for ${plg}Matterbridge${db}`);
1525
+ if (this.commissioningServer)
1526
+ this.setCommissioningServerReachability(this.commissioningServer, true);
1527
+ if (this.matterAggregator)
1528
+ this.setAggregatorReachability(this.matterAggregator, true);
1529
+ }, 60 * 1000);
1522
1530
  }, 1000);
1523
1531
  }
1524
1532
  if (this.bridgeMode === 'childbridge') {
@@ -1555,6 +1563,8 @@ export class Matterbridge extends EventEmitter {
1555
1563
  plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
1556
1564
  this.log.debug(`Adding device ${dev}${registeredDevice.device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
1557
1565
  plugin.commissioningServer.addDevice(registeredDevice.device);
1566
+ if (!plugin.device)
1567
+ plugin.device = registeredDevice.device;
1558
1568
  }
1559
1569
  this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
1560
1570
  await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
@@ -1595,21 +1605,6 @@ export class Matterbridge extends EventEmitter {
1595
1605
  return;
1596
1606
  clearInterval(startMatterInterval);
1597
1607
  this.log.info('Starting matter server...');
1598
- // Setting reachability to true
1599
- this.registeredPlugins.forEach((plugin) => {
1600
- if (!plugin.enabled)
1601
- return;
1602
- this.log.debug(`Setting reachability to true for ${plg}${plugin.name}${db}`);
1603
- plugin.commissioningServer?.setReachability(true);
1604
- this.registeredDevices.forEach((registeredDevice) => {
1605
- if (registeredDevice.plugin === plugin.name) {
1606
- if (plugin.type === 'AccessoryPlatform')
1607
- this.setReachableAttribute(registeredDevice.device, true);
1608
- if (plugin.type === 'DynamicPlatform')
1609
- registeredDevice.device.setBridgedDeviceReachability(true);
1610
- }
1611
- });
1612
- });
1613
1608
  await this.startMatterServer();
1614
1609
  this.log.info('Matter server started');
1615
1610
  for (const plugin of this.registeredPlugins) {
@@ -1628,6 +1623,14 @@ export class Matterbridge extends EventEmitter {
1628
1623
  continue;
1629
1624
  }
1630
1625
  await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
1626
+ // Setting reachability to true
1627
+ this.log.info(`*Setting reachability to true for ${plg}${plugin.name}${db}`);
1628
+ if (plugin.commissioningServer)
1629
+ this.setCommissioningServerReachability(plugin.commissioningServer, true);
1630
+ if (plugin.type === 'AccessoryPlatform' && plugin.device)
1631
+ this.setDeviceReachability(plugin.device, true);
1632
+ if (plugin.type === 'DynamicPlatform' && plugin.aggregator)
1633
+ this.setAggregatorReachability(plugin.aggregator, true);
1631
1634
  }
1632
1635
  Logger.defaultLogLevel = this.debugEnabled ? Level.DEBUG : Level.INFO;
1633
1636
  //clearInterval(startMatterInterval);
@@ -1644,7 +1647,7 @@ export class Matterbridge extends EventEmitter {
1644
1647
  await this.cleanup('No matter server initialized');
1645
1648
  return;
1646
1649
  }
1647
- this.log.debug('Starting matter server');
1650
+ this.log.debug('Starting matter server...');
1648
1651
  await this.matterServer.start();
1649
1652
  this.log.debug('Started matter server');
1650
1653
  }
@@ -1723,8 +1726,8 @@ export class Matterbridge extends EventEmitter {
1723
1726
  if (!commissioningServer.isCommissioned()) {
1724
1727
  this.log.info(`***The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code ...`);
1725
1728
  const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
1726
- storageContext.set('qrPairingCode', qrPairingCode);
1727
- storageContext.set('manualPairingCode', manualPairingCode);
1729
+ await storageContext.set('qrPairingCode', qrPairingCode);
1730
+ await storageContext.set('manualPairingCode', manualPairingCode);
1728
1731
  await nodeContext.set('qrPairingCode', qrPairingCode);
1729
1732
  await nodeContext.set('manualPairingCode', manualPairingCode);
1730
1733
  const QrCode = new QrCodeSchema();
@@ -1764,6 +1767,49 @@ export class Matterbridge extends EventEmitter {
1764
1767
  }
1765
1768
  return plugin;
1766
1769
  }
1770
+ /**
1771
+ * Sets the reachability of a commissioning server and trigger.
1772
+ *
1773
+ * @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
1774
+ * @param {boolean} reachable - The new reachability status.
1775
+ */
1776
+ setCommissioningServerReachability(commissioningServer, reachable) {
1777
+ const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
1778
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
1779
+ basicInformationCluster.setReachableAttribute(reachable);
1780
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
1781
+ basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1782
+ }
1783
+ /**
1784
+ * Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
1785
+ * @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
1786
+ * @param {boolean} reachable - A boolean indicating the reachability status to set.
1787
+ */
1788
+ setAggregatorReachability(matterAggregator, reachable) {
1789
+ const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
1790
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
1791
+ basicInformationCluster.setReachableAttribute(reachable);
1792
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
1793
+ basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1794
+ matterAggregator.getBridgedDevices().forEach((device) => {
1795
+ this.log.debug(`*Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
1796
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
1797
+ device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
1798
+ });
1799
+ }
1800
+ /**
1801
+ * Sets the reachability of a device and trigger.
1802
+ *
1803
+ * @param {MatterbridgeDevice} device - The device to set the reachability for.
1804
+ * @param {boolean} reachable - The new reachability status of the device.
1805
+ */
1806
+ setDeviceReachability(device, reachable) {
1807
+ const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
1808
+ if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
1809
+ basicInformationCluster.setReachableAttribute(reachable);
1810
+ if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
1811
+ basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
1812
+ }
1767
1813
  /**
1768
1814
  * Creates a matter commissioning server.
1769
1815
  *
@@ -1814,9 +1860,16 @@ export class Matterbridge extends EventEmitter {
1814
1860
  const info = commissioningServer.getActiveSessionInformation(fabricIndex);
1815
1861
  let connected = false;
1816
1862
  info.forEach((session) => {
1817
- this.log.debug(`***Active session changed on fabric ${fabricIndex} ${session.fabric?.rootVendorId}/${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
1863
+ this.log.info(`***Active session changed on fabric ${fabricIndex} ${session.fabric?.rootVendorId}/${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
1818
1864
  if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
1819
- this.log.info(`***Controller ${session.fabric?.rootVendorId}/${session.fabric?.label} connected to ${plg}${pluginName}${nf}`);
1865
+ let controllerName = '';
1866
+ if (session.fabric?.rootVendorId === 4937)
1867
+ controllerName = 'AppleHome';
1868
+ if (session.fabric?.rootVendorId === 4362)
1869
+ controllerName = 'SmartThings';
1870
+ if (session.fabric?.rootVendorId === 4939)
1871
+ controllerName = 'HomeAssistant';
1872
+ this.log.info(`***Controller ${session.fabric?.rootVendorId}${controllerName !== '' ? '(' + controllerName + ')' : ''}/${session.fabric?.label} connected to ${plg}${pluginName}${nf} on session ${session.name}`);
1820
1873
  connected = true;
1821
1874
  }
1822
1875
  });
@@ -1874,7 +1927,7 @@ export class Matterbridge extends EventEmitter {
1874
1927
  const info = commissioningServer.getCommissionedFabricInformation(fabricIndex);
1875
1928
  this.log.debug(`***Commissioning changed on fabric ${fabricIndex} for ${plg}${pluginName}${nf}`, debugStringify(info));
1876
1929
  if (info.length === 0) {
1877
- this.log.warn(`***Commissioning removed from fabric ${fabricIndex} for ${plg}${pluginName}${nf}. Resetting the commissioning server ...`);
1930
+ this.log.warn(`***Commissioning removed from fabric ${fabricIndex} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
1878
1931
  await commissioningServer.factoryReset();
1879
1932
  if (pluginName === 'Matterbridge') {
1880
1933
  await this.matterbridgeContext?.clearAll();
@@ -1889,6 +1942,7 @@ export class Matterbridge extends EventEmitter {
1889
1942
  }
1890
1943
  }
1891
1944
  }
1945
+ this.log.warn(`***Restart to activate the pairing for ${plg}${pluginName}${wr}`);
1892
1946
  }
1893
1947
  },
1894
1948
  });
@@ -1902,8 +1956,9 @@ export class Matterbridge extends EventEmitter {
1902
1956
  */
1903
1957
  createMatterServer(storageManager) {
1904
1958
  this.log.debug('Creating matter server');
1905
- this.matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
1959
+ const matterServer = new MatterServer(storageManager, { mdnsAnnounceInterface: undefined });
1906
1960
  this.log.debug('Created matter server');
1961
+ return matterServer;
1907
1962
  }
1908
1963
  /**
1909
1964
  * Creates a Matter Aggregator.
@@ -2249,18 +2304,25 @@ export class Matterbridge extends EventEmitter {
2249
2304
  childProcess.stdout.on('data', (data) => {
2250
2305
  const message = data.toString().trim();
2251
2306
  //this.log.info('\n' + message);
2252
- this.wssSendMessage('spawn', 'stdout', message);
2307
+ this.wssSendMessage('Matterbridge:spawn', 'spawn', message);
2253
2308
  });
2254
2309
  }
2255
2310
  if (childProcess.stderr) {
2256
2311
  childProcess.stderr.on('data', (data) => {
2257
2312
  const message = data.toString().trim();
2258
2313
  //this.log.debug('\n' + message);
2259
- this.wssSendMessage('spawn', 'stderr', message);
2314
+ this.wssSendMessage('Matterbridge:spawn', 'spawn', message);
2260
2315
  });
2261
2316
  }
2262
2317
  });
2263
2318
  }
2319
+ /**
2320
+ * Sends a WebSocket message to all connected clients.
2321
+ *
2322
+ * @param {string} type - The type of the message: Matterbridge, Plugin, Device, ...
2323
+ * @param {string} subType - The subtype of the message: debug info warn error ....
2324
+ * @param {string} message - The content of the message.
2325
+ */
2264
2326
  wssSendMessage(type, subType, message) {
2265
2327
  // Remove ANSI escape codes from the message
2266
2328
  // eslint-disable-next-line no-control-regex
@@ -2280,17 +2342,44 @@ export class Matterbridge extends EventEmitter {
2280
2342
  */
2281
2343
  async initializeFrontend(port = 8283) {
2282
2344
  this.log.debug(`Initializing the frontend on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2283
- // Create a WebSocket server
2284
- this.webSocketServer = new WebSocketServer({ port: 8284 });
2345
+ const wssPort = 8284;
2346
+ const useHttps = false;
2347
+ //const wssHost = (useHttps ? 'wss://' : 'ws://') + `${os.hostname().toLowerCase()}:${wssPort}`;
2348
+ const wssHost = (useHttps ? 'wss://' : 'ws://') + `${this.systemInformation.ipv4Address}:${wssPort}`;
2349
+ if (!useHttps) {
2350
+ // Create a WebSocket server no certificate required
2351
+ this.webSocketServer = new WebSocketServer({ port: wssPort });
2352
+ this.log.info(`WebSocket server listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2353
+ }
2354
+ else {
2355
+ // Define the options for HTTPS server
2356
+ // openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mykey.key -out mycert.pem -config openssl.cnf
2357
+ // For wss connect the browser to https://laptop5_luca:8284/ https://192.168.1.189/log and accept the certificate
2358
+ const serverOptions = {
2359
+ cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/mycert.pem')), // Ensure the path is correct
2360
+ key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/mykey.key')), // Ensure the path is correct
2361
+ // cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.pem')), // Ensure the path is correct
2362
+ // key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.key')), // Ensure the path is correct
2363
+ };
2364
+ // Create an HTTPS server
2365
+ const httpsServer = https.createServer(serverOptions);
2366
+ // Attach WebSocket server to HTTPS server
2367
+ this.webSocketServer = new WebSocketServer({ server: httpsServer });
2368
+ // Listen on a specific port
2369
+ httpsServer.listen(wssPort, () => {
2370
+ this.log.info(`WebSocket server listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
2371
+ });
2372
+ }
2285
2373
  this.webSocketServer.on('connection', (ws) => {
2286
- this.log.debug('WebSocketServer client connected');
2374
+ this.log.info('WebSocketServer client connected');
2287
2375
  this.log.setGlobalCallback(this.wssSendMessage.bind(this));
2288
2376
  this.log.debug('WebSocketServer activated logger callback');
2377
+ this.wssSendMessage('Matterbridge', 'info', 'WebSocketServer client connected to Matterbridge');
2289
2378
  ws.on('message', (message) => {
2290
- this.log.debug(`WebSocketServer received message => ${message}`);
2379
+ this.log.info(`WebSocketServer received message => ${message}`);
2291
2380
  });
2292
2381
  ws.on('close', () => {
2293
- this.log.debug('WebSocketServer client disconnected');
2382
+ this.log.info('WebSocketServer client disconnected');
2294
2383
  if (this.webSocketServer?.clients.size === 0) {
2295
2384
  this.log.setGlobalCallback(undefined);
2296
2385
  this.log.debug('WebSocketServer deactivated logger callback');
@@ -2303,7 +2392,7 @@ export class Matterbridge extends EventEmitter {
2303
2392
  // Serve React build directory
2304
2393
  this.expressApp = express();
2305
2394
  this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
2306
- // Endpoint to provide login code
2395
+ // Endpoint to validate login code
2307
2396
  this.expressApp.post('/api/login', express.json(), async (req, res) => {
2308
2397
  const { password } = req.body;
2309
2398
  this.log.debug('The frontend sent /api/login', password);
@@ -2323,63 +2412,30 @@ export class Matterbridge extends EventEmitter {
2323
2412
  res.json({ valid: false });
2324
2413
  }
2325
2414
  });
2326
- // Endpoint to provide host
2327
- this.expressApp.get('/api/wsshost', express.json(), async (req, res) => {
2328
- this.log.debug('The frontend sent /api/wsshost');
2329
- res.json({ host: os.hostname() });
2330
- });
2331
- // Endpoint to provide port
2332
- this.expressApp.get('/api/wssport', express.json(), async (req, res) => {
2333
- this.log.debug('The frontend sent /api/wssport');
2334
- res.json({ port: 8284 });
2335
- });
2336
- // Endpoint to provide manual pairing code
2337
- this.expressApp.get('/api/pairing-code', (req, res) => {
2338
- this.log.debug('The frontend sent /api/pairing-code');
2415
+ // Endpoint to provide settings
2416
+ this.expressApp.get('/api/settings', express.json(), async (req, res) => {
2339
2417
  if (!this.matterbridgeContext) {
2340
- this.log.error('/api/pairing-code matterbridgeContext not found');
2341
- res.json([]);
2342
- return;
2343
- }
2344
- try {
2345
- const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
2346
- res.json(qrData);
2347
- }
2348
- catch (error) {
2349
- if (this.bridgeMode === 'bridge')
2350
- this.log.error('qrPairingCode for /api/qr-code not found');
2418
+ this.log.error('/api/settings matterbridgeContext not found');
2351
2419
  res.json({});
2352
- }
2353
- });
2354
- // Endpoint to provide QR pairing code
2355
- this.expressApp.get('/api/qr-code', (req, res) => {
2356
- this.log.debug('The frontend sent /api/qr-code');
2357
- if (!this.matterbridgeContext) {
2358
- this.log.error('/api/qr-code matterbridgeContext not found');
2359
- res.json([]);
2360
2420
  return;
2361
2421
  }
2422
+ let qrPairingCode = '';
2423
+ let manualPairingCode = '';
2362
2424
  try {
2363
- const qrData = { qrPairingCode: this.matterbridgeContext.get('qrPairingCode'), manualPairingCode: this.matterbridgeContext.get('manualPairingCode') };
2364
- res.json(qrData);
2425
+ qrPairingCode = await this.matterbridgeContext.get('qrPairingCode');
2426
+ manualPairingCode = await this.matterbridgeContext.get('manualPairingCode');
2365
2427
  }
2366
2428
  catch (error) {
2367
2429
  if (this.bridgeMode === 'bridge')
2368
- this.log.error('qrPairingCode for /api/qr-code not found');
2430
+ this.log.error('pairingCode for /api/settings not found');
2369
2431
  res.json({});
2370
2432
  }
2371
- });
2372
- // Endpoint to provide system information
2373
- this.expressApp.get('/api/system-info', (req, res) => {
2374
- this.log.debug('The frontend sent /api/system-info');
2375
- res.json(this.systemInformation);
2376
- });
2377
- // Endpoint to provide matterbridge information
2378
- this.expressApp.get('/api/matterbridge-info', (req, res) => {
2379
- this.log.debug('The frontend sent /api/matterbridge-info');
2380
2433
  this.matterbridgeInformation.bridgeMode = this.bridgeMode;
2381
2434
  this.matterbridgeInformation.debugEnabled = this.debugEnabled;
2382
- res.json(this.matterbridgeInformation);
2435
+ const response = { wssHost, qrPairingCode, manualPairingCode, systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
2436
+ this.log.debug('The frontend sent /api/settings');
2437
+ this.log.debug('Response:', debugStringify(response));
2438
+ res.json(response);
2383
2439
  });
2384
2440
  // Endpoint to provide plugins
2385
2441
  this.expressApp.get('/api/plugins', (req, res) => {
@@ -2440,7 +2496,7 @@ export class Matterbridge extends EventEmitter {
2440
2496
  }
2441
2497
  catch (error) {
2442
2498
  attributeValue = 'Unavailable';
2443
- this.log.debug(`****${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2499
+ this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
2444
2500
  //console.log(error);
2445
2501
  }
2446
2502
  data.push({
@@ -2469,7 +2525,7 @@ export class Matterbridge extends EventEmitter {
2469
2525
  // Handle the command setpassword from Settings
2470
2526
  if (command === 'setpassword') {
2471
2527
  const password = param.slice(1, -1); // Remove the first and last characters
2472
- this.log.info('setpassword', param, password);
2528
+ this.log.debug('setpassword', param, password);
2473
2529
  await this.nodeContext?.set('password', password);
2474
2530
  }
2475
2531
  // Handle the command debugLevel from Settings
@@ -2504,15 +2560,12 @@ export class Matterbridge extends EventEmitter {
2504
2560
  // Handle the command update from Header
2505
2561
  if (command === 'update') {
2506
2562
  this.log.info('Updating matterbridge...');
2507
- //this.wssSendMessage('cmd', 'update', 'Updating matterbridge');
2508
2563
  try {
2509
2564
  await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--loglevel=verbose']);
2510
2565
  this.log.info('Matterbridge has been updated. Full restart required.');
2511
- //this.wssSendMessage('cmd', 'update', 'Matterbridge has been updated. Full restart required.');
2512
2566
  }
2513
2567
  catch (error) {
2514
2568
  this.log.error('Error updating matterbridge');
2515
- //this.wssSendMessage('cmd', 'update', 'Error updating matterbridge');
2516
2569
  res.json({ message: 'Command received' });
2517
2570
  return;
2518
2571
  }
@@ -2522,15 +2575,12 @@ export class Matterbridge extends EventEmitter {
2522
2575
  if (command === 'installplugin') {
2523
2576
  param = param.replace(/\*/g, '\\');
2524
2577
  this.log.info(`Installing plugin ${plg}${param}${db}...`);
2525
- //this.wssSendMessage('cmd', 'installplugin', `Installing plugin ${param}`);
2526
2578
  try {
2527
2579
  await this.spawnCommand('npm', ['install', '-g', param, '--loglevel=verbose']);
2528
2580
  this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
2529
- //this.wssSendMessage('cmd', 'installplugin', `Plugin ${param} installed. Full restart required.`);
2530
2581
  }
2531
2582
  catch (error) {
2532
2583
  this.log.error(`Error installing plugin ${plg}${param}${er}`);
2533
- //this.wssSendMessage('cmd', 'installplugin', `Error installing plugin${param}`);
2534
2584
  res.json({ message: 'Command received' });
2535
2585
  return;
2536
2586
  }
@@ -2653,9 +2703,26 @@ export class Matterbridge extends EventEmitter {
2653
2703
  this.log.debug('The frontend sent *', req.url);
2654
2704
  res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
2655
2705
  });
2656
- this.expressServer = this.expressApp.listen(port, () => {
2657
- this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
2658
- });
2706
+ if (!useHttps) {
2707
+ // Listen on HTTP
2708
+ this.expressServer = this.expressApp.listen(port, () => {
2709
+ this.log.info(`The frontend is running on ${UNDERLINE}http://localhost:${port}${UNDERLINEOFF}${rs}`);
2710
+ });
2711
+ }
2712
+ else {
2713
+ // SSL certificate and private key paths
2714
+ const options = {
2715
+ cert: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.pem')), // Ensure the path is correct
2716
+ key: await fs.readFile(path.join(this.rootDirectory, 'frontend/certificates/laptop5_luca.key')), // Ensure the path is correct
2717
+ };
2718
+ // Create HTTPS server
2719
+ const httpsServer = https.createServer(options, this.expressApp);
2720
+ // Specify the port to listen on, for example 443 for default HTTPS
2721
+ const PORT = 443;
2722
+ httpsServer.listen(PORT, () => {
2723
+ this.log.info(`The frontend is running on ${UNDERLINE}https://localhost:${PORT}${UNDERLINEOFF}${rs}`);
2724
+ });
2725
+ }
2659
2726
  this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
2660
2727
  }
2661
2728
  /**
@@ -2699,6 +2766,8 @@ export class Matterbridge extends EventEmitter {
2699
2766
  attributes += `Humidity: ${clusterServer.getMeasuredValueAttribute() / 100}% `;
2700
2767
  if (clusterServer.name === 'PressureMeasurement')
2701
2768
  attributes += `Pressure: ${clusterServer.getMeasuredValueAttribute()} `;
2769
+ if (clusterServer.name === 'FlowMeasurement')
2770
+ attributes += `Pressure: ${clusterServer.getMeasuredValueAttribute()} `;
2702
2771
  });
2703
2772
  return attributes;
2704
2773
  }
@@ -2715,34 +2784,22 @@ function restartProcess() {
2715
2784
  stdio: 'inherit',
2716
2785
  });
2717
2786
 
2787
+ // Handle errors
2788
+ newProcess.on('error', (err) => {
2789
+ console.error('Failed to start new process:', err);
2790
+ });
2791
+
2718
2792
  // Unreference the new process so that the current process can exit
2719
2793
  newProcess.unref();
2720
2794
 
2721
2795
  // Exit the current process
2796
+ cleanup();
2722
2797
  process.exit();
2723
2798
  }
2724
2799
 
2725
- import * as WebSocket from 'ws';
2726
-
2727
- const wss = new WebSocket.Server({ port: 8080 });
2728
2800
 
2729
- wss.on('connection', ws => {
2730
- ws.on('message', message => {
2731
- console.log(`Received message => ${message}`)
2732
- });
2733
-
2734
- // Send a message to the frontend
2735
- ws.send('Hello from backend!');
2736
- });
2737
-
2738
- const ws = new WebSocket('ws://localhost:8080');
2739
-
2740
- ws.onmessage = (event) => {
2741
- console.log(`Received message => ${event.data}`);
2742
- };
2743
-
2744
- */
2745
2801
  /*
2802
+ How frontend was created
2746
2803
  npx create-react-app matterbridge-frontend
2747
2804
  cd matterbridge-frontend
2748
2805
  npm install react-router-dom