matterbridge 1.6.5-dev.3 → 1.6.5
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 +1 -1
- 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 +702 -62
- 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 +934 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +29 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDevice.d.ts +6504 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -0
- package/dist/matterbridgeDevice.js +919 -9
- package/dist/matterbridgeDevice.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +65 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +40 -12
- 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 +525 -0
- package/dist/matterbridgeEdge.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +8529 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +997 -16
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +96 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +74 -3
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +147 -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 +45 -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 +231 -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 +78 -2
- 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/npm-shrinkwrap.json +8 -5
- package/package.json +1 -1
- package/tsconfig.production.json +3 -9
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,
|
|
@@ -97,6 +129,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
97
129
|
nodeContext;
|
|
98
130
|
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
99
131
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
132
|
+
// Cleanup
|
|
100
133
|
hasCleanupStarted = false;
|
|
101
134
|
initialized = false;
|
|
102
135
|
execRunningCount = 0;
|
|
@@ -108,16 +141,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
108
141
|
sigtermHandler;
|
|
109
142
|
exceptionHandler;
|
|
110
143
|
rejectionHandler;
|
|
144
|
+
// Frontend
|
|
111
145
|
expressApp;
|
|
112
146
|
httpServer;
|
|
113
147
|
httpsServer;
|
|
114
148
|
webSocketServer;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
149
|
+
// Matter
|
|
150
|
+
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
151
|
+
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
152
|
+
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
153
|
+
port = 5540; // first commissioning server port
|
|
154
|
+
passcode; // first commissioning server passcode
|
|
155
|
+
discriminator; // first commissioning server discriminator
|
|
121
156
|
storageManager;
|
|
122
157
|
matterbridgeContext;
|
|
123
158
|
mattercontrollerContext;
|
|
@@ -128,13 +163,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
128
163
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
129
164
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
130
165
|
static instance;
|
|
166
|
+
// We load asyncronously so is private
|
|
131
167
|
constructor() {
|
|
132
168
|
super();
|
|
169
|
+
// Bind the handler to the instance
|
|
133
170
|
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
134
171
|
}
|
|
135
172
|
matterbridgeMessageHandler;
|
|
173
|
+
/** ***********************************************************************************************************************************/
|
|
174
|
+
/** loadInstance() and cleanup() methods */
|
|
175
|
+
/** ***********************************************************************************************************************************/
|
|
176
|
+
/**
|
|
177
|
+
* Loads an instance of the Matterbridge class.
|
|
178
|
+
* If an instance already exists, return that instance.
|
|
179
|
+
*
|
|
180
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
181
|
+
* @returns The loaded Matterbridge instance.
|
|
182
|
+
*/
|
|
136
183
|
static async loadInstance(initialize = false) {
|
|
137
184
|
if (!Matterbridge.instance) {
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
138
186
|
if (hasParameter('debug'))
|
|
139
187
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
140
188
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -143,6 +191,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
143
191
|
}
|
|
144
192
|
return Matterbridge.instance;
|
|
145
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Call cleanup().
|
|
196
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
197
|
+
*
|
|
198
|
+
*/
|
|
146
199
|
async destroyInstance() {
|
|
147
200
|
await this.cleanup('destroying instance...', false);
|
|
148
201
|
await waiter('destroying instance...', () => {
|
|
@@ -150,42 +203,66 @@ export class Matterbridge extends EventEmitter {
|
|
|
150
203
|
}, false, 60000, 100, false);
|
|
151
204
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
152
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Initializes the Matterbridge application.
|
|
208
|
+
*
|
|
209
|
+
* @remarks
|
|
210
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
211
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
212
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
213
|
+
*
|
|
214
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
215
|
+
*/
|
|
153
216
|
async initialize() {
|
|
217
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
154
218
|
this.port = getIntParameter('port') ?? 5540;
|
|
219
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
155
220
|
this.passcode = getIntParameter('passcode');
|
|
221
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
156
222
|
this.discriminator = getIntParameter('discriminator');
|
|
223
|
+
// Set the restart mode
|
|
157
224
|
if (hasParameter('service'))
|
|
158
225
|
this.restartMode = 'service';
|
|
159
226
|
if (hasParameter('docker'))
|
|
160
227
|
this.restartMode = 'docker';
|
|
228
|
+
// Set the matterbridge directory
|
|
161
229
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
162
230
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
163
|
-
|
|
231
|
+
// Create matterbridge logger
|
|
232
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
233
|
+
// Initialize nodeStorage and nodeContext
|
|
164
234
|
try {
|
|
165
235
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
166
236
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
167
237
|
this.log.debug('Creating node storage context for matterbridge');
|
|
168
238
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
239
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
240
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
241
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
170
242
|
for (const key of keys) {
|
|
171
243
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
245
|
await this.nodeStorage?.storage.get(key);
|
|
173
246
|
}
|
|
174
247
|
const storages = await this.nodeStorage.getStorageNames();
|
|
175
248
|
for (const storage of storages) {
|
|
176
249
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
177
250
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
251
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
178
253
|
const keys = (await nodeContext?.storage.keys());
|
|
179
254
|
keys.forEach(async (key) => {
|
|
180
255
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
181
256
|
await nodeContext?.get(key);
|
|
182
257
|
});
|
|
183
258
|
}
|
|
259
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
184
260
|
this.log.debug('Creating node storage backup...');
|
|
185
261
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
186
262
|
this.log.debug('Created node storage backup');
|
|
187
263
|
}
|
|
188
264
|
catch (error) {
|
|
265
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
189
266
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
190
267
|
if (hasParameter('norestore')) {
|
|
191
268
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -200,41 +277,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
200
277
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
201
278
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
202
279
|
}
|
|
280
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
203
281
|
if (hasParameter('logger')) {
|
|
204
282
|
const level = getParameter('logger');
|
|
205
283
|
if (level === 'debug') {
|
|
206
|
-
this.log.logLevel = "debug"
|
|
284
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
207
285
|
}
|
|
208
286
|
else if (level === 'info') {
|
|
209
|
-
this.log.logLevel = "info"
|
|
287
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
210
288
|
}
|
|
211
289
|
else if (level === 'notice') {
|
|
212
|
-
this.log.logLevel = "notice"
|
|
290
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
213
291
|
}
|
|
214
292
|
else if (level === 'warn') {
|
|
215
|
-
this.log.logLevel = "warn"
|
|
293
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
216
294
|
}
|
|
217
295
|
else if (level === 'error') {
|
|
218
|
-
this.log.logLevel = "error"
|
|
296
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
219
297
|
}
|
|
220
298
|
else if (level === 'fatal') {
|
|
221
|
-
this.log.logLevel = "fatal"
|
|
299
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
222
300
|
}
|
|
223
301
|
else {
|
|
224
302
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
225
|
-
this.log.logLevel = "info"
|
|
303
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
226
304
|
}
|
|
227
305
|
}
|
|
228
306
|
else {
|
|
229
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
307
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
230
308
|
}
|
|
231
309
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
310
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
232
311
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
233
312
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
234
313
|
this.matterbridgeInformation.fileLogger = true;
|
|
235
314
|
}
|
|
236
315
|
this.log.notice('Matterbridge is starting...');
|
|
237
316
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
317
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
238
318
|
if (hasParameter('matterlogger')) {
|
|
239
319
|
const level = getParameter('matterlogger');
|
|
240
320
|
if (level === 'debug') {
|
|
@@ -265,6 +345,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
265
345
|
}
|
|
266
346
|
Logger.format = MatterLogFormat.ANSI;
|
|
267
347
|
Logger.setLogger('default', this.createMatterLogger());
|
|
348
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
268
349
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
269
350
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
270
351
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -273,6 +354,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
273
354
|
});
|
|
274
355
|
}
|
|
275
356
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
357
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
276
358
|
if (hasParameter('mdnsinterface')) {
|
|
277
359
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
278
360
|
}
|
|
@@ -281,6 +363,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
281
363
|
if (this.mdnsInterface === '')
|
|
282
364
|
this.mdnsInterface = undefined;
|
|
283
365
|
}
|
|
366
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
284
367
|
if (hasParameter('ipv4address')) {
|
|
285
368
|
this.ipv4address = getParameter('ipv4address');
|
|
286
369
|
}
|
|
@@ -289,6 +372,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
289
372
|
if (this.ipv4address === '')
|
|
290
373
|
this.ipv4address = undefined;
|
|
291
374
|
}
|
|
375
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
292
376
|
if (hasParameter('ipv6address')) {
|
|
293
377
|
this.ipv6address = getParameter('ipv6address');
|
|
294
378
|
}
|
|
@@ -297,17 +381,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
297
381
|
if (this.ipv6address === '')
|
|
298
382
|
this.ipv6address = undefined;
|
|
299
383
|
}
|
|
384
|
+
// Initialize PluginManager
|
|
300
385
|
this.plugins = new PluginManager(this);
|
|
301
386
|
await this.plugins.loadFromStorage();
|
|
387
|
+
// Initialize DeviceManager
|
|
302
388
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
389
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
303
390
|
for (const plugin of this.plugins) {
|
|
304
391
|
const packageJson = await this.plugins.parse(plugin);
|
|
305
392
|
if (packageJson === null && !hasParameter('add')) {
|
|
393
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
394
|
+
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
306
395
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
307
396
|
try {
|
|
308
397
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
309
398
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
310
399
|
plugin.error = false;
|
|
400
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
311
401
|
}
|
|
312
402
|
catch (error) {
|
|
313
403
|
plugin.error = true;
|
|
@@ -324,6 +414,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
324
414
|
await plugin.nodeContext.set('description', plugin.description);
|
|
325
415
|
await plugin.nodeContext.set('author', plugin.author);
|
|
326
416
|
}
|
|
417
|
+
// Log system info and create .matterbridge directory
|
|
327
418
|
await this.logNodeAndSystemInfo();
|
|
328
419
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
329
420
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -331,6 +422,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
331
422
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
332
423
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
333
424
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
425
|
+
// Check node version and throw error
|
|
334
426
|
const minNodeVersion = 18;
|
|
335
427
|
const nodeVersion = process.versions.node;
|
|
336
428
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -338,10 +430,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
338
430
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
339
431
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
340
432
|
}
|
|
433
|
+
// Register process handlers
|
|
341
434
|
this.registerProcessHandlers();
|
|
435
|
+
// Parse command line
|
|
342
436
|
await this.parseCommandLine();
|
|
343
437
|
this.initialized = true;
|
|
344
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
441
|
+
* @private
|
|
442
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
443
|
+
*/
|
|
345
444
|
async parseCommandLine() {
|
|
346
445
|
if (hasParameter('help')) {
|
|
347
446
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -449,12 +548,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
449
548
|
}
|
|
450
549
|
if (hasParameter('factoryreset')) {
|
|
451
550
|
try {
|
|
551
|
+
// Delete matter storage file
|
|
452
552
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
453
553
|
}
|
|
454
554
|
catch (err) {
|
|
455
555
|
this.log.error(`Error deleting storage: ${err}`);
|
|
456
556
|
}
|
|
457
557
|
try {
|
|
558
|
+
// Delete node storage directory with its subdirectories
|
|
458
559
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
459
560
|
}
|
|
460
561
|
catch (err) {
|
|
@@ -468,6 +569,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
468
569
|
this.emit('shutdown');
|
|
469
570
|
return;
|
|
470
571
|
}
|
|
572
|
+
// Start the matter storage and create the matterbridge context
|
|
471
573
|
try {
|
|
472
574
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
473
575
|
}
|
|
@@ -502,28 +604,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
502
604
|
this.emit('shutdown');
|
|
503
605
|
return;
|
|
504
606
|
}
|
|
607
|
+
// Initialize frontend
|
|
505
608
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
506
609
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
610
|
+
// Check each 60 minutes the latest versions
|
|
507
611
|
this.checkUpdateInterval = setInterval(() => {
|
|
508
612
|
this.getMatterbridgeLatestVersion();
|
|
509
613
|
for (const plugin of this.plugins) {
|
|
510
614
|
this.getPluginLatestVersion(plugin);
|
|
511
615
|
}
|
|
512
616
|
}, 60 * 60 * 1000);
|
|
617
|
+
// Start the matterbridge in mode test
|
|
513
618
|
if (hasParameter('test')) {
|
|
514
619
|
this.bridgeMode = 'bridge';
|
|
515
620
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
516
621
|
return;
|
|
517
622
|
}
|
|
623
|
+
// Start the matterbridge in mode controller
|
|
518
624
|
if (hasParameter('controller')) {
|
|
519
625
|
this.bridgeMode = 'controller';
|
|
520
626
|
await this.startController();
|
|
521
627
|
return;
|
|
522
628
|
}
|
|
629
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
523
630
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
524
631
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
525
632
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
526
633
|
}
|
|
634
|
+
// Start matterbridge in bridge mode
|
|
527
635
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
528
636
|
this.bridgeMode = 'bridge';
|
|
529
637
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -532,6 +640,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
532
640
|
await this.startBridge();
|
|
533
641
|
return;
|
|
534
642
|
}
|
|
643
|
+
// Start matterbridge in childbridge mode
|
|
535
644
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
536
645
|
this.bridgeMode = 'childbridge';
|
|
537
646
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -541,17 +650,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
541
650
|
return;
|
|
542
651
|
}
|
|
543
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Asynchronously loads and starts the registered plugins.
|
|
655
|
+
*
|
|
656
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
657
|
+
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
658
|
+
*
|
|
659
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
660
|
+
*/
|
|
544
661
|
async startPlugins() {
|
|
662
|
+
// Check, load and start the plugins
|
|
545
663
|
for (const plugin of this.plugins) {
|
|
546
664
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
547
665
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
666
|
+
// Check if the plugin is available
|
|
548
667
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
549
668
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
550
669
|
plugin.enabled = false;
|
|
551
670
|
plugin.error = true;
|
|
552
671
|
continue;
|
|
553
672
|
}
|
|
554
|
-
|
|
673
|
+
// Check if the plugin has a new version
|
|
674
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
555
675
|
if (!plugin.enabled) {
|
|
556
676
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
557
677
|
continue;
|
|
@@ -566,20 +686,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
566
686
|
plugin.addedDevices = undefined;
|
|
567
687
|
plugin.qrPairingCode = undefined;
|
|
568
688
|
plugin.manualPairingCode = undefined;
|
|
569
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
689
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
570
690
|
}
|
|
571
691
|
this.wssSendRefreshRequired();
|
|
572
692
|
}
|
|
693
|
+
/**
|
|
694
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
695
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
696
|
+
*/
|
|
573
697
|
registerProcessHandlers() {
|
|
574
698
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
575
699
|
process.removeAllListeners('uncaughtException');
|
|
576
700
|
process.removeAllListeners('unhandledRejection');
|
|
577
701
|
this.exceptionHandler = async (error) => {
|
|
578
702
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
703
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
579
704
|
};
|
|
580
705
|
process.on('uncaughtException', this.exceptionHandler);
|
|
581
706
|
this.rejectionHandler = async (reason, promise) => {
|
|
582
707
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
708
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
583
709
|
};
|
|
584
710
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
585
711
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -592,6 +718,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
592
718
|
};
|
|
593
719
|
process.on('SIGTERM', this.sigtermHandler);
|
|
594
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
723
|
+
*/
|
|
595
724
|
deregisterProcesslHandlers() {
|
|
596
725
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
597
726
|
if (this.exceptionHandler)
|
|
@@ -608,7 +737,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
608
737
|
process.off('SIGTERM', this.sigtermHandler);
|
|
609
738
|
this.sigtermHandler = undefined;
|
|
610
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* Logs the node and system information.
|
|
742
|
+
*/
|
|
611
743
|
async logNodeAndSystemInfo() {
|
|
744
|
+
// IP address information
|
|
612
745
|
const networkInterfaces = os.networkInterfaces();
|
|
613
746
|
this.systemInformation.ipv4Address = '';
|
|
614
747
|
this.systemInformation.ipv6Address = '';
|
|
@@ -628,7 +761,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
628
761
|
this.systemInformation.macAddress = detail.mac;
|
|
629
762
|
}
|
|
630
763
|
}
|
|
631
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
764
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
632
765
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
633
766
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
634
767
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -636,19 +769,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
636
769
|
break;
|
|
637
770
|
}
|
|
638
771
|
}
|
|
772
|
+
// Node information
|
|
639
773
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
640
774
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
641
775
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
642
776
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
777
|
+
// Host system information
|
|
643
778
|
this.systemInformation.hostname = os.hostname();
|
|
644
779
|
this.systemInformation.user = os.userInfo().username;
|
|
645
|
-
this.systemInformation.osType = os.type();
|
|
646
|
-
this.systemInformation.osRelease = os.release();
|
|
647
|
-
this.systemInformation.osPlatform = os.platform();
|
|
648
|
-
this.systemInformation.osArch = os.arch();
|
|
649
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
650
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
651
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
780
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
781
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
782
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
783
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
784
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
785
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
786
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
787
|
+
// Log the system information
|
|
652
788
|
this.log.debug('Host System Information:');
|
|
653
789
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
654
790
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -664,15 +800,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
664
800
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
665
801
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
666
802
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
803
|
+
// Home directory
|
|
667
804
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
668
805
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
669
806
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
807
|
+
// Package root directory
|
|
670
808
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
671
809
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
672
810
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
673
811
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
812
|
+
// Global node_modules directory
|
|
674
813
|
if (this.nodeContext)
|
|
675
814
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
815
|
+
// First run of Matterbridge so the node storage is empty
|
|
676
816
|
if (this.globalModulesDirectory === '') {
|
|
677
817
|
try {
|
|
678
818
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -696,6 +836,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
696
836
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
697
837
|
});
|
|
698
838
|
}
|
|
839
|
+
// Create the data directory .matterbridge in the home directory
|
|
699
840
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
700
841
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
701
842
|
try {
|
|
@@ -719,6 +860,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
719
860
|
}
|
|
720
861
|
}
|
|
721
862
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
863
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
722
864
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
723
865
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
724
866
|
try {
|
|
@@ -742,19 +884,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
742
884
|
}
|
|
743
885
|
}
|
|
744
886
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
887
|
+
// Matterbridge version
|
|
745
888
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
746
889
|
this.matterbridgeVersion = packageJson.version;
|
|
747
890
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
748
891
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
892
|
+
// Matterbridge latest version
|
|
749
893
|
if (this.nodeContext)
|
|
750
894
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
751
895
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
752
896
|
this.getMatterbridgeLatestVersion();
|
|
897
|
+
// Current working directory
|
|
753
898
|
const currentDir = process.cwd();
|
|
754
899
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
900
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
755
901
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
756
902
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
757
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
906
|
+
* @param packageName - The name of the package.
|
|
907
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
908
|
+
*/
|
|
758
909
|
async getLatestVersion(packageName) {
|
|
759
910
|
return new Promise((resolve, reject) => {
|
|
760
911
|
this.execRunningCount++;
|
|
@@ -769,6 +920,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
769
920
|
});
|
|
770
921
|
});
|
|
771
922
|
}
|
|
923
|
+
/**
|
|
924
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
925
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
926
|
+
*/
|
|
772
927
|
async getGlobalNodeModules() {
|
|
773
928
|
return new Promise((resolve, reject) => {
|
|
774
929
|
this.execRunningCount++;
|
|
@@ -783,6 +938,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
783
938
|
});
|
|
784
939
|
});
|
|
785
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
943
|
+
* @private
|
|
944
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
945
|
+
*/
|
|
786
946
|
async getMatterbridgeLatestVersion() {
|
|
787
947
|
this.getLatestVersion('matterbridge')
|
|
788
948
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -799,8 +959,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
799
959
|
})
|
|
800
960
|
.catch((error) => {
|
|
801
961
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
962
|
+
// error.stack && this.log.debug(error.stack);
|
|
802
963
|
});
|
|
803
964
|
}
|
|
965
|
+
/**
|
|
966
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
967
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
968
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
969
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
970
|
+
*
|
|
971
|
+
* @private
|
|
972
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
973
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
974
|
+
*/
|
|
804
975
|
async getPluginLatestVersion(plugin) {
|
|
805
976
|
this.getLatestVersion(plugin.name)
|
|
806
977
|
.then(async (latestVersion) => {
|
|
@@ -812,40 +983,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
812
983
|
})
|
|
813
984
|
.catch((error) => {
|
|
814
985
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
986
|
+
// error.stack && this.log.debug(error.stack);
|
|
815
987
|
});
|
|
816
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
991
|
+
*
|
|
992
|
+
* @returns {Function} The MatterLogger function.
|
|
993
|
+
*/
|
|
817
994
|
createMatterLogger() {
|
|
818
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
995
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
819
996
|
return (_level, formattedLog) => {
|
|
820
997
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
821
998
|
const message = formattedLog.slice(65);
|
|
822
999
|
matterLogger.logName = logger;
|
|
823
1000
|
switch (_level) {
|
|
824
1001
|
case MatterLogLevel.DEBUG:
|
|
825
|
-
matterLogger.log("debug"
|
|
1002
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
826
1003
|
break;
|
|
827
1004
|
case MatterLogLevel.INFO:
|
|
828
|
-
matterLogger.log("info"
|
|
1005
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
829
1006
|
break;
|
|
830
1007
|
case MatterLogLevel.NOTICE:
|
|
831
|
-
matterLogger.log("notice"
|
|
1008
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
832
1009
|
break;
|
|
833
1010
|
case MatterLogLevel.WARN:
|
|
834
|
-
matterLogger.log("warn"
|
|
1011
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
835
1012
|
break;
|
|
836
1013
|
case MatterLogLevel.ERROR:
|
|
837
|
-
matterLogger.log("error"
|
|
1014
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
838
1015
|
break;
|
|
839
1016
|
case MatterLogLevel.FATAL:
|
|
840
|
-
matterLogger.log("fatal"
|
|
1017
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
841
1018
|
break;
|
|
842
1019
|
default:
|
|
843
|
-
matterLogger.log("debug"
|
|
1020
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
844
1021
|
break;
|
|
845
1022
|
}
|
|
846
1023
|
};
|
|
847
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Creates a Matter File Logger.
|
|
1027
|
+
*
|
|
1028
|
+
* @param {string} filePath - The path to the log file.
|
|
1029
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1030
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1031
|
+
*/
|
|
848
1032
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1033
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
849
1034
|
let fileSize = 0;
|
|
850
1035
|
if (unlink) {
|
|
851
1036
|
try {
|
|
@@ -894,53 +1079,83 @@ export class Matterbridge extends EventEmitter {
|
|
|
894
1079
|
}
|
|
895
1080
|
};
|
|
896
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Update matterbridge and cleanup.
|
|
1084
|
+
*/
|
|
897
1085
|
async updateProcess() {
|
|
898
1086
|
await this.cleanup('updating...', false);
|
|
899
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1090
|
+
*/
|
|
900
1091
|
async restartProcess() {
|
|
901
1092
|
await this.cleanup('restarting...', true);
|
|
902
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Shut down the process by exiting the current process.
|
|
1096
|
+
*/
|
|
903
1097
|
async shutdownProcess() {
|
|
904
1098
|
await this.cleanup('shutting down...', false);
|
|
905
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Shut down the process and reset.
|
|
1102
|
+
*/
|
|
906
1103
|
async unregisterAndShutdownProcess() {
|
|
907
1104
|
this.log.info('Unregistering all devices and shutting down...');
|
|
908
|
-
for (const plugin of this.plugins) {
|
|
1105
|
+
for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
|
|
909
1106
|
await this.removeAllBridgedDevices(plugin.name);
|
|
910
1107
|
}
|
|
911
1108
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
912
1109
|
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Shut down the process and reset.
|
|
1112
|
+
*/
|
|
913
1113
|
async shutdownProcessAndReset() {
|
|
914
1114
|
await this.cleanup('shutting down with reset...', false);
|
|
915
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Shut down the process and factory reset.
|
|
1118
|
+
*/
|
|
916
1119
|
async shutdownProcessAndFactoryReset() {
|
|
917
1120
|
await this.cleanup('shutting down with factory reset...', false);
|
|
918
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Cleans up the Matterbridge instance.
|
|
1124
|
+
* @param message - The cleanup message.
|
|
1125
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1126
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1127
|
+
*/
|
|
919
1128
|
async cleanup(message, restart = false) {
|
|
920
1129
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
921
1130
|
this.hasCleanupStarted = true;
|
|
922
1131
|
this.log.info(message);
|
|
1132
|
+
// Deregisters the process handlers
|
|
923
1133
|
this.deregisterProcesslHandlers();
|
|
1134
|
+
// Clear the start matter interval
|
|
924
1135
|
if (this.startMatterInterval) {
|
|
925
1136
|
clearInterval(this.startMatterInterval);
|
|
926
1137
|
this.startMatterInterval = undefined;
|
|
927
1138
|
this.log.debug('Start matter interval cleared');
|
|
928
1139
|
}
|
|
1140
|
+
// Clear the check update interval
|
|
929
1141
|
if (this.checkUpdateInterval) {
|
|
930
1142
|
clearInterval(this.checkUpdateInterval);
|
|
931
1143
|
this.checkUpdateInterval = undefined;
|
|
932
1144
|
this.log.debug('Check update interval cleared');
|
|
933
1145
|
}
|
|
1146
|
+
// Clear the configure timeout
|
|
934
1147
|
if (this.configureTimeout) {
|
|
935
1148
|
clearTimeout(this.configureTimeout);
|
|
936
1149
|
this.configureTimeout = undefined;
|
|
937
1150
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
938
1151
|
}
|
|
1152
|
+
// Clear the reachability timeout
|
|
939
1153
|
if (this.reachabilityTimeout) {
|
|
940
1154
|
clearTimeout(this.reachabilityTimeout);
|
|
941
1155
|
this.reachabilityTimeout = undefined;
|
|
942
1156
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
943
1157
|
}
|
|
1158
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
944
1159
|
for (const plugin of this.plugins) {
|
|
945
1160
|
if (!plugin.enabled || plugin.error)
|
|
946
1161
|
continue;
|
|
@@ -951,24 +1166,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
951
1166
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
952
1167
|
}
|
|
953
1168
|
}
|
|
1169
|
+
// Close the http server
|
|
954
1170
|
if (this.httpServer) {
|
|
955
1171
|
this.httpServer.close();
|
|
956
1172
|
this.httpServer.removeAllListeners();
|
|
957
1173
|
this.httpServer = undefined;
|
|
958
1174
|
this.log.debug('Frontend http server closed successfully');
|
|
959
1175
|
}
|
|
1176
|
+
// Close the https server
|
|
960
1177
|
if (this.httpsServer) {
|
|
961
1178
|
this.httpsServer.close();
|
|
962
1179
|
this.httpsServer.removeAllListeners();
|
|
963
1180
|
this.httpsServer = undefined;
|
|
964
1181
|
this.log.debug('Frontend https server closed successfully');
|
|
965
1182
|
}
|
|
1183
|
+
// Remove listeners from the express app
|
|
966
1184
|
if (this.expressApp) {
|
|
967
1185
|
this.expressApp.removeAllListeners();
|
|
968
1186
|
this.expressApp = undefined;
|
|
969
1187
|
this.log.debug('Frontend app closed successfully');
|
|
970
1188
|
}
|
|
1189
|
+
// Close the WebSocket server
|
|
971
1190
|
if (this.webSocketServer) {
|
|
1191
|
+
// Close all active connections
|
|
972
1192
|
this.webSocketServer.clients.forEach((client) => {
|
|
973
1193
|
if (client.readyState === WebSocket.OPEN) {
|
|
974
1194
|
client.close();
|
|
@@ -984,26 +1204,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
1204
|
});
|
|
985
1205
|
this.webSocketServer = undefined;
|
|
986
1206
|
}
|
|
1207
|
+
// Closing matter
|
|
987
1208
|
await this.stopMatterServer();
|
|
1209
|
+
// Closing matter storage
|
|
988
1210
|
await this.stopMatterStorage();
|
|
1211
|
+
// Remove the matterfilelogger
|
|
989
1212
|
try {
|
|
990
1213
|
Logger.removeLogger('matterfilelogger');
|
|
1214
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
991
1215
|
}
|
|
992
1216
|
catch (error) {
|
|
1217
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
993
1218
|
}
|
|
1219
|
+
// Serialize registeredDevices
|
|
994
1220
|
if (this.nodeStorage && this.nodeContext) {
|
|
995
1221
|
this.log.info('Saving registered devices...');
|
|
996
1222
|
const serializedRegisteredDevices = [];
|
|
997
1223
|
this.devices.forEach(async (device) => {
|
|
998
1224
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1225
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
999
1226
|
if (serializedMatterbridgeDevice)
|
|
1000
1227
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1001
1228
|
});
|
|
1002
1229
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1003
1230
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1231
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1004
1232
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1005
1233
|
await this.nodeContext.close();
|
|
1006
1234
|
this.nodeContext = undefined;
|
|
1235
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1007
1236
|
for (const plugin of this.plugins) {
|
|
1008
1237
|
if (plugin.nodeContext) {
|
|
1009
1238
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1034,13 +1263,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1034
1263
|
}
|
|
1035
1264
|
else {
|
|
1036
1265
|
if (message === 'shutting down with reset...') {
|
|
1266
|
+
// Delete matter storage file
|
|
1037
1267
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1038
1268
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1039
1269
|
this.log.info('Reset done! Remove all paired devices from the controllers.');
|
|
1040
1270
|
}
|
|
1041
1271
|
if (message === 'shutting down with factory reset...') {
|
|
1272
|
+
// Delete matter storage file
|
|
1042
1273
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1043
1274
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1275
|
+
// Delete node storage directory with its subdirectories
|
|
1044
1276
|
this.log.info('Resetting Matterbridge storage...');
|
|
1045
1277
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1046
1278
|
this.log.info('Factory reset done! Remove all paired devices from the controllers.');
|
|
@@ -1053,19 +1285,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1053
1285
|
this.initialized = false;
|
|
1054
1286
|
}
|
|
1055
1287
|
}
|
|
1288
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1056
1289
|
async addBridgedEndpoint(pluginName, device) {
|
|
1290
|
+
// Nothing to do here
|
|
1057
1291
|
}
|
|
1292
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1058
1293
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1294
|
+
// Nothing to do here
|
|
1059
1295
|
}
|
|
1296
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1060
1297
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1298
|
+
// Nothing to do here
|
|
1061
1299
|
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Adds a bridged device to the Matterbridge.
|
|
1302
|
+
* @param pluginName - The name of the plugin.
|
|
1303
|
+
* @param device - The bridged device to add.
|
|
1304
|
+
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1305
|
+
*/
|
|
1062
1306
|
async addBridgedDevice(pluginName, device) {
|
|
1063
1307
|
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1308
|
+
// Check if the plugin is registered
|
|
1064
1309
|
const plugin = this.plugins.get(pluginName);
|
|
1065
1310
|
if (!plugin) {
|
|
1066
1311
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1067
1312
|
return;
|
|
1068
1313
|
}
|
|
1314
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1069
1315
|
if (this.bridgeMode === 'bridge') {
|
|
1070
1316
|
if (!this.matterAggregator) {
|
|
1071
1317
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1073,8 +1319,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1073
1319
|
}
|
|
1074
1320
|
this.matterAggregator.addBridgedDevice(device);
|
|
1075
1321
|
}
|
|
1322
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1323
|
+
// Register and add the device in childbridge mode
|
|
1076
1324
|
if (this.bridgeMode === 'childbridge') {
|
|
1077
1325
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1326
|
+
// Check if the plugin is locked with the commissioning server
|
|
1078
1327
|
if (!plugin.locked) {
|
|
1079
1328
|
plugin.locked = true;
|
|
1080
1329
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1088,6 +1337,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1088
1337
|
}
|
|
1089
1338
|
}
|
|
1090
1339
|
if (plugin.type === 'DynamicPlatform') {
|
|
1340
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1091
1341
|
if (!plugin.locked) {
|
|
1092
1342
|
plugin.locked = true;
|
|
1093
1343
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1095,7 +1345,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1095
1345
|
this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
|
|
1096
1346
|
plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
1097
1347
|
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1098
|
-
plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
|
|
1348
|
+
plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name); // Generate serialNumber and uniqueId
|
|
1099
1349
|
this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
1100
1350
|
plugin.commissioningServer.addDevice(plugin.aggregator);
|
|
1101
1351
|
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -1108,16 +1358,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1108
1358
|
plugin.registeredDevices++;
|
|
1109
1359
|
if (plugin.addedDevices !== undefined)
|
|
1110
1360
|
plugin.addedDevices++;
|
|
1361
|
+
// Add the device to the DeviceManager
|
|
1111
1362
|
this.devices.set(device);
|
|
1112
1363
|
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}`);
|
|
1113
1364
|
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Removes a bridged device from the Matterbridge.
|
|
1367
|
+
* @param pluginName - The name of the plugin.
|
|
1368
|
+
* @param device - The device to be removed.
|
|
1369
|
+
* @returns A Promise that resolves when the device is successfully removed.
|
|
1370
|
+
*/
|
|
1114
1371
|
async removeBridgedDevice(pluginName, device) {
|
|
1115
1372
|
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1373
|
+
// Check if the plugin is registered
|
|
1116
1374
|
const plugin = this.plugins.get(pluginName);
|
|
1117
1375
|
if (!plugin) {
|
|
1118
1376
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1119
1377
|
return;
|
|
1120
1378
|
}
|
|
1379
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1121
1380
|
if (this.bridgeMode === 'bridge') {
|
|
1122
1381
|
if (!this.matterAggregator) {
|
|
1123
1382
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1127,6 +1386,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1127
1386
|
device.setBridgedDeviceReachability(false);
|
|
1128
1387
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1129
1388
|
}
|
|
1389
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1390
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1130
1391
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1131
1392
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1132
1393
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1134,6 +1395,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1134
1395
|
if (plugin.addedDevices !== undefined)
|
|
1135
1396
|
plugin.addedDevices--;
|
|
1136
1397
|
}
|
|
1398
|
+
// Remove the device in childbridge mode
|
|
1137
1399
|
if (this.bridgeMode === 'childbridge') {
|
|
1138
1400
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1139
1401
|
if (!plugin.commissioningServer) {
|
|
@@ -1157,14 +1419,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1157
1419
|
plugin.registeredDevices--;
|
|
1158
1420
|
if (plugin.addedDevices !== undefined)
|
|
1159
1421
|
plugin.addedDevices--;
|
|
1422
|
+
// Remove the commissioning server
|
|
1160
1423
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1161
1424
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1162
1425
|
plugin.commissioningServer = undefined;
|
|
1163
1426
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1164
1427
|
}
|
|
1165
1428
|
}
|
|
1429
|
+
// Remove the device from the DeviceManager
|
|
1166
1430
|
this.devices.remove(device);
|
|
1167
1431
|
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
1434
|
+
*
|
|
1435
|
+
* @param pluginName - The name of the plugin.
|
|
1436
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
1437
|
+
*/
|
|
1168
1438
|
async removeAllBridgedDevices(pluginName) {
|
|
1169
1439
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1170
1440
|
this.devices.forEach(async (device) => {
|
|
@@ -1173,7 +1443,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1173
1443
|
}
|
|
1174
1444
|
});
|
|
1175
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Starts the Matterbridge in bridge mode.
|
|
1448
|
+
* @private
|
|
1449
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1450
|
+
*/
|
|
1176
1451
|
async startBridge() {
|
|
1452
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1177
1453
|
if (!this.storageManager)
|
|
1178
1454
|
throw new Error('No storage manager initialized');
|
|
1179
1455
|
if (!this.matterbridgeContext)
|
|
@@ -1192,6 +1468,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1192
1468
|
let failCount = 0;
|
|
1193
1469
|
this.startMatterInterval = setInterval(async () => {
|
|
1194
1470
|
for (const plugin of this.plugins) {
|
|
1471
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1195
1472
|
if (!plugin.enabled)
|
|
1196
1473
|
continue;
|
|
1197
1474
|
if (plugin.error) {
|
|
@@ -1216,15 +1493,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1216
1493
|
clearInterval(this.startMatterInterval);
|
|
1217
1494
|
this.startMatterInterval = undefined;
|
|
1218
1495
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1496
|
+
// Start the Matter server
|
|
1219
1497
|
await this.startMatterServer();
|
|
1220
1498
|
this.log.notice('Matter server started');
|
|
1499
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1221
1500
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1501
|
+
// Configure the plugins
|
|
1222
1502
|
this.configureTimeout = setTimeout(async () => {
|
|
1223
1503
|
for (const plugin of this.plugins) {
|
|
1224
1504
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1225
1505
|
continue;
|
|
1226
1506
|
try {
|
|
1227
|
-
await this.plugins.configure(plugin);
|
|
1507
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1228
1508
|
}
|
|
1229
1509
|
catch (error) {
|
|
1230
1510
|
plugin.error = true;
|
|
@@ -1233,6 +1513,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1233
1513
|
}
|
|
1234
1514
|
this.wssSendRefreshRequired();
|
|
1235
1515
|
}, 30 * 1000);
|
|
1516
|
+
// Setting reachability to true
|
|
1236
1517
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1237
1518
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1238
1519
|
if (this.commissioningServer)
|
|
@@ -1242,7 +1523,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1242
1523
|
}, 60 * 1000);
|
|
1243
1524
|
}, 1000);
|
|
1244
1525
|
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1528
|
+
* @private
|
|
1529
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1530
|
+
*/
|
|
1245
1531
|
async startChildbridge() {
|
|
1532
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1533
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1246
1534
|
if (!this.storageManager)
|
|
1247
1535
|
throw new Error('No storage manager initialized');
|
|
1248
1536
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
@@ -1252,6 +1540,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1252
1540
|
this.startMatterInterval = setInterval(async () => {
|
|
1253
1541
|
let allStarted = true;
|
|
1254
1542
|
for (const plugin of this.plugins) {
|
|
1543
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1255
1544
|
if (!plugin.enabled)
|
|
1256
1545
|
continue;
|
|
1257
1546
|
if (plugin.error) {
|
|
@@ -1279,14 +1568,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1279
1568
|
clearInterval(this.startMatterInterval);
|
|
1280
1569
|
this.startMatterInterval = undefined;
|
|
1281
1570
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1571
|
+
// Start the Matter server
|
|
1282
1572
|
await this.startMatterServer();
|
|
1283
1573
|
this.log.notice('Matter server started');
|
|
1574
|
+
// Configure the plugins
|
|
1284
1575
|
this.configureTimeout = setTimeout(async () => {
|
|
1285
1576
|
for (const plugin of this.plugins) {
|
|
1286
1577
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1287
1578
|
continue;
|
|
1288
1579
|
try {
|
|
1289
|
-
await this.plugins.configure(plugin);
|
|
1580
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1290
1581
|
}
|
|
1291
1582
|
catch (error) {
|
|
1292
1583
|
plugin.error = true;
|
|
@@ -1315,6 +1606,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1315
1606
|
continue;
|
|
1316
1607
|
}
|
|
1317
1608
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1609
|
+
// Setting reachability to true
|
|
1318
1610
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1319
1611
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1320
1612
|
if (plugin.commissioningServer)
|
|
@@ -1327,6 +1619,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1327
1619
|
}
|
|
1328
1620
|
}, 1000);
|
|
1329
1621
|
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Starts the Matterbridge controller.
|
|
1624
|
+
* @private
|
|
1625
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1626
|
+
*/
|
|
1330
1627
|
async startController() {
|
|
1331
1628
|
if (!this.storageManager) {
|
|
1332
1629
|
this.log.error('No storage manager initialized');
|
|
@@ -1389,7 +1686,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1686
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1390
1687
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1391
1688
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1392
|
-
}
|
|
1689
|
+
} // (hasParameter('pairingcode'))
|
|
1393
1690
|
if (hasParameter('unpairall')) {
|
|
1394
1691
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1395
1692
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1400,6 +1697,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1400
1697
|
return;
|
|
1401
1698
|
}
|
|
1402
1699
|
if (hasParameter('discover')) {
|
|
1700
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1701
|
+
// console.log(discover);
|
|
1403
1702
|
}
|
|
1404
1703
|
if (!this.commissioningController.isCommissioned()) {
|
|
1405
1704
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1440,10 +1739,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1440
1739
|
},
|
|
1441
1740
|
});
|
|
1442
1741
|
node.logStructure();
|
|
1742
|
+
// Get the interaction client
|
|
1443
1743
|
this.log.info('Getting the interaction client');
|
|
1444
1744
|
const interactionClient = await node.getInteractionClient();
|
|
1445
1745
|
let cluster;
|
|
1446
1746
|
let attributes;
|
|
1747
|
+
// Log BasicInformationCluster
|
|
1447
1748
|
cluster = BasicInformationCluster;
|
|
1448
1749
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1449
1750
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1453,6 +1754,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1453
1754
|
attributes.forEach((attribute) => {
|
|
1454
1755
|
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}`);
|
|
1455
1756
|
});
|
|
1757
|
+
// Log PowerSourceCluster
|
|
1456
1758
|
cluster = PowerSourceCluster;
|
|
1457
1759
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1458
1760
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1462,6 +1764,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1462
1764
|
attributes.forEach((attribute) => {
|
|
1463
1765
|
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}`);
|
|
1464
1766
|
});
|
|
1767
|
+
// Log ThreadNetworkDiagnostics
|
|
1465
1768
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1466
1769
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1467
1770
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1471,6 +1774,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1471
1774
|
attributes.forEach((attribute) => {
|
|
1472
1775
|
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}`);
|
|
1473
1776
|
});
|
|
1777
|
+
// Log SwitchCluster
|
|
1474
1778
|
cluster = SwitchCluster;
|
|
1475
1779
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1476
1780
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1491,6 +1795,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1491
1795
|
this.log.info('Subscribed to all attributes and events');
|
|
1492
1796
|
}
|
|
1493
1797
|
}
|
|
1798
|
+
/** ***********************************************************************************************************************************/
|
|
1799
|
+
/** Matter.js methods */
|
|
1800
|
+
/** ***********************************************************************************************************************************/
|
|
1801
|
+
/**
|
|
1802
|
+
* Starts the matter storage process based on the specified storage type and name.
|
|
1803
|
+
* @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
|
|
1804
|
+
* @param {string} storageName - The name of the storage file.
|
|
1805
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1806
|
+
*/
|
|
1494
1807
|
async startMatterStorage(storageType, storageName) {
|
|
1495
1808
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1496
1809
|
if (storageType === 'disk') {
|
|
@@ -1536,6 +1849,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1536
1849
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
1537
1850
|
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
1538
1851
|
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Makes a backup copy of the specified matter JSON storage file.
|
|
1854
|
+
*
|
|
1855
|
+
* @param storageName - The name of the JSON storage file to be backed up.
|
|
1856
|
+
* @param backupName - The name of the backup file to be created.
|
|
1857
|
+
*/
|
|
1539
1858
|
async backupMatterStorage(storageName, backupName) {
|
|
1540
1859
|
try {
|
|
1541
1860
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1556,6 +1875,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1556
1875
|
}
|
|
1557
1876
|
}
|
|
1558
1877
|
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Restore the specified matter JSON storage file.
|
|
1880
|
+
*
|
|
1881
|
+
* @param backupName - The name of the backup file to restore from.
|
|
1882
|
+
* @param storageName - The name of the JSON storage file to restored.
|
|
1883
|
+
*/
|
|
1559
1884
|
async restoreMatterStorage(backupName, storageName) {
|
|
1560
1885
|
try {
|
|
1561
1886
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1576,6 +1901,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1576
1901
|
}
|
|
1577
1902
|
}
|
|
1578
1903
|
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Stops the matter storage.
|
|
1906
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1907
|
+
*/
|
|
1579
1908
|
async stopMatterStorage() {
|
|
1580
1909
|
this.log.debug('Stopping storage');
|
|
1581
1910
|
await this.storageManager?.close();
|
|
@@ -1584,8 +1913,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1584
1913
|
this.matterbridgeContext = undefined;
|
|
1585
1914
|
this.mattercontrollerContext = undefined;
|
|
1586
1915
|
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
1918
|
+
* @param storageManager The storage manager to be used by the Matter server.
|
|
1919
|
+
*
|
|
1920
|
+
*/
|
|
1587
1921
|
createMatterServer(storageManager) {
|
|
1588
1922
|
this.log.debug('Creating matter server');
|
|
1923
|
+
// Validate mdnsInterface
|
|
1589
1924
|
if (this.mdnsInterface) {
|
|
1590
1925
|
const networkInterfaces = os.networkInterfaces();
|
|
1591
1926
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1601,6 +1936,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1601
1936
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1602
1937
|
return matterServer;
|
|
1603
1938
|
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Starts the Matter server.
|
|
1941
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
1942
|
+
*/
|
|
1604
1943
|
async startMatterServer() {
|
|
1605
1944
|
if (!this.matterServer) {
|
|
1606
1945
|
this.log.error('No matter server initialized');
|
|
@@ -1610,7 +1949,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1610
1949
|
this.log.debug('Starting matter server...');
|
|
1611
1950
|
await this.matterServer.start();
|
|
1612
1951
|
this.log.debug('Started matter server');
|
|
1952
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1613
1953
|
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
1956
|
+
*/
|
|
1614
1957
|
async stopMatterServer() {
|
|
1615
1958
|
this.log.debug('Stopping matter commissioningServer');
|
|
1616
1959
|
await this.commissioningServer?.close();
|
|
@@ -1624,22 +1967,78 @@ export class Matterbridge extends EventEmitter {
|
|
|
1624
1967
|
this.matterAggregator = undefined;
|
|
1625
1968
|
this.matterServer = undefined;
|
|
1626
1969
|
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Creates a Matter Aggregator.
|
|
1972
|
+
* @param {StorageContext} context - The storage context.
|
|
1973
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
1974
|
+
*/
|
|
1975
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1627
1976
|
async createMatterAggregator(context, pluginName) {
|
|
1977
|
+
/*
|
|
1978
|
+
const random = randomBytes(8).toString('hex');
|
|
1979
|
+
await context.set('aggregatorSerialNumber', await context.get('aggregatorSerialNumber', random));
|
|
1980
|
+
await context.set('aggregatorUniqueId', await context.get('aggregatorUniqueId', random));
|
|
1981
|
+
|
|
1982
|
+
this.log.debug(`Creating matter aggregator for plugin ${plg}${pluginName}${db} with uniqueId ${await context.get<string>('aggregatorUniqueId')} serialNumber ${await context.get<string>('aggregatorSerialNumber')}`);
|
|
1983
|
+
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')}`);
|
|
1984
|
+
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')}`);
|
|
1985
|
+
*/
|
|
1628
1986
|
const matterAggregator = new Aggregator();
|
|
1987
|
+
/*
|
|
1988
|
+
matterAggregator.addClusterServer(
|
|
1989
|
+
ClusterServer(
|
|
1990
|
+
BasicInformationCluster,
|
|
1991
|
+
{
|
|
1992
|
+
dataModelRevision: 1,
|
|
1993
|
+
location: 'FR',
|
|
1994
|
+
vendorId: VendorId(0xfff1),
|
|
1995
|
+
vendorName: 'Matterbridge',
|
|
1996
|
+
productId: 0x8000,
|
|
1997
|
+
productName: 'Matterbridge aggregator',
|
|
1998
|
+
productLabel: 'Matterbridge aggregator',
|
|
1999
|
+
nodeLabel: 'Matterbridge aggregator',
|
|
2000
|
+
serialNumber: await context.get<string>('aggregatorSerialNumber'),
|
|
2001
|
+
uniqueId: await context.get<string>('aggregatorUniqueId'),
|
|
2002
|
+
softwareVersion: await context.get<number>('softwareVersion', 1),
|
|
2003
|
+
softwareVersionString: await context.get<string>('softwareVersionString', '1.0.0'),
|
|
2004
|
+
hardwareVersion: await context.get<number>('hardwareVersion', 1),
|
|
2005
|
+
hardwareVersionString: await context.get<string>('hardwareVersionString', '1.0.0'),
|
|
2006
|
+
reachable: true,
|
|
2007
|
+
capabilityMinima: { caseSessionsPerFabric: 3, subscriptionsPerFabric: 3 },
|
|
2008
|
+
specificationVersion: Specification.SPECIFICATION_VERSION,
|
|
2009
|
+
maxPathsPerInvoke: 1,
|
|
2010
|
+
},
|
|
2011
|
+
{},
|
|
2012
|
+
{
|
|
2013
|
+
startUp: true,
|
|
2014
|
+
shutDown: true,
|
|
2015
|
+
leave: true,
|
|
2016
|
+
reachableChanged: true,
|
|
2017
|
+
},
|
|
2018
|
+
),
|
|
2019
|
+
);
|
|
2020
|
+
*/
|
|
1629
2021
|
return matterAggregator;
|
|
1630
2022
|
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Creates a matter commissioning server.
|
|
2025
|
+
*
|
|
2026
|
+
* @param {StorageContext} context - The storage context.
|
|
2027
|
+
* @param {string} pluginName - The name of the commissioning server.
|
|
2028
|
+
* @returns {CommissioningServer} The created commissioning server.
|
|
2029
|
+
*/
|
|
1631
2030
|
async createCommisioningServer(context, pluginName) {
|
|
1632
2031
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1633
2032
|
const deviceName = await context.get('deviceName');
|
|
1634
2033
|
const deviceType = await context.get('deviceType');
|
|
1635
2034
|
const vendorId = await context.get('vendorId');
|
|
1636
|
-
const vendorName = await context.get('vendorName');
|
|
2035
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1637
2036
|
const productId = await context.get('productId');
|
|
1638
|
-
const productName = await context.get('productName');
|
|
2037
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1639
2038
|
const serialNumber = await context.get('serialNumber');
|
|
1640
2039
|
const uniqueId = await context.get('uniqueId');
|
|
1641
2040
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1642
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2041
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1643
2042
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1644
2043
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1645
2044
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
|
|
@@ -1647,6 +2046,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1647
2046
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1648
2047
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1649
2048
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${this.port} passcode ${this.passcode} discriminator ${this.discriminator}`);
|
|
2049
|
+
// Validate ipv4address
|
|
1650
2050
|
if (this.ipv4address) {
|
|
1651
2051
|
const networkInterfaces = os.networkInterfaces();
|
|
1652
2052
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1661,6 +2061,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1661
2061
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1662
2062
|
}
|
|
1663
2063
|
}
|
|
2064
|
+
// Validate ipv6address
|
|
1664
2065
|
if (this.ipv6address) {
|
|
1665
2066
|
const networkInterfaces = os.networkInterfaces();
|
|
1666
2067
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1691,7 +2092,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1691
2092
|
nodeLabel: productName,
|
|
1692
2093
|
productLabel: productName,
|
|
1693
2094
|
softwareVersion,
|
|
1694
|
-
softwareVersionString,
|
|
2095
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1695
2096
|
hardwareVersion,
|
|
1696
2097
|
hardwareVersionString,
|
|
1697
2098
|
uniqueId,
|
|
@@ -1787,6 +2188,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1787
2188
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1788
2189
|
return commissioningServer;
|
|
1789
2190
|
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Creates a commissioning server storage context.
|
|
2193
|
+
*
|
|
2194
|
+
* @param pluginName - The name of the plugin.
|
|
2195
|
+
* @param deviceName - The name of the device.
|
|
2196
|
+
* @param deviceType - The type of the device.
|
|
2197
|
+
* @param vendorId - The vendor ID.
|
|
2198
|
+
* @param vendorName - The vendor name.
|
|
2199
|
+
* @param productId - The product ID.
|
|
2200
|
+
* @param productName - The product name.
|
|
2201
|
+
* @param serialNumber - The serial number of the device (optional).
|
|
2202
|
+
* @param uniqueId - The unique ID of the device (optional).
|
|
2203
|
+
* @param softwareVersion - The software version of the device (optional).
|
|
2204
|
+
* @param softwareVersionString - The software version string of the device (optional).
|
|
2205
|
+
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2206
|
+
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2207
|
+
* @returns The storage context for the commissioning server.
|
|
2208
|
+
*/
|
|
1790
2209
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
1791
2210
|
if (!this.storageManager)
|
|
1792
2211
|
throw new Error('No storage manager initialized');
|
|
@@ -1814,6 +2233,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1814
2233
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1815
2234
|
return storageContext;
|
|
1816
2235
|
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Imports the commissioning server context for a specific plugin and device.
|
|
2238
|
+
* @param pluginName - The name of the plugin.
|
|
2239
|
+
* @param device - The MatterbridgeDevice object representing the device.
|
|
2240
|
+
* @returns The commissioning server context.
|
|
2241
|
+
* @throws Error if the BasicInformationCluster is not found.
|
|
2242
|
+
*/
|
|
1817
2243
|
async importCommissioningServerContext(pluginName, device) {
|
|
1818
2244
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
1819
2245
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -1848,6 +2274,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1848
2274
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1849
2275
|
return storageContext;
|
|
1850
2276
|
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Shows the commissioning server QR code for a given plugin.
|
|
2279
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2280
|
+
* @param {StorageContext} storageContext - The storage context instance.
|
|
2281
|
+
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2282
|
+
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2283
|
+
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2284
|
+
*/
|
|
1851
2285
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
1852
2286
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
1853
2287
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -1858,7 +2292,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1858
2292
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
1859
2293
|
const QrCode = new QrCodeSchema();
|
|
1860
2294
|
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`);
|
|
1861
|
-
|
|
2295
|
+
// eslint-disable-next-line no-console
|
|
2296
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
1862
2297
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
1863
2298
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
1864
2299
|
if (pluginName === 'Matterbridge') {
|
|
@@ -1905,6 +2340,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1905
2340
|
}
|
|
1906
2341
|
this.wssSendRefreshRequired();
|
|
1907
2342
|
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2345
|
+
*
|
|
2346
|
+
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2347
|
+
* @returns An array of sanitized exposed fabric information objects.
|
|
2348
|
+
*/
|
|
1908
2349
|
sanitizeFabricInformations(fabricInfo) {
|
|
1909
2350
|
return fabricInfo.map((info) => {
|
|
1910
2351
|
return {
|
|
@@ -1918,6 +2359,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1918
2359
|
};
|
|
1919
2360
|
});
|
|
1920
2361
|
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Sanitizes the session information by converting bigint properties to string.
|
|
2364
|
+
*
|
|
2365
|
+
* @param sessionInfo - The array of session information objects.
|
|
2366
|
+
* @returns An array of sanitized session information objects.
|
|
2367
|
+
*/
|
|
1921
2368
|
sanitizeSessionInformation(sessionInfo) {
|
|
1922
2369
|
return sessionInfo
|
|
1923
2370
|
.filter((session) => session.isPeerActive)
|
|
@@ -1945,6 +2392,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1945
2392
|
};
|
|
1946
2393
|
});
|
|
1947
2394
|
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Sets the reachability of a commissioning server and trigger.
|
|
2397
|
+
*
|
|
2398
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2399
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2400
|
+
*/
|
|
1948
2401
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
1949
2402
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
1950
2403
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -1952,6 +2405,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1952
2405
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
1953
2406
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1954
2407
|
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2410
|
+
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2411
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2412
|
+
*/
|
|
1955
2413
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
1956
2414
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
1957
2415
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -1964,6 +2422,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1964
2422
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1965
2423
|
});
|
|
1966
2424
|
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Sets the reachability of a device and trigger.
|
|
2427
|
+
*
|
|
2428
|
+
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2429
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2430
|
+
*/
|
|
1967
2431
|
setDeviceReachability(device, reachable) {
|
|
1968
2432
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
1969
2433
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2012,6 +2476,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2012
2476
|
}
|
|
2013
2477
|
return vendorName;
|
|
2014
2478
|
};
|
|
2479
|
+
/**
|
|
2480
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
2481
|
+
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
2482
|
+
*/
|
|
2015
2483
|
async getBaseRegisteredPlugins() {
|
|
2016
2484
|
const baseRegisteredPlugins = [];
|
|
2017
2485
|
for (const plugin of this.plugins) {
|
|
@@ -2043,13 +2511,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2043
2511
|
}
|
|
2044
2512
|
return baseRegisteredPlugins;
|
|
2045
2513
|
}
|
|
2514
|
+
/**
|
|
2515
|
+
* Spawns a child process with the given command and arguments.
|
|
2516
|
+
* @param {string} command - The command to execute.
|
|
2517
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2518
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2519
|
+
*/
|
|
2046
2520
|
async spawnCommand(command, args = []) {
|
|
2521
|
+
/*
|
|
2522
|
+
npm > npm.cmd on windows
|
|
2523
|
+
cmd.exe ['dir'] on windows
|
|
2524
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2525
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2526
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2527
|
+
});
|
|
2528
|
+
|
|
2529
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2530
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2531
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2532
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2533
|
+
*/
|
|
2047
2534
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2048
2535
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2536
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2049
2537
|
const argstring = 'npm ' + args.join(' ');
|
|
2050
2538
|
args.splice(0, args.length, '/c', argstring);
|
|
2051
2539
|
command = 'cmd.exe';
|
|
2052
2540
|
}
|
|
2541
|
+
// Decide when using sudo on linux
|
|
2542
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2543
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2053
2544
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2054
2545
|
args.unshift(command);
|
|
2055
2546
|
command = 'sudo';
|
|
@@ -2107,55 +2598,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2107
2598
|
}
|
|
2108
2599
|
});
|
|
2109
2600
|
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Sends a WebSocket message to all connected clients.
|
|
2603
|
+
*
|
|
2604
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2605
|
+
* @param {string} time - The time string of the message
|
|
2606
|
+
* @param {string} name - The logger name of the message
|
|
2607
|
+
* @param {string} message - The content of the message.
|
|
2608
|
+
*/
|
|
2110
2609
|
wssSendMessage(level, time, name, message) {
|
|
2111
2610
|
if (!level || !time || !name || !message)
|
|
2112
2611
|
return;
|
|
2612
|
+
// Remove ANSI escape codes from the message
|
|
2613
|
+
// eslint-disable-next-line no-control-regex
|
|
2113
2614
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2615
|
+
// Remove leading asterisks from the message
|
|
2114
2616
|
message = message.replace(/^\*+/, '');
|
|
2617
|
+
// Replace all occurrences of \t and \n
|
|
2115
2618
|
message = message.replace(/[\t\n]/g, '');
|
|
2619
|
+
// Remove non-printable characters
|
|
2620
|
+
// eslint-disable-next-line no-control-regex
|
|
2116
2621
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2622
|
+
// Replace all occurrences of \" with "
|
|
2117
2623
|
message = message.replace(/\\"/g, '"');
|
|
2624
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2118
2625
|
const maxContinuousLength = 100;
|
|
2119
2626
|
const keepStartLength = 20;
|
|
2120
2627
|
const keepEndLength = 20;
|
|
2628
|
+
// Split the message into words
|
|
2121
2629
|
message = message
|
|
2122
2630
|
.split(' ')
|
|
2123
2631
|
.map((word) => {
|
|
2632
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2124
2633
|
if (word.length > maxContinuousLength) {
|
|
2125
2634
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2126
2635
|
}
|
|
2127
2636
|
return word;
|
|
2128
2637
|
})
|
|
2129
2638
|
.join(' ');
|
|
2639
|
+
// Send the message to all connected clients
|
|
2130
2640
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2131
2641
|
if (client.readyState === WebSocket.OPEN) {
|
|
2132
2642
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2133
2643
|
}
|
|
2134
2644
|
});
|
|
2135
2645
|
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2648
|
+
*
|
|
2649
|
+
*/
|
|
2136
2650
|
wssSendRefreshRequired() {
|
|
2137
2651
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2652
|
+
// Send the message to all connected clients
|
|
2138
2653
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2139
2654
|
if (client.readyState === WebSocket.OPEN) {
|
|
2140
2655
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
|
|
2141
2656
|
}
|
|
2142
2657
|
});
|
|
2143
2658
|
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2661
|
+
*
|
|
2662
|
+
*/
|
|
2144
2663
|
wssSendRestartRequired() {
|
|
2145
2664
|
this.matterbridgeInformation.restartRequired = true;
|
|
2665
|
+
// Send the message to all connected clients
|
|
2146
2666
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2147
2667
|
if (client.readyState === WebSocket.OPEN) {
|
|
2148
2668
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
|
|
2149
2669
|
}
|
|
2150
2670
|
});
|
|
2151
2671
|
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Initializes the frontend of Matterbridge.
|
|
2674
|
+
*
|
|
2675
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
2676
|
+
*/
|
|
2152
2677
|
async initializeFrontend(port = 8283) {
|
|
2153
2678
|
let initializeError = false;
|
|
2154
2679
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2680
|
+
// Create the express app that serves the frontend
|
|
2155
2681
|
this.expressApp = express();
|
|
2682
|
+
// Log all requests to the server for debugging
|
|
2683
|
+
/*
|
|
2684
|
+
if (hasParameter('homedir')) {
|
|
2685
|
+
this.expressApp.use((req, res, next) => {
|
|
2686
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
2687
|
+
next();
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
*/
|
|
2691
|
+
// Serve static files from '/static' endpoint
|
|
2156
2692
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2157
2693
|
if (!hasParameter('ssl')) {
|
|
2694
|
+
// Create an HTTP server and attach the express app
|
|
2158
2695
|
this.httpServer = createServer(this.expressApp);
|
|
2696
|
+
// Listen on the specified port
|
|
2159
2697
|
if (hasParameter('ingress')) {
|
|
2160
2698
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2161
2699
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2169,6 +2707,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2169
2707
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2170
2708
|
});
|
|
2171
2709
|
}
|
|
2710
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2172
2711
|
this.httpServer.on('error', (error) => {
|
|
2173
2712
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2174
2713
|
switch (error.code) {
|
|
@@ -2184,6 +2723,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2184
2723
|
});
|
|
2185
2724
|
}
|
|
2186
2725
|
else {
|
|
2726
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2187
2727
|
let cert;
|
|
2188
2728
|
try {
|
|
2189
2729
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2211,7 +2751,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2211
2751
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2212
2752
|
}
|
|
2213
2753
|
const serverOptions = { cert, key, ca };
|
|
2754
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
2214
2755
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
2756
|
+
// Listen on the specified port
|
|
2215
2757
|
if (hasParameter('ingress')) {
|
|
2216
2758
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2217
2759
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2225,6 +2767,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2225
2767
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2226
2768
|
});
|
|
2227
2769
|
}
|
|
2770
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2228
2771
|
this.httpsServer.on('error', (error) => {
|
|
2229
2772
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2230
2773
|
switch (error.code) {
|
|
@@ -2241,12 +2784,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2241
2784
|
}
|
|
2242
2785
|
if (initializeError)
|
|
2243
2786
|
return;
|
|
2787
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2244
2788
|
const wssPort = port;
|
|
2245
2789
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2246
2790
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2247
2791
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2248
2792
|
const clientIp = request.socket.remoteAddress;
|
|
2249
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
2793
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2250
2794
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2251
2795
|
ws.on('message', (message) => {
|
|
2252
2796
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2279,6 +2823,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2279
2823
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2280
2824
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2281
2825
|
});
|
|
2826
|
+
// Endpoint to validate login code
|
|
2282
2827
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2283
2828
|
const { password } = req.body;
|
|
2284
2829
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2297,12 +2842,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2297
2842
|
this.log.warn('/api/login error wrong password');
|
|
2298
2843
|
res.json({ valid: false });
|
|
2299
2844
|
}
|
|
2845
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2300
2846
|
}
|
|
2301
2847
|
catch (error) {
|
|
2302
2848
|
this.log.error('/api/login error getting password');
|
|
2303
2849
|
res.json({ valid: false });
|
|
2304
2850
|
}
|
|
2305
2851
|
});
|
|
2852
|
+
// Endpoint to provide settings
|
|
2306
2853
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2307
2854
|
this.log.debug('The frontend sent /api/settings');
|
|
2308
2855
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2320,13 +2867,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2320
2867
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2321
2868
|
this.matterbridgeInformation.profile = this.profile;
|
|
2322
2869
|
const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2870
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2323
2871
|
res.json(response);
|
|
2324
2872
|
});
|
|
2873
|
+
// Endpoint to provide plugins
|
|
2325
2874
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2326
2875
|
this.log.debug('The frontend sent /api/plugins');
|
|
2327
2876
|
const response = await this.getBaseRegisteredPlugins();
|
|
2877
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2328
2878
|
res.json(response);
|
|
2329
2879
|
});
|
|
2880
|
+
// Endpoint to provide devices
|
|
2330
2881
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2331
2882
|
this.log.debug('The frontend sent /api/devices');
|
|
2332
2883
|
const data = [];
|
|
@@ -2354,8 +2905,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2354
2905
|
cluster: cluster,
|
|
2355
2906
|
});
|
|
2356
2907
|
});
|
|
2908
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2357
2909
|
res.json(data);
|
|
2358
2910
|
});
|
|
2911
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2359
2912
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2360
2913
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2361
2914
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2375,6 +2928,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2375
2928
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2376
2929
|
if (clusterServer.name === 'EveHistory')
|
|
2377
2930
|
return;
|
|
2931
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2378
2932
|
let attributeValue;
|
|
2379
2933
|
try {
|
|
2380
2934
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2385,6 +2939,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2385
2939
|
catch (error) {
|
|
2386
2940
|
attributeValue = 'Fabric-Scoped';
|
|
2387
2941
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
2942
|
+
// console.log(error);
|
|
2388
2943
|
}
|
|
2389
2944
|
data.push({
|
|
2390
2945
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2397,12 +2952,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2397
2952
|
});
|
|
2398
2953
|
});
|
|
2399
2954
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
2955
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2400
2956
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2401
2957
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2402
2958
|
clusterServers.forEach((clusterServer) => {
|
|
2403
2959
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2404
2960
|
if (clusterServer.name === 'EveHistory')
|
|
2405
2961
|
return;
|
|
2962
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2406
2963
|
let attributeValue;
|
|
2407
2964
|
try {
|
|
2408
2965
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2413,6 +2970,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2413
2970
|
catch (error) {
|
|
2414
2971
|
attributeValue = 'Unavailable';
|
|
2415
2972
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
2973
|
+
// console.log(error);
|
|
2416
2974
|
}
|
|
2417
2975
|
data.push({
|
|
2418
2976
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2429,6 +2987,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2429
2987
|
});
|
|
2430
2988
|
res.json(data);
|
|
2431
2989
|
});
|
|
2990
|
+
// Endpoint to view the log
|
|
2432
2991
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2433
2992
|
this.log.debug('The frontend sent /api/log');
|
|
2434
2993
|
try {
|
|
@@ -2441,10 +3000,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2441
3000
|
res.status(500).send('Error reading log file');
|
|
2442
3001
|
}
|
|
2443
3002
|
});
|
|
3003
|
+
// Endpoint to download the matterbridge log
|
|
2444
3004
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2445
3005
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2446
3006
|
try {
|
|
2447
3007
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3008
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2448
3009
|
}
|
|
2449
3010
|
catch (error) {
|
|
2450
3011
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2456,10 +3017,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2456
3017
|
}
|
|
2457
3018
|
});
|
|
2458
3019
|
});
|
|
3020
|
+
// Endpoint to download the matter log
|
|
2459
3021
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2460
3022
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2461
3023
|
try {
|
|
2462
3024
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3025
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2463
3026
|
}
|
|
2464
3027
|
catch (error) {
|
|
2465
3028
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2471,6 +3034,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2471
3034
|
}
|
|
2472
3035
|
});
|
|
2473
3036
|
});
|
|
3037
|
+
// Endpoint to download the matter storage file
|
|
2474
3038
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2475
3039
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2476
3040
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2480,6 +3044,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2480
3044
|
}
|
|
2481
3045
|
});
|
|
2482
3046
|
});
|
|
3047
|
+
// Endpoint to download the matterbridge storage directory
|
|
2483
3048
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2484
3049
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2485
3050
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2490,6 +3055,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2490
3055
|
}
|
|
2491
3056
|
});
|
|
2492
3057
|
});
|
|
3058
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2493
3059
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2494
3060
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2495
3061
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2500,9 +3066,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2500
3066
|
}
|
|
2501
3067
|
});
|
|
2502
3068
|
});
|
|
3069
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2503
3070
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2504
3071
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2505
3072
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3073
|
+
// 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')));
|
|
2506
3074
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2507
3075
|
if (error) {
|
|
2508
3076
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2510,6 +3078,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2510
3078
|
}
|
|
2511
3079
|
});
|
|
2512
3080
|
});
|
|
3081
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2513
3082
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2514
3083
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2515
3084
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2519,6 +3088,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2519
3088
|
}
|
|
2520
3089
|
});
|
|
2521
3090
|
});
|
|
3091
|
+
// Endpoint to receive commands
|
|
2522
3092
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2523
3093
|
const command = req.params.command;
|
|
2524
3094
|
let param = req.params.param;
|
|
@@ -2528,13 +3098,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2528
3098
|
return;
|
|
2529
3099
|
}
|
|
2530
3100
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3101
|
+
// Handle the command setpassword from Settings
|
|
2531
3102
|
if (command === 'setpassword') {
|
|
2532
|
-
const password = param.slice(1, -1);
|
|
3103
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2533
3104
|
this.log.debug('setpassword', param, password);
|
|
2534
3105
|
await this.nodeContext?.set('password', password);
|
|
2535
3106
|
res.json({ message: 'Command received' });
|
|
2536
3107
|
return;
|
|
2537
3108
|
}
|
|
3109
|
+
// Handle the command setbridgemode from Settings
|
|
2538
3110
|
if (command === 'setbridgemode') {
|
|
2539
3111
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2540
3112
|
this.wssSendRestartRequired();
|
|
@@ -2542,6 +3114,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2542
3114
|
res.json({ message: 'Command received' });
|
|
2543
3115
|
return;
|
|
2544
3116
|
}
|
|
3117
|
+
// Handle the command backup from Settings
|
|
2545
3118
|
if (command === 'backup') {
|
|
2546
3119
|
this.log.notice(`Prepairing the backup...`);
|
|
2547
3120
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2549,25 +3122,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2549
3122
|
res.json({ message: 'Command received' });
|
|
2550
3123
|
return;
|
|
2551
3124
|
}
|
|
3125
|
+
// Handle the command setmbloglevel from Settings
|
|
2552
3126
|
if (command === 'setmbloglevel') {
|
|
2553
3127
|
this.log.debug('Matterbridge log level:', param);
|
|
2554
3128
|
if (param === 'Debug') {
|
|
2555
|
-
this.log.logLevel = "debug"
|
|
3129
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2556
3130
|
}
|
|
2557
3131
|
else if (param === 'Info') {
|
|
2558
|
-
this.log.logLevel = "info"
|
|
3132
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2559
3133
|
}
|
|
2560
3134
|
else if (param === 'Notice') {
|
|
2561
|
-
this.log.logLevel = "notice"
|
|
3135
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2562
3136
|
}
|
|
2563
3137
|
else if (param === 'Warn') {
|
|
2564
|
-
this.log.logLevel = "warn"
|
|
3138
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2565
3139
|
}
|
|
2566
3140
|
else if (param === 'Error') {
|
|
2567
|
-
this.log.logLevel = "error"
|
|
3141
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2568
3142
|
}
|
|
2569
3143
|
else if (param === 'Fatal') {
|
|
2570
|
-
this.log.logLevel = "fatal"
|
|
3144
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2571
3145
|
}
|
|
2572
3146
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2573
3147
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2575,12 +3149,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2575
3149
|
for (const plugin of this.plugins) {
|
|
2576
3150
|
if (!plugin.platform || !plugin.platform.config)
|
|
2577
3151
|
continue;
|
|
2578
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2579
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
3152
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3153
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
2580
3154
|
}
|
|
2581
3155
|
res.json({ message: 'Command received' });
|
|
2582
3156
|
return;
|
|
2583
3157
|
}
|
|
3158
|
+
// Handle the command setmbloglevel from Settings
|
|
2584
3159
|
if (command === 'setmjloglevel') {
|
|
2585
3160
|
this.log.debug('Matter.js log level:', param);
|
|
2586
3161
|
if (param === 'Debug') {
|
|
@@ -2605,41 +3180,47 @@ export class Matterbridge extends EventEmitter {
|
|
|
2605
3180
|
res.json({ message: 'Command received' });
|
|
2606
3181
|
return;
|
|
2607
3182
|
}
|
|
3183
|
+
// Handle the command setmdnsinterface from Settings
|
|
2608
3184
|
if (command === 'setmdnsinterface') {
|
|
2609
|
-
param = param.slice(1, -1);
|
|
3185
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2610
3186
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2611
3187
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2612
3188
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2613
3189
|
res.json({ message: 'Command received' });
|
|
2614
3190
|
return;
|
|
2615
3191
|
}
|
|
3192
|
+
// Handle the command setipv4address from Settings
|
|
2616
3193
|
if (command === 'setipv4address') {
|
|
2617
|
-
param = param.slice(1, -1);
|
|
3194
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2618
3195
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2619
3196
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2620
3197
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2621
3198
|
res.json({ message: 'Command received' });
|
|
2622
3199
|
return;
|
|
2623
3200
|
}
|
|
3201
|
+
// Handle the command setipv6address from Settings
|
|
2624
3202
|
if (command === 'setipv6address') {
|
|
2625
|
-
param = param.slice(1, -1);
|
|
3203
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2626
3204
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2627
3205
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2628
3206
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2629
3207
|
res.json({ message: 'Command received' });
|
|
2630
3208
|
return;
|
|
2631
3209
|
}
|
|
3210
|
+
// Handle the command setmbloglevel from Settings
|
|
2632
3211
|
if (command === 'setmblogfile') {
|
|
2633
3212
|
this.log.debug('Matterbridge file log:', param);
|
|
2634
3213
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2635
3214
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3215
|
+
// Create the file logger for matterbridge
|
|
2636
3216
|
if (param === 'true')
|
|
2637
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3217
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2638
3218
|
else
|
|
2639
3219
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2640
3220
|
res.json({ message: 'Command received' });
|
|
2641
3221
|
return;
|
|
2642
3222
|
}
|
|
3223
|
+
// Handle the command setmbloglevel from Settings
|
|
2643
3224
|
if (command === 'setmjlogfile') {
|
|
2644
3225
|
this.log.debug('Matter file log:', param);
|
|
2645
3226
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2666,36 +3247,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2666
3247
|
res.json({ message: 'Command received' });
|
|
2667
3248
|
return;
|
|
2668
3249
|
}
|
|
3250
|
+
// Handle the command unregister from Settings
|
|
2669
3251
|
if (command === 'unregister') {
|
|
2670
3252
|
await this.unregisterAndShutdownProcess();
|
|
2671
3253
|
res.json({ message: 'Command received' });
|
|
2672
3254
|
return;
|
|
2673
3255
|
}
|
|
3256
|
+
// Handle the command reset from Settings
|
|
2674
3257
|
if (command === 'reset') {
|
|
2675
3258
|
await this.shutdownProcessAndReset();
|
|
2676
3259
|
res.json({ message: 'Command received' });
|
|
2677
3260
|
return;
|
|
2678
3261
|
}
|
|
3262
|
+
// Handle the command factoryreset from Settings
|
|
2679
3263
|
if (command === 'factoryreset') {
|
|
2680
3264
|
await this.shutdownProcessAndFactoryReset();
|
|
2681
3265
|
res.json({ message: 'Command received' });
|
|
2682
3266
|
return;
|
|
2683
3267
|
}
|
|
3268
|
+
// Handle the command shutdown from Header
|
|
2684
3269
|
if (command === 'shutdown') {
|
|
2685
3270
|
await this.shutdownProcess();
|
|
2686
3271
|
res.json({ message: 'Command received' });
|
|
2687
3272
|
return;
|
|
2688
3273
|
}
|
|
3274
|
+
// Handle the command restart from Header
|
|
2689
3275
|
if (command === 'restart') {
|
|
2690
3276
|
await this.restartProcess();
|
|
2691
3277
|
res.json({ message: 'Command received' });
|
|
2692
3278
|
return;
|
|
2693
3279
|
}
|
|
3280
|
+
// Handle the command update from Header
|
|
2694
3281
|
if (command === 'update') {
|
|
2695
3282
|
this.log.info('Updating matterbridge...');
|
|
2696
3283
|
try {
|
|
2697
3284
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2698
3285
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3286
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2699
3287
|
}
|
|
2700
3288
|
catch (error) {
|
|
2701
3289
|
this.log.error('Error updating matterbridge');
|
|
@@ -2705,9 +3293,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2705
3293
|
res.json({ message: 'Command received' });
|
|
2706
3294
|
return;
|
|
2707
3295
|
}
|
|
3296
|
+
// Handle the command saveconfig from Home
|
|
2708
3297
|
if (command === 'saveconfig') {
|
|
2709
3298
|
param = param.replace(/\*/g, '\\');
|
|
2710
3299
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3300
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2711
3301
|
if (!this.plugins.has(param)) {
|
|
2712
3302
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2713
3303
|
}
|
|
@@ -2721,28 +3311,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
2721
3311
|
res.json({ message: 'Command received' });
|
|
2722
3312
|
return;
|
|
2723
3313
|
}
|
|
3314
|
+
// Handle the command installplugin from Home
|
|
2724
3315
|
if (command === 'installplugin') {
|
|
2725
3316
|
param = param.replace(/\*/g, '\\');
|
|
2726
3317
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2727
3318
|
try {
|
|
2728
3319
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2729
3320
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3321
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2730
3322
|
}
|
|
2731
3323
|
catch (error) {
|
|
2732
3324
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2733
3325
|
}
|
|
2734
3326
|
this.wssSendRestartRequired();
|
|
3327
|
+
// Also add the plugin to matterbridge so no return!
|
|
3328
|
+
// res.json({ message: 'Command received' });
|
|
3329
|
+
// return;
|
|
2735
3330
|
}
|
|
3331
|
+
// Handle the command addplugin from Home
|
|
2736
3332
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2737
3333
|
param = param.replace(/\*/g, '\\');
|
|
2738
3334
|
const plugin = await this.plugins.add(param);
|
|
2739
3335
|
if (plugin) {
|
|
2740
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
3336
|
+
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
2741
3337
|
}
|
|
2742
3338
|
res.json({ message: 'Command received' });
|
|
2743
3339
|
this.wssSendRefreshRequired();
|
|
2744
3340
|
return;
|
|
2745
3341
|
}
|
|
3342
|
+
// Handle the command removeplugin from Home
|
|
2746
3343
|
if (command === 'removeplugin') {
|
|
2747
3344
|
if (!this.plugins.has(param)) {
|
|
2748
3345
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2756,6 +3353,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2756
3353
|
this.wssSendRefreshRequired();
|
|
2757
3354
|
return;
|
|
2758
3355
|
}
|
|
3356
|
+
// Handle the command enableplugin from Home
|
|
2759
3357
|
if (command === 'enableplugin') {
|
|
2760
3358
|
if (!this.plugins.has(param)) {
|
|
2761
3359
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2773,13 +3371,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2773
3371
|
plugin.registeredDevices = undefined;
|
|
2774
3372
|
plugin.addedDevices = undefined;
|
|
2775
3373
|
await this.plugins.enable(param);
|
|
2776
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
3374
|
+
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
2777
3375
|
}
|
|
2778
3376
|
}
|
|
2779
3377
|
res.json({ message: 'Command received' });
|
|
2780
3378
|
this.wssSendRefreshRequired();
|
|
2781
3379
|
return;
|
|
2782
3380
|
}
|
|
3381
|
+
// Handle the command disableplugin from Home
|
|
2783
3382
|
if (command === 'disableplugin') {
|
|
2784
3383
|
if (!this.plugins.has(param)) {
|
|
2785
3384
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2796,6 +3395,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2796
3395
|
return;
|
|
2797
3396
|
}
|
|
2798
3397
|
});
|
|
3398
|
+
// Fallback for routing (must be the last route)
|
|
2799
3399
|
this.expressApp.get('*', (req, res) => {
|
|
2800
3400
|
this.log.debug('The frontend sent:', req.url);
|
|
2801
3401
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -2803,6 +3403,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2803
3403
|
});
|
|
2804
3404
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
2805
3405
|
}
|
|
3406
|
+
/**
|
|
3407
|
+
* Retrieves the cluster text description from a given device.
|
|
3408
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3409
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3410
|
+
*/
|
|
2806
3411
|
getClusterTextFromDevice(device) {
|
|
2807
3412
|
const stringifyUserLabel = (endpoint) => {
|
|
2808
3413
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -2825,9 +3430,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2825
3430
|
return '';
|
|
2826
3431
|
};
|
|
2827
3432
|
let attributes = '';
|
|
3433
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2828
3434
|
const clusterServers = device.getAllClusterServers();
|
|
2829
3435
|
clusterServers.forEach((clusterServer) => {
|
|
2830
3436
|
try {
|
|
3437
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2831
3438
|
if (clusterServer.name === 'OnOff')
|
|
2832
3439
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
2833
3440
|
if (clusterServer.name === 'Switch')
|
|
@@ -2878,18 +3485,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
2878
3485
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
2879
3486
|
if (clusterServer.name === 'UserLabel')
|
|
2880
3487
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3488
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2881
3489
|
}
|
|
2882
3490
|
catch (error) {
|
|
2883
3491
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
2884
3492
|
}
|
|
2885
3493
|
});
|
|
3494
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2886
3495
|
return attributes;
|
|
2887
3496
|
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3499
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3500
|
+
*
|
|
3501
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3502
|
+
*/
|
|
2888
3503
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3504
|
+
// Set the bridge mode
|
|
2889
3505
|
this.bridgeMode = 'bridge';
|
|
3506
|
+
// Set the first port to use
|
|
2890
3507
|
this.port = port;
|
|
2891
|
-
|
|
3508
|
+
// Set Matterbridge logger
|
|
3509
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
2892
3510
|
this.log.debug('Matterbridge extension is starting...');
|
|
3511
|
+
// Initialize NodeStorage
|
|
2893
3512
|
this.matterbridgeDirectory = dataPath;
|
|
2894
3513
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
2895
3514
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -2908,10 +3527,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2908
3527
|
};
|
|
2909
3528
|
this.plugins.set(plugin);
|
|
2910
3529
|
this.plugins.saveToStorage();
|
|
3530
|
+
// Log system info and create .matterbridge directory
|
|
2911
3531
|
await this.logNodeAndSystemInfo();
|
|
2912
3532
|
this.matterbridgeDirectory = dataPath;
|
|
3533
|
+
// Set matter.js logger level and format
|
|
2913
3534
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
2914
3535
|
Logger.format = MatterLogFormat.ANSI;
|
|
3536
|
+
// Start the storage and create matterbridgeContext
|
|
2915
3537
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
2916
3538
|
if (!this.storageManager)
|
|
2917
3539
|
return false;
|
|
@@ -2921,7 +3543,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2921
3543
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
2922
3544
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
2923
3545
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
2924
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3546
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
2925
3547
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
2926
3548
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
2927
3549
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -2934,6 +3556,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2934
3556
|
await this.startMatterServer();
|
|
2935
3557
|
this.log.info('Matter server started');
|
|
2936
3558
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3559
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
2937
3560
|
setTimeout(() => {
|
|
2938
3561
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
2939
3562
|
if (this.commissioningServer)
|
|
@@ -2943,14 +3566,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
2943
3566
|
}, 60 * 1000);
|
|
2944
3567
|
return this.commissioningServer.isCommissioned();
|
|
2945
3568
|
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3571
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3572
|
+
*
|
|
3573
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3574
|
+
*/
|
|
2946
3575
|
async stopExtension() {
|
|
3576
|
+
// Closing matter
|
|
2947
3577
|
await this.stopMatterServer();
|
|
3578
|
+
// Clearing the session manager
|
|
3579
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3580
|
+
// Closing storage
|
|
2948
3581
|
await this.stopMatterStorage();
|
|
2949
3582
|
this.log.info('Matter server stopped');
|
|
2950
3583
|
}
|
|
3584
|
+
/**
|
|
3585
|
+
* Checks if the extension is commissioned.
|
|
3586
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3587
|
+
*
|
|
3588
|
+
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3589
|
+
*/
|
|
2951
3590
|
isExtensionCommissioned() {
|
|
2952
3591
|
if (!this.commissioningServer)
|
|
2953
3592
|
return false;
|
|
2954
3593
|
return this.commissioningServer.isCommissioned();
|
|
2955
3594
|
}
|
|
2956
3595
|
}
|
|
3596
|
+
//# sourceMappingURL=matterbridge.js.map
|