matterbridge 1.6.8-dev.15 → 1.6.8-dev.16
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 +8 -4
- package/README-EDGE.md +3 -3
- 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 +483 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +758 -80
- 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 +1100 -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 +108 -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 +136 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +111 -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/frontend/build/asset-manifest.json +6 -6
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/css/{main.f44a67b7.css → main.0f9899a6.css} +2 -2
- package/frontend/build/static/css/main.0f9899a6.css.map +1 -0
- package/frontend/build/static/js/{main.b45edfff.js → main.4087dcdb.js} +3 -3
- package/frontend/build/static/js/main.4087dcdb.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/frontend/build/static/css/main.f44a67b7.css.map +0 -1
- package/frontend/build/static/js/main.b45edfff.js.map +0 -1
- /package/frontend/build/static/js/{main.b45edfff.js.LICENSE.txt → main.4087dcdb.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 1.5.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// Node.js modules
|
|
1
24
|
import { fileURLToPath } from 'url';
|
|
2
25
|
import { promises as fs } from 'fs';
|
|
3
26
|
import { exec, spawn } from 'child_process';
|
|
@@ -6,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,19 +168,40 @@ 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
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Retrieves the list of Matterbridge devices.
|
|
179
|
+
* @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
|
|
180
|
+
*/
|
|
140
181
|
getDevices() {
|
|
141
182
|
return this.devices.array();
|
|
142
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Retrieves the list of registered plugins.
|
|
186
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
187
|
+
*/
|
|
143
188
|
getPlugins() {
|
|
144
189
|
return this.plugins.array();
|
|
145
190
|
}
|
|
146
191
|
matterbridgeMessageHandler;
|
|
192
|
+
/** ***********************************************************************************************************************************/
|
|
193
|
+
/** loadInstance() and cleanup() methods */
|
|
194
|
+
/** ***********************************************************************************************************************************/
|
|
195
|
+
/**
|
|
196
|
+
* Loads an instance of the Matterbridge class.
|
|
197
|
+
* If an instance already exists, return that instance.
|
|
198
|
+
*
|
|
199
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
200
|
+
* @returns The loaded Matterbridge instance.
|
|
201
|
+
*/
|
|
147
202
|
static async loadInstance(initialize = false) {
|
|
148
203
|
if (!Matterbridge.instance) {
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
149
205
|
if (hasParameter('debug'))
|
|
150
206
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
151
207
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -154,6 +210,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
154
210
|
}
|
|
155
211
|
return Matterbridge.instance;
|
|
156
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Call cleanup().
|
|
215
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
216
|
+
*
|
|
217
|
+
*/
|
|
157
218
|
async destroyInstance() {
|
|
158
219
|
await this.cleanup('destroying instance...', false);
|
|
159
220
|
await waiter('destroying instance...', () => {
|
|
@@ -161,39 +222,60 @@ export class Matterbridge extends EventEmitter {
|
|
|
161
222
|
}, false, 60000, 100, false);
|
|
162
223
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
163
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Initializes the Matterbridge application.
|
|
227
|
+
*
|
|
228
|
+
* @remarks
|
|
229
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
230
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
231
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
232
|
+
*
|
|
233
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
234
|
+
*/
|
|
164
235
|
async initialize() {
|
|
236
|
+
// Set the restart mode
|
|
165
237
|
if (hasParameter('service'))
|
|
166
238
|
this.restartMode = 'service';
|
|
167
239
|
if (hasParameter('docker'))
|
|
168
240
|
this.restartMode = 'docker';
|
|
241
|
+
// Set the matterbridge directory
|
|
169
242
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
170
243
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
171
|
-
|
|
244
|
+
// Create matterbridge logger
|
|
245
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
246
|
+
// Initialize nodeStorage and nodeContext
|
|
172
247
|
try {
|
|
173
248
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
174
249
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
175
250
|
this.log.debug('Creating node storage context for matterbridge');
|
|
176
251
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
252
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
254
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
178
255
|
for (const key of keys) {
|
|
179
256
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
258
|
await this.nodeStorage?.storage.get(key);
|
|
181
259
|
}
|
|
182
260
|
const storages = await this.nodeStorage.getStorageNames();
|
|
183
261
|
for (const storage of storages) {
|
|
184
262
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
185
263
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
264
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
266
|
const keys = (await nodeContext?.storage.keys());
|
|
187
267
|
keys.forEach(async (key) => {
|
|
188
268
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
189
269
|
await nodeContext?.get(key);
|
|
190
270
|
});
|
|
191
271
|
}
|
|
272
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
192
273
|
this.log.debug('Creating node storage backup...');
|
|
193
274
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
194
275
|
this.log.debug('Created node storage backup');
|
|
195
276
|
}
|
|
196
277
|
catch (error) {
|
|
278
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
197
279
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
198
280
|
if (hasParameter('norestore')) {
|
|
199
281
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -208,45 +290,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
208
290
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
209
291
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
210
292
|
}
|
|
293
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
211
294
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
295
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
212
296
|
this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
|
|
297
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
213
298
|
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
214
299
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
300
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
215
301
|
if (hasParameter('logger')) {
|
|
216
302
|
const level = getParameter('logger');
|
|
217
303
|
if (level === 'debug') {
|
|
218
|
-
this.log.logLevel = "debug"
|
|
304
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
219
305
|
}
|
|
220
306
|
else if (level === 'info') {
|
|
221
|
-
this.log.logLevel = "info"
|
|
307
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
222
308
|
}
|
|
223
309
|
else if (level === 'notice') {
|
|
224
|
-
this.log.logLevel = "notice"
|
|
310
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
225
311
|
}
|
|
226
312
|
else if (level === 'warn') {
|
|
227
|
-
this.log.logLevel = "warn"
|
|
313
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
228
314
|
}
|
|
229
315
|
else if (level === 'error') {
|
|
230
|
-
this.log.logLevel = "error"
|
|
316
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
231
317
|
}
|
|
232
318
|
else if (level === 'fatal') {
|
|
233
|
-
this.log.logLevel = "fatal"
|
|
319
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
234
320
|
}
|
|
235
321
|
else {
|
|
236
322
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
237
|
-
this.log.logLevel = "info"
|
|
323
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
238
324
|
}
|
|
239
325
|
}
|
|
240
326
|
else {
|
|
241
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
327
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
242
328
|
}
|
|
243
329
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
330
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
244
331
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
245
332
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
246
333
|
this.matterbridgeInformation.fileLogger = true;
|
|
247
334
|
}
|
|
248
335
|
this.log.notice('Matterbridge is starting...');
|
|
249
336
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
337
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
250
338
|
if (hasParameter('matterlogger')) {
|
|
251
339
|
const level = getParameter('matterlogger');
|
|
252
340
|
if (level === 'debug') {
|
|
@@ -277,6 +365,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
277
365
|
}
|
|
278
366
|
Logger.format = MatterLogFormat.ANSI;
|
|
279
367
|
Logger.setLogger('default', this.createMatterLogger());
|
|
368
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
280
369
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
281
370
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
282
371
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -285,6 +374,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
285
374
|
});
|
|
286
375
|
}
|
|
287
376
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
288
378
|
if (hasParameter('mdnsinterface')) {
|
|
289
379
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
290
380
|
}
|
|
@@ -293,6 +383,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
293
383
|
if (this.mdnsInterface === '')
|
|
294
384
|
this.mdnsInterface = undefined;
|
|
295
385
|
}
|
|
386
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
296
387
|
if (hasParameter('ipv4address')) {
|
|
297
388
|
this.ipv4address = getParameter('ipv4address');
|
|
298
389
|
}
|
|
@@ -301,6 +392,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
301
392
|
if (this.ipv4address === '')
|
|
302
393
|
this.ipv4address = undefined;
|
|
303
394
|
}
|
|
395
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
304
396
|
if (hasParameter('ipv6address')) {
|
|
305
397
|
this.ipv6address = getParameter('ipv6address');
|
|
306
398
|
}
|
|
@@ -309,17 +401,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
309
401
|
if (this.ipv6address === '')
|
|
310
402
|
this.ipv6address = undefined;
|
|
311
403
|
}
|
|
404
|
+
// Initialize PluginManager
|
|
312
405
|
this.plugins = new PluginManager(this);
|
|
313
406
|
await this.plugins.loadFromStorage();
|
|
407
|
+
// Initialize DeviceManager
|
|
314
408
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
409
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
315
410
|
for (const plugin of this.plugins) {
|
|
316
411
|
const packageJson = await this.plugins.parse(plugin);
|
|
317
412
|
if (packageJson === null && !hasParameter('add')) {
|
|
413
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
414
|
+
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
318
415
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
319
416
|
try {
|
|
320
417
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
321
418
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
322
419
|
plugin.error = false;
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
323
421
|
}
|
|
324
422
|
catch (error) {
|
|
325
423
|
plugin.error = true;
|
|
@@ -336,6 +434,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
336
434
|
await plugin.nodeContext.set('description', plugin.description);
|
|
337
435
|
await plugin.nodeContext.set('author', plugin.author);
|
|
338
436
|
}
|
|
437
|
+
// Log system info and create .matterbridge directory
|
|
339
438
|
await this.logNodeAndSystemInfo();
|
|
340
439
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
341
440
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -343,6 +442,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
343
442
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
344
443
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
345
444
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
445
|
+
// Check node version and throw error
|
|
346
446
|
const minNodeVersion = 18;
|
|
347
447
|
const nodeVersion = process.versions.node;
|
|
348
448
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -350,10 +450,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
350
450
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
351
451
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
352
452
|
}
|
|
453
|
+
// Register process handlers
|
|
353
454
|
this.registerProcessHandlers();
|
|
455
|
+
// Parse command line
|
|
354
456
|
await this.parseCommandLine();
|
|
355
457
|
this.initialized = true;
|
|
356
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
461
|
+
* @private
|
|
462
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
463
|
+
*/
|
|
357
464
|
async parseCommandLine() {
|
|
358
465
|
if (hasParameter('help')) {
|
|
359
466
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -461,24 +568,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
461
568
|
}
|
|
462
569
|
if (hasParameter('factoryreset')) {
|
|
463
570
|
try {
|
|
464
|
-
|
|
571
|
+
// Delete old matter storage file
|
|
572
|
+
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
573
|
+
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
574
|
+
await fs.unlink(file);
|
|
465
575
|
}
|
|
466
576
|
catch (err) {
|
|
467
|
-
|
|
577
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
578
|
+
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
579
|
+
}
|
|
468
580
|
}
|
|
469
581
|
try {
|
|
470
|
-
|
|
582
|
+
// Delete matter node storage directory with its subdirectories
|
|
583
|
+
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
584
|
+
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
585
|
+
await fs.rm(dir, { recursive: true });
|
|
471
586
|
}
|
|
472
587
|
catch (err) {
|
|
473
|
-
|
|
588
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
589
|
+
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
590
|
+
}
|
|
474
591
|
}
|
|
475
592
|
try {
|
|
476
|
-
|
|
593
|
+
// Delete node storage directory with its subdirectories
|
|
594
|
+
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
595
|
+
this.log.info(`Removing storage directory: ${dir}`);
|
|
596
|
+
await fs.rm(dir, { recursive: true });
|
|
477
597
|
}
|
|
478
598
|
catch (err) {
|
|
479
|
-
|
|
599
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
600
|
+
this.log.error(`Error removing storage directory: ${err}`);
|
|
601
|
+
}
|
|
480
602
|
}
|
|
481
|
-
this.log.info('Factory reset done! Remove all paired
|
|
603
|
+
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
482
604
|
this.nodeContext = undefined;
|
|
483
605
|
this.nodeStorage = undefined;
|
|
484
606
|
this.plugins.clear();
|
|
@@ -486,6 +608,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
486
608
|
this.emit('shutdown');
|
|
487
609
|
return;
|
|
488
610
|
}
|
|
611
|
+
// Start the matter storage and create the matterbridge context
|
|
489
612
|
try {
|
|
490
613
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
491
614
|
}
|
|
@@ -493,7 +616,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
493
616
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
494
617
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
495
618
|
}
|
|
496
|
-
if
|
|
619
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
620
|
+
if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
|
|
497
621
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
498
622
|
await this.matterbridgeContext?.clearAll();
|
|
499
623
|
await this.stopMatterStorage();
|
|
@@ -501,7 +625,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
501
625
|
this.emit('shutdown');
|
|
502
626
|
return;
|
|
503
627
|
}
|
|
504
|
-
if
|
|
628
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
629
|
+
if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
505
630
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
506
631
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
507
632
|
if (plugin) {
|
|
@@ -520,28 +645,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
520
645
|
this.emit('shutdown');
|
|
521
646
|
return;
|
|
522
647
|
}
|
|
648
|
+
// Initialize frontend
|
|
523
649
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
524
650
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
651
|
+
// Check each 60 minutes the latest versions
|
|
525
652
|
this.checkUpdateInterval = setInterval(() => {
|
|
526
653
|
this.getMatterbridgeLatestVersion();
|
|
527
654
|
for (const plugin of this.plugins) {
|
|
528
655
|
this.getPluginLatestVersion(plugin);
|
|
529
656
|
}
|
|
530
657
|
}, 60 * 60 * 1000);
|
|
658
|
+
// Start the matterbridge in mode test
|
|
531
659
|
if (hasParameter('test')) {
|
|
532
660
|
this.bridgeMode = 'bridge';
|
|
533
661
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
534
662
|
return;
|
|
535
663
|
}
|
|
664
|
+
// Start the matterbridge in mode controller
|
|
536
665
|
if (hasParameter('controller')) {
|
|
537
666
|
this.bridgeMode = 'controller';
|
|
538
667
|
await this.startController();
|
|
539
668
|
return;
|
|
540
669
|
}
|
|
670
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
541
671
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
542
672
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
543
673
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
544
674
|
}
|
|
675
|
+
// Start matterbridge in bridge mode
|
|
545
676
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
546
677
|
this.bridgeMode = 'bridge';
|
|
547
678
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -550,6 +681,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
550
681
|
await this.startBridge();
|
|
551
682
|
return;
|
|
552
683
|
}
|
|
684
|
+
// Start matterbridge in childbridge mode
|
|
553
685
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
554
686
|
this.bridgeMode = 'childbridge';
|
|
555
687
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -559,17 +691,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
559
691
|
return;
|
|
560
692
|
}
|
|
561
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Asynchronously loads and starts the registered plugins.
|
|
696
|
+
*
|
|
697
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
698
|
+
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
699
|
+
*
|
|
700
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
701
|
+
*/
|
|
562
702
|
async startPlugins() {
|
|
703
|
+
// Check, load and start the plugins
|
|
563
704
|
for (const plugin of this.plugins) {
|
|
564
705
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
565
706
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
707
|
+
// Check if the plugin is available
|
|
566
708
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
567
709
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
568
710
|
plugin.enabled = false;
|
|
569
711
|
plugin.error = true;
|
|
570
712
|
continue;
|
|
571
713
|
}
|
|
572
|
-
|
|
714
|
+
// Check if the plugin has a new version
|
|
715
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
573
716
|
if (!plugin.enabled) {
|
|
574
717
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
575
718
|
continue;
|
|
@@ -584,20 +727,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
584
727
|
plugin.addedDevices = undefined;
|
|
585
728
|
plugin.qrPairingCode = undefined;
|
|
586
729
|
plugin.manualPairingCode = undefined;
|
|
587
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
730
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
588
731
|
}
|
|
589
732
|
this.wssSendRefreshRequired();
|
|
590
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
736
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
737
|
+
*/
|
|
591
738
|
registerProcessHandlers() {
|
|
592
739
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
593
740
|
process.removeAllListeners('uncaughtException');
|
|
594
741
|
process.removeAllListeners('unhandledRejection');
|
|
595
742
|
this.exceptionHandler = async (error) => {
|
|
596
743
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
744
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
597
745
|
};
|
|
598
746
|
process.on('uncaughtException', this.exceptionHandler);
|
|
599
747
|
this.rejectionHandler = async (reason, promise) => {
|
|
600
748
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
749
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
601
750
|
};
|
|
602
751
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
603
752
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -610,6 +759,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
610
759
|
};
|
|
611
760
|
process.on('SIGTERM', this.sigtermHandler);
|
|
612
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
764
|
+
*/
|
|
613
765
|
deregisterProcesslHandlers() {
|
|
614
766
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
615
767
|
if (this.exceptionHandler)
|
|
@@ -626,7 +778,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
626
778
|
process.off('SIGTERM', this.sigtermHandler);
|
|
627
779
|
this.sigtermHandler = undefined;
|
|
628
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* Logs the node and system information.
|
|
783
|
+
*/
|
|
629
784
|
async logNodeAndSystemInfo() {
|
|
785
|
+
// IP address information
|
|
630
786
|
const networkInterfaces = os.networkInterfaces();
|
|
631
787
|
this.systemInformation.ipv4Address = '';
|
|
632
788
|
this.systemInformation.ipv6Address = '';
|
|
@@ -646,7 +802,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
646
802
|
this.systemInformation.macAddress = detail.mac;
|
|
647
803
|
}
|
|
648
804
|
}
|
|
649
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
805
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
650
806
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
651
807
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
652
808
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -654,19 +810,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
654
810
|
break;
|
|
655
811
|
}
|
|
656
812
|
}
|
|
813
|
+
// Node information
|
|
657
814
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
658
815
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
659
816
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
660
817
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
818
|
+
// Host system information
|
|
661
819
|
this.systemInformation.hostname = os.hostname();
|
|
662
820
|
this.systemInformation.user = os.userInfo().username;
|
|
663
|
-
this.systemInformation.osType = os.type();
|
|
664
|
-
this.systemInformation.osRelease = os.release();
|
|
665
|
-
this.systemInformation.osPlatform = os.platform();
|
|
666
|
-
this.systemInformation.osArch = os.arch();
|
|
667
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
668
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
669
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
821
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
822
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
823
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
824
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
825
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
826
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
827
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
828
|
+
// Log the system information
|
|
670
829
|
this.log.debug('Host System Information:');
|
|
671
830
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
672
831
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -682,15 +841,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
682
841
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
683
842
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
684
843
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
844
|
+
// Home directory
|
|
685
845
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
686
846
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
687
847
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
848
|
+
// Package root directory
|
|
688
849
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
689
850
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
690
851
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
691
852
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
853
|
+
// Global node_modules directory
|
|
692
854
|
if (this.nodeContext)
|
|
693
855
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
856
|
+
// First run of Matterbridge so the node storage is empty
|
|
694
857
|
if (this.globalModulesDirectory === '') {
|
|
695
858
|
try {
|
|
696
859
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -714,6 +877,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
714
877
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
715
878
|
});
|
|
716
879
|
}
|
|
880
|
+
// Create the data directory .matterbridge in the home directory
|
|
717
881
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
718
882
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
719
883
|
try {
|
|
@@ -737,6 +901,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
737
901
|
}
|
|
738
902
|
}
|
|
739
903
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
904
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
740
905
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
741
906
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
742
907
|
try {
|
|
@@ -760,19 +925,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
760
925
|
}
|
|
761
926
|
}
|
|
762
927
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
928
|
+
// Matterbridge version
|
|
763
929
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
764
930
|
this.matterbridgeVersion = packageJson.version;
|
|
765
931
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
766
932
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
933
|
+
// Matterbridge latest version
|
|
767
934
|
if (this.nodeContext)
|
|
768
935
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
769
936
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
770
937
|
this.getMatterbridgeLatestVersion();
|
|
938
|
+
// Current working directory
|
|
771
939
|
const currentDir = process.cwd();
|
|
772
940
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
941
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
773
942
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
774
943
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
775
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
947
|
+
* @param packageName - The name of the package.
|
|
948
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
949
|
+
*/
|
|
776
950
|
async getLatestVersion(packageName) {
|
|
777
951
|
return new Promise((resolve, reject) => {
|
|
778
952
|
this.execRunningCount++;
|
|
@@ -787,6 +961,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
787
961
|
});
|
|
788
962
|
});
|
|
789
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
966
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
967
|
+
*/
|
|
790
968
|
async getGlobalNodeModules() {
|
|
791
969
|
return new Promise((resolve, reject) => {
|
|
792
970
|
this.execRunningCount++;
|
|
@@ -801,6 +979,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
979
|
});
|
|
802
980
|
});
|
|
803
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
984
|
+
* @private
|
|
985
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
986
|
+
*/
|
|
804
987
|
async getMatterbridgeLatestVersion() {
|
|
805
988
|
this.getLatestVersion('matterbridge')
|
|
806
989
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -817,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
817
1000
|
})
|
|
818
1001
|
.catch((error) => {
|
|
819
1002
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
1003
|
+
// error.stack && this.log.debug(error.stack);
|
|
820
1004
|
});
|
|
821
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
1008
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
1009
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
1010
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
1011
|
+
*
|
|
1012
|
+
* @private
|
|
1013
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
1014
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
1015
|
+
*/
|
|
822
1016
|
async getPluginLatestVersion(plugin) {
|
|
823
1017
|
this.getLatestVersion(plugin.name)
|
|
824
1018
|
.then(async (latestVersion) => {
|
|
@@ -830,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
830
1024
|
})
|
|
831
1025
|
.catch((error) => {
|
|
832
1026
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1027
|
+
// error.stack && this.log.debug(error.stack);
|
|
833
1028
|
});
|
|
834
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1032
|
+
*
|
|
1033
|
+
* @returns {Function} The MatterLogger function.
|
|
1034
|
+
*/
|
|
835
1035
|
createMatterLogger() {
|
|
836
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1036
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
837
1037
|
return (_level, formattedLog) => {
|
|
838
1038
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
839
1039
|
const message = formattedLog.slice(65);
|
|
840
1040
|
matterLogger.logName = logger;
|
|
841
1041
|
switch (_level) {
|
|
842
1042
|
case MatterLogLevel.DEBUG:
|
|
843
|
-
matterLogger.log("debug"
|
|
1043
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
844
1044
|
break;
|
|
845
1045
|
case MatterLogLevel.INFO:
|
|
846
|
-
matterLogger.log("info"
|
|
1046
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
847
1047
|
break;
|
|
848
1048
|
case MatterLogLevel.NOTICE:
|
|
849
|
-
matterLogger.log("notice"
|
|
1049
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
850
1050
|
break;
|
|
851
1051
|
case MatterLogLevel.WARN:
|
|
852
|
-
matterLogger.log("warn"
|
|
1052
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
853
1053
|
break;
|
|
854
1054
|
case MatterLogLevel.ERROR:
|
|
855
|
-
matterLogger.log("error"
|
|
1055
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
856
1056
|
break;
|
|
857
1057
|
case MatterLogLevel.FATAL:
|
|
858
|
-
matterLogger.log("fatal"
|
|
1058
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
859
1059
|
break;
|
|
860
1060
|
default:
|
|
861
|
-
matterLogger.log("debug"
|
|
1061
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
862
1062
|
break;
|
|
863
1063
|
}
|
|
864
1064
|
};
|
|
865
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Creates a Matter File Logger.
|
|
1068
|
+
*
|
|
1069
|
+
* @param {string} filePath - The path to the log file.
|
|
1070
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1071
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1072
|
+
*/
|
|
866
1073
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1074
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
867
1075
|
let fileSize = 0;
|
|
868
1076
|
if (unlink) {
|
|
869
1077
|
try {
|
|
@@ -912,53 +1120,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
912
1120
|
}
|
|
913
1121
|
};
|
|
914
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Update matterbridge and cleanup.
|
|
1125
|
+
*/
|
|
915
1126
|
async updateProcess() {
|
|
916
1127
|
await this.cleanup('updating...', false);
|
|
917
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1131
|
+
*/
|
|
918
1132
|
async restartProcess() {
|
|
919
1133
|
await this.cleanup('restarting...', true);
|
|
920
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Shut down the process by exiting the current process.
|
|
1137
|
+
*/
|
|
921
1138
|
async shutdownProcess() {
|
|
922
1139
|
await this.cleanup('shutting down...', false);
|
|
923
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Shut down the process and reset.
|
|
1143
|
+
*/
|
|
924
1144
|
async unregisterAndShutdownProcess() {
|
|
925
1145
|
this.log.info('Unregistering all devices and shutting down...');
|
|
926
|
-
for (const plugin of this.plugins) {
|
|
927
|
-
|
|
1146
|
+
for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
|
|
1147
|
+
if (this.edge)
|
|
1148
|
+
await this.removeAllBridgedEndpoints(plugin.name);
|
|
1149
|
+
else
|
|
1150
|
+
await this.removeAllBridgedDevices(plugin.name);
|
|
928
1151
|
}
|
|
929
1152
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
930
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Shut down the process and reset.
|
|
1156
|
+
*/
|
|
931
1157
|
async shutdownProcessAndReset() {
|
|
932
1158
|
await this.cleanup('shutting down with reset...', false);
|
|
933
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Shut down the process and factory reset.
|
|
1162
|
+
*/
|
|
934
1163
|
async shutdownProcessAndFactoryReset() {
|
|
935
1164
|
await this.cleanup('shutting down with factory reset...', false);
|
|
936
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Cleans up the Matterbridge instance.
|
|
1168
|
+
* @param message - The cleanup message.
|
|
1169
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1170
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1171
|
+
*/
|
|
937
1172
|
async cleanup(message, restart = false) {
|
|
938
1173
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
939
1174
|
this.hasCleanupStarted = true;
|
|
940
1175
|
this.log.info(message);
|
|
1176
|
+
// Deregisters the process handlers
|
|
941
1177
|
this.deregisterProcesslHandlers();
|
|
1178
|
+
// Clear the start matter interval
|
|
942
1179
|
if (this.startMatterInterval) {
|
|
943
1180
|
clearInterval(this.startMatterInterval);
|
|
944
1181
|
this.startMatterInterval = undefined;
|
|
945
1182
|
this.log.debug('Start matter interval cleared');
|
|
946
1183
|
}
|
|
1184
|
+
// Clear the check update interval
|
|
947
1185
|
if (this.checkUpdateInterval) {
|
|
948
1186
|
clearInterval(this.checkUpdateInterval);
|
|
949
1187
|
this.checkUpdateInterval = undefined;
|
|
950
1188
|
this.log.debug('Check update interval cleared');
|
|
951
1189
|
}
|
|
1190
|
+
// Clear the configure timeout
|
|
952
1191
|
if (this.configureTimeout) {
|
|
953
1192
|
clearTimeout(this.configureTimeout);
|
|
954
1193
|
this.configureTimeout = undefined;
|
|
955
1194
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
956
1195
|
}
|
|
1196
|
+
// Clear the reachability timeout
|
|
957
1197
|
if (this.reachabilityTimeout) {
|
|
958
1198
|
clearTimeout(this.reachabilityTimeout);
|
|
959
1199
|
this.reachabilityTimeout = undefined;
|
|
960
1200
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
961
1201
|
}
|
|
1202
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
962
1203
|
for (const plugin of this.plugins) {
|
|
963
1204
|
if (!plugin.enabled || plugin.error)
|
|
964
1205
|
continue;
|
|
@@ -969,6 +1210,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
969
1210
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
970
1211
|
}
|
|
971
1212
|
}
|
|
1213
|
+
// Convert the matter storage to the new format
|
|
972
1214
|
if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
|
|
973
1215
|
if (this.bridgeMode === 'bridge') {
|
|
974
1216
|
await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -981,24 +1223,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
981
1223
|
}
|
|
982
1224
|
}
|
|
983
1225
|
}
|
|
1226
|
+
// Close the http server
|
|
984
1227
|
if (this.httpServer) {
|
|
985
1228
|
this.httpServer.close();
|
|
986
1229
|
this.httpServer.removeAllListeners();
|
|
987
1230
|
this.httpServer = undefined;
|
|
988
1231
|
this.log.debug('Frontend http server closed successfully');
|
|
989
1232
|
}
|
|
1233
|
+
// Close the https server
|
|
990
1234
|
if (this.httpsServer) {
|
|
991
1235
|
this.httpsServer.close();
|
|
992
1236
|
this.httpsServer.removeAllListeners();
|
|
993
1237
|
this.httpsServer = undefined;
|
|
994
1238
|
this.log.debug('Frontend https server closed successfully');
|
|
995
1239
|
}
|
|
1240
|
+
// Remove listeners from the express app
|
|
996
1241
|
if (this.expressApp) {
|
|
997
1242
|
this.expressApp.removeAllListeners();
|
|
998
1243
|
this.expressApp = undefined;
|
|
999
1244
|
this.log.debug('Frontend app closed successfully');
|
|
1000
1245
|
}
|
|
1246
|
+
// Close the WebSocket server
|
|
1001
1247
|
if (this.webSocketServer) {
|
|
1248
|
+
// Close all active connections
|
|
1002
1249
|
this.webSocketServer.clients.forEach((client) => {
|
|
1003
1250
|
if (client.readyState === WebSocket.OPEN) {
|
|
1004
1251
|
client.close();
|
|
@@ -1014,26 +1261,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1014
1261
|
});
|
|
1015
1262
|
this.webSocketServer = undefined;
|
|
1016
1263
|
}
|
|
1264
|
+
// Closing matter
|
|
1017
1265
|
await this.stopMatterServer();
|
|
1266
|
+
// Closing matter storage
|
|
1018
1267
|
await this.stopMatterStorage();
|
|
1268
|
+
// Remove the matterfilelogger
|
|
1019
1269
|
try {
|
|
1020
1270
|
Logger.removeLogger('matterfilelogger');
|
|
1271
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1021
1272
|
}
|
|
1022
1273
|
catch (error) {
|
|
1274
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1023
1275
|
}
|
|
1276
|
+
// Serialize registeredDevices
|
|
1024
1277
|
if (this.nodeStorage && this.nodeContext) {
|
|
1025
1278
|
this.log.info('Saving registered devices...');
|
|
1026
1279
|
const serializedRegisteredDevices = [];
|
|
1027
1280
|
this.devices.forEach(async (device) => {
|
|
1028
1281
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1282
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1029
1283
|
if (serializedMatterbridgeDevice)
|
|
1030
1284
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1031
1285
|
});
|
|
1032
1286
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1033
1287
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1288
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1034
1289
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1035
1290
|
await this.nodeContext.close();
|
|
1036
1291
|
this.nodeContext = undefined;
|
|
1292
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1037
1293
|
for (const plugin of this.plugins) {
|
|
1038
1294
|
if (plugin.nodeContext) {
|
|
1039
1295
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1063,17 +1319,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1063
1319
|
}
|
|
1064
1320
|
}
|
|
1065
1321
|
else {
|
|
1066
|
-
if (message === 'shutting down with reset...') {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1322
|
+
if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
|
|
1323
|
+
try {
|
|
1324
|
+
// Delete old matter storage file
|
|
1325
|
+
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1326
|
+
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1327
|
+
await fs.unlink(file);
|
|
1328
|
+
}
|
|
1329
|
+
catch (error) {
|
|
1330
|
+
this.log.debug(`Error resetting old matter storage file: ${error}`);
|
|
1331
|
+
}
|
|
1332
|
+
try {
|
|
1333
|
+
// Delete matter node storage directory with its subdirectories
|
|
1334
|
+
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1335
|
+
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1336
|
+
await fs.rm(dir, { recursive: true });
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1339
|
+
this.log.debug(`Error resetting matter node storage file: ${error}`);
|
|
1340
|
+
}
|
|
1341
|
+
this.log.info('Reset done! Remove all paired fabrics from the controllers.');
|
|
1070
1342
|
}
|
|
1071
1343
|
if (message === 'shutting down with factory reset...') {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1344
|
+
try {
|
|
1345
|
+
// Delete node storage directory with its subdirectories
|
|
1346
|
+
this.log.info('Resetting Matterbridge storage...');
|
|
1347
|
+
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1348
|
+
}
|
|
1349
|
+
catch (error) {
|
|
1350
|
+
this.log.debug(`Error resetting Matterbridge storage: ${error}`);
|
|
1351
|
+
}
|
|
1352
|
+
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1077
1353
|
}
|
|
1078
1354
|
this.log.notice('Cleanup completed. Shutting down...');
|
|
1079
1355
|
Matterbridge.instance = undefined;
|
|
@@ -1083,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1083
1359
|
this.initialized = false;
|
|
1084
1360
|
}
|
|
1085
1361
|
}
|
|
1362
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1086
1363
|
async addBridgedEndpoint(pluginName, device) {
|
|
1364
|
+
// Nothing to do here
|
|
1087
1365
|
}
|
|
1366
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1088
1367
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1368
|
+
// Nothing to do here
|
|
1089
1369
|
}
|
|
1370
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1090
1371
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1372
|
+
// Nothing to do here
|
|
1091
1373
|
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Adds a bridged device to the Matterbridge.
|
|
1376
|
+
* @param pluginName - The name of the plugin.
|
|
1377
|
+
* @param device - The bridged device to add.
|
|
1378
|
+
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1379
|
+
*/
|
|
1092
1380
|
async addBridgedDevice(pluginName, device) {
|
|
1093
1381
|
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1382
|
+
// Check if the plugin is registered
|
|
1094
1383
|
const plugin = this.plugins.get(pluginName);
|
|
1095
1384
|
if (!plugin) {
|
|
1096
1385
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1097
1386
|
return;
|
|
1098
1387
|
}
|
|
1388
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1099
1389
|
if (this.bridgeMode === 'bridge') {
|
|
1100
1390
|
if (!this.matterAggregator) {
|
|
1101
1391
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1103,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1103
1393
|
}
|
|
1104
1394
|
this.matterAggregator.addBridgedDevice(device);
|
|
1105
1395
|
}
|
|
1396
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1397
|
+
// Register and add the device in childbridge mode
|
|
1106
1398
|
if (this.bridgeMode === 'childbridge') {
|
|
1107
1399
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1400
|
+
// Check if the plugin is locked with the commissioning server
|
|
1108
1401
|
if (!plugin.locked) {
|
|
1109
1402
|
plugin.locked = true;
|
|
1110
1403
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1118,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1118
1411
|
}
|
|
1119
1412
|
}
|
|
1120
1413
|
if (plugin.type === 'DynamicPlatform') {
|
|
1414
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1121
1415
|
if (!plugin.locked) {
|
|
1122
1416
|
plugin.locked = true;
|
|
1123
1417
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1138,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1138
1432
|
plugin.registeredDevices++;
|
|
1139
1433
|
if (plugin.addedDevices !== undefined)
|
|
1140
1434
|
plugin.addedDevices++;
|
|
1435
|
+
// Add the device to the DeviceManager
|
|
1141
1436
|
this.devices.set(device);
|
|
1142
1437
|
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}`);
|
|
1143
1438
|
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Removes a bridged device from the Matterbridge.
|
|
1441
|
+
* @param pluginName - The name of the plugin.
|
|
1442
|
+
* @param device - The device to be removed.
|
|
1443
|
+
* @returns A Promise that resolves when the device is successfully removed.
|
|
1444
|
+
*/
|
|
1144
1445
|
async removeBridgedDevice(pluginName, device) {
|
|
1145
1446
|
this.log.debug(`Removing bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1447
|
+
// Check if the plugin is registered
|
|
1146
1448
|
const plugin = this.plugins.get(pluginName);
|
|
1147
1449
|
if (!plugin) {
|
|
1148
1450
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1149
1451
|
return;
|
|
1150
1452
|
}
|
|
1453
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1151
1454
|
if (this.bridgeMode === 'bridge') {
|
|
1152
1455
|
if (!this.matterAggregator) {
|
|
1153
1456
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1157,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1157
1460
|
device.setBridgedDeviceReachability(false);
|
|
1158
1461
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1159
1462
|
}
|
|
1463
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1464
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1160
1465
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1161
1466
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1162
1467
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1164,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1164
1469
|
if (plugin.addedDevices !== undefined)
|
|
1165
1470
|
plugin.addedDevices--;
|
|
1166
1471
|
}
|
|
1472
|
+
// Remove the device in childbridge mode
|
|
1167
1473
|
if (this.bridgeMode === 'childbridge') {
|
|
1168
1474
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1169
1475
|
if (!plugin.commissioningServer) {
|
|
@@ -1187,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1187
1493
|
plugin.registeredDevices--;
|
|
1188
1494
|
if (plugin.addedDevices !== undefined)
|
|
1189
1495
|
plugin.addedDevices--;
|
|
1496
|
+
// Remove the commissioning server
|
|
1190
1497
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1191
1498
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1192
1499
|
plugin.commissioningServer = undefined;
|
|
1193
1500
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1194
1501
|
}
|
|
1195
1502
|
}
|
|
1503
|
+
// Remove the device from the DeviceManager
|
|
1196
1504
|
this.devices.remove(device);
|
|
1197
1505
|
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Removes all bridged devices associated with a specific plugin.
|
|
1508
|
+
*
|
|
1509
|
+
* @param pluginName - The name of the plugin.
|
|
1510
|
+
* @returns A promise that resolves when all devices have been removed.
|
|
1511
|
+
*/
|
|
1198
1512
|
async removeAllBridgedDevices(pluginName) {
|
|
1199
1513
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1200
1514
|
this.devices.forEach(async (device) => {
|
|
@@ -1203,7 +1517,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1203
1517
|
}
|
|
1204
1518
|
});
|
|
1205
1519
|
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Starts the Matterbridge in bridge mode.
|
|
1522
|
+
* @private
|
|
1523
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1524
|
+
*/
|
|
1206
1525
|
async startBridge() {
|
|
1526
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1207
1527
|
if (!this.storageManager)
|
|
1208
1528
|
throw new Error('No storage manager initialized');
|
|
1209
1529
|
if (!this.matterbridgeContext)
|
|
@@ -1222,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1222
1542
|
let failCount = 0;
|
|
1223
1543
|
this.startMatterInterval = setInterval(async () => {
|
|
1224
1544
|
for (const plugin of this.plugins) {
|
|
1545
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1225
1546
|
if (!plugin.enabled)
|
|
1226
1547
|
continue;
|
|
1227
1548
|
if (plugin.error) {
|
|
@@ -1246,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1246
1567
|
clearInterval(this.startMatterInterval);
|
|
1247
1568
|
this.startMatterInterval = undefined;
|
|
1248
1569
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
+
// Start the Matter server
|
|
1249
1571
|
await this.startMatterServer();
|
|
1250
1572
|
this.log.notice('Matter server started');
|
|
1573
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1251
1574
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1575
|
+
// Configure the plugins
|
|
1252
1576
|
this.configureTimeout = setTimeout(async () => {
|
|
1253
1577
|
for (const plugin of this.plugins) {
|
|
1254
1578
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1255
1579
|
continue;
|
|
1256
1580
|
try {
|
|
1257
|
-
await this.plugins.configure(plugin);
|
|
1581
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1258
1582
|
}
|
|
1259
1583
|
catch (error) {
|
|
1260
1584
|
plugin.error = true;
|
|
@@ -1263,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1263
1587
|
}
|
|
1264
1588
|
this.wssSendRefreshRequired();
|
|
1265
1589
|
}, 30 * 1000);
|
|
1590
|
+
// Setting reachability to true
|
|
1266
1591
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1267
1592
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1268
1593
|
if (this.commissioningServer)
|
|
@@ -1272,7 +1597,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1272
1597
|
}, 60 * 1000);
|
|
1273
1598
|
}, 1000);
|
|
1274
1599
|
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1602
|
+
* @private
|
|
1603
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1604
|
+
*/
|
|
1275
1605
|
async startChildbridge() {
|
|
1606
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1607
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1276
1608
|
if (!this.storageManager)
|
|
1277
1609
|
throw new Error('No storage manager initialized');
|
|
1278
1610
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
@@ -1282,6 +1614,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1282
1614
|
this.startMatterInterval = setInterval(async () => {
|
|
1283
1615
|
let allStarted = true;
|
|
1284
1616
|
for (const plugin of this.plugins) {
|
|
1617
|
+
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1285
1618
|
if (!plugin.enabled)
|
|
1286
1619
|
continue;
|
|
1287
1620
|
if (plugin.error) {
|
|
@@ -1309,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1309
1642
|
clearInterval(this.startMatterInterval);
|
|
1310
1643
|
this.startMatterInterval = undefined;
|
|
1311
1644
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1645
|
+
// Start the Matter server
|
|
1312
1646
|
await this.startMatterServer();
|
|
1313
1647
|
this.log.notice('Matter server started');
|
|
1648
|
+
// Configure the plugins
|
|
1314
1649
|
this.configureTimeout = setTimeout(async () => {
|
|
1315
1650
|
for (const plugin of this.plugins) {
|
|
1316
1651
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1317
1652
|
continue;
|
|
1318
1653
|
try {
|
|
1319
|
-
await this.plugins.configure(plugin);
|
|
1654
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1320
1655
|
}
|
|
1321
1656
|
catch (error) {
|
|
1322
1657
|
plugin.error = true;
|
|
@@ -1345,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1345
1680
|
continue;
|
|
1346
1681
|
}
|
|
1347
1682
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1683
|
+
// Setting reachability to true
|
|
1348
1684
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1349
1685
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1350
1686
|
if (plugin.commissioningServer)
|
|
@@ -1357,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1357
1693
|
}
|
|
1358
1694
|
}, 1000);
|
|
1359
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Starts the Matterbridge controller.
|
|
1698
|
+
* @private
|
|
1699
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
|
+
*/
|
|
1360
1701
|
async startController() {
|
|
1361
1702
|
if (!this.storageManager) {
|
|
1362
1703
|
this.log.error('No storage manager initialized');
|
|
@@ -1419,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1419
1760
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1420
1761
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1421
1762
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1422
|
-
}
|
|
1763
|
+
} // (hasParameter('pairingcode'))
|
|
1423
1764
|
if (hasParameter('unpairall')) {
|
|
1424
1765
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1425
1766
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1430,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1430
1771
|
return;
|
|
1431
1772
|
}
|
|
1432
1773
|
if (hasParameter('discover')) {
|
|
1774
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1775
|
+
// console.log(discover);
|
|
1433
1776
|
}
|
|
1434
1777
|
if (!this.commissioningController.isCommissioned()) {
|
|
1435
1778
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1470,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1470
1813
|
},
|
|
1471
1814
|
});
|
|
1472
1815
|
node.logStructure();
|
|
1816
|
+
// Get the interaction client
|
|
1473
1817
|
this.log.info('Getting the interaction client');
|
|
1474
1818
|
const interactionClient = await node.getInteractionClient();
|
|
1475
1819
|
let cluster;
|
|
1476
1820
|
let attributes;
|
|
1821
|
+
// Log BasicInformationCluster
|
|
1477
1822
|
cluster = BasicInformationCluster;
|
|
1478
1823
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1479
1824
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1483,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1483
1828
|
attributes.forEach((attribute) => {
|
|
1484
1829
|
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}`);
|
|
1485
1830
|
});
|
|
1831
|
+
// Log PowerSourceCluster
|
|
1486
1832
|
cluster = PowerSourceCluster;
|
|
1487
1833
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1488
1834
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1492,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1492
1838
|
attributes.forEach((attribute) => {
|
|
1493
1839
|
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}`);
|
|
1494
1840
|
});
|
|
1841
|
+
// Log ThreadNetworkDiagnostics
|
|
1495
1842
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1496
1843
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1497
1844
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1501,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1501
1848
|
attributes.forEach((attribute) => {
|
|
1502
1849
|
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}`);
|
|
1503
1850
|
});
|
|
1851
|
+
// Log SwitchCluster
|
|
1504
1852
|
cluster = SwitchCluster;
|
|
1505
1853
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1506
1854
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1521,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1521
1869
|
this.log.info('Subscribed to all attributes and events');
|
|
1522
1870
|
}
|
|
1523
1871
|
}
|
|
1872
|
+
/** ***********************************************************************************************************************************/
|
|
1873
|
+
/** Matter.js methods */
|
|
1874
|
+
/** ***********************************************************************************************************************************/
|
|
1875
|
+
/**
|
|
1876
|
+
* Starts the matter storage process based on the specified storage type and name.
|
|
1877
|
+
* @param {string} storageType - The type of storage to start (e.g., 'disk', 'json').
|
|
1878
|
+
* @param {string} storageName - The name of the storage file.
|
|
1879
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1880
|
+
*/
|
|
1524
1881
|
async startMatterStorage(storageType, storageName) {
|
|
1525
1882
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1526
1883
|
if (storageType === 'disk') {
|
|
@@ -1569,6 +1926,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1569
1926
|
await this.matterbridgeContext.set('passcode', this.passcode);
|
|
1570
1927
|
await this.matterbridgeContext.set('discriminator', this.discriminator);
|
|
1571
1928
|
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Convert the old API matter storage to the new API format.
|
|
1931
|
+
* @param {StorageContext} context - The context of Matterbridge or of the plugin.
|
|
1932
|
+
* @param {string} pluginName - The name of the plugin or Matterbridge.
|
|
1933
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1934
|
+
*/
|
|
1572
1935
|
async convertStorage(context, pluginName) {
|
|
1573
1936
|
if (this.edge !== false)
|
|
1574
1937
|
return;
|
|
@@ -1583,13 +1946,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1583
1946
|
else {
|
|
1584
1947
|
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1585
1948
|
}
|
|
1949
|
+
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1586
1950
|
const fabricManagerContext = context.createContext('FabricManager');
|
|
1587
1951
|
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1588
1952
|
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
|
|
1953
|
+
// Read EventHandler from the old storage
|
|
1589
1954
|
const eventHandlerContext = context.createContext('EventHandler');
|
|
1955
|
+
// Read SessionManager from the old storage
|
|
1590
1956
|
const sessionManagerContext = context.createContext('SessionManager');
|
|
1957
|
+
// Read EndpointStructure from the old storage
|
|
1591
1958
|
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1959
|
+
// Read generalCommissioning from the old storage
|
|
1592
1960
|
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1961
|
+
// Read basicInformation from the old storage
|
|
1593
1962
|
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1594
1963
|
const fabricInfo = {};
|
|
1595
1964
|
const fabricInfoArray = [];
|
|
@@ -1643,8 +2012,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1643
2012
|
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
1644
2013
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
1645
2014
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
2015
|
+
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
1646
2016
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
2017
|
+
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
1647
2018
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
|
|
2019
|
+
// ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
|
|
2020
|
+
// From fabric.rootNodeId if fabric.scopedClusterData.get(0x1f).get('acl') is empty
|
|
2021
|
+
// [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
|
|
1648
2022
|
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
1649
2023
|
await nodeStorage
|
|
1650
2024
|
.createContext('root')
|
|
@@ -1667,6 +2041,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1667
2041
|
.createContext('root')
|
|
1668
2042
|
.createContext('productDescription')
|
|
1669
2043
|
.set('vendorId', await context.get('vendorId', 0xfff1));
|
|
2044
|
+
/*
|
|
2045
|
+
"Matterbridge.EndpointStructure": {
|
|
2046
|
+
"unique_d60ca095a002f160-index_0": 1,
|
|
2047
|
+
"unique_d60ca095a002f160-index_0-custom_Switch0": 2,
|
|
2048
|
+
"unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
|
|
2049
|
+
"unique_d60ca095a002f160-index_0-custom_Light0": 4,
|
|
2050
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
|
|
2051
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
|
|
2052
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
|
|
2053
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
|
|
2054
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
|
|
2055
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
|
|
2056
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
|
|
2057
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
|
|
2058
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
|
|
2059
|
+
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
|
|
2060
|
+
"nextEndpointId": 5
|
|
2061
|
+
},
|
|
2062
|
+
*/
|
|
1670
2063
|
const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
|
|
1671
2064
|
this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
|
|
1672
2065
|
for (const key of await endpointStructureContext.keys()) {
|
|
@@ -1745,6 +2138,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1745
2138
|
this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
|
|
1746
2139
|
}
|
|
1747
2140
|
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Makes a backup copy of the specified matter JSON storage file.
|
|
2143
|
+
*
|
|
2144
|
+
* @param storageName - The name of the JSON storage file to be backed up.
|
|
2145
|
+
* @param backupName - The name of the backup file to be created.
|
|
2146
|
+
*/
|
|
1748
2147
|
async backupMatterStorage(storageName, backupName) {
|
|
1749
2148
|
try {
|
|
1750
2149
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1765,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1765
2164
|
}
|
|
1766
2165
|
}
|
|
1767
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Restore the specified matter JSON storage file.
|
|
2169
|
+
*
|
|
2170
|
+
* @param backupName - The name of the backup file to restore from.
|
|
2171
|
+
* @param storageName - The name of the JSON storage file to restored.
|
|
2172
|
+
*/
|
|
1768
2173
|
async restoreMatterStorage(backupName, storageName) {
|
|
1769
2174
|
try {
|
|
1770
2175
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1785,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1785
2190
|
}
|
|
1786
2191
|
}
|
|
1787
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Stops the matter storage.
|
|
2195
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2196
|
+
*/
|
|
1788
2197
|
async stopMatterStorage() {
|
|
1789
2198
|
this.log.debug('Stopping storage');
|
|
1790
2199
|
await this.storageManager?.close();
|
|
@@ -1793,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1793
2202
|
this.matterbridgeContext = undefined;
|
|
1794
2203
|
this.mattercontrollerContext = undefined;
|
|
1795
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
2207
|
+
* @param storageManager The storage manager to be used by the Matter server.
|
|
2208
|
+
*
|
|
2209
|
+
*/
|
|
1796
2210
|
createMatterServer(storageManager) {
|
|
1797
2211
|
this.log.debug('Creating matter server');
|
|
2212
|
+
// Validate mdnsInterface
|
|
1798
2213
|
if (this.mdnsInterface) {
|
|
1799
2214
|
const networkInterfaces = os.networkInterfaces();
|
|
1800
2215
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1810,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1810
2225
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1811
2226
|
return matterServer;
|
|
1812
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Starts the Matter server.
|
|
2230
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2231
|
+
*/
|
|
1813
2232
|
async startMatterServer() {
|
|
1814
2233
|
if (!this.matterServer) {
|
|
1815
2234
|
this.log.error('No matter server initialized');
|
|
@@ -1819,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1819
2238
|
this.log.debug('Starting matter server...');
|
|
1820
2239
|
await this.matterServer.start();
|
|
1821
2240
|
this.log.debug('Started matter server');
|
|
2241
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1822
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2245
|
+
*/
|
|
1823
2246
|
async stopMatterServer() {
|
|
1824
2247
|
this.log.debug('Stopping matter commissioningServer');
|
|
1825
2248
|
await this.commissioningServer?.close();
|
|
@@ -1833,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1833
2256
|
this.matterAggregator = undefined;
|
|
1834
2257
|
this.matterServer = undefined;
|
|
1835
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Creates a Matter Aggregator.
|
|
2261
|
+
* @param {StorageContext} context - The storage context.
|
|
2262
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2263
|
+
*/
|
|
1836
2264
|
async createMatterAggregator(context, pluginName) {
|
|
1837
2265
|
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
1838
2266
|
const matterAggregator = new Aggregator();
|
|
1839
2267
|
return matterAggregator;
|
|
1840
2268
|
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Creates a matter commissioning server.
|
|
2271
|
+
*
|
|
2272
|
+
* @param {StorageContext} context - The storage context.
|
|
2273
|
+
* @param {string} pluginName - The name of the commissioning server.
|
|
2274
|
+
* @returns {CommissioningServer} The created commissioning server.
|
|
2275
|
+
*/
|
|
1841
2276
|
async createCommisioningServer(context, pluginName) {
|
|
1842
2277
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1843
2278
|
const deviceName = await context.get('deviceName');
|
|
1844
2279
|
const deviceType = await context.get('deviceType');
|
|
1845
2280
|
const vendorId = await context.get('vendorId');
|
|
1846
|
-
const vendorName = await context.get('vendorName');
|
|
2281
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1847
2282
|
const productId = await context.get('productId');
|
|
1848
|
-
const productName = await context.get('productName');
|
|
2283
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1849
2284
|
const serialNumber = await context.get('serialNumber');
|
|
1850
2285
|
const uniqueId = await context.get('uniqueId');
|
|
1851
2286
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1852
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2287
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1853
2288
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1854
2289
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1855
2290
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
|
|
@@ -1857,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1857
2292
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1858
2293
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1859
2294
|
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} `);
|
|
2295
|
+
// Validate ipv4address
|
|
1860
2296
|
if (this.ipv4address) {
|
|
1861
2297
|
const networkInterfaces = os.networkInterfaces();
|
|
1862
2298
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1871,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1871
2307
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1872
2308
|
}
|
|
1873
2309
|
}
|
|
2310
|
+
// Validate ipv6address
|
|
1874
2311
|
if (this.ipv6address) {
|
|
1875
2312
|
const networkInterfaces = os.networkInterfaces();
|
|
1876
2313
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1901,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1901
2338
|
nodeLabel: productName,
|
|
1902
2339
|
productLabel: productName,
|
|
1903
2340
|
softwareVersion,
|
|
1904
|
-
softwareVersionString,
|
|
2341
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1905
2342
|
hardwareVersion,
|
|
1906
2343
|
hardwareVersionString,
|
|
1907
2344
|
uniqueId,
|
|
@@ -1997,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1997
2434
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1998
2435
|
return commissioningServer;
|
|
1999
2436
|
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Creates a commissioning server storage context.
|
|
2439
|
+
*
|
|
2440
|
+
* @param pluginName - The name of the plugin.
|
|
2441
|
+
* @param deviceName - The name of the device.
|
|
2442
|
+
* @param deviceType - The type of the device.
|
|
2443
|
+
* @param vendorId - The vendor ID.
|
|
2444
|
+
* @param vendorName - The vendor name.
|
|
2445
|
+
* @param productId - The product ID.
|
|
2446
|
+
* @param productName - The product name.
|
|
2447
|
+
* @param serialNumber - The serial number of the device (optional).
|
|
2448
|
+
* @param uniqueId - The unique ID of the device (optional).
|
|
2449
|
+
* @param softwareVersion - The software version of the device (optional).
|
|
2450
|
+
* @param softwareVersionString - The software version string of the device (optional).
|
|
2451
|
+
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2452
|
+
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2453
|
+
* @returns The storage context for the commissioning server.
|
|
2454
|
+
*/
|
|
2000
2455
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
2001
2456
|
if (!this.storageManager)
|
|
2002
2457
|
throw new Error('No storage manager initialized');
|
|
@@ -2024,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2024
2479
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2025
2480
|
return storageContext;
|
|
2026
2481
|
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Imports the commissioning server context for a specific plugin and device.
|
|
2484
|
+
* @param pluginName - The name of the plugin.
|
|
2485
|
+
* @param device - The MatterbridgeDevice object representing the device.
|
|
2486
|
+
* @returns The commissioning server context.
|
|
2487
|
+
* @throws Error if the BasicInformationCluster is not found.
|
|
2488
|
+
*/
|
|
2027
2489
|
async importCommissioningServerContext(pluginName, device) {
|
|
2028
2490
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
2029
2491
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -2058,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2058
2520
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2059
2521
|
return storageContext;
|
|
2060
2522
|
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Shows the commissioning server QR code for a given plugin.
|
|
2525
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2526
|
+
* @param {StorageContext} storageContext - The storage context instance.
|
|
2527
|
+
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2528
|
+
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2529
|
+
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2530
|
+
*/
|
|
2061
2531
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
2062
2532
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
2063
2533
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -2068,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2068
2538
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
2069
2539
|
const QrCode = new QrCodeSchema();
|
|
2070
2540
|
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`);
|
|
2071
|
-
|
|
2541
|
+
// eslint-disable-next-line no-console
|
|
2542
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
2072
2543
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
2073
2544
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
2074
2545
|
if (pluginName === 'Matterbridge') {
|
|
@@ -2115,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2115
2586
|
}
|
|
2116
2587
|
this.wssSendRefreshRequired();
|
|
2117
2588
|
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2591
|
+
*
|
|
2592
|
+
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2593
|
+
* @returns An array of sanitized exposed fabric information objects.
|
|
2594
|
+
*/
|
|
2118
2595
|
sanitizeFabricInformations(fabricInfo) {
|
|
2119
2596
|
return fabricInfo.map((info) => {
|
|
2120
2597
|
return {
|
|
@@ -2128,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2128
2605
|
};
|
|
2129
2606
|
});
|
|
2130
2607
|
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Sanitizes the session information by converting bigint properties to string.
|
|
2610
|
+
*
|
|
2611
|
+
* @param sessionInfo - The array of session information objects.
|
|
2612
|
+
* @returns An array of sanitized session information objects.
|
|
2613
|
+
*/
|
|
2131
2614
|
sanitizeSessionInformation(sessionInfo) {
|
|
2132
2615
|
return sessionInfo
|
|
2133
2616
|
.filter((session) => session.isPeerActive)
|
|
@@ -2155,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2155
2638
|
};
|
|
2156
2639
|
});
|
|
2157
2640
|
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Sets the reachability of a commissioning server and trigger.
|
|
2643
|
+
*
|
|
2644
|
+
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2645
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2646
|
+
*/
|
|
2158
2647
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2159
2648
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2160
2649
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2162,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2162
2651
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2163
2652
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2164
2653
|
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2656
|
+
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2657
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2658
|
+
*/
|
|
2165
2659
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
2166
2660
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2167
2661
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2174,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2174
2668
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2175
2669
|
});
|
|
2176
2670
|
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Sets the reachability of a device and trigger.
|
|
2673
|
+
*
|
|
2674
|
+
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2675
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2676
|
+
*/
|
|
2177
2677
|
setDeviceReachability(device, reachable) {
|
|
2178
2678
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2179
2679
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2222,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2222
2722
|
}
|
|
2223
2723
|
return vendorName;
|
|
2224
2724
|
};
|
|
2725
|
+
/**
|
|
2726
|
+
* Retrieves the base registered plugins sanitized for res.json().
|
|
2727
|
+
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
2728
|
+
*/
|
|
2225
2729
|
async getBaseRegisteredPlugins() {
|
|
2226
2730
|
const baseRegisteredPlugins = [];
|
|
2227
2731
|
for (const plugin of this.plugins) {
|
|
@@ -2253,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2253
2757
|
}
|
|
2254
2758
|
return baseRegisteredPlugins;
|
|
2255
2759
|
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Spawns a child process with the given command and arguments.
|
|
2762
|
+
* @param {string} command - The command to execute.
|
|
2763
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2764
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2765
|
+
*/
|
|
2256
2766
|
async spawnCommand(command, args = []) {
|
|
2767
|
+
/*
|
|
2768
|
+
npm > npm.cmd on windows
|
|
2769
|
+
cmd.exe ['dir'] on windows
|
|
2770
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2771
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2772
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2776
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2777
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2778
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2779
|
+
*/
|
|
2257
2780
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2258
2781
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2782
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2259
2783
|
const argstring = 'npm ' + args.join(' ');
|
|
2260
2784
|
args.splice(0, args.length, '/c', argstring);
|
|
2261
2785
|
command = 'cmd.exe';
|
|
2262
2786
|
}
|
|
2787
|
+
// Decide when using sudo on linux
|
|
2788
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2789
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2263
2790
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2264
2791
|
args.unshift(command);
|
|
2265
2792
|
command = 'sudo';
|
|
@@ -2317,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2317
2844
|
}
|
|
2318
2845
|
});
|
|
2319
2846
|
}
|
|
2847
|
+
/**
|
|
2848
|
+
* Sends a WebSocket message to all connected clients.
|
|
2849
|
+
*
|
|
2850
|
+
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2851
|
+
* @param {string} time - The time string of the message
|
|
2852
|
+
* @param {string} name - The logger name of the message
|
|
2853
|
+
* @param {string} message - The content of the message.
|
|
2854
|
+
*/
|
|
2320
2855
|
wssSendMessage(level, time, name, message) {
|
|
2321
2856
|
if (!level || !time || !name || !message)
|
|
2322
2857
|
return;
|
|
2858
|
+
// Remove ANSI escape codes from the message
|
|
2859
|
+
// eslint-disable-next-line no-control-regex
|
|
2323
2860
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2861
|
+
// Remove leading asterisks from the message
|
|
2324
2862
|
message = message.replace(/^\*+/, '');
|
|
2863
|
+
// Replace all occurrences of \t and \n
|
|
2325
2864
|
message = message.replace(/[\t\n]/g, '');
|
|
2865
|
+
// Remove non-printable characters
|
|
2866
|
+
// eslint-disable-next-line no-control-regex
|
|
2326
2867
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2868
|
+
// Replace all occurrences of \" with "
|
|
2327
2869
|
message = message.replace(/\\"/g, '"');
|
|
2870
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2328
2871
|
const maxContinuousLength = 100;
|
|
2329
2872
|
const keepStartLength = 20;
|
|
2330
2873
|
const keepEndLength = 20;
|
|
2874
|
+
// Split the message into words
|
|
2331
2875
|
message = message
|
|
2332
2876
|
.split(' ')
|
|
2333
2877
|
.map((word) => {
|
|
2878
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2334
2879
|
if (word.length > maxContinuousLength) {
|
|
2335
2880
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2336
2881
|
}
|
|
2337
2882
|
return word;
|
|
2338
2883
|
})
|
|
2339
2884
|
.join(' ');
|
|
2885
|
+
// Send the message to all connected clients
|
|
2340
2886
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2341
2887
|
if (client.readyState === WebSocket.OPEN) {
|
|
2342
2888
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2343
2889
|
}
|
|
2344
2890
|
});
|
|
2345
2891
|
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2894
|
+
*
|
|
2895
|
+
*/
|
|
2346
2896
|
wssSendRefreshRequired() {
|
|
2347
2897
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2898
|
+
// Send the message to all connected clients
|
|
2348
2899
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2349
2900
|
if (client.readyState === WebSocket.OPEN) {
|
|
2350
2901
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'refresh_required', params: {} }));
|
|
2351
2902
|
}
|
|
2352
2903
|
});
|
|
2353
2904
|
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2907
|
+
*
|
|
2908
|
+
*/
|
|
2354
2909
|
wssSendRestartRequired() {
|
|
2355
2910
|
this.matterbridgeInformation.restartRequired = true;
|
|
2911
|
+
// Send the message to all connected clients
|
|
2356
2912
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2357
2913
|
if (client.readyState === WebSocket.OPEN) {
|
|
2358
2914
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Matterbridge', method: 'restart_required', params: {} }));
|
|
2359
2915
|
}
|
|
2360
2916
|
});
|
|
2361
2917
|
}
|
|
2918
|
+
/**
|
|
2919
|
+
* Initializes the frontend of Matterbridge.
|
|
2920
|
+
*
|
|
2921
|
+
* @param port The port number to run the frontend server on. Default is 8283.
|
|
2922
|
+
*/
|
|
2362
2923
|
async initializeFrontend(port = 8283) {
|
|
2363
2924
|
let initializeError = false;
|
|
2364
2925
|
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2926
|
+
// Create the express app that serves the frontend
|
|
2365
2927
|
this.expressApp = express();
|
|
2928
|
+
// Log all requests to the server for debugging
|
|
2929
|
+
/*
|
|
2930
|
+
if (hasParameter('homedir')) {
|
|
2931
|
+
this.expressApp.use((req, res, next) => {
|
|
2932
|
+
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
2933
|
+
next();
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
*/
|
|
2937
|
+
// Serve static files from '/static' endpoint
|
|
2366
2938
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2367
2939
|
if (!hasParameter('ssl')) {
|
|
2940
|
+
// Create an HTTP server and attach the express app
|
|
2368
2941
|
this.httpServer = createServer(this.expressApp);
|
|
2942
|
+
// Listen on the specified port
|
|
2369
2943
|
if (hasParameter('ingress')) {
|
|
2370
2944
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2371
2945
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2379,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2379
2953
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2380
2954
|
});
|
|
2381
2955
|
}
|
|
2956
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2382
2957
|
this.httpServer.on('error', (error) => {
|
|
2383
2958
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2384
2959
|
switch (error.code) {
|
|
@@ -2394,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2394
2969
|
});
|
|
2395
2970
|
}
|
|
2396
2971
|
else {
|
|
2972
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2397
2973
|
let cert;
|
|
2398
2974
|
try {
|
|
2399
2975
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2421,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2421
2997
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2422
2998
|
}
|
|
2423
2999
|
const serverOptions = { cert, key, ca };
|
|
3000
|
+
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
2424
3001
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
3002
|
+
// Listen on the specified port
|
|
2425
3003
|
if (hasParameter('ingress')) {
|
|
2426
3004
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2427
3005
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2435,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2435
3013
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2436
3014
|
});
|
|
2437
3015
|
}
|
|
3016
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2438
3017
|
this.httpsServer.on('error', (error) => {
|
|
2439
3018
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2440
3019
|
switch (error.code) {
|
|
@@ -2451,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2451
3030
|
}
|
|
2452
3031
|
if (initializeError)
|
|
2453
3032
|
return;
|
|
3033
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2454
3034
|
const wssPort = port;
|
|
2455
3035
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2456
3036
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2457
3037
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2458
3038
|
const clientIp = request.socket.remoteAddress;
|
|
2459
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
3039
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2460
3040
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2461
3041
|
ws.on('message', (message) => {
|
|
2462
3042
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2489,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2489
3069
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2490
3070
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2491
3071
|
});
|
|
3072
|
+
// Endpoint to validate login code
|
|
2492
3073
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2493
3074
|
const { password } = req.body;
|
|
2494
3075
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2507,12 +3088,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2507
3088
|
this.log.warn('/api/login error wrong password');
|
|
2508
3089
|
res.json({ valid: false });
|
|
2509
3090
|
}
|
|
3091
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2510
3092
|
}
|
|
2511
3093
|
catch (error) {
|
|
2512
3094
|
this.log.error('/api/login error getting password');
|
|
2513
3095
|
res.json({ valid: false });
|
|
2514
3096
|
}
|
|
2515
3097
|
});
|
|
3098
|
+
// Endpoint to provide settings
|
|
2516
3099
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2517
3100
|
this.log.debug('The frontend sent /api/settings');
|
|
2518
3101
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2533,13 +3116,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2533
3116
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2534
3117
|
this.matterbridgeInformation.profile = this.profile;
|
|
2535
3118
|
const response = { wssHost, ssl: hasParameter('ssl'), systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
3119
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2536
3120
|
res.json(response);
|
|
2537
3121
|
});
|
|
3122
|
+
// Endpoint to provide plugins
|
|
2538
3123
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2539
3124
|
this.log.debug('The frontend sent /api/plugins');
|
|
2540
3125
|
const response = await this.getBaseRegisteredPlugins();
|
|
3126
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2541
3127
|
res.json(response);
|
|
2542
3128
|
});
|
|
3129
|
+
// Endpoint to provide devices
|
|
2543
3130
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2544
3131
|
this.log.debug('The frontend sent /api/devices');
|
|
2545
3132
|
const devices = [];
|
|
@@ -2572,8 +3159,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2572
3159
|
cluster: cluster,
|
|
2573
3160
|
});
|
|
2574
3161
|
});
|
|
3162
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2575
3163
|
res.json(devices);
|
|
2576
3164
|
});
|
|
3165
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2577
3166
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2578
3167
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2579
3168
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2593,6 +3182,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2593
3182
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2594
3183
|
if (clusterServer.name === 'EveHistory')
|
|
2595
3184
|
return;
|
|
3185
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2596
3186
|
let attributeValue;
|
|
2597
3187
|
try {
|
|
2598
3188
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2603,6 +3193,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2603
3193
|
catch (error) {
|
|
2604
3194
|
attributeValue = 'Fabric-Scoped';
|
|
2605
3195
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3196
|
+
// console.log(error);
|
|
2606
3197
|
}
|
|
2607
3198
|
data.push({
|
|
2608
3199
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2615,12 +3206,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2615
3206
|
});
|
|
2616
3207
|
});
|
|
2617
3208
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2618
3210
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2619
3211
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2620
3212
|
clusterServers.forEach((clusterServer) => {
|
|
2621
3213
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2622
3214
|
if (clusterServer.name === 'EveHistory')
|
|
2623
3215
|
return;
|
|
3216
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2624
3217
|
let attributeValue;
|
|
2625
3218
|
try {
|
|
2626
3219
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2631,6 +3224,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2631
3224
|
catch (error) {
|
|
2632
3225
|
attributeValue = 'Unavailable';
|
|
2633
3226
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3227
|
+
// console.log(error);
|
|
2634
3228
|
}
|
|
2635
3229
|
data.push({
|
|
2636
3230
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2647,6 +3241,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2647
3241
|
});
|
|
2648
3242
|
res.json(data);
|
|
2649
3243
|
});
|
|
3244
|
+
// Endpoint to view the log
|
|
2650
3245
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2651
3246
|
this.log.debug('The frontend sent /api/log');
|
|
2652
3247
|
try {
|
|
@@ -2659,10 +3254,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2659
3254
|
res.status(500).send('Error reading log file');
|
|
2660
3255
|
}
|
|
2661
3256
|
});
|
|
3257
|
+
// Endpoint to download the matterbridge log
|
|
2662
3258
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2663
3259
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2664
3260
|
try {
|
|
2665
3261
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3262
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2666
3263
|
}
|
|
2667
3264
|
catch (error) {
|
|
2668
3265
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2674,10 +3271,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2674
3271
|
}
|
|
2675
3272
|
});
|
|
2676
3273
|
});
|
|
3274
|
+
// Endpoint to download the matter log
|
|
2677
3275
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2678
3276
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2679
3277
|
try {
|
|
2680
3278
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3279
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2681
3280
|
}
|
|
2682
3281
|
catch (error) {
|
|
2683
3282
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2689,6 +3288,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2689
3288
|
}
|
|
2690
3289
|
});
|
|
2691
3290
|
});
|
|
3291
|
+
// Endpoint to download the matter storage file
|
|
2692
3292
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2693
3293
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2694
3294
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2698,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2698
3298
|
}
|
|
2699
3299
|
});
|
|
2700
3300
|
});
|
|
3301
|
+
// Endpoint to download the matterbridge storage directory
|
|
2701
3302
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2702
3303
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2703
3304
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2708,6 +3309,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2708
3309
|
}
|
|
2709
3310
|
});
|
|
2710
3311
|
});
|
|
3312
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2711
3313
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2712
3314
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2713
3315
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2718,9 +3320,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2718
3320
|
}
|
|
2719
3321
|
});
|
|
2720
3322
|
});
|
|
3323
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2721
3324
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2722
3325
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2723
3326
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3327
|
+
// 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')));
|
|
2724
3328
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2725
3329
|
if (error) {
|
|
2726
3330
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2728,6 +3332,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2728
3332
|
}
|
|
2729
3333
|
});
|
|
2730
3334
|
});
|
|
3335
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2731
3336
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2732
3337
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2733
3338
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2737,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2737
3342
|
}
|
|
2738
3343
|
});
|
|
2739
3344
|
});
|
|
3345
|
+
// Endpoint to receive commands
|
|
2740
3346
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2741
3347
|
const command = req.params.command;
|
|
2742
3348
|
let param = req.params.param;
|
|
@@ -2746,13 +3352,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2746
3352
|
return;
|
|
2747
3353
|
}
|
|
2748
3354
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3355
|
+
// Handle the command setpassword from Settings
|
|
2749
3356
|
if (command === 'setpassword') {
|
|
2750
|
-
const password = param.slice(1, -1);
|
|
3357
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2751
3358
|
this.log.debug('setpassword', param, password);
|
|
2752
3359
|
await this.nodeContext?.set('password', password);
|
|
2753
3360
|
res.json({ message: 'Command received' });
|
|
2754
3361
|
return;
|
|
2755
3362
|
}
|
|
3363
|
+
// Handle the command setbridgemode from Settings
|
|
2756
3364
|
if (command === 'setbridgemode') {
|
|
2757
3365
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2758
3366
|
this.wssSendRestartRequired();
|
|
@@ -2760,6 +3368,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2760
3368
|
res.json({ message: 'Command received' });
|
|
2761
3369
|
return;
|
|
2762
3370
|
}
|
|
3371
|
+
// Handle the command backup from Settings
|
|
2763
3372
|
if (command === 'backup') {
|
|
2764
3373
|
this.log.notice(`Prepairing the backup...`);
|
|
2765
3374
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2767,25 +3376,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2767
3376
|
res.json({ message: 'Command received' });
|
|
2768
3377
|
return;
|
|
2769
3378
|
}
|
|
3379
|
+
// Handle the command setmbloglevel from Settings
|
|
2770
3380
|
if (command === 'setmbloglevel') {
|
|
2771
3381
|
this.log.debug('Matterbridge log level:', param);
|
|
2772
3382
|
if (param === 'Debug') {
|
|
2773
|
-
this.log.logLevel = "debug"
|
|
3383
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2774
3384
|
}
|
|
2775
3385
|
else if (param === 'Info') {
|
|
2776
|
-
this.log.logLevel = "info"
|
|
3386
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2777
3387
|
}
|
|
2778
3388
|
else if (param === 'Notice') {
|
|
2779
|
-
this.log.logLevel = "notice"
|
|
3389
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2780
3390
|
}
|
|
2781
3391
|
else if (param === 'Warn') {
|
|
2782
|
-
this.log.logLevel = "warn"
|
|
3392
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2783
3393
|
}
|
|
2784
3394
|
else if (param === 'Error') {
|
|
2785
|
-
this.log.logLevel = "error"
|
|
3395
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2786
3396
|
}
|
|
2787
3397
|
else if (param === 'Fatal') {
|
|
2788
|
-
this.log.logLevel = "fatal"
|
|
3398
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2789
3399
|
}
|
|
2790
3400
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2791
3401
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2793,12 +3403,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2793
3403
|
for (const plugin of this.plugins) {
|
|
2794
3404
|
if (!plugin.platform || !plugin.platform.config)
|
|
2795
3405
|
continue;
|
|
2796
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2797
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
3406
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3407
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
2798
3408
|
}
|
|
2799
3409
|
res.json({ message: 'Command received' });
|
|
2800
3410
|
return;
|
|
2801
3411
|
}
|
|
3412
|
+
// Handle the command setmbloglevel from Settings
|
|
2802
3413
|
if (command === 'setmjloglevel') {
|
|
2803
3414
|
this.log.debug('Matter.js log level:', param);
|
|
2804
3415
|
if (param === 'Debug') {
|
|
@@ -2823,30 +3434,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2823
3434
|
res.json({ message: 'Command received' });
|
|
2824
3435
|
return;
|
|
2825
3436
|
}
|
|
3437
|
+
// Handle the command setmdnsinterface from Settings
|
|
2826
3438
|
if (command === 'setmdnsinterface') {
|
|
2827
|
-
param = param.slice(1, -1);
|
|
3439
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2828
3440
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2829
3441
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2830
3442
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2831
3443
|
res.json({ message: 'Command received' });
|
|
2832
3444
|
return;
|
|
2833
3445
|
}
|
|
3446
|
+
// Handle the command setipv4address from Settings
|
|
2834
3447
|
if (command === 'setipv4address') {
|
|
2835
|
-
param = param.slice(1, -1);
|
|
3448
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2836
3449
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2837
3450
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2838
3451
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2839
3452
|
res.json({ message: 'Command received' });
|
|
2840
3453
|
return;
|
|
2841
3454
|
}
|
|
3455
|
+
// Handle the command setipv6address from Settings
|
|
2842
3456
|
if (command === 'setipv6address') {
|
|
2843
|
-
param = param.slice(1, -1);
|
|
3457
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2844
3458
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2845
3459
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2846
3460
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2847
3461
|
res.json({ message: 'Command received' });
|
|
2848
3462
|
return;
|
|
2849
3463
|
}
|
|
3464
|
+
// Handle the command setmatterport from Settings
|
|
2850
3465
|
if (command === 'setmatterport') {
|
|
2851
3466
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2852
3467
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2855,6 +3470,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2855
3470
|
res.json({ message: 'Command received' });
|
|
2856
3471
|
return;
|
|
2857
3472
|
}
|
|
3473
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2858
3474
|
if (command === 'setmatterdiscriminator') {
|
|
2859
3475
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2860
3476
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2863,6 +3479,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2863
3479
|
res.json({ message: 'Command received' });
|
|
2864
3480
|
return;
|
|
2865
3481
|
}
|
|
3482
|
+
// Handle the command setmatterpasscode from Settings
|
|
2866
3483
|
if (command === 'setmatterpasscode') {
|
|
2867
3484
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2868
3485
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2871,17 +3488,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2871
3488
|
res.json({ message: 'Command received' });
|
|
2872
3489
|
return;
|
|
2873
3490
|
}
|
|
3491
|
+
// Handle the command setmbloglevel from Settings
|
|
2874
3492
|
if (command === 'setmblogfile') {
|
|
2875
3493
|
this.log.debug('Matterbridge file log:', param);
|
|
2876
3494
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2877
3495
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3496
|
+
// Create the file logger for matterbridge
|
|
2878
3497
|
if (param === 'true')
|
|
2879
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3498
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2880
3499
|
else
|
|
2881
3500
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2882
3501
|
res.json({ message: 'Command received' });
|
|
2883
3502
|
return;
|
|
2884
3503
|
}
|
|
3504
|
+
// Handle the command setmbloglevel from Settings
|
|
2885
3505
|
if (command === 'setmjlogfile') {
|
|
2886
3506
|
this.log.debug('Matter file log:', param);
|
|
2887
3507
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2908,36 +3528,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2908
3528
|
res.json({ message: 'Command received' });
|
|
2909
3529
|
return;
|
|
2910
3530
|
}
|
|
3531
|
+
// Handle the command unregister from Settings
|
|
2911
3532
|
if (command === 'unregister') {
|
|
2912
3533
|
await this.unregisterAndShutdownProcess();
|
|
2913
3534
|
res.json({ message: 'Command received' });
|
|
2914
3535
|
return;
|
|
2915
3536
|
}
|
|
3537
|
+
// Handle the command reset from Settings
|
|
2916
3538
|
if (command === 'reset') {
|
|
2917
3539
|
await this.shutdownProcessAndReset();
|
|
2918
3540
|
res.json({ message: 'Command received' });
|
|
2919
3541
|
return;
|
|
2920
3542
|
}
|
|
3543
|
+
// Handle the command factoryreset from Settings
|
|
2921
3544
|
if (command === 'factoryreset') {
|
|
2922
3545
|
await this.shutdownProcessAndFactoryReset();
|
|
2923
3546
|
res.json({ message: 'Command received' });
|
|
2924
3547
|
return;
|
|
2925
3548
|
}
|
|
3549
|
+
// Handle the command shutdown from Header
|
|
2926
3550
|
if (command === 'shutdown') {
|
|
2927
3551
|
await this.shutdownProcess();
|
|
2928
3552
|
res.json({ message: 'Command received' });
|
|
2929
3553
|
return;
|
|
2930
3554
|
}
|
|
3555
|
+
// Handle the command restart from Header
|
|
2931
3556
|
if (command === 'restart') {
|
|
2932
3557
|
await this.restartProcess();
|
|
2933
3558
|
res.json({ message: 'Command received' });
|
|
2934
3559
|
return;
|
|
2935
3560
|
}
|
|
3561
|
+
// Handle the command update from Header
|
|
2936
3562
|
if (command === 'update') {
|
|
2937
3563
|
this.log.info('Updating matterbridge...');
|
|
2938
3564
|
try {
|
|
2939
3565
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2940
3566
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3567
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2941
3568
|
}
|
|
2942
3569
|
catch (error) {
|
|
2943
3570
|
this.log.error('Error updating matterbridge');
|
|
@@ -2947,9 +3574,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2947
3574
|
res.json({ message: 'Command received' });
|
|
2948
3575
|
return;
|
|
2949
3576
|
}
|
|
3577
|
+
// Handle the command saveconfig from Home
|
|
2950
3578
|
if (command === 'saveconfig') {
|
|
2951
3579
|
param = param.replace(/\*/g, '\\');
|
|
2952
3580
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3581
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2953
3582
|
if (!this.plugins.has(param)) {
|
|
2954
3583
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2955
3584
|
}
|
|
@@ -2963,33 +3592,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2963
3592
|
res.json({ message: 'Command received' });
|
|
2964
3593
|
return;
|
|
2965
3594
|
}
|
|
3595
|
+
// Handle the command installplugin from Home
|
|
2966
3596
|
if (command === 'installplugin') {
|
|
2967
3597
|
param = param.replace(/\*/g, '\\');
|
|
2968
3598
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2969
3599
|
try {
|
|
2970
3600
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2971
3601
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3602
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2972
3603
|
}
|
|
2973
3604
|
catch (error) {
|
|
2974
3605
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2975
3606
|
}
|
|
2976
3607
|
this.wssSendRestartRequired();
|
|
2977
3608
|
param = param.split('@')[0];
|
|
3609
|
+
// Also add the plugin to matterbridge so no return!
|
|
2978
3610
|
if (param === 'matterbridge') {
|
|
3611
|
+
// 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
|
|
2979
3612
|
res.json({ message: 'Command received' });
|
|
2980
3613
|
return;
|
|
2981
3614
|
}
|
|
2982
3615
|
}
|
|
3616
|
+
// Handle the command addplugin from Home
|
|
2983
3617
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2984
3618
|
param = param.replace(/\*/g, '\\');
|
|
2985
3619
|
const plugin = await this.plugins.add(param);
|
|
2986
3620
|
if (plugin) {
|
|
2987
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
3621
|
+
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
2988
3622
|
}
|
|
2989
3623
|
res.json({ message: 'Command received' });
|
|
2990
3624
|
this.wssSendRefreshRequired();
|
|
2991
3625
|
return;
|
|
2992
3626
|
}
|
|
3627
|
+
// Handle the command removeplugin from Home
|
|
2993
3628
|
if (command === 'removeplugin') {
|
|
2994
3629
|
if (!this.plugins.has(param)) {
|
|
2995
3630
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3003,6 +3638,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3003
3638
|
this.wssSendRefreshRequired();
|
|
3004
3639
|
return;
|
|
3005
3640
|
}
|
|
3641
|
+
// Handle the command enableplugin from Home
|
|
3006
3642
|
if (command === 'enableplugin') {
|
|
3007
3643
|
if (!this.plugins.has(param)) {
|
|
3008
3644
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3020,13 +3656,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
3020
3656
|
plugin.registeredDevices = undefined;
|
|
3021
3657
|
plugin.addedDevices = undefined;
|
|
3022
3658
|
await this.plugins.enable(param);
|
|
3023
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
3659
|
+
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
3024
3660
|
}
|
|
3025
3661
|
}
|
|
3026
3662
|
res.json({ message: 'Command received' });
|
|
3027
3663
|
this.wssSendRefreshRequired();
|
|
3028
3664
|
return;
|
|
3029
3665
|
}
|
|
3666
|
+
// Handle the command disableplugin from Home
|
|
3030
3667
|
if (command === 'disableplugin') {
|
|
3031
3668
|
if (!this.plugins.has(param)) {
|
|
3032
3669
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3043,6 +3680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3043
3680
|
return;
|
|
3044
3681
|
}
|
|
3045
3682
|
});
|
|
3683
|
+
// Fallback for routing (must be the last route)
|
|
3046
3684
|
this.expressApp.get('*', (req, res) => {
|
|
3047
3685
|
this.log.debug('The frontend sent:', req.url);
|
|
3048
3686
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -3050,6 +3688,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3050
3688
|
});
|
|
3051
3689
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
3052
3690
|
}
|
|
3691
|
+
/**
|
|
3692
|
+
* Retrieves the cluster text description from a given device.
|
|
3693
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3694
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3695
|
+
*/
|
|
3053
3696
|
getClusterTextFromDevice(device) {
|
|
3054
3697
|
const stringifyUserLabel = (endpoint) => {
|
|
3055
3698
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -3072,9 +3715,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3072
3715
|
return '';
|
|
3073
3716
|
};
|
|
3074
3717
|
let attributes = '';
|
|
3718
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3075
3719
|
const clusterServers = device.getAllClusterServers();
|
|
3076
3720
|
clusterServers.forEach((clusterServer) => {
|
|
3077
3721
|
try {
|
|
3722
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3078
3723
|
if (clusterServer.name === 'OnOff')
|
|
3079
3724
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
3080
3725
|
if (clusterServer.name === 'Switch')
|
|
@@ -3125,18 +3770,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
3125
3770
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
3126
3771
|
if (clusterServer.name === 'UserLabel')
|
|
3127
3772
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3773
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3128
3774
|
}
|
|
3129
3775
|
catch (error) {
|
|
3130
3776
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3131
3777
|
}
|
|
3132
3778
|
});
|
|
3779
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3133
3780
|
return attributes;
|
|
3134
3781
|
}
|
|
3782
|
+
/**
|
|
3783
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3784
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3785
|
+
*
|
|
3786
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3787
|
+
*/
|
|
3135
3788
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3789
|
+
// Set the bridge mode
|
|
3136
3790
|
this.bridgeMode = 'bridge';
|
|
3791
|
+
// Set the first port to use
|
|
3137
3792
|
this.port = port;
|
|
3138
|
-
|
|
3793
|
+
// Set Matterbridge logger
|
|
3794
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3139
3795
|
this.log.debug('Matterbridge extension is starting...');
|
|
3796
|
+
// Initialize NodeStorage
|
|
3140
3797
|
this.matterbridgeDirectory = dataPath;
|
|
3141
3798
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3142
3799
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -3155,10 +3812,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
3155
3812
|
};
|
|
3156
3813
|
this.plugins.set(plugin);
|
|
3157
3814
|
this.plugins.saveToStorage();
|
|
3815
|
+
// Log system info and create .matterbridge directory
|
|
3158
3816
|
await this.logNodeAndSystemInfo();
|
|
3159
3817
|
this.matterbridgeDirectory = dataPath;
|
|
3818
|
+
// Set matter.js logger level and format
|
|
3160
3819
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3161
3820
|
Logger.format = MatterLogFormat.ANSI;
|
|
3821
|
+
// Start the storage and create matterbridgeContext
|
|
3162
3822
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3163
3823
|
if (!this.storageManager)
|
|
3164
3824
|
return false;
|
|
@@ -3168,7 +3828,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3168
3828
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3169
3829
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3170
3830
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3171
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3831
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
3172
3832
|
this.matterServer = this.createMatterServer(this.storageManager);
|
|
3173
3833
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3174
3834
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -3181,6 +3841,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3181
3841
|
await this.startMatterServer();
|
|
3182
3842
|
this.log.info('Matter server started');
|
|
3183
3843
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3844
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
3184
3845
|
setTimeout(() => {
|
|
3185
3846
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3186
3847
|
if (this.commissioningServer)
|
|
@@ -3190,14 +3851,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
3190
3851
|
}, 60 * 1000);
|
|
3191
3852
|
return this.commissioningServer.isCommissioned();
|
|
3192
3853
|
}
|
|
3854
|
+
/**
|
|
3855
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3856
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3857
|
+
*
|
|
3858
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3859
|
+
*/
|
|
3193
3860
|
async stopExtension() {
|
|
3861
|
+
// Closing matter
|
|
3194
3862
|
await this.stopMatterServer();
|
|
3863
|
+
// Clearing the session manager
|
|
3864
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3865
|
+
// Closing storage
|
|
3195
3866
|
await this.stopMatterStorage();
|
|
3196
3867
|
this.log.info('Matter server stopped');
|
|
3197
3868
|
}
|
|
3869
|
+
/**
|
|
3870
|
+
* Checks if the extension is commissioned.
|
|
3871
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3872
|
+
*
|
|
3873
|
+
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3874
|
+
*/
|
|
3198
3875
|
isExtensionCommissioned() {
|
|
3199
3876
|
if (!this.commissioningServer)
|
|
3200
3877
|
return false;
|
|
3201
3878
|
return this.commissioningServer.isCommissioned();
|
|
3202
3879
|
}
|
|
3203
3880
|
}
|
|
3881
|
+
//# sourceMappingURL=matterbridge.js.map
|