matterbridge 3.3.3-dev-20251014-c1b948a → 3.3.3-dev-20251015-706d832

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 CHANGED
@@ -25,9 +25,24 @@ Advantages:
25
25
 
26
26
  ## [3.3.3] - Not released
27
27
 
28
+ ### Added
29
+
30
+ - [thread]: Added timestamp to WorkerMessage.
31
+ - [macOS]: Added the [plist configuration guide](README-MACOS-PLIST.md).
32
+ - [frontend]: Added download diagnostic and download history to Download menu.
33
+ - [frontend]: Added icon to open the cpu and memory usage in System Information panel.
34
+ - [thermostat]: Added thermostatRunningState attribute. Thanks Ludovic BOUÉ (https://github.com/Luligu/matterbridge/pull/410).
35
+ - [ElectricalPowerMeasurement]: Added createApparentElectricalPowerMeasurementClusterServer cluster helper. Thanks Ludovic BOUÉ (https://github.com/Luligu/matterbridge/pull/411).
36
+
28
37
  ### Changed
29
38
 
30
39
  - [package]: Updated dependencies.
40
+ - [frontend]: Bumped `frontend` version to 3.2.2.
41
+ - [frontend]: Added update check on start.
42
+ - [frontend]: Added icon to update dev in the Header and removed the yellow badges.
43
+ - [frontend]: Added icon to update plugin latest and dev and removed the yellow badges.
44
+ - [frontend]: Added plugin Path in the Name Tooltip.
45
+ - [history]: Added external and array buffers to the history chart.
31
46
 
32
47
  <a href="https://www.buymeacoffee.com/luligugithub">
33
48
  <img src="bmc-button.svg" alt="Buy me a coffee" width="80">
@@ -0,0 +1,242 @@
1
+ # <img src="frontend/public/matterbridge.svg" alt="Matterbridge Logo" width="64px" height="64px">&nbsp;&nbsp;&nbsp;Matterbridge launchctl configuration (macOS)
2
+
3
+ [![npm version](https://img.shields.io/npm/v/matterbridge.svg)](https://www.npmjs.com/package/matterbridge)
4
+ [![npm downloads](https://img.shields.io/npm/dt/matterbridge.svg)](https://www.npmjs.com/package/matterbridge)
5
+ [![Docker Version](https://img.shields.io/docker/v/luligu/matterbridge?label=docker%20version&sort=semver)](https://hub.docker.com/r/luligu/matterbridge)
6
+ [![Docker Pulls](https://img.shields.io/docker/pulls/luligu/matterbridge.svg)](https://hub.docker.com/r/luligu/matterbridge)
7
+ ![Node.js CI](https://github.com/Luligu/matterbridge/actions/workflows/build.yml/badge.svg)
8
+ ![CodeQL](https://github.com/Luligu/matterbridge/actions/workflows/codeql.yml/badge.svg)
9
+ [![codecov](https://codecov.io/gh/Luligu/matterbridge/branch/main/graph/badge.svg)](https://codecov.io/gh/Luligu/matterbridge)
10
+
11
+ [![power by](https://img.shields.io/badge/powered%20by-matter--history-blue)](https://www.npmjs.com/package/matter-history)
12
+ [![power by](https://img.shields.io/badge/powered%20by-node--ansi--logger-blue)](https://www.npmjs.com/package/node-ansi-logger)
13
+ [![power by](https://img.shields.io/badge/powered%20by-node--persist--manager-blue)](https://www.npmjs.com/package/node-persist-manager)
14
+
15
+ ---
16
+
17
+ # Advanced configuration
18
+
19
+ ## Run matterbridge as system service with launchctl (macOS) and its own global node_modules directory
20
+
21
+ ### Optional: cleanup all previous setups
22
+
23
+ ```bash
24
+ sudo rm -rf ~/Matterbridge
25
+ sudo rm -rf ~/.matterbridge
26
+ sudo rm -rf ~/.mattercert
27
+ sudo rm -rf /usr/local/etc/matterbridge
28
+ sudo rm -f /Library/LaunchDaemons/matterbridge.plist
29
+ sudo rm -f /var/log/matterbridge.log /var/log/matterbridge.err
30
+ sudo npm uninstall matterbridge -g
31
+ ```
32
+
33
+ ### Verify node setup
34
+
35
+ ```bash
36
+ node -v
37
+ npm -v
38
+ ```
39
+
40
+ It should output something like:
41
+
42
+ ```
43
+ v22.20.0
44
+ 10.9.3
45
+ ```
46
+
47
+ ### Check node path
48
+
49
+ ```bash
50
+ which node
51
+ ```
52
+
53
+ It should output something like:
54
+
55
+ ```
56
+ /usr/local/bin/node
57
+ ```
58
+
59
+ In this case you will need in the step below to replace **_MYNODEPATH_** with /usr/local/bin
60
+
61
+ ### First create the Matterbridge directories
62
+
63
+ This will create the required directories if they don't exist and install matterbridge in the matterbridge global node_modules directory
64
+
65
+ ```bash
66
+ sudo mkdir -p /usr/local/etc/matterbridge
67
+ sudo mkdir -p /usr/local/etc/matterbridge/.npm-global
68
+ sudo mkdir -p /usr/local/etc/matterbridge/Matterbridge
69
+ sudo mkdir -p /usr/local/etc/matterbridge/.matterbridge
70
+ sudo mkdir -p /usr/local/etc/matterbridge/.mattercert
71
+ sudo chown -R root:wheel /usr/local/etc/matterbridge
72
+ sudo chown -R root:wheel /usr/local/etc/matterbridge/.npm-global
73
+ sudo chmod -R 755 /usr/local/etc/matterbridge/.npm-global
74
+ sudo NPM_CONFIG_PREFIX=/usr/local/etc/matterbridge/.npm-global npm install -g matterbridge --omit=dev
75
+ ```
76
+
77
+ ### Then create a system launchctl configuration file for Matterbridge
78
+
79
+ - create a launchctl configuration file for Matterbridge
80
+
81
+ ```bash
82
+ sudo nano /Library/LaunchDaemons/matterbridge.plist
83
+ ```
84
+
85
+ - add the following to the file, replacing **_MYNODEPATH_** with the path found in the step before:
86
+
87
+ ```
88
+ <?xml version="1.0" encoding="UTF-8"?>
89
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
90
+ <plist version="1.0">
91
+ <dict>
92
+ <key>EnvironmentVariables</key>
93
+ <dict>
94
+ <key>PATH</key>
95
+ <string>/usr/local/etc/matterbridge/.npm-global/bin:MYNODEPATH</string>
96
+ <key>NPM_CONFIG_PREFIX</key>
97
+ <string>/usr/local/etc/matterbridge/.npm-global</string>
98
+ <key>HOME</key>
99
+ <string>/var/root</string>
100
+ <key>NODE_PATH</key>
101
+ <string>/usr/local/etc/matterbridge/.npm-global/lib/node_modules</string>
102
+ </dict>
103
+ <key>KeepAlive</key>
104
+ <true/>
105
+ <key>Label</key>
106
+ <string>matterbridge</string>
107
+ <key>ProgramArguments</key>
108
+ <array>
109
+ <string>/usr/local/etc/matterbridge/.npm-global/bin/matterbridge</string>
110
+ <string>--homedir</string>
111
+ <string>/usr/local/etc/matterbridge</string>
112
+ <string>--service</string>
113
+ <string>--nosudo</string>
114
+ </array>
115
+ <key>RunAtLoad</key>
116
+ <true/>
117
+ <key>StandardErrorPath</key>
118
+ <string>/var/log/matterbridge.err</string>
119
+ <key>StandardOutPath</key>
120
+ <string>/var/log/matterbridge.log</string>
121
+ <key>WorkingDirectory</key>
122
+ <string>/usr/local/etc/matterbridge</string>
123
+ </dict>
124
+ </plist>
125
+ ```
126
+
127
+ - stop matterbridge
128
+
129
+ ```bash
130
+ sudo launchctl bootout system/matterbridge
131
+ ```
132
+
133
+ - check the plist
134
+
135
+ ```bash
136
+ sudo chown root:wheel /Library/LaunchDaemons/matterbridge.plist
137
+ sudo chmod 644 /Library/LaunchDaemons/matterbridge.plist
138
+ sudo plutil -lint /Library/LaunchDaemons/matterbridge.plist
139
+ sudo plutil -convert xml1 /Library/LaunchDaemons/matterbridge.plist
140
+ ```
141
+
142
+ - bootstrap matterbridge and enable it
143
+
144
+ ```bash
145
+ sudo rm -f /var/log/matterbridge.log /var/log/matterbridge.err
146
+ sudo launchctl bootstrap system /Library/LaunchDaemons/matterbridge.plist
147
+ sudo launchctl enable system/matterbridge
148
+ ```
149
+
150
+ ### Start Matterbridge
151
+
152
+ ```bash
153
+ sudo launchctl kickstart -k system/matterbridge
154
+ ```
155
+
156
+ ### Stop Matterbridge
157
+
158
+ ```bash
159
+ sudo launchctl bootout system/matterbridge
160
+ ```
161
+
162
+ ### Restart Matterbridge
163
+
164
+ ```bash
165
+ sudo launchctl kickstart -k system/matterbridge
166
+ ```
167
+
168
+ ### Show Matterbridge status
169
+
170
+ ```bash
171
+ sudo launchctl print system/matterbridge
172
+ ```
173
+
174
+ ### Show Matterbridge status (only essentials)
175
+
176
+ ```bash
177
+ sudo launchctl print system/matterbridge | grep -E "pid|state"
178
+ ```
179
+
180
+ ### Enable Matterbridge to start automatically on boot
181
+
182
+ ```bash
183
+ sudo launchctl enable system/matterbridge
184
+ ```
185
+
186
+ ### Disable Matterbridge from starting automatically on boot
187
+
188
+ ```bash
189
+ sudo launchctl disable system/matterbridge
190
+ ```
191
+
192
+ ### View the log of Matterbridge in real time (this will show the log with colors)
193
+
194
+ ```bash
195
+ sudo tail -n 1000 -f /var/log/matterbridge.log /var/log/matterbridge.err
196
+ ```
197
+
198
+ ### Optional: automatically rotate logs (every 5 days or at 100 MB, keep 5 compressed backups)
199
+
200
+ ```bash
201
+ sudo tee /etc/newsyslog.d/matterbridge.conf <<'EOF'
202
+ /var/log/matterbridge.log root:wheel 640 5 102400 5 ZC
203
+ /var/log/matterbridge.err root:wheel 640 5 102400 5 ZC
204
+ EOF
205
+ sudo newsyslog -v
206
+ ```
207
+
208
+ ### Optional: remove the password prompt for sudo
209
+
210
+ ```bash
211
+ sudo EDITOR=nano visudo
212
+ ```
213
+
214
+ Add this line at the end of the file (replace USER with your macOS username):
215
+
216
+ ```
217
+ USER ALL=(ALL) NOPASSWD: ALL
218
+ ```
219
+
220
+ Save and validate syntax:
221
+
222
+ ```bash
223
+ sudo visudo -c
224
+ ```
225
+
226
+ ### Optional: ask for password only each 60 minutes
227
+
228
+ ```bash
229
+ sudo EDITOR=nano visudo
230
+ ```
231
+
232
+ Add (or edit) this line anywhere in the file:
233
+
234
+ ```
235
+ Defaults timestamp_timeout = 60
236
+ ```
237
+
238
+ Save and validate syntax:
239
+
240
+ ```bash
241
+ sudo visudo -c
242
+ ```
package/README-SERVICE.md CHANGED
@@ -169,7 +169,7 @@ sudo systemctl restart systemd-journald
169
169
  Run the following command to verify if you can install Matterbridge globally without being prompted for a password:
170
170
 
171
171
  ```bash
172
- sudo npm install -g matterbridge
172
+ sudo npm install -g matterbridge --omit=dev
173
173
  ```
174
174
 
175
175
  If you are not prompted for a password, no further action is required.
@@ -210,3 +210,9 @@ save the file and reload the settings with:
210
210
  sudo chmod 0440 /etc/sudoers.d/matterbridge
211
211
  sudo visudo -c
212
212
  ```
213
+
214
+ Verify if you can install Matterbridge globally without being prompted for a password:
215
+
216
+ ```bash
217
+ sudo npm install -g matterbridge --omit=dev
218
+ ```
package/README.md CHANGED
@@ -84,7 +84,7 @@ Since Matter is designed as "a universal IPv6-based communication protocol for s
84
84
 
85
85
  Avoid using VLAN and firewall blocking the communications between the controllers and Matterbridge.
86
86
 
87
- To pair matterbridge, you need a matter enabled controller (Apple Home, Smart Things, Google Home, Alexa, Hose Assistant etc.).
87
+ To pair matterbridge, you need a matter enabled controller (Apple Home, Smart Things, Google Home, Alexa, Home Assistant etc.).
88
88
 
89
89
  ## Installation
90
90
 
@@ -172,6 +172,10 @@ Config editor:
172
172
 
173
173
  [Service configurations](README-SERVICE.md)
174
174
 
175
+ ### Run matterbridge as a system service with launchctl (macOS only)
176
+
177
+ [Launchctl configurations](README-MACOS-PLIST.md)
178
+
175
179
  ### Run matterbridge with docker and docker compose
176
180
 
177
181
  [Docker configurations](README-DOCKER.md)
@@ -180,10 +184,6 @@ Config editor:
180
184
 
181
185
  [Podman configurations](README-PODMAN.md)
182
186
 
183
- ### Run matterbridge as a service on macOS with mb-service (by [Michael Ahern](https://github.com/michaelahern))
184
-
185
- [Matterbridge Service Command for macOS](https://github.com/michaelahern/mb-service)
186
-
187
187
  ### Run matterbridge with nginx
188
188
 
189
189
  [Nginx configurations](README-NGINX.md)
@@ -2,6 +2,7 @@ if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
2
  console.log('\u001B[32mCli history loaded.\u001B[40;0m');
3
3
  import { writeFileSync } from 'node:fs';
4
4
  import path from 'node:path';
5
+ import os from 'node:os';
5
6
  export const historySize = 2880;
6
7
  export let historyIndex = 0;
7
8
  export function setHistoryIndex(index) {
@@ -32,6 +33,7 @@ export const history = Array.from({ length: historySize }, () => ({
32
33
  }));
33
34
  export function generateHistoryPage(options = {}) {
34
35
  const pageTitle = options.pageTitle ?? 'Matterbridge CPU & Memory History';
36
+ const hostname = options.hostname ?? os.hostname();
35
37
  const outputPath = path.resolve(options.outputPath ?? path.join(process.cwd(), 'history.html'));
36
38
  const bufferLength = history.length;
37
39
  if (bufferLength === 0) {
@@ -101,7 +103,7 @@ export function generateHistoryPage(options = {}) {
101
103
  }
102
104
  h1 {
103
105
  margin-top: 0;
104
- font-size: clamp(1.6rem, 2.2vw, 2.3rem);
106
+ font-size: clamp(1.4rem, 1.8vw, 2.0rem);
105
107
  font-weight: 700;
106
108
  color: var(--accent);
107
109
  }
@@ -231,7 +233,8 @@ export function generateHistoryPage(options = {}) {
231
233
  <div class="container">
232
234
  <header>
233
235
  <h1>${escapeHtml(pageTitle)}</h1>
234
- <p>Generated ${new Date().toLocaleString()}</p>
236
+ <p>Hostname: ${escapeHtml(hostname)}</p>
237
+ <p>Generated: ${new Date().toLocaleString()}</p>
235
238
  </header>
236
239
 
237
240
  <section class="card">
package/dist/frontend.js CHANGED
@@ -405,41 +405,7 @@ export class Frontend extends EventEmitter {
405
405
  });
406
406
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
407
407
  this.log.debug('The frontend sent /api/view-diagnostic');
408
- const serverNodes = [];
409
- if (this.matterbridge.bridgeMode === 'bridge') {
410
- if (this.matterbridge.serverNode)
411
- serverNodes.push(this.matterbridge.serverNode);
412
- }
413
- else if (this.matterbridge.bridgeMode === 'childbridge') {
414
- for (const plugin of this.matterbridge.plugins.array()) {
415
- if (plugin.serverNode)
416
- serverNodes.push(plugin.serverNode);
417
- }
418
- }
419
- for (const device of this.matterbridge.devices.array()) {
420
- if (device.serverNode)
421
- serverNodes.push(device.serverNode);
422
- }
423
- const fs = await import('node:fs');
424
- if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE)))
425
- fs.unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE));
426
- const diagnosticDestination = LogDestination({ name: 'diagnostic', level: MatterLogLevel.INFO, format: MatterLogFormat.formats.plain });
427
- diagnosticDestination.write = async (text, _message) => {
428
- await fs.promises.appendFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), text + '\n', { encoding: 'utf8' });
429
- };
430
- Logger.destinations.diagnostic = diagnosticDestination;
431
- if (!diagnosticDestination.context) {
432
- diagnosticDestination.context = Diagnostic.Context();
433
- }
434
- diagnosticDestination.context.run(() => diagnosticDestination.add(Diagnostic.message({
435
- now: Time.now(),
436
- facility: 'Server nodes:',
437
- level: MatterLogLevel.INFO,
438
- prefix: Logger.nestingLevel ? '⎸'.padEnd(Logger.nestingLevel * 2) : '',
439
- values: [...serverNodes],
440
- })));
441
- delete Logger.destinations.diagnostic;
442
- await wait(500);
408
+ await this.generateDiagnostic();
443
409
  try {
444
410
  const fs = await import('node:fs');
445
411
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
@@ -451,6 +417,26 @@ export class Frontend extends EventEmitter {
451
417
  res.status(500).send('Error reading diagnostic log file.');
452
418
  }
453
419
  });
420
+ this.expressApp.get('/api/download-diagnostic', async (req, res) => {
421
+ this.log.debug(`The frontend sent /api/download-diagnostic`);
422
+ await this.generateDiagnostic();
423
+ try {
424
+ const fs = await import('node:fs');
425
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), fs.constants.F_OK);
426
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
427
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
428
+ }
429
+ catch (error) {
430
+ this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
431
+ }
432
+ res.type('text/plain');
433
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
434
+ if (error) {
435
+ this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
436
+ res.status(500).send('Error downloading the diagnostic log file');
437
+ }
438
+ });
439
+ });
454
440
  this.expressApp.get('/api/viewhistory', async (req, res) => {
455
441
  this.log.debug('The frontend sent /api/viewhistory');
456
442
  try {
@@ -460,8 +446,28 @@ export class Frontend extends EventEmitter {
460
446
  res.send(data);
461
447
  }
462
448
  catch (error) {
463
- this.log.error(`Error reading history log file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
464
- res.status(500).send('Error reading history log file. Please create the history log before loading it.');
449
+ this.log.error(`Error in /api/viewhistory reading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
450
+ res.status(500).send('Error reading history file.');
451
+ }
452
+ });
453
+ this.expressApp.get('/api/downloadhistory', async (req, res) => {
454
+ this.log.debug(`The frontend sent /api/downloadhistory`);
455
+ try {
456
+ const fs = await import('node:fs');
457
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), fs.constants.F_OK);
458
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), 'utf8');
459
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
460
+ res.type('text/plain');
461
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
462
+ if (error) {
463
+ this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
464
+ res.status(500).send('Error downloading history file');
465
+ }
466
+ });
467
+ }
468
+ catch (error) {
469
+ this.log.error(`Error in /api/downloadhistory reading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
470
+ res.status(500).send('Error reading history file.');
465
471
  }
466
472
  });
467
473
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
@@ -999,6 +1005,44 @@ export class Frontend extends EventEmitter {
999
1005
  });
1000
1006
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
1001
1007
  }
1008
+ async generateDiagnostic() {
1009
+ this.log.debug('Generating diagnostic...');
1010
+ const serverNodes = [];
1011
+ if (this.matterbridge.bridgeMode === 'bridge') {
1012
+ if (this.matterbridge.serverNode)
1013
+ serverNodes.push(this.matterbridge.serverNode);
1014
+ }
1015
+ else if (this.matterbridge.bridgeMode === 'childbridge') {
1016
+ for (const plugin of this.matterbridge.plugins.array()) {
1017
+ if (plugin.serverNode)
1018
+ serverNodes.push(plugin.serverNode);
1019
+ }
1020
+ }
1021
+ for (const device of this.matterbridge.devices.array()) {
1022
+ if (device.serverNode)
1023
+ serverNodes.push(device.serverNode);
1024
+ }
1025
+ const fs = await import('node:fs');
1026
+ if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE)))
1027
+ fs.unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE));
1028
+ const diagnosticDestination = LogDestination({ name: 'diagnostic', level: MatterLogLevel.INFO, format: MatterLogFormat.formats.plain });
1029
+ diagnosticDestination.write = async (text, _message) => {
1030
+ await fs.promises.appendFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), text + '\n', { encoding: 'utf8' });
1031
+ };
1032
+ Logger.destinations.diagnostic = diagnosticDestination;
1033
+ if (!diagnosticDestination.context) {
1034
+ diagnosticDestination.context = Diagnostic.Context();
1035
+ }
1036
+ diagnosticDestination.context.run(() => diagnosticDestination.add(Diagnostic.message({
1037
+ now: Time.now(),
1038
+ facility: 'Server nodes:',
1039
+ level: MatterLogLevel.INFO,
1040
+ prefix: Logger.nestingLevel ? '⎸'.padEnd(Logger.nestingLevel * 2) : '',
1041
+ values: [...serverNodes],
1042
+ })));
1043
+ delete Logger.destinations.diagnostic;
1044
+ await wait(500);
1045
+ }
1002
1046
  async wsMessageHandler(client, message) {
1003
1047
  let data;
1004
1048
  const sendResponse = (data) => {
@@ -1286,8 +1330,12 @@ export class Frontend extends EventEmitter {
1286
1330
  await this.matterbridge.shutdownProcessAndFactoryReset();
1287
1331
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1288
1332
  }
1289
- else if (data.method === '/api/generatehistorypage') {
1290
- generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge on ${this.matterbridge.systemInformation.hostname} Cpu & Memory History` });
1333
+ else if (data.method === '/api/viewhistorypage') {
1334
+ generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge Cpu & Memory History` });
1335
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1336
+ }
1337
+ else if (data.method === '/api/downloadhistorypage') {
1338
+ generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge Cpu & Memory History` });
1291
1339
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1292
1340
  }
1293
1341
  else if (data.method === '/api/matter') {
@@ -1,5 +1,5 @@
1
1
  import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, UINT16_MAX, UINT32_MAX, VendorId } from '@matter/main';
2
- import { getClusterNameById, MeasurementType } from '@matter/main/types';
2
+ import { getClusterNameById } from '@matter/main/types';
3
3
  import { Descriptor } from '@matter/main/clusters/descriptor';
4
4
  import { PowerSource } from '@matter/main/clusters/power-source';
5
5
  import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
@@ -60,7 +60,7 @@ import { ThermostatUserInterfaceConfigurationServer } from '@matter/main/behavio
60
60
  import { AnsiLogger, CYAN, YELLOW, db, debugStringify, hk, or, zb } from './logger/export.js';
61
61
  import { isValidNumber, isValidObject, isValidString } from './utils/export.js';
62
62
  import { MatterbridgeServer, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeLiftTiltWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeHepaFilterMonitoringServer, MatterbridgeEnhancedColorControlServer, } from './matterbridgeBehaviors.js';
63
- import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, } from './matterbridgeEndpointHelpers.js';
63
+ import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getApparentElectricalPowerMeasurementClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, } from './matterbridgeEndpointHelpers.js';
64
64
  export class MatterbridgeEndpoint extends Endpoint {
65
65
  static logLevel = "info";
66
66
  mode = undefined;
@@ -782,8 +782,17 @@ export class MatterbridgeEndpoint extends Endpoint {
782
782
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
783
783
  localTemperature: localTemperature * 100,
784
784
  ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
785
- systemMode: Thermostat.SystemMode.Auto,
786
785
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
786
+ systemMode: Thermostat.SystemMode.Auto,
787
+ thermostatRunningState: {
788
+ heat: false,
789
+ cool: false,
790
+ fan: false,
791
+ heatStage2: false,
792
+ coolStage2: false,
793
+ fanStage2: false,
794
+ fanStage3: false,
795
+ },
787
796
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
788
797
  minHeatSetpointLimit: minHeatSetpointLimit * 100,
789
798
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
@@ -806,8 +815,17 @@ export class MatterbridgeEndpoint extends Endpoint {
806
815
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
807
816
  localTemperature: localTemperature * 100,
808
817
  ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
809
- systemMode: Thermostat.SystemMode.Heat,
810
818
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.HeatingOnly,
819
+ systemMode: Thermostat.SystemMode.Heat,
820
+ thermostatRunningState: {
821
+ heat: false,
822
+ cool: false,
823
+ fan: false,
824
+ heatStage2: false,
825
+ coolStage2: false,
826
+ fanStage2: false,
827
+ fanStage3: false,
828
+ },
811
829
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
812
830
  minHeatSetpointLimit: minHeatSetpointLimit * 100,
813
831
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
@@ -822,8 +840,17 @@ export class MatterbridgeEndpoint extends Endpoint {
822
840
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Cooling, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
823
841
  localTemperature: localTemperature * 100,
824
842
  ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
825
- systemMode: Thermostat.SystemMode.Cool,
826
843
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingOnly,
844
+ systemMode: Thermostat.SystemMode.Cool,
845
+ thermostatRunningState: {
846
+ heat: false,
847
+ cool: false,
848
+ fan: false,
849
+ heatStage2: false,
850
+ coolStage2: false,
851
+ fanStage2: false,
852
+ fanStage3: false,
853
+ },
827
854
  occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
828
855
  minCoolSetpointLimit: minCoolSetpointLimit * 100,
829
856
  maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
@@ -1170,59 +1197,15 @@ export class MatterbridgeEndpoint extends Endpoint {
1170
1197
  return this;
1171
1198
  }
1172
1199
  createDefaultElectricalEnergyMeasurementClusterServer(energyImported = null, energyExported = null) {
1173
- this.behaviors.require(ElectricalEnergyMeasurementServer.with(ElectricalEnergyMeasurement.Feature.ImportedEnergy, ElectricalEnergyMeasurement.Feature.ExportedEnergy, ElectricalEnergyMeasurement.Feature.CumulativeEnergy), {
1174
- accuracy: {
1175
- measurementType: MeasurementType.ElectricalEnergy,
1176
- measured: true,
1177
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1178
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1179
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1180
- },
1181
- cumulativeEnergyReset: null,
1182
- cumulativeEnergyImported: energyImported !== null && energyImported >= 0 ? { energy: energyImported } : null,
1183
- cumulativeEnergyExported: energyExported !== null && energyExported >= 0 ? { energy: energyExported } : null,
1184
- });
1200
+ this.behaviors.require(ElectricalEnergyMeasurementServer.with(ElectricalEnergyMeasurement.Feature.ImportedEnergy, ElectricalEnergyMeasurement.Feature.ExportedEnergy, ElectricalEnergyMeasurement.Feature.CumulativeEnergy), getDefaultElectricalEnergyMeasurementClusterServer(energyImported, energyExported));
1185
1201
  return this;
1186
1202
  }
1187
1203
  createDefaultElectricalPowerMeasurementClusterServer(voltage = null, current = null, power = null, frequency = null) {
1188
- this.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), {
1189
- powerMode: ElectricalPowerMeasurement.PowerMode.Ac,
1190
- numberOfMeasurementTypes: 4,
1191
- accuracy: [
1192
- {
1193
- measurementType: MeasurementType.Voltage,
1194
- measured: true,
1195
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1196
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1197
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1198
- },
1199
- {
1200
- measurementType: MeasurementType.ActiveCurrent,
1201
- measured: true,
1202
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1203
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1204
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1205
- },
1206
- {
1207
- measurementType: MeasurementType.ActivePower,
1208
- measured: true,
1209
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1210
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1211
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1212
- },
1213
- {
1214
- measurementType: MeasurementType.Frequency,
1215
- measured: true,
1216
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1217
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1218
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1219
- },
1220
- ],
1221
- voltage: voltage,
1222
- activeCurrent: current,
1223
- activePower: power,
1224
- frequency: frequency,
1225
- });
1204
+ this.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), getDefaultElectricalPowerMeasurementClusterServer(voltage, current, power, frequency));
1205
+ return this;
1206
+ }
1207
+ createApparentElectricalPowerMeasurementClusterServer(voltage = null, apparentCurrent = null, apparentPower = null, frequency = null) {
1208
+ this.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), getApparentElectricalPowerMeasurementClusterServer(voltage, apparentCurrent, apparentPower, frequency));
1226
1209
  return this;
1227
1210
  }
1228
1211
  createDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {