matterbridge 1.6.6-dev.7 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -10
- package/README-DEV.md +3 -3
- package/README.md +4 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +46 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +26 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +1 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matterbridge.d.ts +466 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +712 -65
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +942 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +38 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDevice.d.ts +6674 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -0
- package/dist/matterbridgeDevice.js +1001 -26
- package/dist/matterbridgeDevice.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +82 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +59 -13
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEdge.d.ts +89 -0
- package/dist/matterbridgeEdge.d.ts.map +1 -0
- package/dist/matterbridgeEdge.js +529 -4
- package/dist/matterbridgeEdge.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +9774 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1113 -94
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +114 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +122 -13
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +161 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/matterbridgeWebsocket.d.ts +49 -0
- package/dist/matterbridgeWebsocket.d.ts.map +1 -0
- package/dist/matterbridgeWebsocket.js +50 -0
- package/dist/matterbridgeWebsocket.js.map +1 -0
- package/dist/pluginManager.d.ts +238 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +238 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +236 -89
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/export.d.ts +3 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/utils.d.ts +221 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +252 -7
- package/dist/utils/utils.js.map +1 -0
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.cb537856.js → main.a742de4e.js} +9 -9
- package/frontend/build/static/js/{main.cb537856.js.map → main.a742de4e.js.map} +1 -1
- package/npm-shrinkwrap.json +165 -84
- package/package.json +4 -4
- /package/frontend/build/static/js/{main.cb537856.js.LICENSE.txt → main.a742de4e.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 1.5.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// Node.js modules
|
|
1
24
|
import { fileURLToPath } from 'url';
|
|
2
25
|
import { promises as fs } from 'fs';
|
|
3
26
|
import { exec, spawn } from 'child_process';
|
|
@@ -6,26 +29,35 @@ import EventEmitter from 'events';
|
|
|
6
29
|
import os from 'os';
|
|
7
30
|
import path from 'path';
|
|
8
31
|
import { randomBytes } from 'crypto';
|
|
32
|
+
// Package modules
|
|
9
33
|
import https from 'https';
|
|
10
34
|
import express from 'express';
|
|
11
35
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
36
|
+
// NodeStorage and AnsiLogger modules
|
|
12
37
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
13
38
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
|
|
39
|
+
// Matterbridge
|
|
14
40
|
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
15
41
|
import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
|
|
16
42
|
import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
17
43
|
import { PluginManager } from './pluginManager.js';
|
|
18
44
|
import { DeviceManager } from './deviceManager.js';
|
|
19
45
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
46
|
+
// @matter
|
|
20
47
|
import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer } from '@matter/main';
|
|
21
48
|
import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
|
|
22
49
|
import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
|
|
23
50
|
import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
|
|
51
|
+
// @project-chip
|
|
24
52
|
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
|
|
25
53
|
import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
|
|
54
|
+
// Default colors
|
|
26
55
|
const plg = '\u001B[38;5;33m';
|
|
27
56
|
const dev = '\u001B[38;5;79m';
|
|
28
57
|
const typ = '\u001B[38;5;207m';
|
|
58
|
+
/**
|
|
59
|
+
* Represents the Matterbridge application.
|
|
60
|
+
*/
|
|
29
61
|
export class Matterbridge extends EventEmitter {
|
|
30
62
|
systemInformation = {
|
|
31
63
|
interfaceName: '',
|
|
@@ -61,7 +93,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
61
93
|
restartMode: '',
|
|
62
94
|
edge: hasParameter('edge'),
|
|
63
95
|
profile: getParameter('profile'),
|
|
64
|
-
loggerLevel: "info"
|
|
96
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
65
97
|
fileLogger: false,
|
|
66
98
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
67
99
|
matterFileLogger: false,
|
|
@@ -100,6 +132,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
100
132
|
nodeContext;
|
|
101
133
|
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
102
134
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
135
|
+
// Cleanup
|
|
103
136
|
hasCleanupStarted = false;
|
|
104
137
|
initialized = false;
|
|
105
138
|
execRunningCount = 0;
|
|
@@ -111,16 +144,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
111
144
|
sigtermHandler;
|
|
112
145
|
exceptionHandler;
|
|
113
146
|
rejectionHandler;
|
|
147
|
+
// Frontend
|
|
114
148
|
expressApp;
|
|
115
149
|
httpServer;
|
|
116
150
|
httpsServer;
|
|
117
151
|
webSocketServer;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
152
|
+
// Matter
|
|
153
|
+
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
154
|
+
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
155
|
+
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
156
|
+
port = 5540; // first commissioning server port
|
|
157
|
+
passcode; // first commissioning server passcode
|
|
158
|
+
discriminator; // first commissioning server discriminator
|
|
124
159
|
storageManager;
|
|
125
160
|
matterbridgeContext;
|
|
126
161
|
mattercontrollerContext;
|
|
@@ -131,13 +166,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
131
166
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
132
167
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
133
168
|
static instance;
|
|
169
|
+
// We load asyncronously so is private
|
|
134
170
|
constructor() {
|
|
135
171
|
super();
|
|
172
|
+
// Bind the handler to the instance
|
|
136
173
|
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
137
174
|
}
|
|
138
175
|
matterbridgeMessageHandler;
|
|
176
|
+
/** ***********************************************************************************************************************************/
|
|
177
|
+
/** loadInstance() and cleanup() methods */
|
|
178
|
+
/** ***********************************************************************************************************************************/
|
|
179
|
+
/**
|
|
180
|
+
* Loads an instance of the Matterbridge class.
|
|
181
|
+
* If an instance already exists, return that instance.
|
|
182
|
+
*
|
|
183
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
184
|
+
* @returns The loaded Matterbridge instance.
|
|
185
|
+
*/
|
|
139
186
|
static async loadInstance(initialize = false) {
|
|
140
187
|
if (!Matterbridge.instance) {
|
|
188
|
+
// eslint-disable-next-line no-console
|
|
141
189
|
if (hasParameter('debug'))
|
|
142
190
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
143
191
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -146,6 +194,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
146
194
|
}
|
|
147
195
|
return Matterbridge.instance;
|
|
148
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Call cleanup().
|
|
199
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
200
|
+
*
|
|
201
|
+
*/
|
|
149
202
|
async destroyInstance() {
|
|
150
203
|
await this.cleanup('destroying instance...', false);
|
|
151
204
|
await waiter('destroying instance...', () => {
|
|
@@ -153,39 +206,60 @@ export class Matterbridge extends EventEmitter {
|
|
|
153
206
|
}, false, 60000, 100, false);
|
|
154
207
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
155
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Initializes the Matterbridge application.
|
|
211
|
+
*
|
|
212
|
+
* @remarks
|
|
213
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
214
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
215
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
216
|
+
*
|
|
217
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
218
|
+
*/
|
|
156
219
|
async initialize() {
|
|
220
|
+
// Set the restart mode
|
|
157
221
|
if (hasParameter('service'))
|
|
158
222
|
this.restartMode = 'service';
|
|
159
223
|
if (hasParameter('docker'))
|
|
160
224
|
this.restartMode = 'docker';
|
|
225
|
+
// Set the matterbridge directory
|
|
161
226
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
162
227
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
163
|
-
|
|
228
|
+
// Create matterbridge logger
|
|
229
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
230
|
+
// Initialize nodeStorage and nodeContext
|
|
164
231
|
try {
|
|
165
232
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
166
233
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
167
234
|
this.log.debug('Creating node storage context for matterbridge');
|
|
168
235
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
236
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
238
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
170
239
|
for (const key of keys) {
|
|
171
240
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
242
|
await this.nodeStorage?.storage.get(key);
|
|
173
243
|
}
|
|
174
244
|
const storages = await this.nodeStorage.getStorageNames();
|
|
175
245
|
for (const storage of storages) {
|
|
176
246
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
177
247
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
248
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
178
250
|
const keys = (await nodeContext?.storage.keys());
|
|
179
251
|
keys.forEach(async (key) => {
|
|
180
252
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
181
253
|
await nodeContext?.get(key);
|
|
182
254
|
});
|
|
183
255
|
}
|
|
256
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
184
257
|
this.log.debug('Creating node storage backup...');
|
|
185
258
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
186
259
|
this.log.debug('Created node storage backup');
|
|
187
260
|
}
|
|
188
261
|
catch (error) {
|
|
262
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
189
263
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
190
264
|
if (hasParameter('norestore')) {
|
|
191
265
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -200,44 +274,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
200
274
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
201
275
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
202
276
|
}
|
|
277
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
203
278
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
279
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
204
280
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
|
|
281
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
205
282
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
283
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
206
284
|
if (hasParameter('logger')) {
|
|
207
285
|
const level = getParameter('logger');
|
|
208
286
|
if (level === 'debug') {
|
|
209
|
-
this.log.logLevel = "debug"
|
|
287
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
210
288
|
}
|
|
211
289
|
else if (level === 'info') {
|
|
212
|
-
this.log.logLevel = "info"
|
|
290
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
213
291
|
}
|
|
214
292
|
else if (level === 'notice') {
|
|
215
|
-
this.log.logLevel = "notice"
|
|
293
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
216
294
|
}
|
|
217
295
|
else if (level === 'warn') {
|
|
218
|
-
this.log.logLevel = "warn"
|
|
296
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
219
297
|
}
|
|
220
298
|
else if (level === 'error') {
|
|
221
|
-
this.log.logLevel = "error"
|
|
299
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
222
300
|
}
|
|
223
301
|
else if (level === 'fatal') {
|
|
224
|
-
this.log.logLevel = "fatal"
|
|
302
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
225
303
|
}
|
|
226
304
|
else {
|
|
227
305
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
228
|
-
this.log.logLevel = "info"
|
|
306
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
229
307
|
}
|
|
230
308
|
}
|
|
231
309
|
else {
|
|
232
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
310
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
233
311
|
}
|
|
234
312
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
313
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
235
314
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
236
315
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
237
316
|
this.matterbridgeInformation.fileLogger = true;
|
|
238
317
|
}
|
|
239
318
|
this.log.notice('Matterbridge is starting...');
|
|
240
319
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
320
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
241
321
|
if (hasParameter('matterlogger')) {
|
|
242
322
|
const level = getParameter('matterlogger');
|
|
243
323
|
if (level === 'debug') {
|
|
@@ -268,6 +348,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
268
348
|
}
|
|
269
349
|
Logger.format = MatterLogFormat.ANSI;
|
|
270
350
|
Logger.setLogger('default', this.createMatterLogger());
|
|
351
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
271
352
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
272
353
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
273
354
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -276,6 +357,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
276
357
|
});
|
|
277
358
|
}
|
|
278
359
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
360
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
279
361
|
if (hasParameter('mdnsinterface')) {
|
|
280
362
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
281
363
|
}
|
|
@@ -284,6 +366,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
284
366
|
if (this.mdnsInterface === '')
|
|
285
367
|
this.mdnsInterface = undefined;
|
|
286
368
|
}
|
|
369
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
287
370
|
if (hasParameter('ipv4address')) {
|
|
288
371
|
this.ipv4address = getParameter('ipv4address');
|
|
289
372
|
}
|
|
@@ -292,6 +375,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
292
375
|
if (this.ipv4address === '')
|
|
293
376
|
this.ipv4address = undefined;
|
|
294
377
|
}
|
|
378
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
295
379
|
if (hasParameter('ipv6address')) {
|
|
296
380
|
this.ipv6address = getParameter('ipv6address');
|
|
297
381
|
}
|
|
@@ -300,17 +384,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
300
384
|
if (this.ipv6address === '')
|
|
301
385
|
this.ipv6address = undefined;
|
|
302
386
|
}
|
|
387
|
+
// Initialize PluginManager
|
|
303
388
|
this.plugins = new PluginManager(this);
|
|
304
389
|
await this.plugins.loadFromStorage();
|
|
390
|
+
// Initialize DeviceManager
|
|
305
391
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
392
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
306
393
|
for (const plugin of this.plugins) {
|
|
307
394
|
const packageJson = await this.plugins.parse(plugin);
|
|
308
395
|
if (packageJson === null && !hasParameter('add')) {
|
|
396
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
397
|
+
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
309
398
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
310
399
|
try {
|
|
311
400
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
312
401
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
313
402
|
plugin.error = false;
|
|
403
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
314
404
|
}
|
|
315
405
|
catch (error) {
|
|
316
406
|
plugin.error = true;
|
|
@@ -327,6 +417,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
327
417
|
await plugin.nodeContext.set('description', plugin.description);
|
|
328
418
|
await plugin.nodeContext.set('author', plugin.author);
|
|
329
419
|
}
|
|
420
|
+
// Log system info and create .matterbridge directory
|
|
330
421
|
await this.logNodeAndSystemInfo();
|
|
331
422
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
332
423
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -334,6 +425,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
334
425
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
335
426
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
336
427
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
428
|
+
// Check node version and throw error
|
|
337
429
|
const minNodeVersion = 18;
|
|
338
430
|
const nodeVersion = process.versions.node;
|
|
339
431
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -341,10 +433,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
341
433
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
342
434
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
343
435
|
}
|
|
436
|
+
// Register process handlers
|
|
344
437
|
this.registerProcessHandlers();
|
|
438
|
+
// Parse command line
|
|
345
439
|
await this.parseCommandLine();
|
|
346
440
|
this.initialized = true;
|
|
347
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
444
|
+
* @private
|
|
445
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
446
|
+
*/
|
|
348
447
|
async parseCommandLine() {
|
|
349
448
|
if (hasParameter('help')) {
|
|
350
449
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -452,12 +551,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
452
551
|
}
|
|
453
552
|
if (hasParameter('factoryreset')) {
|
|
454
553
|
try {
|
|
554
|
+
// Delete matter storage file
|
|
455
555
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
456
556
|
}
|
|
457
557
|
catch (err) {
|
|
458
558
|
this.log.error(`Error deleting storage: ${err}`);
|
|
459
559
|
}
|
|
460
560
|
try {
|
|
561
|
+
// Delete node storage directory with its subdirectories
|
|
461
562
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
462
563
|
}
|
|
463
564
|
catch (err) {
|
|
@@ -471,6 +572,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
572
|
this.emit('shutdown');
|
|
472
573
|
return;
|
|
473
574
|
}
|
|
575
|
+
// Start the matter storage and create the matterbridge context
|
|
474
576
|
try {
|
|
475
577
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
476
578
|
}
|
|
@@ -505,28 +607,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
505
607
|
this.emit('shutdown');
|
|
506
608
|
return;
|
|
507
609
|
}
|
|
610
|
+
// Initialize frontend
|
|
508
611
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
509
612
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
613
|
+
// Check each 60 minutes the latest versions
|
|
510
614
|
this.checkUpdateInterval = setInterval(() => {
|
|
511
615
|
this.getMatterbridgeLatestVersion();
|
|
512
616
|
for (const plugin of this.plugins) {
|
|
513
617
|
this.getPluginLatestVersion(plugin);
|
|
514
618
|
}
|
|
515
619
|
}, 60 * 60 * 1000);
|
|
620
|
+
// Start the matterbridge in mode test
|
|
516
621
|
if (hasParameter('test')) {
|
|
517
622
|
this.bridgeMode = 'bridge';
|
|
518
623
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
519
624
|
return;
|
|
520
625
|
}
|
|
626
|
+
// Start the matterbridge in mode controller
|
|
521
627
|
if (hasParameter('controller')) {
|
|
522
628
|
this.bridgeMode = 'controller';
|
|
523
629
|
await this.startController();
|
|
524
630
|
return;
|
|
525
631
|
}
|
|
632
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
526
633
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
527
634
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
528
635
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
529
636
|
}
|
|
637
|
+
// Start matterbridge in bridge mode
|
|
530
638
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
531
639
|
this.bridgeMode = 'bridge';
|
|
532
640
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -535,6 +643,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
535
643
|
await this.startBridge();
|
|
536
644
|
return;
|
|
537
645
|
}
|
|
646
|
+
// Start matterbridge in childbridge mode
|
|
538
647
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
539
648
|
this.bridgeMode = 'childbridge';
|
|
540
649
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -544,17 +653,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
544
653
|
return;
|
|
545
654
|
}
|
|
546
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Asynchronously loads and starts the registered plugins.
|
|
658
|
+
*
|
|
659
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
660
|
+
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
661
|
+
*
|
|
662
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
663
|
+
*/
|
|
547
664
|
async startPlugins() {
|
|
665
|
+
// Check, load and start the plugins
|
|
548
666
|
for (const plugin of this.plugins) {
|
|
549
667
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
550
668
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
669
|
+
// Check if the plugin is available
|
|
551
670
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
552
671
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
553
672
|
plugin.enabled = false;
|
|
554
673
|
plugin.error = true;
|
|
555
674
|
continue;
|
|
556
675
|
}
|
|
557
|
-
|
|
676
|
+
// Check if the plugin has a new version
|
|
677
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
558
678
|
if (!plugin.enabled) {
|
|
559
679
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
560
680
|
continue;
|
|
@@ -569,20 +689,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
569
689
|
plugin.addedDevices = undefined;
|
|
570
690
|
plugin.qrPairingCode = undefined;
|
|
571
691
|
plugin.manualPairingCode = undefined;
|
|
572
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
692
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
573
693
|
}
|
|
574
694
|
this.wssSendRefreshRequired();
|
|
575
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
698
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
699
|
+
*/
|
|
576
700
|
registerProcessHandlers() {
|
|
577
701
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
578
702
|
process.removeAllListeners('uncaughtException');
|
|
579
703
|
process.removeAllListeners('unhandledRejection');
|
|
580
704
|
this.exceptionHandler = async (error) => {
|
|
581
705
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
706
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
582
707
|
};
|
|
583
708
|
process.on('uncaughtException', this.exceptionHandler);
|
|
584
709
|
this.rejectionHandler = async (reason, promise) => {
|
|
585
710
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
711
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
586
712
|
};
|
|
587
713
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
588
714
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -595,6 +721,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
595
721
|
};
|
|
596
722
|
process.on('SIGTERM', this.sigtermHandler);
|
|
597
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
726
|
+
*/
|
|
598
727
|
deregisterProcesslHandlers() {
|
|
599
728
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
600
729
|
if (this.exceptionHandler)
|
|
@@ -611,7 +740,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
611
740
|
process.off('SIGTERM', this.sigtermHandler);
|
|
612
741
|
this.sigtermHandler = undefined;
|
|
613
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Logs the node and system information.
|
|
745
|
+
*/
|
|
614
746
|
async logNodeAndSystemInfo() {
|
|
747
|
+
// IP address information
|
|
615
748
|
const networkInterfaces = os.networkInterfaces();
|
|
616
749
|
this.systemInformation.ipv4Address = '';
|
|
617
750
|
this.systemInformation.ipv6Address = '';
|
|
@@ -631,7 +764,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
631
764
|
this.systemInformation.macAddress = detail.mac;
|
|
632
765
|
}
|
|
633
766
|
}
|
|
634
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
767
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
635
768
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
636
769
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
637
770
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -639,19 +772,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
639
772
|
break;
|
|
640
773
|
}
|
|
641
774
|
}
|
|
775
|
+
// Node information
|
|
642
776
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
643
777
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
644
778
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
645
779
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
780
|
+
// Host system information
|
|
646
781
|
this.systemInformation.hostname = os.hostname();
|
|
647
782
|
this.systemInformation.user = os.userInfo().username;
|
|
648
|
-
this.systemInformation.osType = os.type();
|
|
649
|
-
this.systemInformation.osRelease = os.release();
|
|
650
|
-
this.systemInformation.osPlatform = os.platform();
|
|
651
|
-
this.systemInformation.osArch = os.arch();
|
|
652
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
653
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
654
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
783
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
784
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
785
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
786
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
787
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
788
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
789
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
790
|
+
// Log the system information
|
|
655
791
|
this.log.debug('Host System Information:');
|
|
656
792
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
657
793
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -667,15 +803,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
667
803
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
668
804
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
669
805
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
806
|
+
// Home directory
|
|
670
807
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
671
808
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
672
809
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
810
|
+
// Package root directory
|
|
673
811
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
674
812
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
675
813
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
676
814
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
815
|
+
// Global node_modules directory
|
|
677
816
|
if (this.nodeContext)
|
|
678
817
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
818
|
+
// First run of Matterbridge so the node storage is empty
|
|
679
819
|
if (this.globalModulesDirectory === '') {
|
|
680
820
|
try {
|
|
681
821
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -699,6 +839,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
699
839
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
700
840
|
});
|
|
701
841
|
}
|
|
842
|
+
// Create the data directory .matterbridge in the home directory
|
|
702
843
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
703
844
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
704
845
|
try {
|
|
@@ -722,6 +863,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
722
863
|
}
|
|
723
864
|
}
|
|
724
865
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
866
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
725
867
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
726
868
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
727
869
|
try {
|
|
@@ -745,19 +887,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
745
887
|
}
|
|
746
888
|
}
|
|
747
889
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
890
|
+
// Matterbridge version
|
|
748
891
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
749
892
|
this.matterbridgeVersion = packageJson.version;
|
|
750
893
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
751
894
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
895
|
+
// Matterbridge latest version
|
|
752
896
|
if (this.nodeContext)
|
|
753
897
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
754
898
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
755
899
|
this.getMatterbridgeLatestVersion();
|
|
900
|
+
// Current working directory
|
|
756
901
|
const currentDir = process.cwd();
|
|
757
902
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
903
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
758
904
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
759
905
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
760
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
909
|
+
* @param packageName - The name of the package.
|
|
910
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
911
|
+
*/
|
|
761
912
|
async getLatestVersion(packageName) {
|
|
762
913
|
return new Promise((resolve, reject) => {
|
|
763
914
|
this.execRunningCount++;
|
|
@@ -772,6 +923,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
772
923
|
});
|
|
773
924
|
});
|
|
774
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
928
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
929
|
+
*/
|
|
775
930
|
async getGlobalNodeModules() {
|
|
776
931
|
return new Promise((resolve, reject) => {
|
|
777
932
|
this.execRunningCount++;
|
|
@@ -786,6 +941,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
786
941
|
});
|
|
787
942
|
});
|
|
788
943
|
}
|
|
944
|
+
/**
|
|
945
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
946
|
+
* @private
|
|
947
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
948
|
+
*/
|
|
789
949
|
async getMatterbridgeLatestVersion() {
|
|
790
950
|
this.getLatestVersion('matterbridge')
|
|
791
951
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -802,8 +962,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
802
962
|
})
|
|
803
963
|
.catch((error) => {
|
|
804
964
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
965
|
+
// error.stack && this.log.debug(error.stack);
|
|
805
966
|
});
|
|
806
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
970
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
971
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
972
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
973
|
+
*
|
|
974
|
+
* @private
|
|
975
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
976
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
977
|
+
*/
|
|
807
978
|
async getPluginLatestVersion(plugin) {
|
|
808
979
|
this.getLatestVersion(plugin.name)
|
|
809
980
|
.then(async (latestVersion) => {
|
|
@@ -815,40 +986,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
815
986
|
})
|
|
816
987
|
.catch((error) => {
|
|
817
988
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
989
|
+
// error.stack && this.log.debug(error.stack);
|
|
818
990
|
});
|
|
819
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
994
|
+
*
|
|
995
|
+
* @returns {Function} The MatterLogger function.
|
|
996
|
+
*/
|
|
820
997
|
createMatterLogger() {
|
|
821
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
998
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
822
999
|
return (_level, formattedLog) => {
|
|
823
1000
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
824
1001
|
const message = formattedLog.slice(65);
|
|
825
1002
|
matterLogger.logName = logger;
|
|
826
1003
|
switch (_level) {
|
|
827
1004
|
case MatterLogLevel.DEBUG:
|
|
828
|
-
matterLogger.log("debug"
|
|
1005
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
829
1006
|
break;
|
|
830
1007
|
case MatterLogLevel.INFO:
|
|
831
|
-
matterLogger.log("info"
|
|
1008
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
832
1009
|
break;
|
|
833
1010
|
case MatterLogLevel.NOTICE:
|
|
834
|
-
matterLogger.log("notice"
|
|
1011
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
835
1012
|
break;
|
|
836
1013
|
case MatterLogLevel.WARN:
|
|
837
|
-
matterLogger.log("warn"
|
|
1014
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
838
1015
|
break;
|
|
839
1016
|
case MatterLogLevel.ERROR:
|
|
840
|
-
matterLogger.log("error"
|
|
1017
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
841
1018
|
break;
|
|
842
1019
|
case MatterLogLevel.FATAL:
|
|
843
|
-
matterLogger.log("fatal"
|
|
1020
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
844
1021
|
break;
|
|
845
1022
|
default:
|
|
846
|
-
matterLogger.log("debug"
|
|
1023
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
847
1024
|
break;
|
|
848
1025
|
}
|
|
849
1026
|
};
|
|
850
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Creates a Matter File Logger.
|
|
1030
|
+
*
|
|
1031
|
+
* @param {string} filePath - The path to the log file.
|
|
1032
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1033
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1034
|
+
*/
|
|
851
1035
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1036
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
852
1037
|
let fileSize = 0;
|
|
853
1038
|
if (unlink) {
|
|
854
1039
|
try {
|
|
@@ -897,53 +1082,83 @@ export class Matterbridge extends EventEmitter {
|
|
|
897
1082
|
}
|
|
898
1083
|
};
|
|
899
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Update matterbridge and cleanup.
|
|
1087
|
+
*/
|
|
900
1088
|
async updateProcess() {
|
|
901
1089
|
await this.cleanup('updating...', false);
|
|
902
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1093
|
+
*/
|
|
903
1094
|
async restartProcess() {
|
|
904
1095
|
await this.cleanup('restarting...', true);
|
|
905
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Shut down the process by exiting the current process.
|
|
1099
|
+
*/
|
|
906
1100
|
async shutdownProcess() {
|
|
907
1101
|
await this.cleanup('shutting down...', false);
|
|
908
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Shut down the process and reset.
|
|
1105
|
+
*/
|
|
909
1106
|
async unregisterAndShutdownProcess() {
|
|
910
1107
|
this.log.info('Unregistering all devices and shutting down...');
|
|
911
|
-
for (const plugin of this.plugins) {
|
|
1108
|
+
for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
|
|
912
1109
|
await this.removeAllBridgedDevices(plugin.name);
|
|
913
1110
|
}
|
|
914
1111
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
915
1112
|
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Shut down the process and reset.
|
|
1115
|
+
*/
|
|
916
1116
|
async shutdownProcessAndReset() {
|
|
917
1117
|
await this.cleanup('shutting down with reset...', false);
|
|
918
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Shut down the process and factory reset.
|
|
1121
|
+
*/
|
|
919
1122
|
async shutdownProcessAndFactoryReset() {
|
|
920
1123
|
await this.cleanup('shutting down with factory reset...', false);
|
|
921
1124
|
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Cleans up the Matterbridge instance.
|
|
1127
|
+
* @param message - The cleanup message.
|
|
1128
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1129
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1130
|
+
*/
|
|
922
1131
|
async cleanup(message, restart = false) {
|
|
923
1132
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
924
1133
|
this.hasCleanupStarted = true;
|
|
925
1134
|
this.log.info(message);
|
|
1135
|
+
// Deregisters the process handlers
|
|
926
1136
|
this.deregisterProcesslHandlers();
|
|
1137
|
+
// Clear the start matter interval
|
|
927
1138
|
if (this.startMatterInterval) {
|
|
928
1139
|
clearInterval(this.startMatterInterval);
|
|
929
1140
|
this.startMatterInterval = undefined;
|
|
930
1141
|
this.log.debug('Start matter interval cleared');
|
|
931
1142
|
}
|
|
1143
|
+
// Clear the check update interval
|
|
932
1144
|
if (this.checkUpdateInterval) {
|
|
933
1145
|
clearInterval(this.checkUpdateInterval);
|
|
934
1146
|
this.checkUpdateInterval = undefined;
|
|
935
1147
|
this.log.debug('Check update interval cleared');
|
|
936
1148
|
}
|
|
1149
|
+
// Clear the configure timeout
|
|
937
1150
|
if (this.configureTimeout) {
|
|
938
1151
|
clearTimeout(this.configureTimeout);
|
|
939
1152
|
this.configureTimeout = undefined;
|
|
940
1153
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
941
1154
|
}
|
|
1155
|
+
// Clear the reachability timeout
|
|
942
1156
|
if (this.reachabilityTimeout) {
|
|
943
1157
|
clearTimeout(this.reachabilityTimeout);
|
|
944
1158
|
this.reachabilityTimeout = undefined;
|
|
945
1159
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
946
1160
|
}
|
|
1161
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
947
1162
|
for (const plugin of this.plugins) {
|
|
948
1163
|
if (!plugin.enabled || plugin.error)
|
|
949
1164
|
continue;
|
|
@@ -954,24 +1169,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
954
1169
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
955
1170
|
}
|
|
956
1171
|
}
|
|
1172
|
+
// Close the http server
|
|
957
1173
|
if (this.httpServer) {
|
|
958
1174
|
this.httpServer.close();
|
|
959
1175
|
this.httpServer.removeAllListeners();
|
|
960
1176
|
this.httpServer = undefined;
|
|
961
1177
|
this.log.debug('Frontend http server closed successfully');
|
|
962
1178
|
}
|
|
1179
|
+
// Close the https server
|
|
963
1180
|
if (this.httpsServer) {
|
|
964
1181
|
this.httpsServer.close();
|
|
965
1182
|
this.httpsServer.removeAllListeners();
|
|
966
1183
|
this.httpsServer = undefined;
|
|
967
1184
|
this.log.debug('Frontend https server closed successfully');
|
|
968
1185
|
}
|
|
1186
|
+
// Remove listeners from the express app
|
|
969
1187
|
if (this.expressApp) {
|
|
970
1188
|
this.expressApp.removeAllListeners();
|
|
971
1189
|
this.expressApp = undefined;
|
|
972
1190
|
this.log.debug('Frontend app closed successfully');
|
|
973
1191
|
}
|
|
1192
|
+
// Close the WebSocket server
|
|
974
1193
|
if (this.webSocketServer) {
|
|
1194
|
+
// Close all active connections
|
|
975
1195
|
this.webSocketServer.clients.forEach((client) => {
|
|
976
1196
|
if (client.readyState === WebSocket.OPEN) {
|
|
977
1197
|
client.close();
|
|
@@ -987,26 +1207,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
987
1207
|
});
|
|
988
1208
|
this.webSocketServer = undefined;
|
|
989
1209
|
}
|
|
1210
|
+
// Closing matter
|
|
990
1211
|
await this.stopMatterServer();
|
|
1212
|
+
// Closing matter storage
|
|
991
1213
|
await this.stopMatterStorage();
|
|
1214
|
+
// Remove the matterfilelogger
|
|
992
1215
|
try {
|
|
993
1216
|
Logger.removeLogger('matterfilelogger');
|
|
1217
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
994
1218
|
}
|
|
995
1219
|
catch (error) {
|
|
1220
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
996
1221
|
}
|
|
1222
|
+
// Serialize registeredDevices
|
|
997
1223
|
if (this.nodeStorage && this.nodeContext) {
|
|
998
1224
|
this.log.info('Saving registered devices...');
|
|
999
1225
|
const serializedRegisteredDevices = [];
|
|
1000
1226
|
this.devices.forEach(async (device) => {
|
|
1001
1227
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1228
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1002
1229
|
if (serializedMatterbridgeDevice)
|
|
1003
1230
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1004
1231
|
});
|
|
1005
1232
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1006
1233
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1234
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1007
1235
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1008
1236
|
await this.nodeContext.close();
|
|
1009
1237
|
this.nodeContext = undefined;
|
|
1238
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1010
1239
|
for (const plugin of this.plugins) {
|
|
1011
1240
|
if (plugin.nodeContext) {
|
|
1012
1241
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1037,13 +1266,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1037
1266
|
}
|
|
1038
1267
|
else {
|
|
1039
1268
|
if (message === 'shutting down with reset...') {
|
|
1269
|
+
// Delete matter storage file
|
|
1040
1270
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1041
1271
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1042
1272
|
this.log.info('Reset done! Remove all paired devices from the controllers.');
|
|
1043
1273
|
}
|
|
1044
1274
|
if (message === 'shutting down with factory reset...') {
|
|
1275
|
+
// Delete matter storage file
|
|
1045
1276
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1046
1277
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1278
|
+
// Delete node storage directory with its subdirectories
|
|
1047
1279
|
this.log.info('Resetting Matterbridge storage...');
|
|
1048
1280
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1049
1281
|
this.log.info('Factory reset done! Remove all paired devices from the controllers.');
|
|
@@ -1056,19 +1288,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1056
1288
|
this.initialized = false;
|
|
1057
1289
|
}
|
|
1058
1290
|
}
|
|
1291
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1059
1292
|
async addBridgedEndpoint(pluginName, device) {
|
|
1293
|
+
// Nothing to do here
|
|
1060
1294
|
}
|
|
1295
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1061
1296
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1297
|
+
// Nothing to do here
|
|
1062
1298
|
}
|
|
1299
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1063
1300
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1301
|
+
// Nothing to do here
|
|
1064
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Adds a bridged device to the Matterbridge.
|
|
1305
|
+
* @param pluginName - The name of the plugin.
|
|
1306
|
+
* @param device - The bridged device to add.
|
|
1307
|
+
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1308
|
+
*/
|
|
1065
1309
|
async addBridgedDevice(pluginName, device) {
|
|
1066
1310
|
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1311
|
+
// Check if the plugin is registered
|
|
1067
1312
|
const plugin = this.plugins.get(pluginName);
|
|
1068
1313
|
if (!plugin) {
|
|
1069
1314
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1070
1315
|
return;
|
|
1071
1316
|
}
|
|
1317
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1072
1318
|
if (this.bridgeMode === 'bridge') {
|
|
1073
1319
|
if (!this.matterAggregator) {
|
|
1074
1320
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1076,8 +1322,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1076
1322
|
}
|
|
1077
1323
|
this.matterAggregator.addBridgedDevice(device);
|
|
1078
1324
|
}
|
|
1325
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1326
|
+
// Register and add the device in childbridge mode
|
|
1079
1327
|
if (this.bridgeMode === 'childbridge') {
|
|
1080
1328
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1329
|
+
// Check if the plugin is locked with the commissioning server
|
|
1081
1330
|
if (!plugin.locked) {
|
|
1082
1331
|
plugin.locked = true;
|
|
1083
1332
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1091,6 +1340,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1091
1340
|
}
|
|
1092
1341
|
}
|
|
1093
1342
|
if (plugin.type === 'DynamicPlatform') {
|
|
1343
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1094
1344
|
if (!plugin.locked) {
|
|
1095
1345
|
plugin.locked = true;
|
|
1096
1346
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1098,7 +1348,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1098
1348
|
this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
|
|
1099
1349
|
plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
1100
1350
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1101
|
-
plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
|
|
1351
|
+
plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name); // Generate serialNumber and uniqueId
|
|
1102
1352
|
this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
1103
1353
|
plugin.commissioningServer.addDevice(plugin.aggregator);
|
|
1104
1354
|
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -1111,16 +1361,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1111
1361
|
plugin.registeredDevices++;
|
|
1112
1362
|
if (plugin.addedDevices !== undefined)
|
|
1113
1363
|
plugin.addedDevices++;
|
|
1364
|
+
// Add the device to the DeviceManager
|
|
1114
1365
|
this.devices.set(device);
|
|
1115
1366
|
this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1116
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Removes a bridged device from the Matterbridge.
|
|
1370
|
+
* @param pluginName - The name of the plugin.
|
|
1371
|
+
* @param device - The device to be removed.
|
|
1372
|
+
* @returns A Promise that resolves when the device is successfully removed.
|
|
1373
|
+
*/
|
|
1117
1374
|
async removeBridgedDevice(pluginName, device) {
|
|
1118
1375
|
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1376
|
+
// Check if the plugin is registered
|
|
1119
1377
|
const plugin = this.plugins.get(pluginName);
|
|
1120
1378
|
if (!plugin) {
|
|
1121
1379
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1122
1380
|
return;
|
|
1123
1381
|
}
|
|
1382
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1124
1383
|
if (this.bridgeMode === 'bridge') {
|
|
1125
1384
|
if (!this.matterAggregator) {
|
|
1126
1385
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1130,6 +1389,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1130
1389
|
device.setBridgedDeviceReachability(false);
|
|
1131
1390
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1132
1391
|
}
|
|
1392
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1393
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1133
1394
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1134
1395
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1135
1396
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1137,6 +1398,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1137
1398
|
if (plugin.addedDevices !== undefined)
|
|
1138
1399
|
plugin.addedDevices--;
|
|
1139
1400
|
}
|
|
1401
|
+
// Remove the device in childbridge mode
|
|
1140
1402
|
if (this.bridgeMode === 'childbridge') {
|
|
1141
1403
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1142
1404
|
if (!plugin.commissioningServer) {
|
|
@@ -1160,14 +1422,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1160
1422
|
plugin.registeredDevices--;
|
|
1161
1423
|
if (plugin.addedDevices !== undefined)
|
|
1162
1424
|
plugin.addedDevices--;
|
|
1425
|
+
// Remove the commissioning server
|
|
1163
1426
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1164
1427
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1165
1428
|
plugin.commissioningServer = undefined;
|
|
1166
1429
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1167
1430
|
}
|
|
1168
1431
|
}
|
|
1432
|
+
// Remove the device from the DeviceManager
|
|
1169
1433
|
this.devices.remove(device);
|
|
1170
1434
|
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
1437
|
+
*
|
|
1438
|
+
* @param pluginName - The name of the plugin.
|
|
1439
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
1440
|
+
*/
|
|
1171
1441
|
async removeAllBridgedDevices(pluginName) {
|
|
1172
1442
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1173
1443
|
this.devices.forEach(async (device) => {
|
|
@@ -1176,7 +1446,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1176
1446
|
}
|
|
1177
1447
|
});
|
|
1178
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Starts the Matterbridge in bridge mode.
|
|
1451
|
+
* @private
|
|
1452
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1453
|
+
*/
|
|
1179
1454
|
async startBridge() {
|
|
1455
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1180
1456
|
if (!this.storageManager)
|
|
1181
1457
|
throw new Error('No storage manager initialized');
|
|
1182
1458
|
if (!this.matterbridgeContext)
|
|
@@ -1195,6 +1471,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1195
1471
|
let failCount = 0;
|
|
1196
1472
|
this.startMatterInterval = setInterval(async () => {
|
|
1197
1473
|
for (const plugin of this.plugins) {
|
|
1474
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1198
1475
|
if (!plugin.enabled)
|
|
1199
1476
|
continue;
|
|
1200
1477
|
if (plugin.error) {
|
|
@@ -1219,15 +1496,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1219
1496
|
clearInterval(this.startMatterInterval);
|
|
1220
1497
|
this.startMatterInterval = undefined;
|
|
1221
1498
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1499
|
+
// Start the Matter server
|
|
1222
1500
|
await this.startMatterServer();
|
|
1223
1501
|
this.log.notice('Matter server started');
|
|
1502
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1224
1503
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1504
|
+
// Configure the plugins
|
|
1225
1505
|
this.configureTimeout = setTimeout(async () => {
|
|
1226
1506
|
for (const plugin of this.plugins) {
|
|
1227
1507
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1228
1508
|
continue;
|
|
1229
1509
|
try {
|
|
1230
|
-
await this.plugins.configure(plugin);
|
|
1510
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1231
1511
|
}
|
|
1232
1512
|
catch (error) {
|
|
1233
1513
|
plugin.error = true;
|
|
@@ -1236,6 +1516,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1236
1516
|
}
|
|
1237
1517
|
this.wssSendRefreshRequired();
|
|
1238
1518
|
}, 30 * 1000);
|
|
1519
|
+
// Setting reachability to true
|
|
1239
1520
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1240
1521
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1241
1522
|
if (this.commissioningServer)
|
|
@@ -1245,7 +1526,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1245
1526
|
}, 60 * 1000);
|
|
1246
1527
|
}, 1000);
|
|
1247
1528
|
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1531
|
+
* @private
|
|
1532
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1533
|
+
*/
|
|
1248
1534
|
async startChildbridge() {
|
|
1535
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1536
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1249
1537
|
if (!this.storageManager)
|
|
1250
1538
|
throw new Error('No storage manager initialized');
|
|
1251
1539
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
@@ -1255,6 +1543,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1255
1543
|
this.startMatterInterval = setInterval(async () => {
|
|
1256
1544
|
let allStarted = true;
|
|
1257
1545
|
for (const plugin of this.plugins) {
|
|
1546
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1258
1547
|
if (!plugin.enabled)
|
|
1259
1548
|
continue;
|
|
1260
1549
|
if (plugin.error) {
|
|
@@ -1282,14 +1571,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1282
1571
|
clearInterval(this.startMatterInterval);
|
|
1283
1572
|
this.startMatterInterval = undefined;
|
|
1284
1573
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1574
|
+
// Start the Matter server
|
|
1285
1575
|
await this.startMatterServer();
|
|
1286
1576
|
this.log.notice('Matter server started');
|
|
1577
|
+
// Configure the plugins
|
|
1287
1578
|
this.configureTimeout = setTimeout(async () => {
|
|
1288
1579
|
for (const plugin of this.plugins) {
|
|
1289
1580
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1290
1581
|
continue;
|
|
1291
1582
|
try {
|
|
1292
|
-
await this.plugins.configure(plugin);
|
|
1583
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1293
1584
|
}
|
|
1294
1585
|
catch (error) {
|
|
1295
1586
|
plugin.error = true;
|
|
@@ -1318,6 +1609,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1318
1609
|
continue;
|
|
1319
1610
|
}
|
|
1320
1611
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1612
|
+
// Setting reachability to true
|
|
1321
1613
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1322
1614
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1323
1615
|
if (plugin.commissioningServer)
|
|
@@ -1330,6 +1622,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1330
1622
|
}
|
|
1331
1623
|
}, 1000);
|
|
1332
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Starts the Matterbridge controller.
|
|
1627
|
+
* @private
|
|
1628
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1629
|
+
*/
|
|
1333
1630
|
async startController() {
|
|
1334
1631
|
if (!this.storageManager) {
|
|
1335
1632
|
this.log.error('No storage manager initialized');
|
|
@@ -1392,7 +1689,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1392
1689
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1393
1690
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1394
1691
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1395
|
-
}
|
|
1692
|
+
} // (hasParameter('pairingcode'))
|
|
1396
1693
|
if (hasParameter('unpairall')) {
|
|
1397
1694
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1398
1695
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1403,6 +1700,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1403
1700
|
return;
|
|
1404
1701
|
}
|
|
1405
1702
|
if (hasParameter('discover')) {
|
|
1703
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1704
|
+
// console.log(discover);
|
|
1406
1705
|
}
|
|
1407
1706
|
if (!this.commissioningController.isCommissioned()) {
|
|
1408
1707
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1443,10 +1742,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1443
1742
|
},
|
|
1444
1743
|
});
|
|
1445
1744
|
node.logStructure();
|
|
1745
|
+
// Get the interaction client
|
|
1446
1746
|
this.log.info('Getting the interaction client');
|
|
1447
1747
|
const interactionClient = await node.getInteractionClient();
|
|
1448
1748
|
let cluster;
|
|
1449
1749
|
let attributes;
|
|
1750
|
+
// Log BasicInformationCluster
|
|
1450
1751
|
cluster = BasicInformationCluster;
|
|
1451
1752
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1452
1753
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1456,6 +1757,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1456
1757
|
attributes.forEach((attribute) => {
|
|
1457
1758
|
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1458
1759
|
});
|
|
1760
|
+
// Log PowerSourceCluster
|
|
1459
1761
|
cluster = PowerSourceCluster;
|
|
1460
1762
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1461
1763
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1465,6 +1767,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1465
1767
|
attributes.forEach((attribute) => {
|
|
1466
1768
|
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1467
1769
|
});
|
|
1770
|
+
// Log ThreadNetworkDiagnostics
|
|
1468
1771
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1469
1772
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1470
1773
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1474,6 +1777,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1474
1777
|
attributes.forEach((attribute) => {
|
|
1475
1778
|
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1476
1779
|
});
|
|
1780
|
+
// Log SwitchCluster
|
|
1477
1781
|
cluster = SwitchCluster;
|
|
1478
1782
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1479
1783
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1494,6 +1798,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1494
1798
|
this.log.info('Subscribed to all attributes and events');
|
|
1495
1799
|
}
|
|
1496
1800
|
}
|
|
1801
|
+
/** ***********************************************************************************************************************************/
|
|
1802
|
+
/** Matter.js methods */
|
|
1803
|
+
/** ***********************************************************************************************************************************/
|
|
1804
|
+
/**
|
|
1805
|
+
* Starts the matter storage process based on the specified storage type and name.
|
|
1806
|
+
* @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
|
|
1807
|
+
* @param {string} storageName - The name of the storage file.
|
|
1808
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1809
|
+
*/
|
|
1497
1810
|
async startMatterStorage(storageType, storageName) {
|
|
1498
1811
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1499
1812
|
if (storageType === 'disk') {
|
|
@@ -1539,6 +1852,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1539
1852
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
1540
1853
|
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
1541
1854
|
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Makes a backup copy of the specified matter JSON storage file.
|
|
1857
|
+
*
|
|
1858
|
+
* @param storageName - The name of the JSON storage file to be backed up.
|
|
1859
|
+
* @param backupName - The name of the backup file to be created.
|
|
1860
|
+
*/
|
|
1542
1861
|
async backupMatterStorage(storageName, backupName) {
|
|
1543
1862
|
try {
|
|
1544
1863
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1559,6 +1878,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1559
1878
|
}
|
|
1560
1879
|
}
|
|
1561
1880
|
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Restore the specified matter JSON storage file.
|
|
1883
|
+
*
|
|
1884
|
+
* @param backupName - The name of the backup file to restore from.
|
|
1885
|
+
* @param storageName - The name of the JSON storage file to restored.
|
|
1886
|
+
*/
|
|
1562
1887
|
async restoreMatterStorage(backupName, storageName) {
|
|
1563
1888
|
try {
|
|
1564
1889
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1579,6 +1904,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1579
1904
|
}
|
|
1580
1905
|
}
|
|
1581
1906
|
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Stops the matter storage.
|
|
1909
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1910
|
+
*/
|
|
1582
1911
|
async stopMatterStorage() {
|
|
1583
1912
|
this.log.debug('Stopping storage');
|
|
1584
1913
|
await this.storageManager?.close();
|
|
@@ -1587,8 +1916,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1587
1916
|
this.matterbridgeContext = undefined;
|
|
1588
1917
|
this.mattercontrollerContext = undefined;
|
|
1589
1918
|
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
1921
|
+
* @param storageManager The storage manager to be used by the Matter server.
|
|
1922
|
+
*
|
|
1923
|
+
*/
|
|
1590
1924
|
createMatterServer(storageManager) {
|
|
1591
1925
|
this.log.debug('Creating matter server');
|
|
1926
|
+
// Validate mdnsInterface
|
|
1592
1927
|
if (this.mdnsInterface) {
|
|
1593
1928
|
const networkInterfaces = os.networkInterfaces();
|
|
1594
1929
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1604,6 +1939,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1604
1939
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1605
1940
|
return matterServer;
|
|
1606
1941
|
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Starts the Matter server.
|
|
1944
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
1945
|
+
*/
|
|
1607
1946
|
async startMatterServer() {
|
|
1608
1947
|
if (!this.matterServer) {
|
|
1609
1948
|
this.log.error('No matter server initialized');
|
|
@@ -1613,7 +1952,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1613
1952
|
this.log.debug('Starting matter server...');
|
|
1614
1953
|
await this.matterServer.start();
|
|
1615
1954
|
this.log.debug('Started matter server');
|
|
1955
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1616
1956
|
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
1959
|
+
*/
|
|
1617
1960
|
async stopMatterServer() {
|
|
1618
1961
|
this.log.debug('Stopping matter commissioningServer');
|
|
1619
1962
|
await this.commissioningServer?.close();
|
|
@@ -1627,22 +1970,78 @@ export class Matterbridge extends EventEmitter {
|
|
|
1627
1970
|
this.matterAggregator = undefined;
|
|
1628
1971
|
this.matterServer = undefined;
|
|
1629
1972
|
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Creates a Matter Aggregator.
|
|
1975
|
+
* @param {StorageContext} context - The storage context.
|
|
1976
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
1977
|
+
*/
|
|
1978
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1630
1979
|
async createMatterAggregator(context, pluginName) {
|
|
1980
|
+
/*
|
|
1981
|
+
const random = randomBytes(8).toString('hex');
|
|
1982
|
+
await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
|
|
1983
|
+
await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
|
|
1984
|
+
|
|
1985
|
+
this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with uniqueId ${await context.get<string>('aggregatorUniqueId')} serialNumber ${await context.get<string>('aggregatorSerialNumber')}`);
|
|
1986
|
+
this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with softwareVersion ${await context.get<number>('softwareVersion', 1)} softwareVersionString ${await context.get<string>('softwareVersionString', '1.0.0')}`);
|
|
1987
|
+
this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with hardwareVersion ${await context.get<number>('hardwareVersion', 1)} hardwareVersionString ${await context.get<string>('hardwareVersionString', '1.0.0')}`);
|
|
1988
|
+
*/
|
|
1631
1989
|
const matterAggregator = new Aggregator();
|
|
1990
|
+
/*
|
|
1991
|
+
matterAggregator.addClusterServer(
|
|
1992
|
+
ClusterServer(
|
|
1993
|
+
BasicInformationCluster,
|
|
1994
|
+
{
|
|
1995
|
+
dataModelRevision: 1,
|
|
1996
|
+
location: 'FR',
|
|
1997
|
+
vendorId: VendorId(0xfff1),
|
|
1998
|
+
vendorName: 'Matterbridge',
|
|
1999
|
+
productId: 0x8000,
|
|
2000
|
+
productName: 'Matterbridge aggregator',
|
|
2001
|
+
productLabel: 'Matterbridge aggregator',
|
|
2002
|
+
nodeLabel: 'Matterbridge aggregator',
|
|
2003
|
+
serialNumber: await context.get<string>('aggregatorSerialNumber'),
|
|
2004
|
+
uniqueId: await context.get<string>('aggregatorUniqueId'),
|
|
2005
|
+
softwareVersion: await context.get<number>('softwareVersion', 1),
|
|
2006
|
+
softwareVersionString: await context.get<string>('softwareVersionString', '1.0.0'),
|
|
2007
|
+
hardwareVersion: await context.get<number>('hardwareVersion', 1),
|
|
2008
|
+
hardwareVersionString: await context.get<string>('hardwareVersionString', '1.0.0'),
|
|
2009
|
+
reachable: true,
|
|
2010
|
+
capabilityMinima: { caseSessionsPerFabric: 3, subscriptionsPerFabric: 3 },
|
|
2011
|
+
specificationVersion: Specification.SPECIFICATION_VERSION,
|
|
2012
|
+
maxPathsPerInvoke: 1,
|
|
2013
|
+
},
|
|
2014
|
+
{},
|
|
2015
|
+
{
|
|
2016
|
+
startUp: true,
|
|
2017
|
+
shutDown: true,
|
|
2018
|
+
leave: true,
|
|
2019
|
+
reachableChanged: true,
|
|
2020
|
+
},
|
|
2021
|
+
),
|
|
2022
|
+
);
|
|
2023
|
+
*/
|
|
1632
2024
|
return matterAggregator;
|
|
1633
2025
|
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Creates a matter commissioning server.
|
|
2028
|
+
*
|
|
2029
|
+
* @param {StorageContext} context - The storage context.
|
|
2030
|
+
* @param {string} pluginName - The name of the commissioning server.
|
|
2031
|
+
* @returns {CommissioningServer} The created commissioning server.
|
|
2032
|
+
*/
|
|
1634
2033
|
async createCommisioningServer(context, pluginName) {
|
|
1635
2034
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1636
2035
|
const deviceName = await context.get('deviceName');
|
|
1637
2036
|
const deviceType = await context.get('deviceType');
|
|
1638
2037
|
const vendorId = await context.get('vendorId');
|
|
1639
|
-
const vendorName = await context.get('vendorName');
|
|
2038
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1640
2039
|
const productId = await context.get('productId');
|
|
1641
|
-
const productName = await context.get('productName');
|
|
2040
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1642
2041
|
const serialNumber = await context.get('serialNumber');
|
|
1643
2042
|
const uniqueId = await context.get('uniqueId');
|
|
1644
2043
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1645
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2044
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1646
2045
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1647
2046
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1648
2047
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
|
|
@@ -1650,6 +2049,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1650
2049
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1651
2050
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1652
2051
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
|
|
2052
|
+
// Validate ipv4address
|
|
1653
2053
|
if (this.ipv4address) {
|
|
1654
2054
|
const networkInterfaces = os.networkInterfaces();
|
|
1655
2055
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1664,6 +2064,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1664
2064
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1665
2065
|
}
|
|
1666
2066
|
}
|
|
2067
|
+
// Validate ipv6address
|
|
1667
2068
|
if (this.ipv6address) {
|
|
1668
2069
|
const networkInterfaces = os.networkInterfaces();
|
|
1669
2070
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1694,7 +2095,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1694
2095
|
nodeLabel: productName,
|
|
1695
2096
|
productLabel: productName,
|
|
1696
2097
|
softwareVersion,
|
|
1697
|
-
softwareVersionString,
|
|
2098
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1698
2099
|
hardwareVersion,
|
|
1699
2100
|
hardwareVersionString,
|
|
1700
2101
|
uniqueId,
|
|
@@ -1790,6 +2191,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1790
2191
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1791
2192
|
return commissioningServer;
|
|
1792
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Creates a commissioning server storage context.
|
|
2196
|
+
*
|
|
2197
|
+
* @param pluginName - The name of the plugin.
|
|
2198
|
+
* @param deviceName - The name of the device.
|
|
2199
|
+
* @param deviceType - The type of the device.
|
|
2200
|
+
* @param vendorId - The vendor ID.
|
|
2201
|
+
* @param vendorName - The vendor name.
|
|
2202
|
+
* @param productId - The product ID.
|
|
2203
|
+
* @param productName - The product name.
|
|
2204
|
+
* @param serialNumber - The serial number of the device (optional).
|
|
2205
|
+
* @param uniqueId - The unique ID of the device (optional).
|
|
2206
|
+
* @param softwareVersion - The software version of the device (optional).
|
|
2207
|
+
* @param softwareVersionString - The software version string of the device (optional).
|
|
2208
|
+
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2209
|
+
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2210
|
+
* @returns The storage context for the commissioning server.
|
|
2211
|
+
*/
|
|
1793
2212
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
1794
2213
|
if (!this.storageManager)
|
|
1795
2214
|
throw new Error('No storage manager initialized');
|
|
@@ -1817,6 +2236,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1817
2236
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1818
2237
|
return storageContext;
|
|
1819
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Imports the commissioning server context for a specific plugin and device.
|
|
2241
|
+
* @param pluginName - The name of the plugin.
|
|
2242
|
+
* @param device - The MatterbridgeDevice object representing the device.
|
|
2243
|
+
* @returns The commissioning server context.
|
|
2244
|
+
* @throws Error if the BasicInformationCluster is not found.
|
|
2245
|
+
*/
|
|
1820
2246
|
async importCommissioningServerContext(pluginName, device) {
|
|
1821
2247
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
1822
2248
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -1851,6 +2277,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1851
2277
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1852
2278
|
return storageContext;
|
|
1853
2279
|
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Shows the commissioning server QR code for a given plugin.
|
|
2282
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2283
|
+
* @param {StorageContext} storageContext - The storage context instance.
|
|
2284
|
+
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2285
|
+
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2286
|
+
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2287
|
+
*/
|
|
1854
2288
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
1855
2289
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
1856
2290
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -1861,7 +2295,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1861
2295
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
1862
2296
|
const QrCode = new QrCodeSchema();
|
|
1863
2297
|
this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
|
|
1864
|
-
|
|
2298
|
+
// eslint-disable-next-line no-console
|
|
2299
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
1865
2300
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
1866
2301
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
1867
2302
|
if (pluginName === 'Matterbridge') {
|
|
@@ -1908,6 +2343,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1908
2343
|
}
|
|
1909
2344
|
this.wssSendRefreshRequired();
|
|
1910
2345
|
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2348
|
+
*
|
|
2349
|
+
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2350
|
+
* @returns An array of sanitized exposed fabric information objects.
|
|
2351
|
+
*/
|
|
1911
2352
|
sanitizeFabricInformations(fabricInfo) {
|
|
1912
2353
|
return fabricInfo.map((info) => {
|
|
1913
2354
|
return {
|
|
@@ -1921,6 +2362,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1921
2362
|
};
|
|
1922
2363
|
});
|
|
1923
2364
|
}
|
|
2365
|
+
/**
|
|
2366
|
+
* Sanitizes the session information by converting bigint properties to string.
|
|
2367
|
+
*
|
|
2368
|
+
* @param sessionInfo - The array of session information objects.
|
|
2369
|
+
* @returns An array of sanitized session information objects.
|
|
2370
|
+
*/
|
|
1924
2371
|
sanitizeSessionInformation(sessionInfo) {
|
|
1925
2372
|
return sessionInfo
|
|
1926
2373
|
.filter((session) => session.isPeerActive)
|
|
@@ -1948,6 +2395,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1948
2395
|
};
|
|
1949
2396
|
});
|
|
1950
2397
|
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Sets the reachability of a commissioning server and trigger.
|
|
2400
|
+
*
|
|
2401
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2402
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2403
|
+
*/
|
|
1951
2404
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
1952
2405
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
1953
2406
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -1955,6 +2408,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1955
2408
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
1956
2409
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1957
2410
|
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2413
|
+
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2414
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2415
|
+
*/
|
|
1958
2416
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
1959
2417
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
1960
2418
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -1967,6 +2425,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1967
2425
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1968
2426
|
});
|
|
1969
2427
|
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Sets the reachability of a device and trigger.
|
|
2430
|
+
*
|
|
2431
|
+
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2432
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2433
|
+
*/
|
|
1970
2434
|
setDeviceReachability(device, reachable) {
|
|
1971
2435
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
1972
2436
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2015,6 +2479,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2015
2479
|
}
|
|
2016
2480
|
return vendorName;
|
|
2017
2481
|
};
|
|
2482
|
+
/**
|
|
2483
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
2484
|
+
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
2485
|
+
*/
|
|
2018
2486
|
async getBaseRegisteredPlugins() {
|
|
2019
2487
|
const baseRegisteredPlugins = [];
|
|
2020
2488
|
for (const plugin of this.plugins) {
|
|
@@ -2046,13 +2514,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2046
2514
|
}
|
|
2047
2515
|
return baseRegisteredPlugins;
|
|
2048
2516
|
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Spawns a child process with the given command and arguments.
|
|
2519
|
+
* @param {string} command - The command to execute.
|
|
2520
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2521
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2522
|
+
*/
|
|
2049
2523
|
async spawnCommand(command, args = []) {
|
|
2524
|
+
/*
|
|
2525
|
+
npm > npm.cmd on windows
|
|
2526
|
+
cmd.exe ['dir'] on windows
|
|
2527
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2528
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2529
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2533
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2534
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2535
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2536
|
+
*/
|
|
2050
2537
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2051
2538
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2539
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2052
2540
|
const argstring = 'npm ' + args.join(' ');
|
|
2053
2541
|
args.splice(0, args.length, '/c', argstring);
|
|
2054
2542
|
command = 'cmd.exe';
|
|
2055
2543
|
}
|
|
2544
|
+
// Decide when using sudo on linux
|
|
2545
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2546
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2056
2547
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2057
2548
|
args.unshift(command);
|
|
2058
2549
|
command = 'sudo';
|
|
@@ -2110,55 +2601,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2110
2601
|
}
|
|
2111
2602
|
});
|
|
2112
2603
|
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Sends a WebSocket message to all connected clients.
|
|
2606
|
+
*
|
|
2607
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2608
|
+
* @param {string} time - The time string of the message
|
|
2609
|
+
* @param {string} name - The logger name of the message
|
|
2610
|
+
* @param {string} message - The content of the message.
|
|
2611
|
+
*/
|
|
2113
2612
|
wssSendMessage(level, time, name, message) {
|
|
2114
2613
|
if (!level || !time || !name || !message)
|
|
2115
2614
|
return;
|
|
2615
|
+
// Remove ANSI escape codes from the message
|
|
2616
|
+
// eslint-disable-next-line no-control-regex
|
|
2116
2617
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2618
|
+
// Remove leading asterisks from the message
|
|
2117
2619
|
message = message.replace(/^\*+/, '');
|
|
2620
|
+
// Replace all occurrences of \t and \n
|
|
2118
2621
|
message = message.replace(/[\t\n]/g, '');
|
|
2622
|
+
// Remove non-printable characters
|
|
2623
|
+
// eslint-disable-next-line no-control-regex
|
|
2119
2624
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2625
|
+
// Replace all occurrences of \" with "
|
|
2120
2626
|
message = message.replace(/\\"/g, '"');
|
|
2627
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2121
2628
|
const maxContinuousLength = 100;
|
|
2122
2629
|
const keepStartLength = 20;
|
|
2123
2630
|
const keepEndLength = 20;
|
|
2631
|
+
// Split the message into words
|
|
2124
2632
|
message = message
|
|
2125
2633
|
.split(' ')
|
|
2126
2634
|
.map((word) => {
|
|
2635
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2127
2636
|
if (word.length > maxContinuousLength) {
|
|
2128
2637
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2129
2638
|
}
|
|
2130
2639
|
return word;
|
|
2131
2640
|
})
|
|
2132
2641
|
.join(' ');
|
|
2642
|
+
// Send the message to all connected clients
|
|
2133
2643
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2134
2644
|
if (client.readyState === WebSocket.OPEN) {
|
|
2135
2645
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2136
2646
|
}
|
|
2137
2647
|
});
|
|
2138
2648
|
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2651
|
+
*
|
|
2652
|
+
*/
|
|
2139
2653
|
wssSendRefreshRequired() {
|
|
2140
2654
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2655
|
+
// Send the message to all connected clients
|
|
2141
2656
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2142
2657
|
if (client.readyState === WebSocket.OPEN) {
|
|
2143
2658
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
|
|
2144
2659
|
}
|
|
2145
2660
|
});
|
|
2146
2661
|
}
|
|
2662
|
+
/**
|
|
2663
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2664
|
+
*
|
|
2665
|
+
*/
|
|
2147
2666
|
wssSendRestartRequired() {
|
|
2148
2667
|
this.matterbridgeInformation.restartRequired = true;
|
|
2668
|
+
// Send the message to all connected clients
|
|
2149
2669
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2150
2670
|
if (client.readyState === WebSocket.OPEN) {
|
|
2151
2671
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
|
|
2152
2672
|
}
|
|
2153
2673
|
});
|
|
2154
2674
|
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Initializes the frontend of Matterbridge.
|
|
2677
|
+
*
|
|
2678
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
2679
|
+
*/
|
|
2155
2680
|
async initializeFrontend(port = 8283) {
|
|
2156
2681
|
let initializeError = false;
|
|
2157
2682
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2683
|
+
// Create the express app that serves the frontend
|
|
2158
2684
|
this.expressApp = express();
|
|
2685
|
+
// Log all requests to the server for debugging
|
|
2686
|
+
/*
|
|
2687
|
+
if (hasParameter('homedir')) {
|
|
2688
|
+
this.expressApp.use((req, res, next) => {
|
|
2689
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
2690
|
+
next();
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
*/
|
|
2694
|
+
// Serve static files from '/static' endpoint
|
|
2159
2695
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2160
2696
|
if (!hasParameter('ssl')) {
|
|
2697
|
+
// Create an HTTP server and attach the express app
|
|
2161
2698
|
this.httpServer = createServer(this.expressApp);
|
|
2699
|
+
// Listen on the specified port
|
|
2162
2700
|
if (hasParameter('ingress')) {
|
|
2163
2701
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2164
2702
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2172,6 +2710,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2172
2710
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2173
2711
|
});
|
|
2174
2712
|
}
|
|
2713
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2175
2714
|
this.httpServer.on('error', (error) => {
|
|
2176
2715
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2177
2716
|
switch (error.code) {
|
|
@@ -2187,6 +2726,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2187
2726
|
});
|
|
2188
2727
|
}
|
|
2189
2728
|
else {
|
|
2729
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2190
2730
|
let cert;
|
|
2191
2731
|
try {
|
|
2192
2732
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2214,7 +2754,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2214
2754
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2215
2755
|
}
|
|
2216
2756
|
const serverOptions = { cert, key, ca };
|
|
2757
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
2217
2758
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
2759
|
+
// Listen on the specified port
|
|
2218
2760
|
if (hasParameter('ingress')) {
|
|
2219
2761
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2220
2762
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2228,6 +2770,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2228
2770
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2229
2771
|
});
|
|
2230
2772
|
}
|
|
2773
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2231
2774
|
this.httpsServer.on('error', (error) => {
|
|
2232
2775
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2233
2776
|
switch (error.code) {
|
|
@@ -2244,12 +2787,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2244
2787
|
}
|
|
2245
2788
|
if (initializeError)
|
|
2246
2789
|
return;
|
|
2790
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2247
2791
|
const wssPort = port;
|
|
2248
2792
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2249
2793
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2250
2794
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2251
2795
|
const clientIp = request.socket.remoteAddress;
|
|
2252
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
2796
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2253
2797
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2254
2798
|
ws.on('message', (message) => {
|
|
2255
2799
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2282,6 +2826,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2282
2826
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2283
2827
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2284
2828
|
});
|
|
2829
|
+
// Endpoint to validate login code
|
|
2285
2830
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2286
2831
|
const { password } = req.body;
|
|
2287
2832
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2300,12 +2845,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2300
2845
|
this.log.warn('/api/login error wrong password');
|
|
2301
2846
|
res.json({ valid: false });
|
|
2302
2847
|
}
|
|
2848
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2303
2849
|
}
|
|
2304
2850
|
catch (error) {
|
|
2305
2851
|
this.log.error('/api/login error getting password');
|
|
2306
2852
|
res.json({ valid: false });
|
|
2307
2853
|
}
|
|
2308
2854
|
});
|
|
2855
|
+
// Endpoint to provide settings
|
|
2309
2856
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2310
2857
|
this.log.debug('The frontend sent /api/settings');
|
|
2311
2858
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2326,16 +2873,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2326
2873
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2327
2874
|
this.matterbridgeInformation.profile = this.profile;
|
|
2328
2875
|
const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2876
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2329
2877
|
res.json(response);
|
|
2330
2878
|
});
|
|
2879
|
+
// Endpoint to provide plugins
|
|
2331
2880
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2332
2881
|
this.log.debug('The frontend sent /api/plugins');
|
|
2333
2882
|
const response = await this.getBaseRegisteredPlugins();
|
|
2883
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2334
2884
|
res.json(response);
|
|
2335
2885
|
});
|
|
2886
|
+
// Endpoint to provide devices
|
|
2336
2887
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2337
2888
|
this.log.debug('The frontend sent /api/devices');
|
|
2338
|
-
const
|
|
2889
|
+
const devices = [];
|
|
2339
2890
|
this.devices.forEach(async (device) => {
|
|
2340
2891
|
const pluginName = device.plugin ?? 'Unknown';
|
|
2341
2892
|
if (this.edge)
|
|
@@ -2346,22 +2897,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
2346
2897
|
let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
|
|
2347
2898
|
if (!serial)
|
|
2348
2899
|
serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
|
|
2900
|
+
let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
|
|
2901
|
+
if (!productUrl)
|
|
2902
|
+
productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
|
|
2349
2903
|
let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
|
|
2350
2904
|
if (!uniqueId)
|
|
2351
2905
|
uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
|
|
2352
2906
|
const cluster = this.getClusterTextFromDevice(device);
|
|
2353
|
-
|
|
2907
|
+
devices.push({
|
|
2354
2908
|
pluginName,
|
|
2355
2909
|
type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
|
|
2356
2910
|
endpoint: device.number,
|
|
2357
2911
|
name,
|
|
2358
2912
|
serial,
|
|
2913
|
+
productUrl,
|
|
2914
|
+
configUrl: device.configUrl,
|
|
2359
2915
|
uniqueId,
|
|
2360
2916
|
cluster: cluster,
|
|
2361
2917
|
});
|
|
2362
2918
|
});
|
|
2363
|
-
|
|
2919
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2920
|
+
res.json(devices);
|
|
2364
2921
|
});
|
|
2922
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2365
2923
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2366
2924
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2367
2925
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2381,6 +2939,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2381
2939
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2382
2940
|
if (clusterServer.name === 'EveHistory')
|
|
2383
2941
|
return;
|
|
2942
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2384
2943
|
let attributeValue;
|
|
2385
2944
|
try {
|
|
2386
2945
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2391,6 +2950,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2391
2950
|
catch (error) {
|
|
2392
2951
|
attributeValue = 'Fabric-Scoped';
|
|
2393
2952
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
2953
|
+
// console.log(error);
|
|
2394
2954
|
}
|
|
2395
2955
|
data.push({
|
|
2396
2956
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2403,12 +2963,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2403
2963
|
});
|
|
2404
2964
|
});
|
|
2405
2965
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
2966
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2406
2967
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2407
2968
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2408
2969
|
clusterServers.forEach((clusterServer) => {
|
|
2409
2970
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2410
2971
|
if (clusterServer.name === 'EveHistory')
|
|
2411
2972
|
return;
|
|
2973
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2412
2974
|
let attributeValue;
|
|
2413
2975
|
try {
|
|
2414
2976
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2419,6 +2981,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2419
2981
|
catch (error) {
|
|
2420
2982
|
attributeValue = 'Unavailable';
|
|
2421
2983
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
2984
|
+
// console.log(error);
|
|
2422
2985
|
}
|
|
2423
2986
|
data.push({
|
|
2424
2987
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2435,6 +2998,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2435
2998
|
});
|
|
2436
2999
|
res.json(data);
|
|
2437
3000
|
});
|
|
3001
|
+
// Endpoint to view the log
|
|
2438
3002
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2439
3003
|
this.log.debug('The frontend sent /api/log');
|
|
2440
3004
|
try {
|
|
@@ -2447,10 +3011,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2447
3011
|
res.status(500).send('Error reading log file');
|
|
2448
3012
|
}
|
|
2449
3013
|
});
|
|
3014
|
+
// Endpoint to download the matterbridge log
|
|
2450
3015
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2451
3016
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2452
3017
|
try {
|
|
2453
3018
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3019
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2454
3020
|
}
|
|
2455
3021
|
catch (error) {
|
|
2456
3022
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2462,10 +3028,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2462
3028
|
}
|
|
2463
3029
|
});
|
|
2464
3030
|
});
|
|
3031
|
+
// Endpoint to download the matter log
|
|
2465
3032
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2466
3033
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2467
3034
|
try {
|
|
2468
3035
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3036
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2469
3037
|
}
|
|
2470
3038
|
catch (error) {
|
|
2471
3039
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2477,6 +3045,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2477
3045
|
}
|
|
2478
3046
|
});
|
|
2479
3047
|
});
|
|
3048
|
+
// Endpoint to download the matter storage file
|
|
2480
3049
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2481
3050
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2482
3051
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2486,6 +3055,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2486
3055
|
}
|
|
2487
3056
|
});
|
|
2488
3057
|
});
|
|
3058
|
+
// Endpoint to download the matterbridge storage directory
|
|
2489
3059
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2490
3060
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2491
3061
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2496,6 +3066,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2496
3066
|
}
|
|
2497
3067
|
});
|
|
2498
3068
|
});
|
|
3069
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2499
3070
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2500
3071
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2501
3072
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2506,9 +3077,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2506
3077
|
}
|
|
2507
3078
|
});
|
|
2508
3079
|
});
|
|
3080
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2509
3081
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2510
3082
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2511
3083
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3084
|
+
// await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
2512
3085
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2513
3086
|
if (error) {
|
|
2514
3087
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2516,6 +3089,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2516
3089
|
}
|
|
2517
3090
|
});
|
|
2518
3091
|
});
|
|
3092
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2519
3093
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2520
3094
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2521
3095
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2525,6 +3099,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2525
3099
|
}
|
|
2526
3100
|
});
|
|
2527
3101
|
});
|
|
3102
|
+
// Endpoint to receive commands
|
|
2528
3103
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2529
3104
|
const command = req.params.command;
|
|
2530
3105
|
let param = req.params.param;
|
|
@@ -2534,13 +3109,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2534
3109
|
return;
|
|
2535
3110
|
}
|
|
2536
3111
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3112
|
+
// Handle the command setpassword from Settings
|
|
2537
3113
|
if (command === 'setpassword') {
|
|
2538
|
-
const password = param.slice(1, -1);
|
|
3114
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2539
3115
|
this.log.debug('setpassword', param, password);
|
|
2540
3116
|
await this.nodeContext?.set('password', password);
|
|
2541
3117
|
res.json({ message: 'Command received' });
|
|
2542
3118
|
return;
|
|
2543
3119
|
}
|
|
3120
|
+
// Handle the command setbridgemode from Settings
|
|
2544
3121
|
if (command === 'setbridgemode') {
|
|
2545
3122
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2546
3123
|
this.wssSendRestartRequired();
|
|
@@ -2548,6 +3125,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2548
3125
|
res.json({ message: 'Command received' });
|
|
2549
3126
|
return;
|
|
2550
3127
|
}
|
|
3128
|
+
// Handle the command backup from Settings
|
|
2551
3129
|
if (command === 'backup') {
|
|
2552
3130
|
this.log.notice(`Prepairing the backup...`);
|
|
2553
3131
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2555,25 +3133,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2555
3133
|
res.json({ message: 'Command received' });
|
|
2556
3134
|
return;
|
|
2557
3135
|
}
|
|
3136
|
+
// Handle the command setmbloglevel from Settings
|
|
2558
3137
|
if (command === 'setmbloglevel') {
|
|
2559
3138
|
this.log.debug('Matterbridge log level:', param);
|
|
2560
3139
|
if (param === 'Debug') {
|
|
2561
|
-
this.log.logLevel = "debug"
|
|
3140
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2562
3141
|
}
|
|
2563
3142
|
else if (param === 'Info') {
|
|
2564
|
-
this.log.logLevel = "info"
|
|
3143
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2565
3144
|
}
|
|
2566
3145
|
else if (param === 'Notice') {
|
|
2567
|
-
this.log.logLevel = "notice"
|
|
3146
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2568
3147
|
}
|
|
2569
3148
|
else if (param === 'Warn') {
|
|
2570
|
-
this.log.logLevel = "warn"
|
|
3149
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2571
3150
|
}
|
|
2572
3151
|
else if (param === 'Error') {
|
|
2573
|
-
this.log.logLevel = "error"
|
|
3152
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2574
3153
|
}
|
|
2575
3154
|
else if (param === 'Fatal') {
|
|
2576
|
-
this.log.logLevel = "fatal"
|
|
3155
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2577
3156
|
}
|
|
2578
3157
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2579
3158
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2581,12 +3160,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2581
3160
|
for (const plugin of this.plugins) {
|
|
2582
3161
|
if (!plugin.platform || !plugin.platform.config)
|
|
2583
3162
|
continue;
|
|
2584
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2585
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
3163
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3164
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
2586
3165
|
}
|
|
2587
3166
|
res.json({ message: 'Command received' });
|
|
2588
3167
|
return;
|
|
2589
3168
|
}
|
|
3169
|
+
// Handle the command setmbloglevel from Settings
|
|
2590
3170
|
if (command === 'setmjloglevel') {
|
|
2591
3171
|
this.log.debug('Matter.js log level:', param);
|
|
2592
3172
|
if (param === 'Debug') {
|
|
@@ -2611,30 +3191,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2611
3191
|
res.json({ message: 'Command received' });
|
|
2612
3192
|
return;
|
|
2613
3193
|
}
|
|
3194
|
+
// Handle the command setmdnsinterface from Settings
|
|
2614
3195
|
if (command === 'setmdnsinterface') {
|
|
2615
|
-
param = param.slice(1, -1);
|
|
3196
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2616
3197
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2617
3198
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2618
3199
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2619
3200
|
res.json({ message: 'Command received' });
|
|
2620
3201
|
return;
|
|
2621
3202
|
}
|
|
3203
|
+
// Handle the command setipv4address from Settings
|
|
2622
3204
|
if (command === 'setipv4address') {
|
|
2623
|
-
param = param.slice(1, -1);
|
|
3205
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2624
3206
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2625
3207
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2626
3208
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2627
3209
|
res.json({ message: 'Command received' });
|
|
2628
3210
|
return;
|
|
2629
3211
|
}
|
|
3212
|
+
// Handle the command setipv6address from Settings
|
|
2630
3213
|
if (command === 'setipv6address') {
|
|
2631
|
-
param = param.slice(1, -1);
|
|
3214
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2632
3215
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2633
3216
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2634
3217
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2635
3218
|
res.json({ message: 'Command received' });
|
|
2636
3219
|
return;
|
|
2637
3220
|
}
|
|
3221
|
+
// Handle the command setmatterport from Settings
|
|
2638
3222
|
if (command === 'setmatterport') {
|
|
2639
3223
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2640
3224
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2643,6 +3227,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2643
3227
|
res.json({ message: 'Command received' });
|
|
2644
3228
|
return;
|
|
2645
3229
|
}
|
|
3230
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2646
3231
|
if (command === 'setmatterdiscriminator') {
|
|
2647
3232
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2648
3233
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2651,6 +3236,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2651
3236
|
res.json({ message: 'Command received' });
|
|
2652
3237
|
return;
|
|
2653
3238
|
}
|
|
3239
|
+
// Handle the command setmatterpasscode from Settings
|
|
2654
3240
|
if (command === 'setmatterpasscode') {
|
|
2655
3241
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2656
3242
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2659,17 +3245,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2659
3245
|
res.json({ message: 'Command received' });
|
|
2660
3246
|
return;
|
|
2661
3247
|
}
|
|
3248
|
+
// Handle the command setmbloglevel from Settings
|
|
2662
3249
|
if (command === 'setmblogfile') {
|
|
2663
3250
|
this.log.debug('Matterbridge file log:', param);
|
|
2664
3251
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2665
3252
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3253
|
+
// Create the file logger for matterbridge
|
|
2666
3254
|
if (param === 'true')
|
|
2667
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3255
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2668
3256
|
else
|
|
2669
3257
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2670
3258
|
res.json({ message: 'Command received' });
|
|
2671
3259
|
return;
|
|
2672
3260
|
}
|
|
3261
|
+
// Handle the command setmbloglevel from Settings
|
|
2673
3262
|
if (command === 'setmjlogfile') {
|
|
2674
3263
|
this.log.debug('Matter file log:', param);
|
|
2675
3264
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2696,36 +3285,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2696
3285
|
res.json({ message: 'Command received' });
|
|
2697
3286
|
return;
|
|
2698
3287
|
}
|
|
3288
|
+
// Handle the command unregister from Settings
|
|
2699
3289
|
if (command === 'unregister') {
|
|
2700
3290
|
await this.unregisterAndShutdownProcess();
|
|
2701
3291
|
res.json({ message: 'Command received' });
|
|
2702
3292
|
return;
|
|
2703
3293
|
}
|
|
3294
|
+
// Handle the command reset from Settings
|
|
2704
3295
|
if (command === 'reset') {
|
|
2705
3296
|
await this.shutdownProcessAndReset();
|
|
2706
3297
|
res.json({ message: 'Command received' });
|
|
2707
3298
|
return;
|
|
2708
3299
|
}
|
|
3300
|
+
// Handle the command factoryreset from Settings
|
|
2709
3301
|
if (command === 'factoryreset') {
|
|
2710
3302
|
await this.shutdownProcessAndFactoryReset();
|
|
2711
3303
|
res.json({ message: 'Command received' });
|
|
2712
3304
|
return;
|
|
2713
3305
|
}
|
|
3306
|
+
// Handle the command shutdown from Header
|
|
2714
3307
|
if (command === 'shutdown') {
|
|
2715
3308
|
await this.shutdownProcess();
|
|
2716
3309
|
res.json({ message: 'Command received' });
|
|
2717
3310
|
return;
|
|
2718
3311
|
}
|
|
3312
|
+
// Handle the command restart from Header
|
|
2719
3313
|
if (command === 'restart') {
|
|
2720
3314
|
await this.restartProcess();
|
|
2721
3315
|
res.json({ message: 'Command received' });
|
|
2722
3316
|
return;
|
|
2723
3317
|
}
|
|
3318
|
+
// Handle the command update from Header
|
|
2724
3319
|
if (command === 'update') {
|
|
2725
3320
|
this.log.info('Updating matterbridge...');
|
|
2726
3321
|
try {
|
|
2727
3322
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2728
3323
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3324
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2729
3325
|
}
|
|
2730
3326
|
catch (error) {
|
|
2731
3327
|
this.log.error('Error updating matterbridge');
|
|
@@ -2735,9 +3331,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2735
3331
|
res.json({ message: 'Command received' });
|
|
2736
3332
|
return;
|
|
2737
3333
|
}
|
|
3334
|
+
// Handle the command saveconfig from Home
|
|
2738
3335
|
if (command === 'saveconfig') {
|
|
2739
3336
|
param = param.replace(/\*/g, '\\');
|
|
2740
3337
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3338
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2741
3339
|
if (!this.plugins.has(param)) {
|
|
2742
3340
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2743
3341
|
}
|
|
@@ -2751,33 +3349,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2751
3349
|
res.json({ message: 'Command received' });
|
|
2752
3350
|
return;
|
|
2753
3351
|
}
|
|
3352
|
+
// Handle the command installplugin from Home
|
|
2754
3353
|
if (command === 'installplugin') {
|
|
2755
3354
|
param = param.replace(/\*/g, '\\');
|
|
2756
3355
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2757
3356
|
try {
|
|
2758
3357
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2759
3358
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3359
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2760
3360
|
}
|
|
2761
3361
|
catch (error) {
|
|
2762
3362
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2763
3363
|
}
|
|
2764
3364
|
this.wssSendRestartRequired();
|
|
2765
3365
|
param = param.split('@')[0];
|
|
3366
|
+
// Also add the plugin to matterbridge so no return!
|
|
2766
3367
|
if (param === 'matterbridge') {
|
|
3368
|
+
// If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
|
|
2767
3369
|
res.json({ message: 'Command received' });
|
|
2768
3370
|
return;
|
|
2769
3371
|
}
|
|
2770
3372
|
}
|
|
3373
|
+
// Handle the command addplugin from Home
|
|
2771
3374
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2772
3375
|
param = param.replace(/\*/g, '\\');
|
|
2773
3376
|
const plugin = await this.plugins.add(param);
|
|
2774
3377
|
if (plugin) {
|
|
2775
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
3378
|
+
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
2776
3379
|
}
|
|
2777
3380
|
res.json({ message: 'Command received' });
|
|
2778
3381
|
this.wssSendRefreshRequired();
|
|
2779
3382
|
return;
|
|
2780
3383
|
}
|
|
3384
|
+
// Handle the command removeplugin from Home
|
|
2781
3385
|
if (command === 'removeplugin') {
|
|
2782
3386
|
if (!this.plugins.has(param)) {
|
|
2783
3387
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2791,6 +3395,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2791
3395
|
this.wssSendRefreshRequired();
|
|
2792
3396
|
return;
|
|
2793
3397
|
}
|
|
3398
|
+
// Handle the command enableplugin from Home
|
|
2794
3399
|
if (command === 'enableplugin') {
|
|
2795
3400
|
if (!this.plugins.has(param)) {
|
|
2796
3401
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2808,13 +3413,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2808
3413
|
plugin.registeredDevices = undefined;
|
|
2809
3414
|
plugin.addedDevices = undefined;
|
|
2810
3415
|
await this.plugins.enable(param);
|
|
2811
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
3416
|
+
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
2812
3417
|
}
|
|
2813
3418
|
}
|
|
2814
3419
|
res.json({ message: 'Command received' });
|
|
2815
3420
|
this.wssSendRefreshRequired();
|
|
2816
3421
|
return;
|
|
2817
3422
|
}
|
|
3423
|
+
// Handle the command disableplugin from Home
|
|
2818
3424
|
if (command === 'disableplugin') {
|
|
2819
3425
|
if (!this.plugins.has(param)) {
|
|
2820
3426
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2831,6 +3437,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2831
3437
|
return;
|
|
2832
3438
|
}
|
|
2833
3439
|
});
|
|
3440
|
+
// Fallback for routing (must be the last route)
|
|
2834
3441
|
this.expressApp.get('*', (req, res) => {
|
|
2835
3442
|
this.log.debug('The frontend sent:', req.url);
|
|
2836
3443
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -2838,6 +3445,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2838
3445
|
});
|
|
2839
3446
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
2840
3447
|
}
|
|
3448
|
+
/**
|
|
3449
|
+
* Retrieves the cluster text description from a given device.
|
|
3450
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3451
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3452
|
+
*/
|
|
2841
3453
|
getClusterTextFromDevice(device) {
|
|
2842
3454
|
const stringifyUserLabel = (endpoint) => {
|
|
2843
3455
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -2860,9 +3472,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2860
3472
|
return '';
|
|
2861
3473
|
};
|
|
2862
3474
|
let attributes = '';
|
|
3475
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2863
3476
|
const clusterServers = device.getAllClusterServers();
|
|
2864
3477
|
clusterServers.forEach((clusterServer) => {
|
|
2865
3478
|
try {
|
|
3479
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2866
3480
|
if (clusterServer.name === 'OnOff')
|
|
2867
3481
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
2868
3482
|
if (clusterServer.name === 'Switch')
|
|
@@ -2913,18 +3527,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
2913
3527
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
2914
3528
|
if (clusterServer.name === 'UserLabel')
|
|
2915
3529
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3530
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2916
3531
|
}
|
|
2917
3532
|
catch (error) {
|
|
2918
3533
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
2919
3534
|
}
|
|
2920
3535
|
});
|
|
3536
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2921
3537
|
return attributes;
|
|
2922
3538
|
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3541
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3542
|
+
*
|
|
3543
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3544
|
+
*/
|
|
2923
3545
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3546
|
+
// Set the bridge mode
|
|
2924
3547
|
this.bridgeMode = 'bridge';
|
|
3548
|
+
// Set the first port to use
|
|
2925
3549
|
this.port = port;
|
|
2926
|
-
|
|
3550
|
+
// Set Matterbridge logger
|
|
3551
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
2927
3552
|
this.log.debug('Matterbridge extension is starting...');
|
|
3553
|
+
// Initialize NodeStorage
|
|
2928
3554
|
this.matterbridgeDirectory = dataPath;
|
|
2929
3555
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
2930
3556
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -2943,10 +3569,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2943
3569
|
};
|
|
2944
3570
|
this.plugins.set(plugin);
|
|
2945
3571
|
this.plugins.saveToStorage();
|
|
3572
|
+
// Log system info and create .matterbridge directory
|
|
2946
3573
|
await this.logNodeAndSystemInfo();
|
|
2947
3574
|
this.matterbridgeDirectory = dataPath;
|
|
3575
|
+
// Set matter.js logger level and format
|
|
2948
3576
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
2949
3577
|
Logger.format = MatterLogFormat.ANSI;
|
|
3578
|
+
// Start the storage and create matterbridgeContext
|
|
2950
3579
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
2951
3580
|
if (!this.storageManager)
|
|
2952
3581
|
return false;
|
|
@@ -2956,7 +3585,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2956
3585
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
2957
3586
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
2958
3587
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
2959
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3588
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
2960
3589
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
2961
3590
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
2962
3591
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -2969,6 +3598,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2969
3598
|
await this.startMatterServer();
|
|
2970
3599
|
this.log.info('Matter server started');
|
|
2971
3600
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3601
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
2972
3602
|
setTimeout(() => {
|
|
2973
3603
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
2974
3604
|
if (this.commissioningServer)
|
|
@@ -2978,14 +3608,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
2978
3608
|
}, 60 * 1000);
|
|
2979
3609
|
return this.commissioningServer.isCommissioned();
|
|
2980
3610
|
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3613
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3614
|
+
*
|
|
3615
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3616
|
+
*/
|
|
2981
3617
|
async stopExtension() {
|
|
3618
|
+
// Closing matter
|
|
2982
3619
|
await this.stopMatterServer();
|
|
3620
|
+
// Clearing the session manager
|
|
3621
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3622
|
+
// Closing storage
|
|
2983
3623
|
await this.stopMatterStorage();
|
|
2984
3624
|
this.log.info('Matter server stopped');
|
|
2985
3625
|
}
|
|
3626
|
+
/**
|
|
3627
|
+
* Checks if the extension is commissioned.
|
|
3628
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3629
|
+
*
|
|
3630
|
+
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3631
|
+
*/
|
|
2986
3632
|
isExtensionCommissioned() {
|
|
2987
3633
|
if (!this.commissioningServer)
|
|
2988
3634
|
return false;
|
|
2989
3635
|
return this.commissioningServer.isCommissioned();
|
|
2990
3636
|
}
|
|
2991
3637
|
}
|
|
3638
|
+
//# sourceMappingURL=matterbridge.js.map
|