matterbridge 1.6.7-dev.3 → 1.6.7
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 +5 -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 +6 -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 +660 -61
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +942 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +29 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDevice.d.ts +7063 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -0
- package/dist/matterbridgeDevice.js +1026 -10
- package/dist/matterbridgeDevice.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +109 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +98 -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 +92 -0
- package/dist/matterbridgeEdge.d.ts.map +1 -0
- package/dist/matterbridgeEdge.js +530 -0
- package/dist/matterbridgeEdge.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +10164 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1163 -15
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +114 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +91 -3
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +162 -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 +238 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -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 +2 -2
- package/package.json +2 -1
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: '',
|
|
@@ -62,7 +94,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
62
94
|
edge: hasParameter('edge'),
|
|
63
95
|
readOnly: hasParameter('readonly'),
|
|
64
96
|
profile: getParameter('profile'),
|
|
65
|
-
loggerLevel: "info"
|
|
97
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
66
98
|
fileLogger: false,
|
|
67
99
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
68
100
|
matterFileLogger: false,
|
|
@@ -101,6 +133,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
101
133
|
nodeContext;
|
|
102
134
|
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
103
135
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
136
|
+
// Cleanup
|
|
104
137
|
hasCleanupStarted = false;
|
|
105
138
|
initialized = false;
|
|
106
139
|
execRunningCount = 0;
|
|
@@ -112,16 +145,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
112
145
|
sigtermHandler;
|
|
113
146
|
exceptionHandler;
|
|
114
147
|
rejectionHandler;
|
|
148
|
+
// Frontend
|
|
115
149
|
expressApp;
|
|
116
150
|
httpServer;
|
|
117
151
|
httpsServer;
|
|
118
152
|
webSocketServer;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
// Matter
|
|
154
|
+
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
155
|
+
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
156
|
+
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
157
|
+
port = 5540; // first commissioning server port
|
|
158
|
+
passcode; // first commissioning server passcode
|
|
159
|
+
discriminator; // first commissioning server discriminator
|
|
125
160
|
storageManager;
|
|
126
161
|
matterbridgeContext;
|
|
127
162
|
mattercontrollerContext;
|
|
@@ -132,13 +167,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
132
167
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
133
168
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
134
169
|
static instance;
|
|
170
|
+
// We load asyncronously so is private
|
|
135
171
|
constructor() {
|
|
136
172
|
super();
|
|
173
|
+
// Bind the handler to the instance
|
|
137
174
|
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
138
175
|
}
|
|
139
176
|
matterbridgeMessageHandler;
|
|
177
|
+
/** ***********************************************************************************************************************************/
|
|
178
|
+
/** loadInstance() and cleanup() methods */
|
|
179
|
+
/** ***********************************************************************************************************************************/
|
|
180
|
+
/**
|
|
181
|
+
* Loads an instance of the Matterbridge class.
|
|
182
|
+
* If an instance already exists, return that instance.
|
|
183
|
+
*
|
|
184
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
185
|
+
* @returns The loaded Matterbridge instance.
|
|
186
|
+
*/
|
|
140
187
|
static async loadInstance(initialize = false) {
|
|
141
188
|
if (!Matterbridge.instance) {
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
142
190
|
if (hasParameter('debug'))
|
|
143
191
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
144
192
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -147,6 +195,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
147
195
|
}
|
|
148
196
|
return Matterbridge.instance;
|
|
149
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Call cleanup().
|
|
200
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
201
|
+
*
|
|
202
|
+
*/
|
|
150
203
|
async destroyInstance() {
|
|
151
204
|
await this.cleanup('destroying instance...', false);
|
|
152
205
|
await waiter('destroying instance...', () => {
|
|
@@ -154,39 +207,60 @@ export class Matterbridge extends EventEmitter {
|
|
|
154
207
|
}, false, 60000, 100, false);
|
|
155
208
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
156
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Initializes the Matterbridge application.
|
|
212
|
+
*
|
|
213
|
+
* @remarks
|
|
214
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
215
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
216
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
217
|
+
*
|
|
218
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
219
|
+
*/
|
|
157
220
|
async initialize() {
|
|
221
|
+
// Set the restart mode
|
|
158
222
|
if (hasParameter('service'))
|
|
159
223
|
this.restartMode = 'service';
|
|
160
224
|
if (hasParameter('docker'))
|
|
161
225
|
this.restartMode = 'docker';
|
|
226
|
+
// Set the matterbridge directory
|
|
162
227
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
163
228
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
164
|
-
|
|
229
|
+
// Create matterbridge logger
|
|
230
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
231
|
+
// Initialize nodeStorage and nodeContext
|
|
165
232
|
try {
|
|
166
233
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
167
234
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
168
235
|
this.log.debug('Creating node storage context for matterbridge');
|
|
169
236
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
237
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
239
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
171
240
|
for (const key of keys) {
|
|
172
241
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
242
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
173
243
|
await this.nodeStorage?.storage.get(key);
|
|
174
244
|
}
|
|
175
245
|
const storages = await this.nodeStorage.getStorageNames();
|
|
176
246
|
for (const storage of storages) {
|
|
177
247
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
178
248
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
249
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
251
|
const keys = (await nodeContext?.storage.keys());
|
|
180
252
|
keys.forEach(async (key) => {
|
|
181
253
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
182
254
|
await nodeContext?.get(key);
|
|
183
255
|
});
|
|
184
256
|
}
|
|
257
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
185
258
|
this.log.debug('Creating node storage backup...');
|
|
186
259
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
187
260
|
this.log.debug('Created node storage backup');
|
|
188
261
|
}
|
|
189
262
|
catch (error) {
|
|
263
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
190
264
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
191
265
|
if (hasParameter('norestore')) {
|
|
192
266
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -201,45 +275,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
201
275
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
202
276
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
203
277
|
}
|
|
278
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
204
279
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
280
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
205
281
|
this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
|
|
282
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
206
283
|
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
207
284
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
285
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
208
286
|
if (hasParameter('logger')) {
|
|
209
287
|
const level = getParameter('logger');
|
|
210
288
|
if (level === 'debug') {
|
|
211
|
-
this.log.logLevel = "debug"
|
|
289
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
212
290
|
}
|
|
213
291
|
else if (level === 'info') {
|
|
214
|
-
this.log.logLevel = "info"
|
|
292
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
215
293
|
}
|
|
216
294
|
else if (level === 'notice') {
|
|
217
|
-
this.log.logLevel = "notice"
|
|
295
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
218
296
|
}
|
|
219
297
|
else if (level === 'warn') {
|
|
220
|
-
this.log.logLevel = "warn"
|
|
298
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
221
299
|
}
|
|
222
300
|
else if (level === 'error') {
|
|
223
|
-
this.log.logLevel = "error"
|
|
301
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
224
302
|
}
|
|
225
303
|
else if (level === 'fatal') {
|
|
226
|
-
this.log.logLevel = "fatal"
|
|
304
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
227
305
|
}
|
|
228
306
|
else {
|
|
229
307
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
230
|
-
this.log.logLevel = "info"
|
|
308
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
231
309
|
}
|
|
232
310
|
}
|
|
233
311
|
else {
|
|
234
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
312
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
235
313
|
}
|
|
236
314
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
315
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
237
316
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
238
317
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
239
318
|
this.matterbridgeInformation.fileLogger = true;
|
|
240
319
|
}
|
|
241
320
|
this.log.notice('Matterbridge is starting...');
|
|
242
321
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
322
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
243
323
|
if (hasParameter('matterlogger')) {
|
|
244
324
|
const level = getParameter('matterlogger');
|
|
245
325
|
if (level === 'debug') {
|
|
@@ -270,6 +350,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
270
350
|
}
|
|
271
351
|
Logger.format = MatterLogFormat.ANSI;
|
|
272
352
|
Logger.setLogger('default', this.createMatterLogger());
|
|
353
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
273
354
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
274
355
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
275
356
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -278,6 +359,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
278
359
|
});
|
|
279
360
|
}
|
|
280
361
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
362
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
281
363
|
if (hasParameter('mdnsinterface')) {
|
|
282
364
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
283
365
|
}
|
|
@@ -286,6 +368,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
286
368
|
if (this.mdnsInterface === '')
|
|
287
369
|
this.mdnsInterface = undefined;
|
|
288
370
|
}
|
|
371
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
289
372
|
if (hasParameter('ipv4address')) {
|
|
290
373
|
this.ipv4address = getParameter('ipv4address');
|
|
291
374
|
}
|
|
@@ -294,6 +377,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
294
377
|
if (this.ipv4address === '')
|
|
295
378
|
this.ipv4address = undefined;
|
|
296
379
|
}
|
|
380
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
297
381
|
if (hasParameter('ipv6address')) {
|
|
298
382
|
this.ipv6address = getParameter('ipv6address');
|
|
299
383
|
}
|
|
@@ -302,17 +386,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
302
386
|
if (this.ipv6address === '')
|
|
303
387
|
this.ipv6address = undefined;
|
|
304
388
|
}
|
|
389
|
+
// Initialize PluginManager
|
|
305
390
|
this.plugins = new PluginManager(this);
|
|
306
391
|
await this.plugins.loadFromStorage();
|
|
392
|
+
// Initialize DeviceManager
|
|
307
393
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
394
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
308
395
|
for (const plugin of this.plugins) {
|
|
309
396
|
const packageJson = await this.plugins.parse(plugin);
|
|
310
397
|
if (packageJson === null && !hasParameter('add')) {
|
|
398
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
399
|
+
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
311
400
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
312
401
|
try {
|
|
313
402
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
314
403
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
315
404
|
plugin.error = false;
|
|
405
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
316
406
|
}
|
|
317
407
|
catch (error) {
|
|
318
408
|
plugin.error = true;
|
|
@@ -329,6 +419,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
329
419
|
await plugin.nodeContext.set('description', plugin.description);
|
|
330
420
|
await plugin.nodeContext.set('author', plugin.author);
|
|
331
421
|
}
|
|
422
|
+
// Log system info and create .matterbridge directory
|
|
332
423
|
await this.logNodeAndSystemInfo();
|
|
333
424
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
334
425
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -336,6 +427,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
336
427
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
337
428
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
338
429
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
430
|
+
// Check node version and throw error
|
|
339
431
|
const minNodeVersion = 18;
|
|
340
432
|
const nodeVersion = process.versions.node;
|
|
341
433
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -343,10 +435,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
343
435
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
344
436
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
345
437
|
}
|
|
438
|
+
// Register process handlers
|
|
346
439
|
this.registerProcessHandlers();
|
|
440
|
+
// Parse command line
|
|
347
441
|
await this.parseCommandLine();
|
|
348
442
|
this.initialized = true;
|
|
349
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
446
|
+
* @private
|
|
447
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
448
|
+
*/
|
|
350
449
|
async parseCommandLine() {
|
|
351
450
|
if (hasParameter('help')) {
|
|
352
451
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -454,12 +553,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
454
553
|
}
|
|
455
554
|
if (hasParameter('factoryreset')) {
|
|
456
555
|
try {
|
|
556
|
+
// Delete matter storage file
|
|
457
557
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
458
558
|
}
|
|
459
559
|
catch (err) {
|
|
460
560
|
this.log.error(`Error deleting storage: ${err}`);
|
|
461
561
|
}
|
|
462
562
|
try {
|
|
563
|
+
// Delete node storage directory with its subdirectories
|
|
463
564
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
464
565
|
}
|
|
465
566
|
catch (err) {
|
|
@@ -473,6 +574,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
473
574
|
this.emit('shutdown');
|
|
474
575
|
return;
|
|
475
576
|
}
|
|
577
|
+
// Start the matter storage and create the matterbridge context
|
|
476
578
|
try {
|
|
477
579
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
478
580
|
}
|
|
@@ -507,28 +609,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
507
609
|
this.emit('shutdown');
|
|
508
610
|
return;
|
|
509
611
|
}
|
|
612
|
+
// Initialize frontend
|
|
510
613
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
511
614
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
615
|
+
// Check each 60 minutes the latest versions
|
|
512
616
|
this.checkUpdateInterval = setInterval(() => {
|
|
513
617
|
this.getMatterbridgeLatestVersion();
|
|
514
618
|
for (const plugin of this.plugins) {
|
|
515
619
|
this.getPluginLatestVersion(plugin);
|
|
516
620
|
}
|
|
517
621
|
}, 60 * 60 * 1000);
|
|
622
|
+
// Start the matterbridge in mode test
|
|
518
623
|
if (hasParameter('test')) {
|
|
519
624
|
this.bridgeMode = 'bridge';
|
|
520
625
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
521
626
|
return;
|
|
522
627
|
}
|
|
628
|
+
// Start the matterbridge in mode controller
|
|
523
629
|
if (hasParameter('controller')) {
|
|
524
630
|
this.bridgeMode = 'controller';
|
|
525
631
|
await this.startController();
|
|
526
632
|
return;
|
|
527
633
|
}
|
|
634
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
528
635
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
529
636
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
530
637
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
531
638
|
}
|
|
639
|
+
// Start matterbridge in bridge mode
|
|
532
640
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
533
641
|
this.bridgeMode = 'bridge';
|
|
534
642
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -537,6 +645,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
537
645
|
await this.startBridge();
|
|
538
646
|
return;
|
|
539
647
|
}
|
|
648
|
+
// Start matterbridge in childbridge mode
|
|
540
649
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
541
650
|
this.bridgeMode = 'childbridge';
|
|
542
651
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -546,17 +655,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
546
655
|
return;
|
|
547
656
|
}
|
|
548
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Asynchronously loads and starts the registered plugins.
|
|
660
|
+
*
|
|
661
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
662
|
+
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
663
|
+
*
|
|
664
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
665
|
+
*/
|
|
549
666
|
async startPlugins() {
|
|
667
|
+
// Check, load and start the plugins
|
|
550
668
|
for (const plugin of this.plugins) {
|
|
551
669
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
552
670
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
671
|
+
// Check if the plugin is available
|
|
553
672
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
554
673
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
555
674
|
plugin.enabled = false;
|
|
556
675
|
plugin.error = true;
|
|
557
676
|
continue;
|
|
558
677
|
}
|
|
559
|
-
|
|
678
|
+
// Check if the plugin has a new version
|
|
679
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
560
680
|
if (!plugin.enabled) {
|
|
561
681
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
562
682
|
continue;
|
|
@@ -571,20 +691,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
571
691
|
plugin.addedDevices = undefined;
|
|
572
692
|
plugin.qrPairingCode = undefined;
|
|
573
693
|
plugin.manualPairingCode = undefined;
|
|
574
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
694
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
575
695
|
}
|
|
576
696
|
this.wssSendRefreshRequired();
|
|
577
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
700
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
701
|
+
*/
|
|
578
702
|
registerProcessHandlers() {
|
|
579
703
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
580
704
|
process.removeAllListeners('uncaughtException');
|
|
581
705
|
process.removeAllListeners('unhandledRejection');
|
|
582
706
|
this.exceptionHandler = async (error) => {
|
|
583
707
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
708
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
584
709
|
};
|
|
585
710
|
process.on('uncaughtException', this.exceptionHandler);
|
|
586
711
|
this.rejectionHandler = async (reason, promise) => {
|
|
587
712
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
713
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
588
714
|
};
|
|
589
715
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
590
716
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -597,6 +723,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
597
723
|
};
|
|
598
724
|
process.on('SIGTERM', this.sigtermHandler);
|
|
599
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
728
|
+
*/
|
|
600
729
|
deregisterProcesslHandlers() {
|
|
601
730
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
602
731
|
if (this.exceptionHandler)
|
|
@@ -613,7 +742,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
613
742
|
process.off('SIGTERM', this.sigtermHandler);
|
|
614
743
|
this.sigtermHandler = undefined;
|
|
615
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Logs the node and system information.
|
|
747
|
+
*/
|
|
616
748
|
async logNodeAndSystemInfo() {
|
|
749
|
+
// IP address information
|
|
617
750
|
const networkInterfaces = os.networkInterfaces();
|
|
618
751
|
this.systemInformation.ipv4Address = '';
|
|
619
752
|
this.systemInformation.ipv6Address = '';
|
|
@@ -633,7 +766,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
633
766
|
this.systemInformation.macAddress = detail.mac;
|
|
634
767
|
}
|
|
635
768
|
}
|
|
636
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
769
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
637
770
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
638
771
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
639
772
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -641,19 +774,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
641
774
|
break;
|
|
642
775
|
}
|
|
643
776
|
}
|
|
777
|
+
// Node information
|
|
644
778
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
645
779
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
646
780
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
647
781
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
782
|
+
// Host system information
|
|
648
783
|
this.systemInformation.hostname = os.hostname();
|
|
649
784
|
this.systemInformation.user = os.userInfo().username;
|
|
650
|
-
this.systemInformation.osType = os.type();
|
|
651
|
-
this.systemInformation.osRelease = os.release();
|
|
652
|
-
this.systemInformation.osPlatform = os.platform();
|
|
653
|
-
this.systemInformation.osArch = os.arch();
|
|
654
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
655
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
656
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
785
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
786
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
787
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
788
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
789
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
790
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
791
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
792
|
+
// Log the system information
|
|
657
793
|
this.log.debug('Host System Information:');
|
|
658
794
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
659
795
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -669,15 +805,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
669
805
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
670
806
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
671
807
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
808
|
+
// Home directory
|
|
672
809
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
673
810
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
674
811
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
812
|
+
// Package root directory
|
|
675
813
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
676
814
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
677
815
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
678
816
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
817
|
+
// Global node_modules directory
|
|
679
818
|
if (this.nodeContext)
|
|
680
819
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
820
|
+
// First run of Matterbridge so the node storage is empty
|
|
681
821
|
if (this.globalModulesDirectory === '') {
|
|
682
822
|
try {
|
|
683
823
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -701,6 +841,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
701
841
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
702
842
|
});
|
|
703
843
|
}
|
|
844
|
+
// Create the data directory .matterbridge in the home directory
|
|
704
845
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
705
846
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
706
847
|
try {
|
|
@@ -724,6 +865,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
724
865
|
}
|
|
725
866
|
}
|
|
726
867
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
868
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
727
869
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
728
870
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
729
871
|
try {
|
|
@@ -747,19 +889,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
747
889
|
}
|
|
748
890
|
}
|
|
749
891
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
892
|
+
// Matterbridge version
|
|
750
893
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
751
894
|
this.matterbridgeVersion = packageJson.version;
|
|
752
895
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
753
896
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
897
|
+
// Matterbridge latest version
|
|
754
898
|
if (this.nodeContext)
|
|
755
899
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
756
900
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
757
901
|
this.getMatterbridgeLatestVersion();
|
|
902
|
+
// Current working directory
|
|
758
903
|
const currentDir = process.cwd();
|
|
759
904
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
905
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
760
906
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
761
907
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
762
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
911
|
+
* @param packageName - The name of the package.
|
|
912
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
913
|
+
*/
|
|
763
914
|
async getLatestVersion(packageName) {
|
|
764
915
|
return new Promise((resolve, reject) => {
|
|
765
916
|
this.execRunningCount++;
|
|
@@ -774,6 +925,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
774
925
|
});
|
|
775
926
|
});
|
|
776
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
930
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
931
|
+
*/
|
|
777
932
|
async getGlobalNodeModules() {
|
|
778
933
|
return new Promise((resolve, reject) => {
|
|
779
934
|
this.execRunningCount++;
|
|
@@ -788,6 +943,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
788
943
|
});
|
|
789
944
|
});
|
|
790
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
948
|
+
* @private
|
|
949
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
950
|
+
*/
|
|
791
951
|
async getMatterbridgeLatestVersion() {
|
|
792
952
|
this.getLatestVersion('matterbridge')
|
|
793
953
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -804,8 +964,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
804
964
|
})
|
|
805
965
|
.catch((error) => {
|
|
806
966
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
967
|
+
// error.stack && this.log.debug(error.stack);
|
|
807
968
|
});
|
|
808
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
972
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
973
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
974
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
975
|
+
*
|
|
976
|
+
* @private
|
|
977
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
978
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
979
|
+
*/
|
|
809
980
|
async getPluginLatestVersion(plugin) {
|
|
810
981
|
this.getLatestVersion(plugin.name)
|
|
811
982
|
.then(async (latestVersion) => {
|
|
@@ -817,40 +988,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
817
988
|
})
|
|
818
989
|
.catch((error) => {
|
|
819
990
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
991
|
+
// error.stack && this.log.debug(error.stack);
|
|
820
992
|
});
|
|
821
993
|
}
|
|
994
|
+
/**
|
|
995
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
996
|
+
*
|
|
997
|
+
* @returns {Function} The MatterLogger function.
|
|
998
|
+
*/
|
|
822
999
|
createMatterLogger() {
|
|
823
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1000
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
824
1001
|
return (_level, formattedLog) => {
|
|
825
1002
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
826
1003
|
const message = formattedLog.slice(65);
|
|
827
1004
|
matterLogger.logName = logger;
|
|
828
1005
|
switch (_level) {
|
|
829
1006
|
case MatterLogLevel.DEBUG:
|
|
830
|
-
matterLogger.log("debug"
|
|
1007
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
831
1008
|
break;
|
|
832
1009
|
case MatterLogLevel.INFO:
|
|
833
|
-
matterLogger.log("info"
|
|
1010
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
834
1011
|
break;
|
|
835
1012
|
case MatterLogLevel.NOTICE:
|
|
836
|
-
matterLogger.log("notice"
|
|
1013
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
837
1014
|
break;
|
|
838
1015
|
case MatterLogLevel.WARN:
|
|
839
|
-
matterLogger.log("warn"
|
|
1016
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
840
1017
|
break;
|
|
841
1018
|
case MatterLogLevel.ERROR:
|
|
842
|
-
matterLogger.log("error"
|
|
1019
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
843
1020
|
break;
|
|
844
1021
|
case MatterLogLevel.FATAL:
|
|
845
|
-
matterLogger.log("fatal"
|
|
1022
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
846
1023
|
break;
|
|
847
1024
|
default:
|
|
848
|
-
matterLogger.log("debug"
|
|
1025
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
849
1026
|
break;
|
|
850
1027
|
}
|
|
851
1028
|
};
|
|
852
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Creates a Matter File Logger.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {string} filePath - The path to the log file.
|
|
1034
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1035
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1036
|
+
*/
|
|
853
1037
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1038
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
854
1039
|
let fileSize = 0;
|
|
855
1040
|
if (unlink) {
|
|
856
1041
|
try {
|
|
@@ -899,53 +1084,83 @@ export class Matterbridge extends EventEmitter {
|
|
|
899
1084
|
}
|
|
900
1085
|
};
|
|
901
1086
|
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Update matterbridge and cleanup.
|
|
1089
|
+
*/
|
|
902
1090
|
async updateProcess() {
|
|
903
1091
|
await this.cleanup('updating...', false);
|
|
904
1092
|
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1095
|
+
*/
|
|
905
1096
|
async restartProcess() {
|
|
906
1097
|
await this.cleanup('restarting...', true);
|
|
907
1098
|
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Shut down the process by exiting the current process.
|
|
1101
|
+
*/
|
|
908
1102
|
async shutdownProcess() {
|
|
909
1103
|
await this.cleanup('shutting down...', false);
|
|
910
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Shut down the process and reset.
|
|
1107
|
+
*/
|
|
911
1108
|
async unregisterAndShutdownProcess() {
|
|
912
1109
|
this.log.info('Unregistering all devices and shutting down...');
|
|
913
|
-
for (const plugin of this.plugins) {
|
|
1110
|
+
for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
|
|
914
1111
|
await this.removeAllBridgedDevices(plugin.name);
|
|
915
1112
|
}
|
|
916
1113
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
917
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Shut down the process and reset.
|
|
1117
|
+
*/
|
|
918
1118
|
async shutdownProcessAndReset() {
|
|
919
1119
|
await this.cleanup('shutting down with reset...', false);
|
|
920
1120
|
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Shut down the process and factory reset.
|
|
1123
|
+
*/
|
|
921
1124
|
async shutdownProcessAndFactoryReset() {
|
|
922
1125
|
await this.cleanup('shutting down with factory reset...', false);
|
|
923
1126
|
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Cleans up the Matterbridge instance.
|
|
1129
|
+
* @param message - The cleanup message.
|
|
1130
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1131
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1132
|
+
*/
|
|
924
1133
|
async cleanup(message, restart = false) {
|
|
925
1134
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
926
1135
|
this.hasCleanupStarted = true;
|
|
927
1136
|
this.log.info(message);
|
|
1137
|
+
// Deregisters the process handlers
|
|
928
1138
|
this.deregisterProcesslHandlers();
|
|
1139
|
+
// Clear the start matter interval
|
|
929
1140
|
if (this.startMatterInterval) {
|
|
930
1141
|
clearInterval(this.startMatterInterval);
|
|
931
1142
|
this.startMatterInterval = undefined;
|
|
932
1143
|
this.log.debug('Start matter interval cleared');
|
|
933
1144
|
}
|
|
1145
|
+
// Clear the check update interval
|
|
934
1146
|
if (this.checkUpdateInterval) {
|
|
935
1147
|
clearInterval(this.checkUpdateInterval);
|
|
936
1148
|
this.checkUpdateInterval = undefined;
|
|
937
1149
|
this.log.debug('Check update interval cleared');
|
|
938
1150
|
}
|
|
1151
|
+
// Clear the configure timeout
|
|
939
1152
|
if (this.configureTimeout) {
|
|
940
1153
|
clearTimeout(this.configureTimeout);
|
|
941
1154
|
this.configureTimeout = undefined;
|
|
942
1155
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
943
1156
|
}
|
|
1157
|
+
// Clear the reachability timeout
|
|
944
1158
|
if (this.reachabilityTimeout) {
|
|
945
1159
|
clearTimeout(this.reachabilityTimeout);
|
|
946
1160
|
this.reachabilityTimeout = undefined;
|
|
947
1161
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
948
1162
|
}
|
|
1163
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
949
1164
|
for (const plugin of this.plugins) {
|
|
950
1165
|
if (!plugin.enabled || plugin.error)
|
|
951
1166
|
continue;
|
|
@@ -956,24 +1171,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
956
1171
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
957
1172
|
}
|
|
958
1173
|
}
|
|
1174
|
+
// Close the http server
|
|
959
1175
|
if (this.httpServer) {
|
|
960
1176
|
this.httpServer.close();
|
|
961
1177
|
this.httpServer.removeAllListeners();
|
|
962
1178
|
this.httpServer = undefined;
|
|
963
1179
|
this.log.debug('Frontend http server closed successfully');
|
|
964
1180
|
}
|
|
1181
|
+
// Close the https server
|
|
965
1182
|
if (this.httpsServer) {
|
|
966
1183
|
this.httpsServer.close();
|
|
967
1184
|
this.httpsServer.removeAllListeners();
|
|
968
1185
|
this.httpsServer = undefined;
|
|
969
1186
|
this.log.debug('Frontend https server closed successfully');
|
|
970
1187
|
}
|
|
1188
|
+
// Remove listeners from the express app
|
|
971
1189
|
if (this.expressApp) {
|
|
972
1190
|
this.expressApp.removeAllListeners();
|
|
973
1191
|
this.expressApp = undefined;
|
|
974
1192
|
this.log.debug('Frontend app closed successfully');
|
|
975
1193
|
}
|
|
1194
|
+
// Close the WebSocket server
|
|
976
1195
|
if (this.webSocketServer) {
|
|
1196
|
+
// Close all active connections
|
|
977
1197
|
this.webSocketServer.clients.forEach((client) => {
|
|
978
1198
|
if (client.readyState === WebSocket.OPEN) {
|
|
979
1199
|
client.close();
|
|
@@ -989,26 +1209,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
989
1209
|
});
|
|
990
1210
|
this.webSocketServer = undefined;
|
|
991
1211
|
}
|
|
1212
|
+
// Closing matter
|
|
992
1213
|
await this.stopMatterServer();
|
|
1214
|
+
// Closing matter storage
|
|
993
1215
|
await this.stopMatterStorage();
|
|
1216
|
+
// Remove the matterfilelogger
|
|
994
1217
|
try {
|
|
995
1218
|
Logger.removeLogger('matterfilelogger');
|
|
1219
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
996
1220
|
}
|
|
997
1221
|
catch (error) {
|
|
1222
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
998
1223
|
}
|
|
1224
|
+
// Serialize registeredDevices
|
|
999
1225
|
if (this.nodeStorage && this.nodeContext) {
|
|
1000
1226
|
this.log.info('Saving registered devices...');
|
|
1001
1227
|
const serializedRegisteredDevices = [];
|
|
1002
1228
|
this.devices.forEach(async (device) => {
|
|
1003
1229
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1230
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1004
1231
|
if (serializedMatterbridgeDevice)
|
|
1005
1232
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1006
1233
|
});
|
|
1007
1234
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1008
1235
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1236
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1009
1237
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1010
1238
|
await this.nodeContext.close();
|
|
1011
1239
|
this.nodeContext = undefined;
|
|
1240
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1012
1241
|
for (const plugin of this.plugins) {
|
|
1013
1242
|
if (plugin.nodeContext) {
|
|
1014
1243
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1039,13 +1268,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1039
1268
|
}
|
|
1040
1269
|
else {
|
|
1041
1270
|
if (message === 'shutting down with reset...') {
|
|
1271
|
+
// Delete matter storage file
|
|
1042
1272
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1043
1273
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1044
1274
|
this.log.info('Reset done! Remove all paired devices from the controllers.');
|
|
1045
1275
|
}
|
|
1046
1276
|
if (message === 'shutting down with factory reset...') {
|
|
1277
|
+
// Delete matter storage file
|
|
1047
1278
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1048
1279
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1280
|
+
// Delete node storage directory with its subdirectories
|
|
1049
1281
|
this.log.info('Resetting Matterbridge storage...');
|
|
1050
1282
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1051
1283
|
this.log.info('Factory reset done! Remove all paired devices from the controllers.');
|
|
@@ -1058,19 +1290,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1058
1290
|
this.initialized = false;
|
|
1059
1291
|
}
|
|
1060
1292
|
}
|
|
1293
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1061
1294
|
async addBridgedEndpoint(pluginName, device) {
|
|
1295
|
+
// Nothing to do here
|
|
1062
1296
|
}
|
|
1297
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1063
1298
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1299
|
+
// Nothing to do here
|
|
1064
1300
|
}
|
|
1301
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1065
1302
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1303
|
+
// Nothing to do here
|
|
1066
1304
|
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Adds a bridged device to the Matterbridge.
|
|
1307
|
+
* @param pluginName - The name of the plugin.
|
|
1308
|
+
* @param device - The bridged device to add.
|
|
1309
|
+
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1310
|
+
*/
|
|
1067
1311
|
async addBridgedDevice(pluginName, device) {
|
|
1068
1312
|
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1313
|
+
// Check if the plugin is registered
|
|
1069
1314
|
const plugin = this.plugins.get(pluginName);
|
|
1070
1315
|
if (!plugin) {
|
|
1071
1316
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1072
1317
|
return;
|
|
1073
1318
|
}
|
|
1319
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1074
1320
|
if (this.bridgeMode === 'bridge') {
|
|
1075
1321
|
if (!this.matterAggregator) {
|
|
1076
1322
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1078,8 +1324,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1078
1324
|
}
|
|
1079
1325
|
this.matterAggregator.addBridgedDevice(device);
|
|
1080
1326
|
}
|
|
1327
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1328
|
+
// Register and add the device in childbridge mode
|
|
1081
1329
|
if (this.bridgeMode === 'childbridge') {
|
|
1082
1330
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1331
|
+
// Check if the plugin is locked with the commissioning server
|
|
1083
1332
|
if (!plugin.locked) {
|
|
1084
1333
|
plugin.locked = true;
|
|
1085
1334
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1093,6 +1342,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1093
1342
|
}
|
|
1094
1343
|
}
|
|
1095
1344
|
if (plugin.type === 'DynamicPlatform') {
|
|
1345
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1096
1346
|
if (!plugin.locked) {
|
|
1097
1347
|
plugin.locked = true;
|
|
1098
1348
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1113,16 +1363,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1113
1363
|
plugin.registeredDevices++;
|
|
1114
1364
|
if (plugin.addedDevices !== undefined)
|
|
1115
1365
|
plugin.addedDevices++;
|
|
1366
|
+
// Add the device to the DeviceManager
|
|
1116
1367
|
this.devices.set(device);
|
|
1117
1368
|
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}`);
|
|
1118
1369
|
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Removes a bridged device from the Matterbridge.
|
|
1372
|
+
* @param pluginName - The name of the plugin.
|
|
1373
|
+
* @param device - The device to be removed.
|
|
1374
|
+
* @returns A Promise that resolves when the device is successfully removed.
|
|
1375
|
+
*/
|
|
1119
1376
|
async removeBridgedDevice(pluginName, device) {
|
|
1120
1377
|
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1378
|
+
// Check if the plugin is registered
|
|
1121
1379
|
const plugin = this.plugins.get(pluginName);
|
|
1122
1380
|
if (!plugin) {
|
|
1123
1381
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1124
1382
|
return;
|
|
1125
1383
|
}
|
|
1384
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1126
1385
|
if (this.bridgeMode === 'bridge') {
|
|
1127
1386
|
if (!this.matterAggregator) {
|
|
1128
1387
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1132,6 +1391,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1132
1391
|
device.setBridgedDeviceReachability(false);
|
|
1133
1392
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1134
1393
|
}
|
|
1394
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1395
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1135
1396
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1136
1397
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1137
1398
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1139,6 +1400,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1139
1400
|
if (plugin.addedDevices !== undefined)
|
|
1140
1401
|
plugin.addedDevices--;
|
|
1141
1402
|
}
|
|
1403
|
+
// Remove the device in childbridge mode
|
|
1142
1404
|
if (this.bridgeMode === 'childbridge') {
|
|
1143
1405
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1144
1406
|
if (!plugin.commissioningServer) {
|
|
@@ -1162,14 +1424,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1162
1424
|
plugin.registeredDevices--;
|
|
1163
1425
|
if (plugin.addedDevices !== undefined)
|
|
1164
1426
|
plugin.addedDevices--;
|
|
1427
|
+
// Remove the commissioning server
|
|
1165
1428
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1166
1429
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1167
1430
|
plugin.commissioningServer = undefined;
|
|
1168
1431
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1169
1432
|
}
|
|
1170
1433
|
}
|
|
1434
|
+
// Remove the device from the DeviceManager
|
|
1171
1435
|
this.devices.remove(device);
|
|
1172
1436
|
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
1439
|
+
*
|
|
1440
|
+
* @param pluginName - The name of the plugin.
|
|
1441
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
1442
|
+
*/
|
|
1173
1443
|
async removeAllBridgedDevices(pluginName) {
|
|
1174
1444
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1175
1445
|
this.devices.forEach(async (device) => {
|
|
@@ -1178,7 +1448,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1178
1448
|
}
|
|
1179
1449
|
});
|
|
1180
1450
|
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Starts the Matterbridge in bridge mode.
|
|
1453
|
+
* @private
|
|
1454
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1455
|
+
*/
|
|
1181
1456
|
async startBridge() {
|
|
1457
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1182
1458
|
if (!this.storageManager)
|
|
1183
1459
|
throw new Error('No storage manager initialized');
|
|
1184
1460
|
if (!this.matterbridgeContext)
|
|
@@ -1197,6 +1473,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1197
1473
|
let failCount = 0;
|
|
1198
1474
|
this.startMatterInterval = setInterval(async () => {
|
|
1199
1475
|
for (const plugin of this.plugins) {
|
|
1476
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1200
1477
|
if (!plugin.enabled)
|
|
1201
1478
|
continue;
|
|
1202
1479
|
if (plugin.error) {
|
|
@@ -1221,15 +1498,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1221
1498
|
clearInterval(this.startMatterInterval);
|
|
1222
1499
|
this.startMatterInterval = undefined;
|
|
1223
1500
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1501
|
+
// Start the Matter server
|
|
1224
1502
|
await this.startMatterServer();
|
|
1225
1503
|
this.log.notice('Matter server started');
|
|
1504
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1226
1505
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1506
|
+
// Configure the plugins
|
|
1227
1507
|
this.configureTimeout = setTimeout(async () => {
|
|
1228
1508
|
for (const plugin of this.plugins) {
|
|
1229
1509
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1230
1510
|
continue;
|
|
1231
1511
|
try {
|
|
1232
|
-
await this.plugins.configure(plugin);
|
|
1512
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1233
1513
|
}
|
|
1234
1514
|
catch (error) {
|
|
1235
1515
|
plugin.error = true;
|
|
@@ -1238,6 +1518,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1238
1518
|
}
|
|
1239
1519
|
this.wssSendRefreshRequired();
|
|
1240
1520
|
}, 30 * 1000);
|
|
1521
|
+
// Setting reachability to true
|
|
1241
1522
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1242
1523
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1243
1524
|
if (this.commissioningServer)
|
|
@@ -1247,7 +1528,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1247
1528
|
}, 60 * 1000);
|
|
1248
1529
|
}, 1000);
|
|
1249
1530
|
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1533
|
+
* @private
|
|
1534
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1535
|
+
*/
|
|
1250
1536
|
async startChildbridge() {
|
|
1537
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1538
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1251
1539
|
if (!this.storageManager)
|
|
1252
1540
|
throw new Error('No storage manager initialized');
|
|
1253
1541
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
@@ -1257,6 +1545,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1257
1545
|
this.startMatterInterval = setInterval(async () => {
|
|
1258
1546
|
let allStarted = true;
|
|
1259
1547
|
for (const plugin of this.plugins) {
|
|
1548
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1260
1549
|
if (!plugin.enabled)
|
|
1261
1550
|
continue;
|
|
1262
1551
|
if (plugin.error) {
|
|
@@ -1284,14 +1573,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1284
1573
|
clearInterval(this.startMatterInterval);
|
|
1285
1574
|
this.startMatterInterval = undefined;
|
|
1286
1575
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1576
|
+
// Start the Matter server
|
|
1287
1577
|
await this.startMatterServer();
|
|
1288
1578
|
this.log.notice('Matter server started');
|
|
1579
|
+
// Configure the plugins
|
|
1289
1580
|
this.configureTimeout = setTimeout(async () => {
|
|
1290
1581
|
for (const plugin of this.plugins) {
|
|
1291
1582
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1292
1583
|
continue;
|
|
1293
1584
|
try {
|
|
1294
|
-
await this.plugins.configure(plugin);
|
|
1585
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1295
1586
|
}
|
|
1296
1587
|
catch (error) {
|
|
1297
1588
|
plugin.error = true;
|
|
@@ -1320,6 +1611,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1320
1611
|
continue;
|
|
1321
1612
|
}
|
|
1322
1613
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1614
|
+
// Setting reachability to true
|
|
1323
1615
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1324
1616
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1325
1617
|
if (plugin.commissioningServer)
|
|
@@ -1332,6 +1624,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1332
1624
|
}
|
|
1333
1625
|
}, 1000);
|
|
1334
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Starts the Matterbridge controller.
|
|
1629
|
+
* @private
|
|
1630
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1631
|
+
*/
|
|
1335
1632
|
async startController() {
|
|
1336
1633
|
if (!this.storageManager) {
|
|
1337
1634
|
this.log.error('No storage manager initialized');
|
|
@@ -1394,7 +1691,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1394
1691
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1395
1692
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1396
1693
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1397
|
-
}
|
|
1694
|
+
} // (hasParameter('pairingcode'))
|
|
1398
1695
|
if (hasParameter('unpairall')) {
|
|
1399
1696
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1400
1697
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1405,6 +1702,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1405
1702
|
return;
|
|
1406
1703
|
}
|
|
1407
1704
|
if (hasParameter('discover')) {
|
|
1705
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1706
|
+
// console.log(discover);
|
|
1408
1707
|
}
|
|
1409
1708
|
if (!this.commissioningController.isCommissioned()) {
|
|
1410
1709
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1445,10 +1744,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1445
1744
|
},
|
|
1446
1745
|
});
|
|
1447
1746
|
node.logStructure();
|
|
1747
|
+
// Get the interaction client
|
|
1448
1748
|
this.log.info('Getting the interaction client');
|
|
1449
1749
|
const interactionClient = await node.getInteractionClient();
|
|
1450
1750
|
let cluster;
|
|
1451
1751
|
let attributes;
|
|
1752
|
+
// Log BasicInformationCluster
|
|
1452
1753
|
cluster = BasicInformationCluster;
|
|
1453
1754
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1454
1755
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1458,6 +1759,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1458
1759
|
attributes.forEach((attribute) => {
|
|
1459
1760
|
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}`);
|
|
1460
1761
|
});
|
|
1762
|
+
// Log PowerSourceCluster
|
|
1461
1763
|
cluster = PowerSourceCluster;
|
|
1462
1764
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1463
1765
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1467,6 +1769,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1467
1769
|
attributes.forEach((attribute) => {
|
|
1468
1770
|
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}`);
|
|
1469
1771
|
});
|
|
1772
|
+
// Log ThreadNetworkDiagnostics
|
|
1470
1773
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1471
1774
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1472
1775
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1476,6 +1779,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
1779
|
attributes.forEach((attribute) => {
|
|
1477
1780
|
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}`);
|
|
1478
1781
|
});
|
|
1782
|
+
// Log SwitchCluster
|
|
1479
1783
|
cluster = SwitchCluster;
|
|
1480
1784
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1481
1785
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1496,6 +1800,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1496
1800
|
this.log.info('Subscribed to all attributes and events');
|
|
1497
1801
|
}
|
|
1498
1802
|
}
|
|
1803
|
+
/** ***********************************************************************************************************************************/
|
|
1804
|
+
/** Matter.js methods */
|
|
1805
|
+
/** ***********************************************************************************************************************************/
|
|
1806
|
+
/**
|
|
1807
|
+
* Starts the matter storage process based on the specified storage type and name.
|
|
1808
|
+
* @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
|
|
1809
|
+
* @param {string} storageName - The name of the storage file.
|
|
1810
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1811
|
+
*/
|
|
1499
1812
|
async startMatterStorage(storageType, storageName) {
|
|
1500
1813
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1501
1814
|
if (storageType === 'disk') {
|
|
@@ -1541,6 +1854,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1541
1854
|
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
1542
1855
|
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
1543
1856
|
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Makes a backup copy of the specified matter JSON storage file.
|
|
1859
|
+
*
|
|
1860
|
+
* @param storageName - The name of the JSON storage file to be backed up.
|
|
1861
|
+
* @param backupName - The name of the backup file to be created.
|
|
1862
|
+
*/
|
|
1544
1863
|
async backupMatterStorage(storageName, backupName) {
|
|
1545
1864
|
try {
|
|
1546
1865
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1561,6 +1880,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1561
1880
|
}
|
|
1562
1881
|
}
|
|
1563
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Restore the specified matter JSON storage file.
|
|
1885
|
+
*
|
|
1886
|
+
* @param backupName - The name of the backup file to restore from.
|
|
1887
|
+
* @param storageName - The name of the JSON storage file to restored.
|
|
1888
|
+
*/
|
|
1564
1889
|
async restoreMatterStorage(backupName, storageName) {
|
|
1565
1890
|
try {
|
|
1566
1891
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1581,6 +1906,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1581
1906
|
}
|
|
1582
1907
|
}
|
|
1583
1908
|
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Stops the matter storage.
|
|
1911
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1912
|
+
*/
|
|
1584
1913
|
async stopMatterStorage() {
|
|
1585
1914
|
this.log.debug('Stopping storage');
|
|
1586
1915
|
await this.storageManager?.close();
|
|
@@ -1589,8 +1918,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1589
1918
|
this.matterbridgeContext = undefined;
|
|
1590
1919
|
this.mattercontrollerContext = undefined;
|
|
1591
1920
|
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
1923
|
+
* @param storageManager The storage manager to be used by the Matter server.
|
|
1924
|
+
*
|
|
1925
|
+
*/
|
|
1592
1926
|
createMatterServer(storageManager) {
|
|
1593
1927
|
this.log.debug('Creating matter server');
|
|
1928
|
+
// Validate mdnsInterface
|
|
1594
1929
|
if (this.mdnsInterface) {
|
|
1595
1930
|
const networkInterfaces = os.networkInterfaces();
|
|
1596
1931
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1606,6 +1941,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1606
1941
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1607
1942
|
return matterServer;
|
|
1608
1943
|
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Starts the Matter server.
|
|
1946
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
1947
|
+
*/
|
|
1609
1948
|
async startMatterServer() {
|
|
1610
1949
|
if (!this.matterServer) {
|
|
1611
1950
|
this.log.error('No matter server initialized');
|
|
@@ -1615,7 +1954,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
1954
|
this.log.debug('Starting matter server...');
|
|
1616
1955
|
await this.matterServer.start();
|
|
1617
1956
|
this.log.debug('Started matter server');
|
|
1957
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1618
1958
|
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
1961
|
+
*/
|
|
1619
1962
|
async stopMatterServer() {
|
|
1620
1963
|
this.log.debug('Stopping matter commissioningServer');
|
|
1621
1964
|
await this.commissioningServer?.close();
|
|
@@ -1629,22 +1972,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1629
1972
|
this.matterAggregator = undefined;
|
|
1630
1973
|
this.matterServer = undefined;
|
|
1631
1974
|
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Creates a Matter Aggregator.
|
|
1977
|
+
* @param {StorageContext} context - The storage context.
|
|
1978
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
1979
|
+
*/
|
|
1632
1980
|
async createMatterAggregator(context, pluginName) {
|
|
1981
|
+
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
1633
1982
|
const matterAggregator = new Aggregator();
|
|
1634
1983
|
return matterAggregator;
|
|
1635
1984
|
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Creates a matter commissioning server.
|
|
1987
|
+
*
|
|
1988
|
+
* @param {StorageContext} context - The storage context.
|
|
1989
|
+
* @param {string} pluginName - The name of the commissioning server.
|
|
1990
|
+
* @returns {CommissioningServer} The created commissioning server.
|
|
1991
|
+
*/
|
|
1636
1992
|
async createCommisioningServer(context, pluginName) {
|
|
1637
1993
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1638
1994
|
const deviceName = await context.get('deviceName');
|
|
1639
1995
|
const deviceType = await context.get('deviceType');
|
|
1640
1996
|
const vendorId = await context.get('vendorId');
|
|
1641
|
-
const vendorName = await context.get('vendorName');
|
|
1997
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1642
1998
|
const productId = await context.get('productId');
|
|
1643
|
-
const productName = await context.get('productName');
|
|
1999
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1644
2000
|
const serialNumber = await context.get('serialNumber');
|
|
1645
2001
|
const uniqueId = await context.get('uniqueId');
|
|
1646
2002
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1647
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2003
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1648
2004
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1649
2005
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1650
2006
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
|
|
@@ -1652,6 +2008,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1652
2008
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1653
2009
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1654
2010
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
|
|
2011
|
+
// Validate ipv4address
|
|
1655
2012
|
if (this.ipv4address) {
|
|
1656
2013
|
const networkInterfaces = os.networkInterfaces();
|
|
1657
2014
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1666,6 +2023,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1666
2023
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1667
2024
|
}
|
|
1668
2025
|
}
|
|
2026
|
+
// Validate ipv6address
|
|
1669
2027
|
if (this.ipv6address) {
|
|
1670
2028
|
const networkInterfaces = os.networkInterfaces();
|
|
1671
2029
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1696,7 +2054,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1696
2054
|
nodeLabel: productName,
|
|
1697
2055
|
productLabel: productName,
|
|
1698
2056
|
softwareVersion,
|
|
1699
|
-
softwareVersionString,
|
|
2057
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1700
2058
|
hardwareVersion,
|
|
1701
2059
|
hardwareVersionString,
|
|
1702
2060
|
uniqueId,
|
|
@@ -1792,6 +2150,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1792
2150
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1793
2151
|
return commissioningServer;
|
|
1794
2152
|
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Creates a commissioning server storage context.
|
|
2155
|
+
*
|
|
2156
|
+
* @param pluginName - The name of the plugin.
|
|
2157
|
+
* @param deviceName - The name of the device.
|
|
2158
|
+
* @param deviceType - The type of the device.
|
|
2159
|
+
* @param vendorId - The vendor ID.
|
|
2160
|
+
* @param vendorName - The vendor name.
|
|
2161
|
+
* @param productId - The product ID.
|
|
2162
|
+
* @param productName - The product name.
|
|
2163
|
+
* @param serialNumber - The serial number of the device (optional).
|
|
2164
|
+
* @param uniqueId - The unique ID of the device (optional).
|
|
2165
|
+
* @param softwareVersion - The software version of the device (optional).
|
|
2166
|
+
* @param softwareVersionString - The software version string of the device (optional).
|
|
2167
|
+
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2168
|
+
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2169
|
+
* @returns The storage context for the commissioning server.
|
|
2170
|
+
*/
|
|
1795
2171
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
1796
2172
|
if (!this.storageManager)
|
|
1797
2173
|
throw new Error('No storage manager initialized');
|
|
@@ -1819,6 +2195,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1819
2195
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1820
2196
|
return storageContext;
|
|
1821
2197
|
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Imports the commissioning server context for a specific plugin and device.
|
|
2200
|
+
* @param pluginName - The name of the plugin.
|
|
2201
|
+
* @param device - The MatterbridgeDevice object representing the device.
|
|
2202
|
+
* @returns The commissioning server context.
|
|
2203
|
+
* @throws Error if the BasicInformationCluster is not found.
|
|
2204
|
+
*/
|
|
1822
2205
|
async importCommissioningServerContext(pluginName, device) {
|
|
1823
2206
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
1824
2207
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -1853,6 +2236,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1853
2236
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1854
2237
|
return storageContext;
|
|
1855
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Shows the commissioning server QR code for a given plugin.
|
|
2241
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2242
|
+
* @param {StorageContext} storageContext - The storage context instance.
|
|
2243
|
+
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2244
|
+
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2245
|
+
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2246
|
+
*/
|
|
1856
2247
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
1857
2248
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
1858
2249
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -1863,7 +2254,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1863
2254
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
1864
2255
|
const QrCode = new QrCodeSchema();
|
|
1865
2256
|
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`);
|
|
1866
|
-
|
|
2257
|
+
// eslint-disable-next-line no-console
|
|
2258
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
1867
2259
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
1868
2260
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
1869
2261
|
if (pluginName === 'Matterbridge') {
|
|
@@ -1910,6 +2302,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1910
2302
|
}
|
|
1911
2303
|
this.wssSendRefreshRequired();
|
|
1912
2304
|
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2307
|
+
*
|
|
2308
|
+
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2309
|
+
* @returns An array of sanitized exposed fabric information objects.
|
|
2310
|
+
*/
|
|
1913
2311
|
sanitizeFabricInformations(fabricInfo) {
|
|
1914
2312
|
return fabricInfo.map((info) => {
|
|
1915
2313
|
return {
|
|
@@ -1923,6 +2321,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1923
2321
|
};
|
|
1924
2322
|
});
|
|
1925
2323
|
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Sanitizes the session information by converting bigint properties to string.
|
|
2326
|
+
*
|
|
2327
|
+
* @param sessionInfo - The array of session information objects.
|
|
2328
|
+
* @returns An array of sanitized session information objects.
|
|
2329
|
+
*/
|
|
1926
2330
|
sanitizeSessionInformation(sessionInfo) {
|
|
1927
2331
|
return sessionInfo
|
|
1928
2332
|
.filter((session) => session.isPeerActive)
|
|
@@ -1950,6 +2354,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1950
2354
|
};
|
|
1951
2355
|
});
|
|
1952
2356
|
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Sets the reachability of a commissioning server and trigger.
|
|
2359
|
+
*
|
|
2360
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2361
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2362
|
+
*/
|
|
1953
2363
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
1954
2364
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
1955
2365
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -1957,6 +2367,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1957
2367
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
1958
2368
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1959
2369
|
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2372
|
+
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2373
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2374
|
+
*/
|
|
1960
2375
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
1961
2376
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
1962
2377
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -1969,6 +2384,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1969
2384
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1970
2385
|
});
|
|
1971
2386
|
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Sets the reachability of a device and trigger.
|
|
2389
|
+
*
|
|
2390
|
+
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2391
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2392
|
+
*/
|
|
1972
2393
|
setDeviceReachability(device, reachable) {
|
|
1973
2394
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
1974
2395
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2017,6 +2438,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2017
2438
|
}
|
|
2018
2439
|
return vendorName;
|
|
2019
2440
|
};
|
|
2441
|
+
/**
|
|
2442
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
2443
|
+
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
2444
|
+
*/
|
|
2020
2445
|
async getBaseRegisteredPlugins() {
|
|
2021
2446
|
const baseRegisteredPlugins = [];
|
|
2022
2447
|
for (const plugin of this.plugins) {
|
|
@@ -2048,13 +2473,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2048
2473
|
}
|
|
2049
2474
|
return baseRegisteredPlugins;
|
|
2050
2475
|
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Spawns a child process with the given command and arguments.
|
|
2478
|
+
* @param {string} command - The command to execute.
|
|
2479
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2480
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2481
|
+
*/
|
|
2051
2482
|
async spawnCommand(command, args = []) {
|
|
2483
|
+
/*
|
|
2484
|
+
npm > npm.cmd on windows
|
|
2485
|
+
cmd.exe ['dir'] on windows
|
|
2486
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2487
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2488
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2492
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2493
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2494
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2495
|
+
*/
|
|
2052
2496
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2053
2497
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2498
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2054
2499
|
const argstring = 'npm ' + args.join(' ');
|
|
2055
2500
|
args.splice(0, args.length, '/c', argstring);
|
|
2056
2501
|
command = 'cmd.exe';
|
|
2057
2502
|
}
|
|
2503
|
+
// Decide when using sudo on linux
|
|
2504
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2505
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2058
2506
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2059
2507
|
args.unshift(command);
|
|
2060
2508
|
command = 'sudo';
|
|
@@ -2112,55 +2560,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2112
2560
|
}
|
|
2113
2561
|
});
|
|
2114
2562
|
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Sends a WebSocket message to all connected clients.
|
|
2565
|
+
*
|
|
2566
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2567
|
+
* @param {string} time - The time string of the message
|
|
2568
|
+
* @param {string} name - The logger name of the message
|
|
2569
|
+
* @param {string} message - The content of the message.
|
|
2570
|
+
*/
|
|
2115
2571
|
wssSendMessage(level, time, name, message) {
|
|
2116
2572
|
if (!level || !time || !name || !message)
|
|
2117
2573
|
return;
|
|
2574
|
+
// Remove ANSI escape codes from the message
|
|
2575
|
+
// eslint-disable-next-line no-control-regex
|
|
2118
2576
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2577
|
+
// Remove leading asterisks from the message
|
|
2119
2578
|
message = message.replace(/^\*+/, '');
|
|
2579
|
+
// Replace all occurrences of \t and \n
|
|
2120
2580
|
message = message.replace(/[\t\n]/g, '');
|
|
2581
|
+
// Remove non-printable characters
|
|
2582
|
+
// eslint-disable-next-line no-control-regex
|
|
2121
2583
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2584
|
+
// Replace all occurrences of \" with "
|
|
2122
2585
|
message = message.replace(/\\"/g, '"');
|
|
2586
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2123
2587
|
const maxContinuousLength = 100;
|
|
2124
2588
|
const keepStartLength = 20;
|
|
2125
2589
|
const keepEndLength = 20;
|
|
2590
|
+
// Split the message into words
|
|
2126
2591
|
message = message
|
|
2127
2592
|
.split(' ')
|
|
2128
2593
|
.map((word) => {
|
|
2594
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2129
2595
|
if (word.length > maxContinuousLength) {
|
|
2130
2596
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2131
2597
|
}
|
|
2132
2598
|
return word;
|
|
2133
2599
|
})
|
|
2134
2600
|
.join(' ');
|
|
2601
|
+
// Send the message to all connected clients
|
|
2135
2602
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2136
2603
|
if (client.readyState === WebSocket.OPEN) {
|
|
2137
2604
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2138
2605
|
}
|
|
2139
2606
|
});
|
|
2140
2607
|
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2610
|
+
*
|
|
2611
|
+
*/
|
|
2141
2612
|
wssSendRefreshRequired() {
|
|
2142
2613
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2614
|
+
// Send the message to all connected clients
|
|
2143
2615
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2144
2616
|
if (client.readyState === WebSocket.OPEN) {
|
|
2145
2617
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
|
|
2146
2618
|
}
|
|
2147
2619
|
});
|
|
2148
2620
|
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2623
|
+
*
|
|
2624
|
+
*/
|
|
2149
2625
|
wssSendRestartRequired() {
|
|
2150
2626
|
this.matterbridgeInformation.restartRequired = true;
|
|
2627
|
+
// Send the message to all connected clients
|
|
2151
2628
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2152
2629
|
if (client.readyState === WebSocket.OPEN) {
|
|
2153
2630
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
|
|
2154
2631
|
}
|
|
2155
2632
|
});
|
|
2156
2633
|
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Initializes the frontend of Matterbridge.
|
|
2636
|
+
*
|
|
2637
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
2638
|
+
*/
|
|
2157
2639
|
async initializeFrontend(port = 8283) {
|
|
2158
2640
|
let initializeError = false;
|
|
2159
2641
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2642
|
+
// Create the express app that serves the frontend
|
|
2160
2643
|
this.expressApp = express();
|
|
2644
|
+
// Log all requests to the server for debugging
|
|
2645
|
+
/*
|
|
2646
|
+
if (hasParameter('homedir')) {
|
|
2647
|
+
this.expressApp.use((req, res, next) => {
|
|
2648
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
2649
|
+
next();
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
*/
|
|
2653
|
+
// Serve static files from '/static' endpoint
|
|
2161
2654
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2162
2655
|
if (!hasParameter('ssl')) {
|
|
2656
|
+
// Create an HTTP server and attach the express app
|
|
2163
2657
|
this.httpServer = createServer(this.expressApp);
|
|
2658
|
+
// Listen on the specified port
|
|
2164
2659
|
if (hasParameter('ingress')) {
|
|
2165
2660
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2166
2661
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2174,6 +2669,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2174
2669
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2175
2670
|
});
|
|
2176
2671
|
}
|
|
2672
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2177
2673
|
this.httpServer.on('error', (error) => {
|
|
2178
2674
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2179
2675
|
switch (error.code) {
|
|
@@ -2189,6 +2685,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2189
2685
|
});
|
|
2190
2686
|
}
|
|
2191
2687
|
else {
|
|
2688
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2192
2689
|
let cert;
|
|
2193
2690
|
try {
|
|
2194
2691
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2216,7 +2713,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2216
2713
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2217
2714
|
}
|
|
2218
2715
|
const serverOptions = { cert, key, ca };
|
|
2716
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
2219
2717
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
2718
|
+
// Listen on the specified port
|
|
2220
2719
|
if (hasParameter('ingress')) {
|
|
2221
2720
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2222
2721
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2230,6 +2729,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2230
2729
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2231
2730
|
});
|
|
2232
2731
|
}
|
|
2732
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2233
2733
|
this.httpsServer.on('error', (error) => {
|
|
2234
2734
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2235
2735
|
switch (error.code) {
|
|
@@ -2246,12 +2746,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2246
2746
|
}
|
|
2247
2747
|
if (initializeError)
|
|
2248
2748
|
return;
|
|
2749
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2249
2750
|
const wssPort = port;
|
|
2250
2751
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2251
2752
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2252
2753
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2253
2754
|
const clientIp = request.socket.remoteAddress;
|
|
2254
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
2755
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2255
2756
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2256
2757
|
ws.on('message', (message) => {
|
|
2257
2758
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2284,6 +2785,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2284
2785
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2285
2786
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2286
2787
|
});
|
|
2788
|
+
// Endpoint to validate login code
|
|
2287
2789
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2288
2790
|
const { password } = req.body;
|
|
2289
2791
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2302,12 +2804,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2302
2804
|
this.log.warn('/api/login error wrong password');
|
|
2303
2805
|
res.json({ valid: false });
|
|
2304
2806
|
}
|
|
2807
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2305
2808
|
}
|
|
2306
2809
|
catch (error) {
|
|
2307
2810
|
this.log.error('/api/login error getting password');
|
|
2308
2811
|
res.json({ valid: false });
|
|
2309
2812
|
}
|
|
2310
2813
|
});
|
|
2814
|
+
// Endpoint to provide settings
|
|
2311
2815
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2312
2816
|
this.log.debug('The frontend sent /api/settings');
|
|
2313
2817
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2328,13 +2832,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2328
2832
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2329
2833
|
this.matterbridgeInformation.profile = this.profile;
|
|
2330
2834
|
const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2835
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2331
2836
|
res.json(response);
|
|
2332
2837
|
});
|
|
2838
|
+
// Endpoint to provide plugins
|
|
2333
2839
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2334
2840
|
this.log.debug('The frontend sent /api/plugins');
|
|
2335
2841
|
const response = await this.getBaseRegisteredPlugins();
|
|
2842
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2336
2843
|
res.json(response);
|
|
2337
2844
|
});
|
|
2845
|
+
// Endpoint to provide devices
|
|
2338
2846
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2339
2847
|
this.log.debug('The frontend sent /api/devices');
|
|
2340
2848
|
const devices = [];
|
|
@@ -2367,8 +2875,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2367
2875
|
cluster: cluster,
|
|
2368
2876
|
});
|
|
2369
2877
|
});
|
|
2878
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2370
2879
|
res.json(devices);
|
|
2371
2880
|
});
|
|
2881
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2372
2882
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2373
2883
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2374
2884
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2388,6 +2898,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2388
2898
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2389
2899
|
if (clusterServer.name === 'EveHistory')
|
|
2390
2900
|
return;
|
|
2901
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2391
2902
|
let attributeValue;
|
|
2392
2903
|
try {
|
|
2393
2904
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2398,6 +2909,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2398
2909
|
catch (error) {
|
|
2399
2910
|
attributeValue = 'Fabric-Scoped';
|
|
2400
2911
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
2912
|
+
// console.log(error);
|
|
2401
2913
|
}
|
|
2402
2914
|
data.push({
|
|
2403
2915
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2410,12 +2922,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2410
2922
|
});
|
|
2411
2923
|
});
|
|
2412
2924
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
2925
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2413
2926
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2414
2927
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2415
2928
|
clusterServers.forEach((clusterServer) => {
|
|
2416
2929
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2417
2930
|
if (clusterServer.name === 'EveHistory')
|
|
2418
2931
|
return;
|
|
2932
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2419
2933
|
let attributeValue;
|
|
2420
2934
|
try {
|
|
2421
2935
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2426,6 +2940,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2426
2940
|
catch (error) {
|
|
2427
2941
|
attributeValue = 'Unavailable';
|
|
2428
2942
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
2943
|
+
// console.log(error);
|
|
2429
2944
|
}
|
|
2430
2945
|
data.push({
|
|
2431
2946
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2442,6 +2957,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2442
2957
|
});
|
|
2443
2958
|
res.json(data);
|
|
2444
2959
|
});
|
|
2960
|
+
// Endpoint to view the log
|
|
2445
2961
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2446
2962
|
this.log.debug('The frontend sent /api/log');
|
|
2447
2963
|
try {
|
|
@@ -2454,10 +2970,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2454
2970
|
res.status(500).send('Error reading log file');
|
|
2455
2971
|
}
|
|
2456
2972
|
});
|
|
2973
|
+
// Endpoint to download the matterbridge log
|
|
2457
2974
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2458
2975
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2459
2976
|
try {
|
|
2460
2977
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
2978
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2461
2979
|
}
|
|
2462
2980
|
catch (error) {
|
|
2463
2981
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2469,10 +2987,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2469
2987
|
}
|
|
2470
2988
|
});
|
|
2471
2989
|
});
|
|
2990
|
+
// Endpoint to download the matter log
|
|
2472
2991
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2473
2992
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2474
2993
|
try {
|
|
2475
2994
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
2995
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2476
2996
|
}
|
|
2477
2997
|
catch (error) {
|
|
2478
2998
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2484,6 +3004,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2484
3004
|
}
|
|
2485
3005
|
});
|
|
2486
3006
|
});
|
|
3007
|
+
// Endpoint to download the matter storage file
|
|
2487
3008
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2488
3009
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2489
3010
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2493,6 +3014,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2493
3014
|
}
|
|
2494
3015
|
});
|
|
2495
3016
|
});
|
|
3017
|
+
// Endpoint to download the matterbridge storage directory
|
|
2496
3018
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2497
3019
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2498
3020
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2503,6 +3025,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2503
3025
|
}
|
|
2504
3026
|
});
|
|
2505
3027
|
});
|
|
3028
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2506
3029
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2507
3030
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2508
3031
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2513,9 +3036,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2513
3036
|
}
|
|
2514
3037
|
});
|
|
2515
3038
|
});
|
|
3039
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2516
3040
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2517
3041
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2518
3042
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3043
|
+
// 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')));
|
|
2519
3044
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2520
3045
|
if (error) {
|
|
2521
3046
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2523,6 +3048,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2523
3048
|
}
|
|
2524
3049
|
});
|
|
2525
3050
|
});
|
|
3051
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2526
3052
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2527
3053
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2528
3054
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2532,6 +3058,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2532
3058
|
}
|
|
2533
3059
|
});
|
|
2534
3060
|
});
|
|
3061
|
+
// Endpoint to receive commands
|
|
2535
3062
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2536
3063
|
const command = req.params.command;
|
|
2537
3064
|
let param = req.params.param;
|
|
@@ -2541,13 +3068,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2541
3068
|
return;
|
|
2542
3069
|
}
|
|
2543
3070
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3071
|
+
// Handle the command setpassword from Settings
|
|
2544
3072
|
if (command === 'setpassword') {
|
|
2545
|
-
const password = param.slice(1, -1);
|
|
3073
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2546
3074
|
this.log.debug('setpassword', param, password);
|
|
2547
3075
|
await this.nodeContext?.set('password', password);
|
|
2548
3076
|
res.json({ message: 'Command received' });
|
|
2549
3077
|
return;
|
|
2550
3078
|
}
|
|
3079
|
+
// Handle the command setbridgemode from Settings
|
|
2551
3080
|
if (command === 'setbridgemode') {
|
|
2552
3081
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2553
3082
|
this.wssSendRestartRequired();
|
|
@@ -2555,6 +3084,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2555
3084
|
res.json({ message: 'Command received' });
|
|
2556
3085
|
return;
|
|
2557
3086
|
}
|
|
3087
|
+
// Handle the command backup from Settings
|
|
2558
3088
|
if (command === 'backup') {
|
|
2559
3089
|
this.log.notice(`Prepairing the backup...`);
|
|
2560
3090
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2562,25 +3092,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2562
3092
|
res.json({ message: 'Command received' });
|
|
2563
3093
|
return;
|
|
2564
3094
|
}
|
|
3095
|
+
// Handle the command setmbloglevel from Settings
|
|
2565
3096
|
if (command === 'setmbloglevel') {
|
|
2566
3097
|
this.log.debug('Matterbridge log level:', param);
|
|
2567
3098
|
if (param === 'Debug') {
|
|
2568
|
-
this.log.logLevel = "debug"
|
|
3099
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2569
3100
|
}
|
|
2570
3101
|
else if (param === 'Info') {
|
|
2571
|
-
this.log.logLevel = "info"
|
|
3102
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2572
3103
|
}
|
|
2573
3104
|
else if (param === 'Notice') {
|
|
2574
|
-
this.log.logLevel = "notice"
|
|
3105
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2575
3106
|
}
|
|
2576
3107
|
else if (param === 'Warn') {
|
|
2577
|
-
this.log.logLevel = "warn"
|
|
3108
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2578
3109
|
}
|
|
2579
3110
|
else if (param === 'Error') {
|
|
2580
|
-
this.log.logLevel = "error"
|
|
3111
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2581
3112
|
}
|
|
2582
3113
|
else if (param === 'Fatal') {
|
|
2583
|
-
this.log.logLevel = "fatal"
|
|
3114
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2584
3115
|
}
|
|
2585
3116
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2586
3117
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2588,12 +3119,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2588
3119
|
for (const plugin of this.plugins) {
|
|
2589
3120
|
if (!plugin.platform || !plugin.platform.config)
|
|
2590
3121
|
continue;
|
|
2591
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2592
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
3122
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3123
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
2593
3124
|
}
|
|
2594
3125
|
res.json({ message: 'Command received' });
|
|
2595
3126
|
return;
|
|
2596
3127
|
}
|
|
3128
|
+
// Handle the command setmbloglevel from Settings
|
|
2597
3129
|
if (command === 'setmjloglevel') {
|
|
2598
3130
|
this.log.debug('Matter.js log level:', param);
|
|
2599
3131
|
if (param === 'Debug') {
|
|
@@ -2618,30 +3150,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2618
3150
|
res.json({ message: 'Command received' });
|
|
2619
3151
|
return;
|
|
2620
3152
|
}
|
|
3153
|
+
// Handle the command setmdnsinterface from Settings
|
|
2621
3154
|
if (command === 'setmdnsinterface') {
|
|
2622
|
-
param = param.slice(1, -1);
|
|
3155
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2623
3156
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2624
3157
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2625
3158
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2626
3159
|
res.json({ message: 'Command received' });
|
|
2627
3160
|
return;
|
|
2628
3161
|
}
|
|
3162
|
+
// Handle the command setipv4address from Settings
|
|
2629
3163
|
if (command === 'setipv4address') {
|
|
2630
|
-
param = param.slice(1, -1);
|
|
3164
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2631
3165
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2632
3166
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2633
3167
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2634
3168
|
res.json({ message: 'Command received' });
|
|
2635
3169
|
return;
|
|
2636
3170
|
}
|
|
3171
|
+
// Handle the command setipv6address from Settings
|
|
2637
3172
|
if (command === 'setipv6address') {
|
|
2638
|
-
param = param.slice(1, -1);
|
|
3173
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2639
3174
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2640
3175
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2641
3176
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2642
3177
|
res.json({ message: 'Command received' });
|
|
2643
3178
|
return;
|
|
2644
3179
|
}
|
|
3180
|
+
// Handle the command setmatterport from Settings
|
|
2645
3181
|
if (command === 'setmatterport') {
|
|
2646
3182
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2647
3183
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2650,6 +3186,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2650
3186
|
res.json({ message: 'Command received' });
|
|
2651
3187
|
return;
|
|
2652
3188
|
}
|
|
3189
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2653
3190
|
if (command === 'setmatterdiscriminator') {
|
|
2654
3191
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2655
3192
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2658,6 +3195,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2658
3195
|
res.json({ message: 'Command received' });
|
|
2659
3196
|
return;
|
|
2660
3197
|
}
|
|
3198
|
+
// Handle the command setmatterpasscode from Settings
|
|
2661
3199
|
if (command === 'setmatterpasscode') {
|
|
2662
3200
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2663
3201
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2666,17 +3204,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2666
3204
|
res.json({ message: 'Command received' });
|
|
2667
3205
|
return;
|
|
2668
3206
|
}
|
|
3207
|
+
// Handle the command setmbloglevel from Settings
|
|
2669
3208
|
if (command === 'setmblogfile') {
|
|
2670
3209
|
this.log.debug('Matterbridge file log:', param);
|
|
2671
3210
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2672
3211
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3212
|
+
// Create the file logger for matterbridge
|
|
2673
3213
|
if (param === 'true')
|
|
2674
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3214
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2675
3215
|
else
|
|
2676
3216
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2677
3217
|
res.json({ message: 'Command received' });
|
|
2678
3218
|
return;
|
|
2679
3219
|
}
|
|
3220
|
+
// Handle the command setmbloglevel from Settings
|
|
2680
3221
|
if (command === 'setmjlogfile') {
|
|
2681
3222
|
this.log.debug('Matter file log:', param);
|
|
2682
3223
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2703,36 +3244,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2703
3244
|
res.json({ message: 'Command received' });
|
|
2704
3245
|
return;
|
|
2705
3246
|
}
|
|
3247
|
+
// Handle the command unregister from Settings
|
|
2706
3248
|
if (command === 'unregister') {
|
|
2707
3249
|
await this.unregisterAndShutdownProcess();
|
|
2708
3250
|
res.json({ message: 'Command received' });
|
|
2709
3251
|
return;
|
|
2710
3252
|
}
|
|
3253
|
+
// Handle the command reset from Settings
|
|
2711
3254
|
if (command === 'reset') {
|
|
2712
3255
|
await this.shutdownProcessAndReset();
|
|
2713
3256
|
res.json({ message: 'Command received' });
|
|
2714
3257
|
return;
|
|
2715
3258
|
}
|
|
3259
|
+
// Handle the command factoryreset from Settings
|
|
2716
3260
|
if (command === 'factoryreset') {
|
|
2717
3261
|
await this.shutdownProcessAndFactoryReset();
|
|
2718
3262
|
res.json({ message: 'Command received' });
|
|
2719
3263
|
return;
|
|
2720
3264
|
}
|
|
3265
|
+
// Handle the command shutdown from Header
|
|
2721
3266
|
if (command === 'shutdown') {
|
|
2722
3267
|
await this.shutdownProcess();
|
|
2723
3268
|
res.json({ message: 'Command received' });
|
|
2724
3269
|
return;
|
|
2725
3270
|
}
|
|
3271
|
+
// Handle the command restart from Header
|
|
2726
3272
|
if (command === 'restart') {
|
|
2727
3273
|
await this.restartProcess();
|
|
2728
3274
|
res.json({ message: 'Command received' });
|
|
2729
3275
|
return;
|
|
2730
3276
|
}
|
|
3277
|
+
// Handle the command update from Header
|
|
2731
3278
|
if (command === 'update') {
|
|
2732
3279
|
this.log.info('Updating matterbridge...');
|
|
2733
3280
|
try {
|
|
2734
3281
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2735
3282
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3283
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2736
3284
|
}
|
|
2737
3285
|
catch (error) {
|
|
2738
3286
|
this.log.error('Error updating matterbridge');
|
|
@@ -2742,9 +3290,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2742
3290
|
res.json({ message: 'Command received' });
|
|
2743
3291
|
return;
|
|
2744
3292
|
}
|
|
3293
|
+
// Handle the command saveconfig from Home
|
|
2745
3294
|
if (command === 'saveconfig') {
|
|
2746
3295
|
param = param.replace(/\*/g, '\\');
|
|
2747
3296
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3297
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2748
3298
|
if (!this.plugins.has(param)) {
|
|
2749
3299
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2750
3300
|
}
|
|
@@ -2758,33 +3308,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2758
3308
|
res.json({ message: 'Command received' });
|
|
2759
3309
|
return;
|
|
2760
3310
|
}
|
|
3311
|
+
// Handle the command installplugin from Home
|
|
2761
3312
|
if (command === 'installplugin') {
|
|
2762
3313
|
param = param.replace(/\*/g, '\\');
|
|
2763
3314
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2764
3315
|
try {
|
|
2765
3316
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2766
3317
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3318
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2767
3319
|
}
|
|
2768
3320
|
catch (error) {
|
|
2769
3321
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2770
3322
|
}
|
|
2771
3323
|
this.wssSendRestartRequired();
|
|
2772
3324
|
param = param.split('@')[0];
|
|
3325
|
+
// Also add the plugin to matterbridge so no return!
|
|
2773
3326
|
if (param === 'matterbridge') {
|
|
3327
|
+
// If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
|
|
2774
3328
|
res.json({ message: 'Command received' });
|
|
2775
3329
|
return;
|
|
2776
3330
|
}
|
|
2777
3331
|
}
|
|
3332
|
+
// Handle the command addplugin from Home
|
|
2778
3333
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2779
3334
|
param = param.replace(/\*/g, '\\');
|
|
2780
3335
|
const plugin = await this.plugins.add(param);
|
|
2781
3336
|
if (plugin) {
|
|
2782
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
3337
|
+
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
2783
3338
|
}
|
|
2784
3339
|
res.json({ message: 'Command received' });
|
|
2785
3340
|
this.wssSendRefreshRequired();
|
|
2786
3341
|
return;
|
|
2787
3342
|
}
|
|
3343
|
+
// Handle the command removeplugin from Home
|
|
2788
3344
|
if (command === 'removeplugin') {
|
|
2789
3345
|
if (!this.plugins.has(param)) {
|
|
2790
3346
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2798,6 +3354,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2798
3354
|
this.wssSendRefreshRequired();
|
|
2799
3355
|
return;
|
|
2800
3356
|
}
|
|
3357
|
+
// Handle the command enableplugin from Home
|
|
2801
3358
|
if (command === 'enableplugin') {
|
|
2802
3359
|
if (!this.plugins.has(param)) {
|
|
2803
3360
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2815,13 +3372,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2815
3372
|
plugin.registeredDevices = undefined;
|
|
2816
3373
|
plugin.addedDevices = undefined;
|
|
2817
3374
|
await this.plugins.enable(param);
|
|
2818
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
3375
|
+
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
2819
3376
|
}
|
|
2820
3377
|
}
|
|
2821
3378
|
res.json({ message: 'Command received' });
|
|
2822
3379
|
this.wssSendRefreshRequired();
|
|
2823
3380
|
return;
|
|
2824
3381
|
}
|
|
3382
|
+
// Handle the command disableplugin from Home
|
|
2825
3383
|
if (command === 'disableplugin') {
|
|
2826
3384
|
if (!this.plugins.has(param)) {
|
|
2827
3385
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2838,6 +3396,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2838
3396
|
return;
|
|
2839
3397
|
}
|
|
2840
3398
|
});
|
|
3399
|
+
// Fallback for routing (must be the last route)
|
|
2841
3400
|
this.expressApp.get('*', (req, res) => {
|
|
2842
3401
|
this.log.debug('The frontend sent:', req.url);
|
|
2843
3402
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -2845,6 +3404,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2845
3404
|
});
|
|
2846
3405
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
2847
3406
|
}
|
|
3407
|
+
/**
|
|
3408
|
+
* Retrieves the cluster text description from a given device.
|
|
3409
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3410
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3411
|
+
*/
|
|
2848
3412
|
getClusterTextFromDevice(device) {
|
|
2849
3413
|
const stringifyUserLabel = (endpoint) => {
|
|
2850
3414
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -2867,9 +3431,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2867
3431
|
return '';
|
|
2868
3432
|
};
|
|
2869
3433
|
let attributes = '';
|
|
3434
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2870
3435
|
const clusterServers = device.getAllClusterServers();
|
|
2871
3436
|
clusterServers.forEach((clusterServer) => {
|
|
2872
3437
|
try {
|
|
3438
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2873
3439
|
if (clusterServer.name === 'OnOff')
|
|
2874
3440
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
2875
3441
|
if (clusterServer.name === 'Switch')
|
|
@@ -2920,18 +3486,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
2920
3486
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
2921
3487
|
if (clusterServer.name === 'UserLabel')
|
|
2922
3488
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3489
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2923
3490
|
}
|
|
2924
3491
|
catch (error) {
|
|
2925
3492
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
2926
3493
|
}
|
|
2927
3494
|
});
|
|
3495
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2928
3496
|
return attributes;
|
|
2929
3497
|
}
|
|
3498
|
+
/**
|
|
3499
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3500
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3501
|
+
*
|
|
3502
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3503
|
+
*/
|
|
2930
3504
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3505
|
+
// Set the bridge mode
|
|
2931
3506
|
this.bridgeMode = 'bridge';
|
|
3507
|
+
// Set the first port to use
|
|
2932
3508
|
this.port = port;
|
|
2933
|
-
|
|
3509
|
+
// Set Matterbridge logger
|
|
3510
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
2934
3511
|
this.log.debug('Matterbridge extension is starting...');
|
|
3512
|
+
// Initialize NodeStorage
|
|
2935
3513
|
this.matterbridgeDirectory = dataPath;
|
|
2936
3514
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
2937
3515
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -2950,10 +3528,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2950
3528
|
};
|
|
2951
3529
|
this.plugins.set(plugin);
|
|
2952
3530
|
this.plugins.saveToStorage();
|
|
3531
|
+
// Log system info and create .matterbridge directory
|
|
2953
3532
|
await this.logNodeAndSystemInfo();
|
|
2954
3533
|
this.matterbridgeDirectory = dataPath;
|
|
3534
|
+
// Set matter.js logger level and format
|
|
2955
3535
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
2956
3536
|
Logger.format = MatterLogFormat.ANSI;
|
|
3537
|
+
// Start the storage and create matterbridgeContext
|
|
2957
3538
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
2958
3539
|
if (!this.storageManager)
|
|
2959
3540
|
return false;
|
|
@@ -2963,7 +3544,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2963
3544
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
2964
3545
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
2965
3546
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
2966
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3547
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
2967
3548
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
2968
3549
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
2969
3550
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -2976,6 +3557,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2976
3557
|
await this.startMatterServer();
|
|
2977
3558
|
this.log.info('Matter server started');
|
|
2978
3559
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3560
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
2979
3561
|
setTimeout(() => {
|
|
2980
3562
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
2981
3563
|
if (this.commissioningServer)
|
|
@@ -2985,14 +3567,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
2985
3567
|
}, 60 * 1000);
|
|
2986
3568
|
return this.commissioningServer.isCommissioned();
|
|
2987
3569
|
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3572
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3573
|
+
*
|
|
3574
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3575
|
+
*/
|
|
2988
3576
|
async stopExtension() {
|
|
3577
|
+
// Closing matter
|
|
2989
3578
|
await this.stopMatterServer();
|
|
3579
|
+
// Clearing the session manager
|
|
3580
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3581
|
+
// Closing storage
|
|
2990
3582
|
await this.stopMatterStorage();
|
|
2991
3583
|
this.log.info('Matter server stopped');
|
|
2992
3584
|
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Checks if the extension is commissioned.
|
|
3587
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3588
|
+
*
|
|
3589
|
+
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3590
|
+
*/
|
|
2993
3591
|
isExtensionCommissioned() {
|
|
2994
3592
|
if (!this.commissioningServer)
|
|
2995
3593
|
return false;
|
|
2996
3594
|
return this.commissioningServer.isCommissioned();
|
|
2997
3595
|
}
|
|
2998
3596
|
}
|
|
3597
|
+
//# sourceMappingURL=matterbridge.js.map
|