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 +15 -0
- package/README-MACOS-PLIST.md +242 -0
- package/README-SERVICE.md +7 -1
- package/README.md +5 -5
- package/dist/cliHistory.js +5 -2
- package/dist/frontend.js +87 -39
- package/dist/matterbridgeEndpoint.js +38 -55
- package/dist/matterbridgeEndpointHelpers.js +95 -1
- package/frontend/build/assets/index.js +4 -4
- package/frontend/build/assets/vendor_mdi.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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"> Matterbridge launchctl configuration (macOS)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/matterbridge)
|
|
4
|
+
[](https://www.npmjs.com/package/matterbridge)
|
|
5
|
+
[](https://hub.docker.com/r/luligu/matterbridge)
|
|
6
|
+
[](https://hub.docker.com/r/luligu/matterbridge)
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
[](https://codecov.io/gh/Luligu/matterbridge)
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/matter-history)
|
|
12
|
+
[](https://www.npmjs.com/package/node-ansi-logger)
|
|
13
|
+
[](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,
|
|
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)
|
package/dist/cliHistory.js
CHANGED
|
@@ -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.
|
|
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>
|
|
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
|
-
|
|
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
|
|
464
|
-
res.status(500).send('Error reading history
|
|
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/
|
|
1290
|
-
generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge
|
|
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
|
|
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
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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) {
|