matterbridge 1.6.8-dev.5 → 1.6.8-dev.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- 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 +473 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +720 -66
- 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 +997 -10
- package/dist/matterbridgeDevice.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +109 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +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 +1121 -12
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +123 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +99 -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 +253 -8
- 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,27 +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, StorageService, Environment } 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';
|
|
26
54
|
import { aggregator } from './matterbridgeDeviceTypes.js';
|
|
55
|
+
// Default colors
|
|
27
56
|
const plg = '\u001B[38;5;33m';
|
|
28
57
|
const dev = '\u001B[38;5;79m';
|
|
29
58
|
const typ = '\u001B[38;5;207m';
|
|
59
|
+
/**
|
|
60
|
+
* Represents the Matterbridge application.
|
|
61
|
+
*/
|
|
30
62
|
export class Matterbridge extends EventEmitter {
|
|
31
63
|
systemInformation = {
|
|
32
64
|
interfaceName: '',
|
|
@@ -63,7 +95,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
63
95
|
edge: hasParameter('edge'),
|
|
64
96
|
readOnly: hasParameter('readonly'),
|
|
65
97
|
profile: getParameter('profile'),
|
|
66
|
-
loggerLevel: "info"
|
|
98
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
67
99
|
fileLogger: false,
|
|
68
100
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
69
101
|
matterFileLogger: false,
|
|
@@ -102,6 +134,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
102
134
|
nodeContext;
|
|
103
135
|
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
104
136
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
|
+
// Cleanup
|
|
105
138
|
hasCleanupStarted = false;
|
|
106
139
|
initialized = false;
|
|
107
140
|
execRunningCount = 0;
|
|
@@ -113,16 +146,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
113
146
|
sigtermHandler;
|
|
114
147
|
exceptionHandler;
|
|
115
148
|
rejectionHandler;
|
|
149
|
+
// Frontend
|
|
116
150
|
expressApp;
|
|
117
151
|
httpServer;
|
|
118
152
|
httpsServer;
|
|
119
153
|
webSocketServer;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
126
161
|
storageManager;
|
|
127
162
|
matterbridgeContext;
|
|
128
163
|
mattercontrollerContext;
|
|
@@ -133,13 +168,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
133
168
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
134
169
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
135
170
|
static instance;
|
|
171
|
+
// We load asyncronously so is private
|
|
136
172
|
constructor() {
|
|
137
173
|
super();
|
|
174
|
+
// Bind the handler to the instance
|
|
138
175
|
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
139
176
|
}
|
|
140
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
|
+
*/
|
|
141
188
|
static async loadInstance(initialize = false) {
|
|
142
189
|
if (!Matterbridge.instance) {
|
|
190
|
+
// eslint-disable-next-line no-console
|
|
143
191
|
if (hasParameter('debug'))
|
|
144
192
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
145
193
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -148,6 +196,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
148
196
|
}
|
|
149
197
|
return Matterbridge.instance;
|
|
150
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Call cleanup().
|
|
201
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
202
|
+
*
|
|
203
|
+
*/
|
|
151
204
|
async destroyInstance() {
|
|
152
205
|
await this.cleanup('destroying instance...', false);
|
|
153
206
|
await waiter('destroying instance...', () => {
|
|
@@ -155,39 +208,60 @@ export class Matterbridge extends EventEmitter {
|
|
|
155
208
|
}, false, 60000, 100, false);
|
|
156
209
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
157
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
|
+
*/
|
|
158
221
|
async initialize() {
|
|
222
|
+
// Set the restart mode
|
|
159
223
|
if (hasParameter('service'))
|
|
160
224
|
this.restartMode = 'service';
|
|
161
225
|
if (hasParameter('docker'))
|
|
162
226
|
this.restartMode = 'docker';
|
|
227
|
+
// Set the matterbridge directory
|
|
163
228
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
164
229
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
165
|
-
|
|
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
|
|
166
233
|
try {
|
|
167
234
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
168
235
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
169
236
|
this.log.debug('Creating node storage context for matterbridge');
|
|
170
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
|
|
171
240
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
172
241
|
for (const key of keys) {
|
|
173
242
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
243
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
244
|
await this.nodeStorage?.storage.get(key);
|
|
175
245
|
}
|
|
176
246
|
const storages = await this.nodeStorage.getStorageNames();
|
|
177
247
|
for (const storage of storages) {
|
|
178
248
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
179
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
|
|
180
252
|
const keys = (await nodeContext?.storage.keys());
|
|
181
253
|
keys.forEach(async (key) => {
|
|
182
254
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
183
255
|
await nodeContext?.get(key);
|
|
184
256
|
});
|
|
185
257
|
}
|
|
258
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
186
259
|
this.log.debug('Creating node storage backup...');
|
|
187
260
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
188
261
|
this.log.debug('Created node storage backup');
|
|
189
262
|
}
|
|
190
263
|
catch (error) {
|
|
264
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
191
265
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
192
266
|
if (hasParameter('norestore')) {
|
|
193
267
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -202,45 +276,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
202
276
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
203
277
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
204
278
|
}
|
|
279
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
205
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)
|
|
206
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)
|
|
207
284
|
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
208
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)
|
|
209
287
|
if (hasParameter('logger')) {
|
|
210
288
|
const level = getParameter('logger');
|
|
211
289
|
if (level === 'debug') {
|
|
212
|
-
this.log.logLevel = "debug"
|
|
290
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
213
291
|
}
|
|
214
292
|
else if (level === 'info') {
|
|
215
|
-
this.log.logLevel = "info"
|
|
293
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
216
294
|
}
|
|
217
295
|
else if (level === 'notice') {
|
|
218
|
-
this.log.logLevel = "notice"
|
|
296
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
219
297
|
}
|
|
220
298
|
else if (level === 'warn') {
|
|
221
|
-
this.log.logLevel = "warn"
|
|
299
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
222
300
|
}
|
|
223
301
|
else if (level === 'error') {
|
|
224
|
-
this.log.logLevel = "error"
|
|
302
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
225
303
|
}
|
|
226
304
|
else if (level === 'fatal') {
|
|
227
|
-
this.log.logLevel = "fatal"
|
|
305
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
228
306
|
}
|
|
229
307
|
else {
|
|
230
308
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
231
|
-
this.log.logLevel = "info"
|
|
309
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
232
310
|
}
|
|
233
311
|
}
|
|
234
312
|
else {
|
|
235
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
313
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
236
314
|
}
|
|
237
315
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
316
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
238
317
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
239
318
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
240
319
|
this.matterbridgeInformation.fileLogger = true;
|
|
241
320
|
}
|
|
242
321
|
this.log.notice('Matterbridge is starting...');
|
|
243
322
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
323
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
244
324
|
if (hasParameter('matterlogger')) {
|
|
245
325
|
const level = getParameter('matterlogger');
|
|
246
326
|
if (level === 'debug') {
|
|
@@ -271,6 +351,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
271
351
|
}
|
|
272
352
|
Logger.format = MatterLogFormat.ANSI;
|
|
273
353
|
Logger.setLogger('default', this.createMatterLogger());
|
|
354
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
274
355
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
275
356
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
276
357
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -279,6 +360,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
279
360
|
});
|
|
280
361
|
}
|
|
281
362
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
363
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
282
364
|
if (hasParameter('mdnsinterface')) {
|
|
283
365
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
284
366
|
}
|
|
@@ -287,6 +369,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
287
369
|
if (this.mdnsInterface === '')
|
|
288
370
|
this.mdnsInterface = undefined;
|
|
289
371
|
}
|
|
372
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
290
373
|
if (hasParameter('ipv4address')) {
|
|
291
374
|
this.ipv4address = getParameter('ipv4address');
|
|
292
375
|
}
|
|
@@ -295,6 +378,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
295
378
|
if (this.ipv4address === '')
|
|
296
379
|
this.ipv4address = undefined;
|
|
297
380
|
}
|
|
381
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
298
382
|
if (hasParameter('ipv6address')) {
|
|
299
383
|
this.ipv6address = getParameter('ipv6address');
|
|
300
384
|
}
|
|
@@ -303,17 +387,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
303
387
|
if (this.ipv6address === '')
|
|
304
388
|
this.ipv6address = undefined;
|
|
305
389
|
}
|
|
390
|
+
// Initialize PluginManager
|
|
306
391
|
this.plugins = new PluginManager(this);
|
|
307
392
|
await this.plugins.loadFromStorage();
|
|
393
|
+
// Initialize DeviceManager
|
|
308
394
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
395
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
309
396
|
for (const plugin of this.plugins) {
|
|
310
397
|
const packageJson = await this.plugins.parse(plugin);
|
|
311
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
|
|
312
401
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
313
402
|
try {
|
|
314
403
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
315
404
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
316
405
|
plugin.error = false;
|
|
406
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
317
407
|
}
|
|
318
408
|
catch (error) {
|
|
319
409
|
plugin.error = true;
|
|
@@ -330,6 +420,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
330
420
|
await plugin.nodeContext.set('description', plugin.description);
|
|
331
421
|
await plugin.nodeContext.set('author', plugin.author);
|
|
332
422
|
}
|
|
423
|
+
// Log system info and create .matterbridge directory
|
|
333
424
|
await this.logNodeAndSystemInfo();
|
|
334
425
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
335
426
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -337,6 +428,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
337
428
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
338
429
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
339
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
|
|
340
432
|
const minNodeVersion = 18;
|
|
341
433
|
const nodeVersion = process.versions.node;
|
|
342
434
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -344,10 +436,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
344
436
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
345
437
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
346
438
|
}
|
|
439
|
+
// Register process handlers
|
|
347
440
|
this.registerProcessHandlers();
|
|
441
|
+
// Parse command line
|
|
348
442
|
await this.parseCommandLine();
|
|
349
443
|
this.initialized = true;
|
|
350
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
|
+
*/
|
|
351
450
|
async parseCommandLine() {
|
|
352
451
|
if (hasParameter('help')) {
|
|
353
452
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -455,12 +554,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
455
554
|
}
|
|
456
555
|
if (hasParameter('factoryreset')) {
|
|
457
556
|
try {
|
|
557
|
+
// Delete matter storage file
|
|
458
558
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
459
559
|
}
|
|
460
560
|
catch (err) {
|
|
461
561
|
this.log.error(`Error deleting storage: ${err}`);
|
|
462
562
|
}
|
|
463
563
|
try {
|
|
564
|
+
// Delete node storage directory with its subdirectories
|
|
464
565
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
465
566
|
}
|
|
466
567
|
catch (err) {
|
|
@@ -474,6 +575,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
474
575
|
this.emit('shutdown');
|
|
475
576
|
return;
|
|
476
577
|
}
|
|
578
|
+
// Start the matter storage and create the matterbridge context
|
|
477
579
|
try {
|
|
478
580
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
479
581
|
}
|
|
@@ -508,28 +610,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
508
610
|
this.emit('shutdown');
|
|
509
611
|
return;
|
|
510
612
|
}
|
|
613
|
+
// Initialize frontend
|
|
511
614
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
512
615
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
616
|
+
// Check each 60 minutes the latest versions
|
|
513
617
|
this.checkUpdateInterval = setInterval(() => {
|
|
514
618
|
this.getMatterbridgeLatestVersion();
|
|
515
619
|
for (const plugin of this.plugins) {
|
|
516
620
|
this.getPluginLatestVersion(plugin);
|
|
517
621
|
}
|
|
518
622
|
}, 60 * 60 * 1000);
|
|
623
|
+
// Start the matterbridge in mode test
|
|
519
624
|
if (hasParameter('test')) {
|
|
520
625
|
this.bridgeMode = 'bridge';
|
|
521
626
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
522
627
|
return;
|
|
523
628
|
}
|
|
629
|
+
// Start the matterbridge in mode controller
|
|
524
630
|
if (hasParameter('controller')) {
|
|
525
631
|
this.bridgeMode = 'controller';
|
|
526
632
|
await this.startController();
|
|
527
633
|
return;
|
|
528
634
|
}
|
|
635
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
529
636
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
530
637
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
531
638
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
532
639
|
}
|
|
640
|
+
// Start matterbridge in bridge mode
|
|
533
641
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
534
642
|
this.bridgeMode = 'bridge';
|
|
535
643
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -538,6 +646,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
538
646
|
await this.startBridge();
|
|
539
647
|
return;
|
|
540
648
|
}
|
|
649
|
+
// Start matterbridge in childbridge mode
|
|
541
650
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
542
651
|
this.bridgeMode = 'childbridge';
|
|
543
652
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -547,17 +656,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
547
656
|
return;
|
|
548
657
|
}
|
|
549
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
|
+
*/
|
|
550
667
|
async startPlugins() {
|
|
668
|
+
// Check, load and start the plugins
|
|
551
669
|
for (const plugin of this.plugins) {
|
|
552
670
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
553
671
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
672
|
+
// Check if the plugin is available
|
|
554
673
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
555
674
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
556
675
|
plugin.enabled = false;
|
|
557
676
|
plugin.error = true;
|
|
558
677
|
continue;
|
|
559
678
|
}
|
|
560
|
-
|
|
679
|
+
// Check if the plugin has a new version
|
|
680
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
561
681
|
if (!plugin.enabled) {
|
|
562
682
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
563
683
|
continue;
|
|
@@ -572,20 +692,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
572
692
|
plugin.addedDevices = undefined;
|
|
573
693
|
plugin.qrPairingCode = undefined;
|
|
574
694
|
plugin.manualPairingCode = undefined;
|
|
575
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
695
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
576
696
|
}
|
|
577
697
|
this.wssSendRefreshRequired();
|
|
578
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
|
+
*/
|
|
579
703
|
registerProcessHandlers() {
|
|
580
704
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
581
705
|
process.removeAllListeners('uncaughtException');
|
|
582
706
|
process.removeAllListeners('unhandledRejection');
|
|
583
707
|
this.exceptionHandler = async (error) => {
|
|
584
708
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
709
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
585
710
|
};
|
|
586
711
|
process.on('uncaughtException', this.exceptionHandler);
|
|
587
712
|
this.rejectionHandler = async (reason, promise) => {
|
|
588
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...');
|
|
589
715
|
};
|
|
590
716
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
591
717
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -598,6 +724,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
598
724
|
};
|
|
599
725
|
process.on('SIGTERM', this.sigtermHandler);
|
|
600
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
729
|
+
*/
|
|
601
730
|
deregisterProcesslHandlers() {
|
|
602
731
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
603
732
|
if (this.exceptionHandler)
|
|
@@ -614,7 +743,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
614
743
|
process.off('SIGTERM', this.sigtermHandler);
|
|
615
744
|
this.sigtermHandler = undefined;
|
|
616
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* Logs the node and system information.
|
|
748
|
+
*/
|
|
617
749
|
async logNodeAndSystemInfo() {
|
|
750
|
+
// IP address information
|
|
618
751
|
const networkInterfaces = os.networkInterfaces();
|
|
619
752
|
this.systemInformation.ipv4Address = '';
|
|
620
753
|
this.systemInformation.ipv6Address = '';
|
|
@@ -634,7 +767,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
634
767
|
this.systemInformation.macAddress = detail.mac;
|
|
635
768
|
}
|
|
636
769
|
}
|
|
637
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
770
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
638
771
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
639
772
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
640
773
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -642,19 +775,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
642
775
|
break;
|
|
643
776
|
}
|
|
644
777
|
}
|
|
778
|
+
// Node information
|
|
645
779
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
646
780
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
647
781
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
648
782
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
783
|
+
// Host system information
|
|
649
784
|
this.systemInformation.hostname = os.hostname();
|
|
650
785
|
this.systemInformation.user = os.userInfo().username;
|
|
651
|
-
this.systemInformation.osType = os.type();
|
|
652
|
-
this.systemInformation.osRelease = os.release();
|
|
653
|
-
this.systemInformation.osPlatform = os.platform();
|
|
654
|
-
this.systemInformation.osArch = os.arch();
|
|
655
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
656
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
657
|
-
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
|
|
658
794
|
this.log.debug('Host System Information:');
|
|
659
795
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
660
796
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -670,15 +806,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
670
806
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
671
807
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
672
808
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
809
|
+
// Home directory
|
|
673
810
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
674
811
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
675
812
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
813
|
+
// Package root directory
|
|
676
814
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
677
815
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
678
816
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
679
817
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
818
|
+
// Global node_modules directory
|
|
680
819
|
if (this.nodeContext)
|
|
681
820
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
821
|
+
// First run of Matterbridge so the node storage is empty
|
|
682
822
|
if (this.globalModulesDirectory === '') {
|
|
683
823
|
try {
|
|
684
824
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -702,6 +842,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
702
842
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
703
843
|
});
|
|
704
844
|
}
|
|
845
|
+
// Create the data directory .matterbridge in the home directory
|
|
705
846
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
706
847
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
707
848
|
try {
|
|
@@ -725,6 +866,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
725
866
|
}
|
|
726
867
|
}
|
|
727
868
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
869
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
728
870
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
729
871
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
730
872
|
try {
|
|
@@ -748,19 +890,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
748
890
|
}
|
|
749
891
|
}
|
|
750
892
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
893
|
+
// Matterbridge version
|
|
751
894
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
752
895
|
this.matterbridgeVersion = packageJson.version;
|
|
753
896
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
754
897
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
898
|
+
// Matterbridge latest version
|
|
755
899
|
if (this.nodeContext)
|
|
756
900
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
757
901
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
758
902
|
this.getMatterbridgeLatestVersion();
|
|
903
|
+
// Current working directory
|
|
759
904
|
const currentDir = process.cwd();
|
|
760
905
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
906
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
761
907
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
762
908
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
763
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
|
+
*/
|
|
764
915
|
async getLatestVersion(packageName) {
|
|
765
916
|
return new Promise((resolve, reject) => {
|
|
766
917
|
this.execRunningCount++;
|
|
@@ -775,6 +926,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
775
926
|
});
|
|
776
927
|
});
|
|
777
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
|
+
*/
|
|
778
933
|
async getGlobalNodeModules() {
|
|
779
934
|
return new Promise((resolve, reject) => {
|
|
780
935
|
this.execRunningCount++;
|
|
@@ -789,6 +944,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
789
944
|
});
|
|
790
945
|
});
|
|
791
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
|
+
*/
|
|
792
952
|
async getMatterbridgeLatestVersion() {
|
|
793
953
|
this.getLatestVersion('matterbridge')
|
|
794
954
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -805,8 +965,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
805
965
|
})
|
|
806
966
|
.catch((error) => {
|
|
807
967
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
968
|
+
// error.stack && this.log.debug(error.stack);
|
|
808
969
|
});
|
|
809
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
|
+
*/
|
|
810
981
|
async getPluginLatestVersion(plugin) {
|
|
811
982
|
this.getLatestVersion(plugin.name)
|
|
812
983
|
.then(async (latestVersion) => {
|
|
@@ -818,40 +989,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
818
989
|
})
|
|
819
990
|
.catch((error) => {
|
|
820
991
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
992
|
+
// error.stack && this.log.debug(error.stack);
|
|
821
993
|
});
|
|
822
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
|
+
*/
|
|
823
1000
|
createMatterLogger() {
|
|
824
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1001
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
825
1002
|
return (_level, formattedLog) => {
|
|
826
1003
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
827
1004
|
const message = formattedLog.slice(65);
|
|
828
1005
|
matterLogger.logName = logger;
|
|
829
1006
|
switch (_level) {
|
|
830
1007
|
case MatterLogLevel.DEBUG:
|
|
831
|
-
matterLogger.log("debug"
|
|
1008
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
832
1009
|
break;
|
|
833
1010
|
case MatterLogLevel.INFO:
|
|
834
|
-
matterLogger.log("info"
|
|
1011
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
835
1012
|
break;
|
|
836
1013
|
case MatterLogLevel.NOTICE:
|
|
837
|
-
matterLogger.log("notice"
|
|
1014
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
838
1015
|
break;
|
|
839
1016
|
case MatterLogLevel.WARN:
|
|
840
|
-
matterLogger.log("warn"
|
|
1017
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
841
1018
|
break;
|
|
842
1019
|
case MatterLogLevel.ERROR:
|
|
843
|
-
matterLogger.log("error"
|
|
1020
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
844
1021
|
break;
|
|
845
1022
|
case MatterLogLevel.FATAL:
|
|
846
|
-
matterLogger.log("fatal"
|
|
1023
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
847
1024
|
break;
|
|
848
1025
|
default:
|
|
849
|
-
matterLogger.log("debug"
|
|
1026
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
850
1027
|
break;
|
|
851
1028
|
}
|
|
852
1029
|
};
|
|
853
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
|
+
*/
|
|
854
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
|
|
855
1040
|
let fileSize = 0;
|
|
856
1041
|
if (unlink) {
|
|
857
1042
|
try {
|
|
@@ -900,53 +1085,83 @@ export class Matterbridge extends EventEmitter {
|
|
|
900
1085
|
}
|
|
901
1086
|
};
|
|
902
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Update matterbridge and cleanup.
|
|
1090
|
+
*/
|
|
903
1091
|
async updateProcess() {
|
|
904
1092
|
await this.cleanup('updating...', false);
|
|
905
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1096
|
+
*/
|
|
906
1097
|
async restartProcess() {
|
|
907
1098
|
await this.cleanup('restarting...', true);
|
|
908
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Shut down the process by exiting the current process.
|
|
1102
|
+
*/
|
|
909
1103
|
async shutdownProcess() {
|
|
910
1104
|
await this.cleanup('shutting down...', false);
|
|
911
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Shut down the process and reset.
|
|
1108
|
+
*/
|
|
912
1109
|
async unregisterAndShutdownProcess() {
|
|
913
1110
|
this.log.info('Unregistering all devices and shutting down...');
|
|
914
|
-
for (const plugin of this.plugins) {
|
|
1111
|
+
for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
|
|
915
1112
|
await this.removeAllBridgedDevices(plugin.name);
|
|
916
1113
|
}
|
|
917
1114
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
918
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Shut down the process and reset.
|
|
1118
|
+
*/
|
|
919
1119
|
async shutdownProcessAndReset() {
|
|
920
1120
|
await this.cleanup('shutting down with reset...', false);
|
|
921
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Shut down the process and factory reset.
|
|
1124
|
+
*/
|
|
922
1125
|
async shutdownProcessAndFactoryReset() {
|
|
923
1126
|
await this.cleanup('shutting down with factory reset...', false);
|
|
924
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
|
+
*/
|
|
925
1134
|
async cleanup(message, restart = false) {
|
|
926
1135
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
927
1136
|
this.hasCleanupStarted = true;
|
|
928
1137
|
this.log.info(message);
|
|
1138
|
+
// Deregisters the process handlers
|
|
929
1139
|
this.deregisterProcesslHandlers();
|
|
1140
|
+
// Clear the start matter interval
|
|
930
1141
|
if (this.startMatterInterval) {
|
|
931
1142
|
clearInterval(this.startMatterInterval);
|
|
932
1143
|
this.startMatterInterval = undefined;
|
|
933
1144
|
this.log.debug('Start matter interval cleared');
|
|
934
1145
|
}
|
|
1146
|
+
// Clear the check update interval
|
|
935
1147
|
if (this.checkUpdateInterval) {
|
|
936
1148
|
clearInterval(this.checkUpdateInterval);
|
|
937
1149
|
this.checkUpdateInterval = undefined;
|
|
938
1150
|
this.log.debug('Check update interval cleared');
|
|
939
1151
|
}
|
|
1152
|
+
// Clear the configure timeout
|
|
940
1153
|
if (this.configureTimeout) {
|
|
941
1154
|
clearTimeout(this.configureTimeout);
|
|
942
1155
|
this.configureTimeout = undefined;
|
|
943
1156
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
944
1157
|
}
|
|
1158
|
+
// Clear the reachability timeout
|
|
945
1159
|
if (this.reachabilityTimeout) {
|
|
946
1160
|
clearTimeout(this.reachabilityTimeout);
|
|
947
1161
|
this.reachabilityTimeout = undefined;
|
|
948
1162
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
949
1163
|
}
|
|
1164
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
950
1165
|
for (const plugin of this.plugins) {
|
|
951
1166
|
if (!plugin.enabled || plugin.error)
|
|
952
1167
|
continue;
|
|
@@ -957,24 +1172,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
957
1172
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
958
1173
|
}
|
|
959
1174
|
}
|
|
1175
|
+
// Close the http server
|
|
960
1176
|
if (this.httpServer) {
|
|
961
1177
|
this.httpServer.close();
|
|
962
1178
|
this.httpServer.removeAllListeners();
|
|
963
1179
|
this.httpServer = undefined;
|
|
964
1180
|
this.log.debug('Frontend http server closed successfully');
|
|
965
1181
|
}
|
|
1182
|
+
// Close the https server
|
|
966
1183
|
if (this.httpsServer) {
|
|
967
1184
|
this.httpsServer.close();
|
|
968
1185
|
this.httpsServer.removeAllListeners();
|
|
969
1186
|
this.httpsServer = undefined;
|
|
970
1187
|
this.log.debug('Frontend https server closed successfully');
|
|
971
1188
|
}
|
|
1189
|
+
// Remove listeners from the express app
|
|
972
1190
|
if (this.expressApp) {
|
|
973
1191
|
this.expressApp.removeAllListeners();
|
|
974
1192
|
this.expressApp = undefined;
|
|
975
1193
|
this.log.debug('Frontend app closed successfully');
|
|
976
1194
|
}
|
|
1195
|
+
// Close the WebSocket server
|
|
977
1196
|
if (this.webSocketServer) {
|
|
1197
|
+
// Close all active connections
|
|
978
1198
|
this.webSocketServer.clients.forEach((client) => {
|
|
979
1199
|
if (client.readyState === WebSocket.OPEN) {
|
|
980
1200
|
client.close();
|
|
@@ -990,29 +1210,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
990
1210
|
});
|
|
991
1211
|
this.webSocketServer = undefined;
|
|
992
1212
|
}
|
|
993
|
-
|
|
994
|
-
|
|
1213
|
+
// Convert the matter storage to the new format
|
|
1214
|
+
if (hasParameter('convert') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
|
|
1215
|
+
await this.convertStorage(this.matterbridgeContext, 'Mattebridge');
|
|
995
1216
|
}
|
|
1217
|
+
// Closing matter
|
|
996
1218
|
await this.stopMatterServer();
|
|
1219
|
+
// Closing matter storage
|
|
997
1220
|
await this.stopMatterStorage();
|
|
1221
|
+
// Remove the matterfilelogger
|
|
998
1222
|
try {
|
|
999
1223
|
Logger.removeLogger('matterfilelogger');
|
|
1224
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1000
1225
|
}
|
|
1001
1226
|
catch (error) {
|
|
1227
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1002
1228
|
}
|
|
1229
|
+
// Serialize registeredDevices
|
|
1003
1230
|
if (this.nodeStorage && this.nodeContext) {
|
|
1004
1231
|
this.log.info('Saving registered devices...');
|
|
1005
1232
|
const serializedRegisteredDevices = [];
|
|
1006
1233
|
this.devices.forEach(async (device) => {
|
|
1007
1234
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1235
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1008
1236
|
if (serializedMatterbridgeDevice)
|
|
1009
1237
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1010
1238
|
});
|
|
1011
1239
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1012
1240
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1241
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1013
1242
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1014
1243
|
await this.nodeContext.close();
|
|
1015
1244
|
this.nodeContext = undefined;
|
|
1245
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1016
1246
|
for (const plugin of this.plugins) {
|
|
1017
1247
|
if (plugin.nodeContext) {
|
|
1018
1248
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1043,13 +1273,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1043
1273
|
}
|
|
1044
1274
|
else {
|
|
1045
1275
|
if (message === 'shutting down with reset...') {
|
|
1276
|
+
// Delete matter storage file
|
|
1046
1277
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1047
1278
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1048
1279
|
this.log.info('Reset done! Remove all paired devices from the controllers.');
|
|
1049
1280
|
}
|
|
1050
1281
|
if (message === 'shutting down with factory reset...') {
|
|
1282
|
+
// Delete matter storage file
|
|
1051
1283
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1052
1284
|
await fs.unlink(path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
1285
|
+
// Delete node storage directory with its subdirectories
|
|
1053
1286
|
this.log.info('Resetting Matterbridge storage...');
|
|
1054
1287
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1055
1288
|
this.log.info('Factory reset done! Remove all paired devices from the controllers.');
|
|
@@ -1062,19 +1295,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1062
1295
|
this.initialized = false;
|
|
1063
1296
|
}
|
|
1064
1297
|
}
|
|
1298
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1065
1299
|
async addBridgedEndpoint(pluginName, device) {
|
|
1300
|
+
// Nothing to do here
|
|
1066
1301
|
}
|
|
1302
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1067
1303
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1304
|
+
// Nothing to do here
|
|
1068
1305
|
}
|
|
1306
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1069
1307
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1308
|
+
// Nothing to do here
|
|
1070
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Adds a bridged device to the Matterbridge.
|
|
1312
|
+
* @param pluginName - The name of the plugin.
|
|
1313
|
+
* @param device - The bridged device to add.
|
|
1314
|
+
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1315
|
+
*/
|
|
1071
1316
|
async addBridgedDevice(pluginName, device) {
|
|
1072
1317
|
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1318
|
+
// Check if the plugin is registered
|
|
1073
1319
|
const plugin = this.plugins.get(pluginName);
|
|
1074
1320
|
if (!plugin) {
|
|
1075
1321
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1076
1322
|
return;
|
|
1077
1323
|
}
|
|
1324
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1078
1325
|
if (this.bridgeMode === 'bridge') {
|
|
1079
1326
|
if (!this.matterAggregator) {
|
|
1080
1327
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1082,8 +1329,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
1329
|
}
|
|
1083
1330
|
this.matterAggregator.addBridgedDevice(device);
|
|
1084
1331
|
}
|
|
1332
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1333
|
+
// Register and add the device in childbridge mode
|
|
1085
1334
|
if (this.bridgeMode === 'childbridge') {
|
|
1086
1335
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1336
|
+
// Check if the plugin is locked with the commissioning server
|
|
1087
1337
|
if (!plugin.locked) {
|
|
1088
1338
|
plugin.locked = true;
|
|
1089
1339
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1097,6 +1347,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1097
1347
|
}
|
|
1098
1348
|
}
|
|
1099
1349
|
if (plugin.type === 'DynamicPlatform') {
|
|
1350
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1100
1351
|
if (!plugin.locked) {
|
|
1101
1352
|
plugin.locked = true;
|
|
1102
1353
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1117,16 +1368,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1117
1368
|
plugin.registeredDevices++;
|
|
1118
1369
|
if (plugin.addedDevices !== undefined)
|
|
1119
1370
|
plugin.addedDevices++;
|
|
1371
|
+
// Add the device to the DeviceManager
|
|
1120
1372
|
this.devices.set(device);
|
|
1121
1373
|
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}`);
|
|
1122
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Removes a bridged device from the Matterbridge.
|
|
1377
|
+
* @param pluginName - The name of the plugin.
|
|
1378
|
+
* @param device - The device to be removed.
|
|
1379
|
+
* @returns A Promise that resolves when the device is successfully removed.
|
|
1380
|
+
*/
|
|
1123
1381
|
async removeBridgedDevice(pluginName, device) {
|
|
1124
1382
|
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1383
|
+
// Check if the plugin is registered
|
|
1125
1384
|
const plugin = this.plugins.get(pluginName);
|
|
1126
1385
|
if (!plugin) {
|
|
1127
1386
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1128
1387
|
return;
|
|
1129
1388
|
}
|
|
1389
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1130
1390
|
if (this.bridgeMode === 'bridge') {
|
|
1131
1391
|
if (!this.matterAggregator) {
|
|
1132
1392
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1136,6 +1396,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1136
1396
|
device.setBridgedDeviceReachability(false);
|
|
1137
1397
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1138
1398
|
}
|
|
1399
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1400
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1139
1401
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1140
1402
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1141
1403
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1143,6 +1405,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1143
1405
|
if (plugin.addedDevices !== undefined)
|
|
1144
1406
|
plugin.addedDevices--;
|
|
1145
1407
|
}
|
|
1408
|
+
// Remove the device in childbridge mode
|
|
1146
1409
|
if (this.bridgeMode === 'childbridge') {
|
|
1147
1410
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1148
1411
|
if (!plugin.commissioningServer) {
|
|
@@ -1166,14 +1429,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1166
1429
|
plugin.registeredDevices--;
|
|
1167
1430
|
if (plugin.addedDevices !== undefined)
|
|
1168
1431
|
plugin.addedDevices--;
|
|
1432
|
+
// Remove the commissioning server
|
|
1169
1433
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1170
1434
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1171
1435
|
plugin.commissioningServer = undefined;
|
|
1172
1436
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1173
1437
|
}
|
|
1174
1438
|
}
|
|
1439
|
+
// Remove the device from the DeviceManager
|
|
1175
1440
|
this.devices.remove(device);
|
|
1176
1441
|
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
1444
|
+
*
|
|
1445
|
+
* @param pluginName - The name of the plugin.
|
|
1446
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
1447
|
+
*/
|
|
1177
1448
|
async removeAllBridgedDevices(pluginName) {
|
|
1178
1449
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1179
1450
|
this.devices.forEach(async (device) => {
|
|
@@ -1182,7 +1453,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1182
1453
|
}
|
|
1183
1454
|
});
|
|
1184
1455
|
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Starts the Matterbridge in bridge mode.
|
|
1458
|
+
* @private
|
|
1459
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1460
|
+
*/
|
|
1185
1461
|
async startBridge() {
|
|
1462
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1186
1463
|
if (!this.storageManager)
|
|
1187
1464
|
throw new Error('No storage manager initialized');
|
|
1188
1465
|
if (!this.matterbridgeContext)
|
|
@@ -1201,6 +1478,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1201
1478
|
let failCount = 0;
|
|
1202
1479
|
this.startMatterInterval = setInterval(async () => {
|
|
1203
1480
|
for (const plugin of this.plugins) {
|
|
1481
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1204
1482
|
if (!plugin.enabled)
|
|
1205
1483
|
continue;
|
|
1206
1484
|
if (plugin.error) {
|
|
@@ -1225,15 +1503,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1225
1503
|
clearInterval(this.startMatterInterval);
|
|
1226
1504
|
this.startMatterInterval = undefined;
|
|
1227
1505
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1506
|
+
// Start the Matter server
|
|
1228
1507
|
await this.startMatterServer();
|
|
1229
1508
|
this.log.notice('Matter server started');
|
|
1509
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1230
1510
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1511
|
+
// Configure the plugins
|
|
1231
1512
|
this.configureTimeout = setTimeout(async () => {
|
|
1232
1513
|
for (const plugin of this.plugins) {
|
|
1233
1514
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1234
1515
|
continue;
|
|
1235
1516
|
try {
|
|
1236
|
-
await this.plugins.configure(plugin);
|
|
1517
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1237
1518
|
}
|
|
1238
1519
|
catch (error) {
|
|
1239
1520
|
plugin.error = true;
|
|
@@ -1242,6 +1523,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1242
1523
|
}
|
|
1243
1524
|
this.wssSendRefreshRequired();
|
|
1244
1525
|
}, 30 * 1000);
|
|
1526
|
+
// Setting reachability to true
|
|
1245
1527
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1246
1528
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1247
1529
|
if (this.commissioningServer)
|
|
@@ -1251,7 +1533,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1251
1533
|
}, 60 * 1000);
|
|
1252
1534
|
}, 1000);
|
|
1253
1535
|
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1538
|
+
* @private
|
|
1539
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1540
|
+
*/
|
|
1254
1541
|
async startChildbridge() {
|
|
1542
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1543
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1255
1544
|
if (!this.storageManager)
|
|
1256
1545
|
throw new Error('No storage manager initialized');
|
|
1257
1546
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
@@ -1261,6 +1550,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1261
1550
|
this.startMatterInterval = setInterval(async () => {
|
|
1262
1551
|
let allStarted = true;
|
|
1263
1552
|
for (const plugin of this.plugins) {
|
|
1553
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1264
1554
|
if (!plugin.enabled)
|
|
1265
1555
|
continue;
|
|
1266
1556
|
if (plugin.error) {
|
|
@@ -1288,14 +1578,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1288
1578
|
clearInterval(this.startMatterInterval);
|
|
1289
1579
|
this.startMatterInterval = undefined;
|
|
1290
1580
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1581
|
+
// Start the Matter server
|
|
1291
1582
|
await this.startMatterServer();
|
|
1292
1583
|
this.log.notice('Matter server started');
|
|
1584
|
+
// Configure the plugins
|
|
1293
1585
|
this.configureTimeout = setTimeout(async () => {
|
|
1294
1586
|
for (const plugin of this.plugins) {
|
|
1295
1587
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1296
1588
|
continue;
|
|
1297
1589
|
try {
|
|
1298
|
-
await this.plugins.configure(plugin);
|
|
1590
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1299
1591
|
}
|
|
1300
1592
|
catch (error) {
|
|
1301
1593
|
plugin.error = true;
|
|
@@ -1324,6 +1616,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1324
1616
|
continue;
|
|
1325
1617
|
}
|
|
1326
1618
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1619
|
+
// Setting reachability to true
|
|
1327
1620
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1328
1621
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1329
1622
|
if (plugin.commissioningServer)
|
|
@@ -1336,6 +1629,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1336
1629
|
}
|
|
1337
1630
|
}, 1000);
|
|
1338
1631
|
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Starts the Matterbridge controller.
|
|
1634
|
+
* @private
|
|
1635
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1636
|
+
*/
|
|
1339
1637
|
async startController() {
|
|
1340
1638
|
if (!this.storageManager) {
|
|
1341
1639
|
this.log.error('No storage manager initialized');
|
|
@@ -1398,7 +1696,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1398
1696
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1399
1697
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1400
1698
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1401
|
-
}
|
|
1699
|
+
} // (hasParameter('pairingcode'))
|
|
1402
1700
|
if (hasParameter('unpairall')) {
|
|
1403
1701
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1404
1702
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1409,6 +1707,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1409
1707
|
return;
|
|
1410
1708
|
}
|
|
1411
1709
|
if (hasParameter('discover')) {
|
|
1710
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1711
|
+
// console.log(discover);
|
|
1412
1712
|
}
|
|
1413
1713
|
if (!this.commissioningController.isCommissioned()) {
|
|
1414
1714
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1449,10 +1749,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1449
1749
|
},
|
|
1450
1750
|
});
|
|
1451
1751
|
node.logStructure();
|
|
1752
|
+
// Get the interaction client
|
|
1452
1753
|
this.log.info('Getting the interaction client');
|
|
1453
1754
|
const interactionClient = await node.getInteractionClient();
|
|
1454
1755
|
let cluster;
|
|
1455
1756
|
let attributes;
|
|
1757
|
+
// Log BasicInformationCluster
|
|
1456
1758
|
cluster = BasicInformationCluster;
|
|
1457
1759
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1458
1760
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1462,6 +1764,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1462
1764
|
attributes.forEach((attribute) => {
|
|
1463
1765
|
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1464
1766
|
});
|
|
1767
|
+
// Log PowerSourceCluster
|
|
1465
1768
|
cluster = PowerSourceCluster;
|
|
1466
1769
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1467
1770
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1471,6 +1774,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1471
1774
|
attributes.forEach((attribute) => {
|
|
1472
1775
|
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1473
1776
|
});
|
|
1777
|
+
// Log ThreadNetworkDiagnostics
|
|
1474
1778
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1475
1779
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1476
1780
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1480,6 +1784,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1480
1784
|
attributes.forEach((attribute) => {
|
|
1481
1785
|
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}`);
|
|
1482
1786
|
});
|
|
1787
|
+
// Log SwitchCluster
|
|
1483
1788
|
cluster = SwitchCluster;
|
|
1484
1789
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1485
1790
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1500,6 +1805,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1500
1805
|
this.log.info('Subscribed to all attributes and events');
|
|
1501
1806
|
}
|
|
1502
1807
|
}
|
|
1808
|
+
/** ***********************************************************************************************************************************/
|
|
1809
|
+
/** Matter.js methods */
|
|
1810
|
+
/** ***********************************************************************************************************************************/
|
|
1811
|
+
/**
|
|
1812
|
+
* Starts the matter storage process based on the specified storage type and name.
|
|
1813
|
+
* @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
|
|
1814
|
+
* @param {string} storageName - The name of the storage file.
|
|
1815
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1816
|
+
*/
|
|
1503
1817
|
async startMatterStorage(storageType, storageName) {
|
|
1504
1818
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1505
1819
|
if (storageType === 'disk') {
|
|
@@ -1548,6 +1862,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1548
1862
|
await this.matterbridgeContext.set('passcode', this.passcode);
|
|
1549
1863
|
await this.matterbridgeContext.set('discriminator', this.discriminator);
|
|
1550
1864
|
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Convert the old API matter storage to the new API format.
|
|
1867
|
+
* @param {StorageContext} context - The context of Matterbridge or of the plugin.
|
|
1868
|
+
* @param {string} pluginName - The name of the plugin or Matterbridge.
|
|
1869
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1870
|
+
*/
|
|
1551
1871
|
async convertStorage(context, pluginName) {
|
|
1552
1872
|
const storageService = Environment.default.get(StorageService);
|
|
1553
1873
|
Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
|
|
@@ -1559,13 +1879,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1559
1879
|
else {
|
|
1560
1880
|
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1561
1881
|
}
|
|
1882
|
+
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1562
1883
|
const fabricManagerContext = context.createContext('FabricManager');
|
|
1563
1884
|
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1564
1885
|
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 1);
|
|
1886
|
+
// Read EventHandler from the old storage
|
|
1565
1887
|
const eventHandlerContext = context.createContext('EventHandler');
|
|
1888
|
+
// Read SessionManager from the old storage
|
|
1566
1889
|
const sessionManagerContext = context.createContext('SessionManager');
|
|
1890
|
+
// Read EndpointStructure from the old storage
|
|
1567
1891
|
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1892
|
+
// Read generalCommissioning from the old storage
|
|
1568
1893
|
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1894
|
+
// Read basicInformation from the old storage
|
|
1569
1895
|
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1570
1896
|
const fabricInfo = {};
|
|
1571
1897
|
const fabricInfoArray = [];
|
|
@@ -1592,22 +1918,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
1592
1918
|
label: fabric.label,
|
|
1593
1919
|
});
|
|
1594
1920
|
nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
|
|
1921
|
+
// eslint-disable-next-line no-useless-escape
|
|
1595
1922
|
trcArray.push('{\"__object__\":\"Uint8Array\",\"__value__\":\"' + Buffer.from(fabric.rootCert).toString('hex') + '\"}');
|
|
1923
|
+
// eslint-disable-next-line no-useless-escape
|
|
1596
1924
|
aclArray.push({ fabricIndex: fabric.fabricIndex, privilege: 5, authMode: 2, subjects: ['{\"__object__\":\"BigInt\",\"__value__\":\"' + fabric.rootNodeId.toString().replace('n', '') + '\"}'], targets: null });
|
|
1925
|
+
// this.log.debug(`- fabricinfo ${fabric.fabricIndex}`, fabricInfo[fabric.fabricIndex]);
|
|
1597
1926
|
}
|
|
1598
1927
|
await nodeStorage.createContext('fabrics').set('fabrics', fabrics);
|
|
1599
1928
|
await nodeStorage.createContext('fabrics').set('nextFabricIndex', nextFabricIndex);
|
|
1600
1929
|
await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
|
|
1601
1930
|
await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
|
|
1602
1931
|
await nodeStorage.createContext('root').set('__number__', 0);
|
|
1603
|
-
await nodeStorage.createContext('root').set('
|
|
1932
|
+
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
|
|
1604
1933
|
await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
|
|
1605
1934
|
await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
|
|
1606
1935
|
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
1607
1936
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
1608
1937
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
1938
|
+
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
1609
1939
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
1940
|
+
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
1610
1941
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
|
|
1942
|
+
// ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
|
|
1943
|
+
// From fabric.rootNodeId
|
|
1944
|
+
// [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
|
|
1611
1945
|
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
1612
1946
|
await nodeStorage
|
|
1613
1947
|
.createContext('root')
|
|
@@ -1621,11 +1955,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1621
1955
|
await nodeStorage.createContext('root').createContext('network').set('operationalPort', 5540);
|
|
1622
1956
|
await nodeStorage.createContext('root').createContext('productDescription').set('productId', 0x8000);
|
|
1623
1957
|
await nodeStorage.createContext('root').createContext('productDescription').set('vendorId', 0xfff1);
|
|
1958
|
+
/*
|
|
1959
|
+
"Matterbridge.EndpointStructure": {
|
|
1960
|
+
"unique_d60ca095a002f160-index_0": 1,
|
|
1961
|
+
"unique_d60ca095a002f160-index_0-custom_Switch0": 2,
|
|
1962
|
+
"unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
|
|
1963
|
+
"unique_d60ca095a002f160-index_0-custom_Light0": 4,
|
|
1964
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
|
|
1965
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
|
|
1966
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
|
|
1967
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
|
|
1968
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
|
|
1969
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
|
|
1970
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
|
|
1971
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
|
|
1972
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
|
|
1973
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
|
|
1974
|
+
"nextEndpointId": 5
|
|
1975
|
+
},
|
|
1976
|
+
*/
|
|
1624
1977
|
for (const key of await endpointStructureContext.keys()) {
|
|
1625
|
-
if (key === 'nextEndpointId')
|
|
1978
|
+
if (key === 'nextEndpointId') {
|
|
1979
|
+
await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
|
|
1626
1980
|
continue;
|
|
1981
|
+
}
|
|
1627
1982
|
const parts = key.split('-');
|
|
1628
1983
|
const number = await endpointStructureContext.get(key);
|
|
1984
|
+
// this.log.debug(`- endpointStructure key ${key} value ${number}`);
|
|
1629
1985
|
if (parts.length === 2) {
|
|
1630
1986
|
this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.__number__:${number}`);
|
|
1631
1987
|
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', number);
|
|
@@ -1642,11 +1998,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
1998
|
}
|
|
1643
1999
|
}
|
|
1644
2000
|
}
|
|
1645
|
-
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
|
|
1646
2001
|
await nodeStorage.createContext('persist').set('converted', true);
|
|
2002
|
+
await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
|
|
2003
|
+
await nodeStorage.createContext('persist').set('deviceType', await context.get('deviceType'));
|
|
2004
|
+
await nodeStorage.createContext('persist').set('vendorId', await context.get('vendorId'));
|
|
2005
|
+
await nodeStorage.createContext('persist').set('vendorName', await context.get('vendorName'));
|
|
2006
|
+
await nodeStorage.createContext('persist').set('productId', await context.get('productId'));
|
|
2007
|
+
await nodeStorage.createContext('persist').set('productName', await context.get('productName'));
|
|
2008
|
+
await nodeStorage.createContext('persist').set('nodeLabel', await context.get('nodeLabel'));
|
|
2009
|
+
await nodeStorage.createContext('persist').set('productLabel', await context.get('productLabel'));
|
|
2010
|
+
await nodeStorage.createContext('persist').set('serialNumber', 'SN' + (await context.get('serialNumber')));
|
|
2011
|
+
await nodeStorage.createContext('persist').set('uniqueId', 'UI' + (await context.get('uniqueId')));
|
|
2012
|
+
await nodeStorage.createContext('persist').set('softwareVersion', await context.get('softwareVersion'));
|
|
2013
|
+
await nodeStorage.createContext('persist').set('softwareVersionString', await context.get('softwareVersionString'));
|
|
2014
|
+
await nodeStorage.createContext('persist').set('hardwareVersion', await context.get('hardwareVersion'));
|
|
2015
|
+
await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
|
|
1647
2016
|
await context.set('converted', true);
|
|
1648
2017
|
this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
|
|
1649
2018
|
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Makes a backup copy of the specified matter JSON storage file.
|
|
2021
|
+
*
|
|
2022
|
+
* @param storageName - The name of the JSON storage file to be backed up.
|
|
2023
|
+
* @param backupName - The name of the backup file to be created.
|
|
2024
|
+
*/
|
|
1650
2025
|
async backupMatterStorage(storageName, backupName) {
|
|
1651
2026
|
try {
|
|
1652
2027
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1667,6 +2042,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1667
2042
|
}
|
|
1668
2043
|
}
|
|
1669
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Restore the specified matter JSON storage file.
|
|
2047
|
+
*
|
|
2048
|
+
* @param backupName - The name of the backup file to restore from.
|
|
2049
|
+
* @param storageName - The name of the JSON storage file to restored.
|
|
2050
|
+
*/
|
|
1670
2051
|
async restoreMatterStorage(backupName, storageName) {
|
|
1671
2052
|
try {
|
|
1672
2053
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1687,6 +2068,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1687
2068
|
}
|
|
1688
2069
|
}
|
|
1689
2070
|
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Stops the matter storage.
|
|
2073
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2074
|
+
*/
|
|
1690
2075
|
async stopMatterStorage() {
|
|
1691
2076
|
this.log.debug('Stopping storage');
|
|
1692
2077
|
await this.storageManager?.close();
|
|
@@ -1695,8 +2080,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1695
2080
|
this.matterbridgeContext = undefined;
|
|
1696
2081
|
this.mattercontrollerContext = undefined;
|
|
1697
2082
|
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
2085
|
+
* @param storageManager The storage manager to be used by the Matter server.
|
|
2086
|
+
*
|
|
2087
|
+
*/
|
|
1698
2088
|
createMatterServer(storageManager) {
|
|
1699
2089
|
this.log.debug('Creating matter server');
|
|
2090
|
+
// Validate mdnsInterface
|
|
1700
2091
|
if (this.mdnsInterface) {
|
|
1701
2092
|
const networkInterfaces = os.networkInterfaces();
|
|
1702
2093
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1712,6 +2103,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1712
2103
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1713
2104
|
return matterServer;
|
|
1714
2105
|
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Starts the Matter server.
|
|
2108
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2109
|
+
*/
|
|
1715
2110
|
async startMatterServer() {
|
|
1716
2111
|
if (!this.matterServer) {
|
|
1717
2112
|
this.log.error('No matter server initialized');
|
|
@@ -1721,7 +2116,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1721
2116
|
this.log.debug('Starting matter server...');
|
|
1722
2117
|
await this.matterServer.start();
|
|
1723
2118
|
this.log.debug('Started matter server');
|
|
2119
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1724
2120
|
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2123
|
+
*/
|
|
1725
2124
|
async stopMatterServer() {
|
|
1726
2125
|
this.log.debug('Stopping matter commissioningServer');
|
|
1727
2126
|
await this.commissioningServer?.close();
|
|
@@ -1735,23 +2134,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1735
2134
|
this.matterAggregator = undefined;
|
|
1736
2135
|
this.matterServer = undefined;
|
|
1737
2136
|
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Creates a Matter Aggregator.
|
|
2139
|
+
* @param {StorageContext} context - The storage context.
|
|
2140
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2141
|
+
*/
|
|
1738
2142
|
async createMatterAggregator(context, pluginName) {
|
|
1739
2143
|
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
1740
2144
|
const matterAggregator = new Aggregator();
|
|
1741
2145
|
return matterAggregator;
|
|
1742
2146
|
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Creates a matter commissioning server.
|
|
2149
|
+
*
|
|
2150
|
+
* @param {StorageContext} context - The storage context.
|
|
2151
|
+
* @param {string} pluginName - The name of the commissioning server.
|
|
2152
|
+
* @returns {CommissioningServer} The created commissioning server.
|
|
2153
|
+
*/
|
|
1743
2154
|
async createCommisioningServer(context, pluginName) {
|
|
1744
2155
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1745
2156
|
const deviceName = await context.get('deviceName');
|
|
1746
2157
|
const deviceType = await context.get('deviceType');
|
|
1747
2158
|
const vendorId = await context.get('vendorId');
|
|
1748
|
-
const vendorName = await context.get('vendorName');
|
|
2159
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1749
2160
|
const productId = await context.get('productId');
|
|
1750
|
-
const productName = await context.get('productName');
|
|
2161
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1751
2162
|
const serialNumber = await context.get('serialNumber');
|
|
1752
2163
|
const uniqueId = await context.get('uniqueId');
|
|
1753
2164
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1754
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2165
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1755
2166
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1756
2167
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1757
2168
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
|
|
@@ -1759,6 +2170,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1759
2170
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1760
2171
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1761
2172
|
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} `);
|
|
2173
|
+
// Validate ipv4address
|
|
1762
2174
|
if (this.ipv4address) {
|
|
1763
2175
|
const networkInterfaces = os.networkInterfaces();
|
|
1764
2176
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1773,6 +2185,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1773
2185
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1774
2186
|
}
|
|
1775
2187
|
}
|
|
2188
|
+
// Validate ipv6address
|
|
1776
2189
|
if (this.ipv6address) {
|
|
1777
2190
|
const networkInterfaces = os.networkInterfaces();
|
|
1778
2191
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1803,7 +2216,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1803
2216
|
nodeLabel: productName,
|
|
1804
2217
|
productLabel: productName,
|
|
1805
2218
|
softwareVersion,
|
|
1806
|
-
softwareVersionString,
|
|
2219
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1807
2220
|
hardwareVersion,
|
|
1808
2221
|
hardwareVersionString,
|
|
1809
2222
|
uniqueId,
|
|
@@ -1899,6 +2312,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1899
2312
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1900
2313
|
return commissioningServer;
|
|
1901
2314
|
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Creates a commissioning server storage context.
|
|
2317
|
+
*
|
|
2318
|
+
* @param pluginName - The name of the plugin.
|
|
2319
|
+
* @param deviceName - The name of the device.
|
|
2320
|
+
* @param deviceType - The type of the device.
|
|
2321
|
+
* @param vendorId - The vendor ID.
|
|
2322
|
+
* @param vendorName - The vendor name.
|
|
2323
|
+
* @param productId - The product ID.
|
|
2324
|
+
* @param productName - The product name.
|
|
2325
|
+
* @param serialNumber - The serial number of the device (optional).
|
|
2326
|
+
* @param uniqueId - The unique ID of the device (optional).
|
|
2327
|
+
* @param softwareVersion - The software version of the device (optional).
|
|
2328
|
+
* @param softwareVersionString - The software version string of the device (optional).
|
|
2329
|
+
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2330
|
+
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2331
|
+
* @returns The storage context for the commissioning server.
|
|
2332
|
+
*/
|
|
1902
2333
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
1903
2334
|
if (!this.storageManager)
|
|
1904
2335
|
throw new Error('No storage manager initialized');
|
|
@@ -1926,6 +2357,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1926
2357
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1927
2358
|
return storageContext;
|
|
1928
2359
|
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Imports the commissioning server context for a specific plugin and device.
|
|
2362
|
+
* @param pluginName - The name of the plugin.
|
|
2363
|
+
* @param device - The MatterbridgeDevice object representing the device.
|
|
2364
|
+
* @returns The commissioning server context.
|
|
2365
|
+
* @throws Error if the BasicInformationCluster is not found.
|
|
2366
|
+
*/
|
|
1929
2367
|
async importCommissioningServerContext(pluginName, device) {
|
|
1930
2368
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
1931
2369
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -1960,6 +2398,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1960
2398
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1961
2399
|
return storageContext;
|
|
1962
2400
|
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Shows the commissioning server QR code for a given plugin.
|
|
2403
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2404
|
+
* @param {StorageContext} storageContext - The storage context instance.
|
|
2405
|
+
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2406
|
+
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2407
|
+
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2408
|
+
*/
|
|
1963
2409
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
1964
2410
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
1965
2411
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -1970,7 +2416,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1970
2416
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
1971
2417
|
const QrCode = new QrCodeSchema();
|
|
1972
2418
|
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`);
|
|
1973
|
-
|
|
2419
|
+
// eslint-disable-next-line no-console
|
|
2420
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
1974
2421
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
1975
2422
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
1976
2423
|
if (pluginName === 'Matterbridge') {
|
|
@@ -2017,6 +2464,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2017
2464
|
}
|
|
2018
2465
|
this.wssSendRefreshRequired();
|
|
2019
2466
|
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2469
|
+
*
|
|
2470
|
+
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2471
|
+
* @returns An array of sanitized exposed fabric information objects.
|
|
2472
|
+
*/
|
|
2020
2473
|
sanitizeFabricInformations(fabricInfo) {
|
|
2021
2474
|
return fabricInfo.map((info) => {
|
|
2022
2475
|
return {
|
|
@@ -2030,6 +2483,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2030
2483
|
};
|
|
2031
2484
|
});
|
|
2032
2485
|
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Sanitizes the session information by converting bigint properties to string.
|
|
2488
|
+
*
|
|
2489
|
+
* @param sessionInfo - The array of session information objects.
|
|
2490
|
+
* @returns An array of sanitized session information objects.
|
|
2491
|
+
*/
|
|
2033
2492
|
sanitizeSessionInformation(sessionInfo) {
|
|
2034
2493
|
return sessionInfo
|
|
2035
2494
|
.filter((session) => session.isPeerActive)
|
|
@@ -2057,6 +2516,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2057
2516
|
};
|
|
2058
2517
|
});
|
|
2059
2518
|
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Sets the reachability of a commissioning server and trigger.
|
|
2521
|
+
*
|
|
2522
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2523
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2524
|
+
*/
|
|
2060
2525
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2061
2526
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2062
2527
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2064,6 +2529,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2064
2529
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2065
2530
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2066
2531
|
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2534
|
+
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2535
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2536
|
+
*/
|
|
2067
2537
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
2068
2538
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2069
2539
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2076,6 +2546,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2076
2546
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2077
2547
|
});
|
|
2078
2548
|
}
|
|
2549
|
+
/**
|
|
2550
|
+
* Sets the reachability of a device and trigger.
|
|
2551
|
+
*
|
|
2552
|
+
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2553
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2554
|
+
*/
|
|
2079
2555
|
setDeviceReachability(device, reachable) {
|
|
2080
2556
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2081
2557
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2124,6 +2600,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2124
2600
|
}
|
|
2125
2601
|
return vendorName;
|
|
2126
2602
|
};
|
|
2603
|
+
/**
|
|
2604
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
2605
|
+
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
2606
|
+
*/
|
|
2127
2607
|
async getBaseRegisteredPlugins() {
|
|
2128
2608
|
const baseRegisteredPlugins = [];
|
|
2129
2609
|
for (const plugin of this.plugins) {
|
|
@@ -2155,13 +2635,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2155
2635
|
}
|
|
2156
2636
|
return baseRegisteredPlugins;
|
|
2157
2637
|
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Spawns a child process with the given command and arguments.
|
|
2640
|
+
* @param {string} command - The command to execute.
|
|
2641
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2642
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2643
|
+
*/
|
|
2158
2644
|
async spawnCommand(command, args = []) {
|
|
2645
|
+
/*
|
|
2646
|
+
npm > npm.cmd on windows
|
|
2647
|
+
cmd.exe ['dir'] on windows
|
|
2648
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2649
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2650
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2653
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2654
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2655
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2656
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2657
|
+
*/
|
|
2159
2658
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2160
2659
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2660
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2161
2661
|
const argstring = 'npm ' + args.join(' ');
|
|
2162
2662
|
args.splice(0, args.length, '/c', argstring);
|
|
2163
2663
|
command = 'cmd.exe';
|
|
2164
2664
|
}
|
|
2665
|
+
// Decide when using sudo on linux
|
|
2666
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2667
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2165
2668
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2166
2669
|
args.unshift(command);
|
|
2167
2670
|
command = 'sudo';
|
|
@@ -2219,55 +2722,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2219
2722
|
}
|
|
2220
2723
|
});
|
|
2221
2724
|
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Sends a WebSocket message to all connected clients.
|
|
2727
|
+
*
|
|
2728
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2729
|
+
* @param {string} time - The time string of the message
|
|
2730
|
+
* @param {string} name - The logger name of the message
|
|
2731
|
+
* @param {string} message - The content of the message.
|
|
2732
|
+
*/
|
|
2222
2733
|
wssSendMessage(level, time, name, message) {
|
|
2223
2734
|
if (!level || !time || !name || !message)
|
|
2224
2735
|
return;
|
|
2736
|
+
// Remove ANSI escape codes from the message
|
|
2737
|
+
// eslint-disable-next-line no-control-regex
|
|
2225
2738
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2739
|
+
// Remove leading asterisks from the message
|
|
2226
2740
|
message = message.replace(/^\*+/, '');
|
|
2741
|
+
// Replace all occurrences of \t and \n
|
|
2227
2742
|
message = message.replace(/[\t\n]/g, '');
|
|
2743
|
+
// Remove non-printable characters
|
|
2744
|
+
// eslint-disable-next-line no-control-regex
|
|
2228
2745
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2746
|
+
// Replace all occurrences of \" with "
|
|
2229
2747
|
message = message.replace(/\\"/g, '"');
|
|
2748
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2230
2749
|
const maxContinuousLength = 100;
|
|
2231
2750
|
const keepStartLength = 20;
|
|
2232
2751
|
const keepEndLength = 20;
|
|
2752
|
+
// Split the message into words
|
|
2233
2753
|
message = message
|
|
2234
2754
|
.split(' ')
|
|
2235
2755
|
.map((word) => {
|
|
2756
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2236
2757
|
if (word.length > maxContinuousLength) {
|
|
2237
2758
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2238
2759
|
}
|
|
2239
2760
|
return word;
|
|
2240
2761
|
})
|
|
2241
2762
|
.join(' ');
|
|
2763
|
+
// Send the message to all connected clients
|
|
2242
2764
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2243
2765
|
if (client.readyState === WebSocket.OPEN) {
|
|
2244
2766
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2245
2767
|
}
|
|
2246
2768
|
});
|
|
2247
2769
|
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2772
|
+
*
|
|
2773
|
+
*/
|
|
2248
2774
|
wssSendRefreshRequired() {
|
|
2249
2775
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2776
|
+
// Send the message to all connected clients
|
|
2250
2777
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2251
2778
|
if (client.readyState === WebSocket.OPEN) {
|
|
2252
2779
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
|
|
2253
2780
|
}
|
|
2254
2781
|
});
|
|
2255
2782
|
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2785
|
+
*
|
|
2786
|
+
*/
|
|
2256
2787
|
wssSendRestartRequired() {
|
|
2257
2788
|
this.matterbridgeInformation.restartRequired = true;
|
|
2789
|
+
// Send the message to all connected clients
|
|
2258
2790
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2259
2791
|
if (client.readyState === WebSocket.OPEN) {
|
|
2260
2792
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
|
|
2261
2793
|
}
|
|
2262
2794
|
});
|
|
2263
2795
|
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Initializes the frontend of Matterbridge.
|
|
2798
|
+
*
|
|
2799
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
2800
|
+
*/
|
|
2264
2801
|
async initializeFrontend(port = 8283) {
|
|
2265
2802
|
let initializeError = false;
|
|
2266
2803
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2804
|
+
// Create the express app that serves the frontend
|
|
2267
2805
|
this.expressApp = express();
|
|
2806
|
+
// Log all requests to the server for debugging
|
|
2807
|
+
/*
|
|
2808
|
+
if (hasParameter('homedir')) {
|
|
2809
|
+
this.expressApp.use((req, res, next) => {
|
|
2810
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
2811
|
+
next();
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
*/
|
|
2815
|
+
// Serve static files from '/static' endpoint
|
|
2268
2816
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2269
2817
|
if (!hasParameter('ssl')) {
|
|
2818
|
+
// Create an HTTP server and attach the express app
|
|
2270
2819
|
this.httpServer = createServer(this.expressApp);
|
|
2820
|
+
// Listen on the specified port
|
|
2271
2821
|
if (hasParameter('ingress')) {
|
|
2272
2822
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2273
2823
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2281,6 +2831,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2281
2831
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2282
2832
|
});
|
|
2283
2833
|
}
|
|
2834
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2284
2835
|
this.httpServer.on('error', (error) => {
|
|
2285
2836
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2286
2837
|
switch (error.code) {
|
|
@@ -2296,6 +2847,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2296
2847
|
});
|
|
2297
2848
|
}
|
|
2298
2849
|
else {
|
|
2850
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2299
2851
|
let cert;
|
|
2300
2852
|
try {
|
|
2301
2853
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2323,7 +2875,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2323
2875
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2324
2876
|
}
|
|
2325
2877
|
const serverOptions = { cert, key, ca };
|
|
2878
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
2326
2879
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
2880
|
+
// Listen on the specified port
|
|
2327
2881
|
if (hasParameter('ingress')) {
|
|
2328
2882
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2329
2883
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2337,6 +2891,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2337
2891
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2338
2892
|
});
|
|
2339
2893
|
}
|
|
2894
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2340
2895
|
this.httpsServer.on('error', (error) => {
|
|
2341
2896
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2342
2897
|
switch (error.code) {
|
|
@@ -2353,12 +2908,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2353
2908
|
}
|
|
2354
2909
|
if (initializeError)
|
|
2355
2910
|
return;
|
|
2911
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2356
2912
|
const wssPort = port;
|
|
2357
2913
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2358
2914
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2359
2915
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2360
2916
|
const clientIp = request.socket.remoteAddress;
|
|
2361
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
2917
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2362
2918
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2363
2919
|
ws.on('message', (message) => {
|
|
2364
2920
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2391,6 +2947,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2391
2947
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2392
2948
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2393
2949
|
});
|
|
2950
|
+
// Endpoint to validate login code
|
|
2394
2951
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2395
2952
|
const { password } = req.body;
|
|
2396
2953
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2409,12 +2966,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2409
2966
|
this.log.warn('/api/login error wrong password');
|
|
2410
2967
|
res.json({ valid: false });
|
|
2411
2968
|
}
|
|
2969
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2412
2970
|
}
|
|
2413
2971
|
catch (error) {
|
|
2414
2972
|
this.log.error('/api/login error getting password');
|
|
2415
2973
|
res.json({ valid: false });
|
|
2416
2974
|
}
|
|
2417
2975
|
});
|
|
2976
|
+
// Endpoint to provide settings
|
|
2418
2977
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2419
2978
|
this.log.debug('The frontend sent /api/settings');
|
|
2420
2979
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2435,13 +2994,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2435
2994
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2436
2995
|
this.matterbridgeInformation.profile = this.profile;
|
|
2437
2996
|
const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
2997
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2438
2998
|
res.json(response);
|
|
2439
2999
|
});
|
|
3000
|
+
// Endpoint to provide plugins
|
|
2440
3001
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2441
3002
|
this.log.debug('The frontend sent /api/plugins');
|
|
2442
3003
|
const response = await this.getBaseRegisteredPlugins();
|
|
3004
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2443
3005
|
res.json(response);
|
|
2444
3006
|
});
|
|
3007
|
+
// Endpoint to provide devices
|
|
2445
3008
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2446
3009
|
this.log.debug('The frontend sent /api/devices');
|
|
2447
3010
|
const devices = [];
|
|
@@ -2474,8 +3037,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2474
3037
|
cluster: cluster,
|
|
2475
3038
|
});
|
|
2476
3039
|
});
|
|
3040
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2477
3041
|
res.json(devices);
|
|
2478
3042
|
});
|
|
3043
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2479
3044
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2480
3045
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2481
3046
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2495,6 +3060,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2495
3060
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2496
3061
|
if (clusterServer.name === 'EveHistory')
|
|
2497
3062
|
return;
|
|
3063
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2498
3064
|
let attributeValue;
|
|
2499
3065
|
try {
|
|
2500
3066
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2505,6 +3071,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2505
3071
|
catch (error) {
|
|
2506
3072
|
attributeValue = 'Fabric-Scoped';
|
|
2507
3073
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3074
|
+
// console.log(error);
|
|
2508
3075
|
}
|
|
2509
3076
|
data.push({
|
|
2510
3077
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2517,12 +3084,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2517
3084
|
});
|
|
2518
3085
|
});
|
|
2519
3086
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3087
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2520
3088
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2521
3089
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2522
3090
|
clusterServers.forEach((clusterServer) => {
|
|
2523
3091
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2524
3092
|
if (clusterServer.name === 'EveHistory')
|
|
2525
3093
|
return;
|
|
3094
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2526
3095
|
let attributeValue;
|
|
2527
3096
|
try {
|
|
2528
3097
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2533,6 +3102,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2533
3102
|
catch (error) {
|
|
2534
3103
|
attributeValue = 'Unavailable';
|
|
2535
3104
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3105
|
+
// console.log(error);
|
|
2536
3106
|
}
|
|
2537
3107
|
data.push({
|
|
2538
3108
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2549,6 +3119,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2549
3119
|
});
|
|
2550
3120
|
res.json(data);
|
|
2551
3121
|
});
|
|
3122
|
+
// Endpoint to view the log
|
|
2552
3123
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2553
3124
|
this.log.debug('The frontend sent /api/log');
|
|
2554
3125
|
try {
|
|
@@ -2561,10 +3132,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2561
3132
|
res.status(500).send('Error reading log file');
|
|
2562
3133
|
}
|
|
2563
3134
|
});
|
|
3135
|
+
// Endpoint to download the matterbridge log
|
|
2564
3136
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2565
3137
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2566
3138
|
try {
|
|
2567
3139
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3140
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2568
3141
|
}
|
|
2569
3142
|
catch (error) {
|
|
2570
3143
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2576,10 +3149,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2576
3149
|
}
|
|
2577
3150
|
});
|
|
2578
3151
|
});
|
|
3152
|
+
// Endpoint to download the matter log
|
|
2579
3153
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2580
3154
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2581
3155
|
try {
|
|
2582
3156
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3157
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2583
3158
|
}
|
|
2584
3159
|
catch (error) {
|
|
2585
3160
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2591,6 +3166,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2591
3166
|
}
|
|
2592
3167
|
});
|
|
2593
3168
|
});
|
|
3169
|
+
// Endpoint to download the matter storage file
|
|
2594
3170
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2595
3171
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2596
3172
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2600,6 +3176,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2600
3176
|
}
|
|
2601
3177
|
});
|
|
2602
3178
|
});
|
|
3179
|
+
// Endpoint to download the matterbridge storage directory
|
|
2603
3180
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2604
3181
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2605
3182
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2610,6 +3187,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2610
3187
|
}
|
|
2611
3188
|
});
|
|
2612
3189
|
});
|
|
3190
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2613
3191
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2614
3192
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2615
3193
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2620,9 +3198,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2620
3198
|
}
|
|
2621
3199
|
});
|
|
2622
3200
|
});
|
|
3201
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2623
3202
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2624
3203
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2625
3204
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3205
|
+
// 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')));
|
|
2626
3206
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2627
3207
|
if (error) {
|
|
2628
3208
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2630,6 +3210,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2630
3210
|
}
|
|
2631
3211
|
});
|
|
2632
3212
|
});
|
|
3213
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2633
3214
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2634
3215
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2635
3216
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2639,6 +3220,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2639
3220
|
}
|
|
2640
3221
|
});
|
|
2641
3222
|
});
|
|
3223
|
+
// Endpoint to receive commands
|
|
2642
3224
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2643
3225
|
const command = req.params.command;
|
|
2644
3226
|
let param = req.params.param;
|
|
@@ -2648,13 +3230,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2648
3230
|
return;
|
|
2649
3231
|
}
|
|
2650
3232
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3233
|
+
// Handle the command setpassword from Settings
|
|
2651
3234
|
if (command === 'setpassword') {
|
|
2652
|
-
const password = param.slice(1, -1);
|
|
3235
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2653
3236
|
this.log.debug('setpassword', param, password);
|
|
2654
3237
|
await this.nodeContext?.set('password', password);
|
|
2655
3238
|
res.json({ message: 'Command received' });
|
|
2656
3239
|
return;
|
|
2657
3240
|
}
|
|
3241
|
+
// Handle the command setbridgemode from Settings
|
|
2658
3242
|
if (command === 'setbridgemode') {
|
|
2659
3243
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2660
3244
|
this.wssSendRestartRequired();
|
|
@@ -2662,6 +3246,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2662
3246
|
res.json({ message: 'Command received' });
|
|
2663
3247
|
return;
|
|
2664
3248
|
}
|
|
3249
|
+
// Handle the command backup from Settings
|
|
2665
3250
|
if (command === 'backup') {
|
|
2666
3251
|
this.log.notice(`Prepairing the backup...`);
|
|
2667
3252
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2669,25 +3254,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2669
3254
|
res.json({ message: 'Command received' });
|
|
2670
3255
|
return;
|
|
2671
3256
|
}
|
|
3257
|
+
// Handle the command setmbloglevel from Settings
|
|
2672
3258
|
if (command === 'setmbloglevel') {
|
|
2673
3259
|
this.log.debug('Matterbridge log level:', param);
|
|
2674
3260
|
if (param === 'Debug') {
|
|
2675
|
-
this.log.logLevel = "debug"
|
|
3261
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2676
3262
|
}
|
|
2677
3263
|
else if (param === 'Info') {
|
|
2678
|
-
this.log.logLevel = "info"
|
|
3264
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2679
3265
|
}
|
|
2680
3266
|
else if (param === 'Notice') {
|
|
2681
|
-
this.log.logLevel = "notice"
|
|
3267
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2682
3268
|
}
|
|
2683
3269
|
else if (param === 'Warn') {
|
|
2684
|
-
this.log.logLevel = "warn"
|
|
3270
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2685
3271
|
}
|
|
2686
3272
|
else if (param === 'Error') {
|
|
2687
|
-
this.log.logLevel = "error"
|
|
3273
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2688
3274
|
}
|
|
2689
3275
|
else if (param === 'Fatal') {
|
|
2690
|
-
this.log.logLevel = "fatal"
|
|
3276
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2691
3277
|
}
|
|
2692
3278
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2693
3279
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2695,12 +3281,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2695
3281
|
for (const plugin of this.plugins) {
|
|
2696
3282
|
if (!plugin.platform || !plugin.platform.config)
|
|
2697
3283
|
continue;
|
|
2698
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2699
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
3284
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3285
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
2700
3286
|
}
|
|
2701
3287
|
res.json({ message: 'Command received' });
|
|
2702
3288
|
return;
|
|
2703
3289
|
}
|
|
3290
|
+
// Handle the command setmbloglevel from Settings
|
|
2704
3291
|
if (command === 'setmjloglevel') {
|
|
2705
3292
|
this.log.debug('Matter.js log level:', param);
|
|
2706
3293
|
if (param === 'Debug') {
|
|
@@ -2725,30 +3312,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2725
3312
|
res.json({ message: 'Command received' });
|
|
2726
3313
|
return;
|
|
2727
3314
|
}
|
|
3315
|
+
// Handle the command setmdnsinterface from Settings
|
|
2728
3316
|
if (command === 'setmdnsinterface') {
|
|
2729
|
-
param = param.slice(1, -1);
|
|
3317
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2730
3318
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2731
3319
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2732
3320
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2733
3321
|
res.json({ message: 'Command received' });
|
|
2734
3322
|
return;
|
|
2735
3323
|
}
|
|
3324
|
+
// Handle the command setipv4address from Settings
|
|
2736
3325
|
if (command === 'setipv4address') {
|
|
2737
|
-
param = param.slice(1, -1);
|
|
3326
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2738
3327
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2739
3328
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2740
3329
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2741
3330
|
res.json({ message: 'Command received' });
|
|
2742
3331
|
return;
|
|
2743
3332
|
}
|
|
3333
|
+
// Handle the command setipv6address from Settings
|
|
2744
3334
|
if (command === 'setipv6address') {
|
|
2745
|
-
param = param.slice(1, -1);
|
|
3335
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2746
3336
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2747
3337
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2748
3338
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2749
3339
|
res.json({ message: 'Command received' });
|
|
2750
3340
|
return;
|
|
2751
3341
|
}
|
|
3342
|
+
// Handle the command setmatterport from Settings
|
|
2752
3343
|
if (command === 'setmatterport') {
|
|
2753
3344
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2754
3345
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2757,6 +3348,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2757
3348
|
res.json({ message: 'Command received' });
|
|
2758
3349
|
return;
|
|
2759
3350
|
}
|
|
3351
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2760
3352
|
if (command === 'setmatterdiscriminator') {
|
|
2761
3353
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2762
3354
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2765,6 +3357,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2765
3357
|
res.json({ message: 'Command received' });
|
|
2766
3358
|
return;
|
|
2767
3359
|
}
|
|
3360
|
+
// Handle the command setmatterpasscode from Settings
|
|
2768
3361
|
if (command === 'setmatterpasscode') {
|
|
2769
3362
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2770
3363
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2773,17 +3366,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2773
3366
|
res.json({ message: 'Command received' });
|
|
2774
3367
|
return;
|
|
2775
3368
|
}
|
|
3369
|
+
// Handle the command setmbloglevel from Settings
|
|
2776
3370
|
if (command === 'setmblogfile') {
|
|
2777
3371
|
this.log.debug('Matterbridge file log:', param);
|
|
2778
3372
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2779
3373
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3374
|
+
// Create the file logger for matterbridge
|
|
2780
3375
|
if (param === 'true')
|
|
2781
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3376
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2782
3377
|
else
|
|
2783
3378
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2784
3379
|
res.json({ message: 'Command received' });
|
|
2785
3380
|
return;
|
|
2786
3381
|
}
|
|
3382
|
+
// Handle the command setmbloglevel from Settings
|
|
2787
3383
|
if (command === 'setmjlogfile') {
|
|
2788
3384
|
this.log.debug('Matter file log:', param);
|
|
2789
3385
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2810,36 +3406,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2810
3406
|
res.json({ message: 'Command received' });
|
|
2811
3407
|
return;
|
|
2812
3408
|
}
|
|
3409
|
+
// Handle the command unregister from Settings
|
|
2813
3410
|
if (command === 'unregister') {
|
|
2814
3411
|
await this.unregisterAndShutdownProcess();
|
|
2815
3412
|
res.json({ message: 'Command received' });
|
|
2816
3413
|
return;
|
|
2817
3414
|
}
|
|
3415
|
+
// Handle the command reset from Settings
|
|
2818
3416
|
if (command === 'reset') {
|
|
2819
3417
|
await this.shutdownProcessAndReset();
|
|
2820
3418
|
res.json({ message: 'Command received' });
|
|
2821
3419
|
return;
|
|
2822
3420
|
}
|
|
3421
|
+
// Handle the command factoryreset from Settings
|
|
2823
3422
|
if (command === 'factoryreset') {
|
|
2824
3423
|
await this.shutdownProcessAndFactoryReset();
|
|
2825
3424
|
res.json({ message: 'Command received' });
|
|
2826
3425
|
return;
|
|
2827
3426
|
}
|
|
3427
|
+
// Handle the command shutdown from Header
|
|
2828
3428
|
if (command === 'shutdown') {
|
|
2829
3429
|
await this.shutdownProcess();
|
|
2830
3430
|
res.json({ message: 'Command received' });
|
|
2831
3431
|
return;
|
|
2832
3432
|
}
|
|
3433
|
+
// Handle the command restart from Header
|
|
2833
3434
|
if (command === 'restart') {
|
|
2834
3435
|
await this.restartProcess();
|
|
2835
3436
|
res.json({ message: 'Command received' });
|
|
2836
3437
|
return;
|
|
2837
3438
|
}
|
|
3439
|
+
// Handle the command update from Header
|
|
2838
3440
|
if (command === 'update') {
|
|
2839
3441
|
this.log.info('Updating matterbridge...');
|
|
2840
3442
|
try {
|
|
2841
3443
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2842
3444
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3445
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2843
3446
|
}
|
|
2844
3447
|
catch (error) {
|
|
2845
3448
|
this.log.error('Error updating matterbridge');
|
|
@@ -2849,9 +3452,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2849
3452
|
res.json({ message: 'Command received' });
|
|
2850
3453
|
return;
|
|
2851
3454
|
}
|
|
3455
|
+
// Handle the command saveconfig from Home
|
|
2852
3456
|
if (command === 'saveconfig') {
|
|
2853
3457
|
param = param.replace(/\*/g, '\\');
|
|
2854
3458
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3459
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2855
3460
|
if (!this.plugins.has(param)) {
|
|
2856
3461
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2857
3462
|
}
|
|
@@ -2865,33 +3470,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2865
3470
|
res.json({ message: 'Command received' });
|
|
2866
3471
|
return;
|
|
2867
3472
|
}
|
|
3473
|
+
// Handle the command installplugin from Home
|
|
2868
3474
|
if (command === 'installplugin') {
|
|
2869
3475
|
param = param.replace(/\*/g, '\\');
|
|
2870
3476
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2871
3477
|
try {
|
|
2872
3478
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2873
3479
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3480
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2874
3481
|
}
|
|
2875
3482
|
catch (error) {
|
|
2876
3483
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2877
3484
|
}
|
|
2878
3485
|
this.wssSendRestartRequired();
|
|
2879
3486
|
param = param.split('@')[0];
|
|
3487
|
+
// Also add the plugin to matterbridge so no return!
|
|
2880
3488
|
if (param === 'matterbridge') {
|
|
3489
|
+
// 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
|
|
2881
3490
|
res.json({ message: 'Command received' });
|
|
2882
3491
|
return;
|
|
2883
3492
|
}
|
|
2884
3493
|
}
|
|
3494
|
+
// Handle the command addplugin from Home
|
|
2885
3495
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2886
3496
|
param = param.replace(/\*/g, '\\');
|
|
2887
3497
|
const plugin = await this.plugins.add(param);
|
|
2888
3498
|
if (plugin) {
|
|
2889
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
3499
|
+
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
2890
3500
|
}
|
|
2891
3501
|
res.json({ message: 'Command received' });
|
|
2892
3502
|
this.wssSendRefreshRequired();
|
|
2893
3503
|
return;
|
|
2894
3504
|
}
|
|
3505
|
+
// Handle the command removeplugin from Home
|
|
2895
3506
|
if (command === 'removeplugin') {
|
|
2896
3507
|
if (!this.plugins.has(param)) {
|
|
2897
3508
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2905,6 +3516,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2905
3516
|
this.wssSendRefreshRequired();
|
|
2906
3517
|
return;
|
|
2907
3518
|
}
|
|
3519
|
+
// Handle the command enableplugin from Home
|
|
2908
3520
|
if (command === 'enableplugin') {
|
|
2909
3521
|
if (!this.plugins.has(param)) {
|
|
2910
3522
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2922,13 +3534,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2922
3534
|
plugin.registeredDevices = undefined;
|
|
2923
3535
|
plugin.addedDevices = undefined;
|
|
2924
3536
|
await this.plugins.enable(param);
|
|
2925
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
3537
|
+
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
2926
3538
|
}
|
|
2927
3539
|
}
|
|
2928
3540
|
res.json({ message: 'Command received' });
|
|
2929
3541
|
this.wssSendRefreshRequired();
|
|
2930
3542
|
return;
|
|
2931
3543
|
}
|
|
3544
|
+
// Handle the command disableplugin from Home
|
|
2932
3545
|
if (command === 'disableplugin') {
|
|
2933
3546
|
if (!this.plugins.has(param)) {
|
|
2934
3547
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2945,6 +3558,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2945
3558
|
return;
|
|
2946
3559
|
}
|
|
2947
3560
|
});
|
|
3561
|
+
// Fallback for routing (must be the last route)
|
|
2948
3562
|
this.expressApp.get('*', (req, res) => {
|
|
2949
3563
|
this.log.debug('The frontend sent:', req.url);
|
|
2950
3564
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -2952,6 +3566,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2952
3566
|
});
|
|
2953
3567
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
2954
3568
|
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Retrieves the cluster text description from a given device.
|
|
3571
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3572
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3573
|
+
*/
|
|
2955
3574
|
getClusterTextFromDevice(device) {
|
|
2956
3575
|
const stringifyUserLabel = (endpoint) => {
|
|
2957
3576
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -2974,9 +3593,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2974
3593
|
return '';
|
|
2975
3594
|
};
|
|
2976
3595
|
let attributes = '';
|
|
3596
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
2977
3597
|
const clusterServers = device.getAllClusterServers();
|
|
2978
3598
|
clusterServers.forEach((clusterServer) => {
|
|
2979
3599
|
try {
|
|
3600
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
2980
3601
|
if (clusterServer.name === 'OnOff')
|
|
2981
3602
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
2982
3603
|
if (clusterServer.name === 'Switch')
|
|
@@ -3027,18 +3648,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
3027
3648
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
3028
3649
|
if (clusterServer.name === 'UserLabel')
|
|
3029
3650
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3651
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3030
3652
|
}
|
|
3031
3653
|
catch (error) {
|
|
3032
3654
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3033
3655
|
}
|
|
3034
3656
|
});
|
|
3657
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3035
3658
|
return attributes;
|
|
3036
3659
|
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3662
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3663
|
+
*
|
|
3664
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3665
|
+
*/
|
|
3037
3666
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3667
|
+
// Set the bridge mode
|
|
3038
3668
|
this.bridgeMode = 'bridge';
|
|
3669
|
+
// Set the first port to use
|
|
3039
3670
|
this.port = port;
|
|
3040
|
-
|
|
3671
|
+
// Set Matterbridge logger
|
|
3672
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3041
3673
|
this.log.debug('Matterbridge extension is starting...');
|
|
3674
|
+
// Initialize NodeStorage
|
|
3042
3675
|
this.matterbridgeDirectory = dataPath;
|
|
3043
3676
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3044
3677
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -3057,10 +3690,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
3057
3690
|
};
|
|
3058
3691
|
this.plugins.set(plugin);
|
|
3059
3692
|
this.plugins.saveToStorage();
|
|
3693
|
+
// Log system info and create .matterbridge directory
|
|
3060
3694
|
await this.logNodeAndSystemInfo();
|
|
3061
3695
|
this.matterbridgeDirectory = dataPath;
|
|
3696
|
+
// Set matter.js logger level and format
|
|
3062
3697
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3063
3698
|
Logger.format = MatterLogFormat.ANSI;
|
|
3699
|
+
// Start the storage and create matterbridgeContext
|
|
3064
3700
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3065
3701
|
if (!this.storageManager)
|
|
3066
3702
|
return false;
|
|
@@ -3070,7 +3706,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3070
3706
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3071
3707
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3072
3708
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3073
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3709
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
3074
3710
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
3075
3711
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3076
3712
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -3083,6 +3719,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3083
3719
|
await this.startMatterServer();
|
|
3084
3720
|
this.log.info('Matter server started');
|
|
3085
3721
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3722
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
3086
3723
|
setTimeout(() => {
|
|
3087
3724
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3088
3725
|
if (this.commissioningServer)
|
|
@@ -3092,14 +3729,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
3092
3729
|
}, 60 * 1000);
|
|
3093
3730
|
return this.commissioningServer.isCommissioned();
|
|
3094
3731
|
}
|
|
3732
|
+
/**
|
|
3733
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3734
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3735
|
+
*
|
|
3736
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3737
|
+
*/
|
|
3095
3738
|
async stopExtension() {
|
|
3739
|
+
// Closing matter
|
|
3096
3740
|
await this.stopMatterServer();
|
|
3741
|
+
// Clearing the session manager
|
|
3742
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3743
|
+
// Closing storage
|
|
3097
3744
|
await this.stopMatterStorage();
|
|
3098
3745
|
this.log.info('Matter server stopped');
|
|
3099
3746
|
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Checks if the extension is commissioned.
|
|
3749
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3750
|
+
*
|
|
3751
|
+
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3752
|
+
*/
|
|
3100
3753
|
isExtensionCommissioned() {
|
|
3101
3754
|
if (!this.commissioningServer)
|
|
3102
3755
|
return false;
|
|
3103
3756
|
return this.commissioningServer.isCommissioned();
|
|
3104
3757
|
}
|
|
3105
3758
|
}
|
|
3759
|
+
//# sourceMappingURL=matterbridge.js.map
|