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