matterbridge 1.6.8-dev.9 → 1.7.0
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 +21 -1
- package/README-DOCKER.md +8 -6
- package/README-EDGE.md +76 -0
- package/README-SERVICE.md +3 -3
- package/README.md +6 -3
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +46 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +26 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/export.d.ts +11 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +4 -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 +818 -110
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +942 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +29 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDevice.d.ts +7077 -0
- package/dist/matterbridgeDevice.d.ts.map +1 -0
- package/dist/matterbridgeDevice.js +996 -9
- package/dist/matterbridgeDevice.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +109 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +82 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEdge.d.ts +91 -0
- package/dist/matterbridgeEdge.d.ts.map +1 -0
- package/dist/matterbridgeEdge.js +558 -11
- package/dist/matterbridgeEdge.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +10151 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1120 -11
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +140 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +126 -4
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +169 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/matterbridgeWebsocket.d.ts +49 -0
- package/dist/matterbridgeWebsocket.d.ts.map +1 -0
- package/dist/matterbridgeWebsocket.js +139 -17
- package/dist/matterbridgeWebsocket.js.map +1 -0
- package/dist/pluginManager.d.ts +238 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +238 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/export.d.ts +3 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/utils.d.ts +221 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +252 -7
- package/dist/utils/utils.js.map +1 -0
- package/frontend/build/asset-manifest.json +6 -6
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/css/{main.823e08b6.css → main.f1fce054.css} +2 -2
- package/frontend/build/static/css/main.f1fce054.css.map +1 -0
- package/frontend/build/static/js/{main.4dd7e165.js → main.5caad8c7.js} +15 -15
- package/frontend/build/static/js/main.5caad8c7.js.map +1 -0
- package/npm-shrinkwrap.json +30 -17
- package/package.json +2 -1
- package/frontend/build/static/css/main.823e08b6.css.map +0 -1
- package/frontend/build/static/js/main.4dd7e165.js.map +0 -1
- /package/frontend/build/static/js/{main.4dd7e165.js.LICENSE.txt → main.5caad8c7.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,18 +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
|
+
}
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
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 });
|
|
474
597
|
}
|
|
475
|
-
|
|
598
|
+
catch (err) {
|
|
599
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
600
|
+
this.log.error(`Error removing storage directory: ${err}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
476
604
|
this.nodeContext = undefined;
|
|
477
605
|
this.nodeStorage = undefined;
|
|
478
606
|
this.plugins.clear();
|
|
@@ -480,6 +608,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
480
608
|
this.emit('shutdown');
|
|
481
609
|
return;
|
|
482
610
|
}
|
|
611
|
+
// Start the matter storage and create the matterbridge context
|
|
483
612
|
try {
|
|
484
613
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
485
614
|
}
|
|
@@ -487,7 +616,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
487
616
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
488
617
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
489
618
|
}
|
|
490
|
-
if
|
|
619
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
620
|
+
if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
|
|
491
621
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
492
622
|
await this.matterbridgeContext?.clearAll();
|
|
493
623
|
await this.stopMatterStorage();
|
|
@@ -495,7 +625,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
495
625
|
this.emit('shutdown');
|
|
496
626
|
return;
|
|
497
627
|
}
|
|
498
|
-
if
|
|
628
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
629
|
+
if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
499
630
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
500
631
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
501
632
|
if (plugin) {
|
|
@@ -514,28 +645,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
514
645
|
this.emit('shutdown');
|
|
515
646
|
return;
|
|
516
647
|
}
|
|
648
|
+
// Initialize frontend
|
|
517
649
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
518
650
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
651
|
+
// Check each 60 minutes the latest versions
|
|
519
652
|
this.checkUpdateInterval = setInterval(() => {
|
|
520
653
|
this.getMatterbridgeLatestVersion();
|
|
521
654
|
for (const plugin of this.plugins) {
|
|
522
655
|
this.getPluginLatestVersion(plugin);
|
|
523
656
|
}
|
|
524
657
|
}, 60 * 60 * 1000);
|
|
658
|
+
// Start the matterbridge in mode test
|
|
525
659
|
if (hasParameter('test')) {
|
|
526
660
|
this.bridgeMode = 'bridge';
|
|
527
661
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
528
662
|
return;
|
|
529
663
|
}
|
|
664
|
+
// Start the matterbridge in mode controller
|
|
530
665
|
if (hasParameter('controller')) {
|
|
531
666
|
this.bridgeMode = 'controller';
|
|
532
667
|
await this.startController();
|
|
533
668
|
return;
|
|
534
669
|
}
|
|
670
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
535
671
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
536
672
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
537
673
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
538
674
|
}
|
|
675
|
+
// Start matterbridge in bridge mode
|
|
539
676
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
540
677
|
this.bridgeMode = 'bridge';
|
|
541
678
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -544,6 +681,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
544
681
|
await this.startBridge();
|
|
545
682
|
return;
|
|
546
683
|
}
|
|
684
|
+
// Start matterbridge in childbridge mode
|
|
547
685
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
548
686
|
this.bridgeMode = 'childbridge';
|
|
549
687
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -553,17 +691,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
553
691
|
return;
|
|
554
692
|
}
|
|
555
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
|
+
*/
|
|
556
702
|
async startPlugins() {
|
|
703
|
+
// Check, load and start the plugins
|
|
557
704
|
for (const plugin of this.plugins) {
|
|
558
705
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
559
706
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
707
|
+
// Check if the plugin is available
|
|
560
708
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
561
709
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
562
710
|
plugin.enabled = false;
|
|
563
711
|
plugin.error = true;
|
|
564
712
|
continue;
|
|
565
713
|
}
|
|
566
|
-
|
|
714
|
+
// Check if the plugin has a new version
|
|
715
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
567
716
|
if (!plugin.enabled) {
|
|
568
717
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
569
718
|
continue;
|
|
@@ -578,20 +727,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
578
727
|
plugin.addedDevices = undefined;
|
|
579
728
|
plugin.qrPairingCode = undefined;
|
|
580
729
|
plugin.manualPairingCode = undefined;
|
|
581
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
730
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
582
731
|
}
|
|
583
732
|
this.wssSendRefreshRequired();
|
|
584
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
|
+
*/
|
|
585
738
|
registerProcessHandlers() {
|
|
586
739
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
587
740
|
process.removeAllListeners('uncaughtException');
|
|
588
741
|
process.removeAllListeners('unhandledRejection');
|
|
589
742
|
this.exceptionHandler = async (error) => {
|
|
590
743
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
744
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
591
745
|
};
|
|
592
746
|
process.on('uncaughtException', this.exceptionHandler);
|
|
593
747
|
this.rejectionHandler = async (reason, promise) => {
|
|
594
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...');
|
|
595
750
|
};
|
|
596
751
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
597
752
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -604,6 +759,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
604
759
|
};
|
|
605
760
|
process.on('SIGTERM', this.sigtermHandler);
|
|
606
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
764
|
+
*/
|
|
607
765
|
deregisterProcesslHandlers() {
|
|
608
766
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
609
767
|
if (this.exceptionHandler)
|
|
@@ -620,7 +778,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
620
778
|
process.off('SIGTERM', this.sigtermHandler);
|
|
621
779
|
this.sigtermHandler = undefined;
|
|
622
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* Logs the node and system information.
|
|
783
|
+
*/
|
|
623
784
|
async logNodeAndSystemInfo() {
|
|
785
|
+
// IP address information
|
|
624
786
|
const networkInterfaces = os.networkInterfaces();
|
|
625
787
|
this.systemInformation.ipv4Address = '';
|
|
626
788
|
this.systemInformation.ipv6Address = '';
|
|
@@ -640,7 +802,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
640
802
|
this.systemInformation.macAddress = detail.mac;
|
|
641
803
|
}
|
|
642
804
|
}
|
|
643
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
805
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
644
806
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
645
807
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
646
808
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -648,19 +810,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
648
810
|
break;
|
|
649
811
|
}
|
|
650
812
|
}
|
|
813
|
+
// Node information
|
|
651
814
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
652
815
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
653
816
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
654
817
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
818
|
+
// Host system information
|
|
655
819
|
this.systemInformation.hostname = os.hostname();
|
|
656
820
|
this.systemInformation.user = os.userInfo().username;
|
|
657
|
-
this.systemInformation.osType = os.type();
|
|
658
|
-
this.systemInformation.osRelease = os.release();
|
|
659
|
-
this.systemInformation.osPlatform = os.platform();
|
|
660
|
-
this.systemInformation.osArch = os.arch();
|
|
661
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
662
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
663
|
-
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
|
|
664
829
|
this.log.debug('Host System Information:');
|
|
665
830
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
666
831
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -676,15 +841,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
676
841
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
677
842
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
678
843
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
844
|
+
// Home directory
|
|
679
845
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
680
846
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
681
847
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
848
|
+
// Package root directory
|
|
682
849
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
683
850
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
684
851
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
685
852
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
853
|
+
// Global node_modules directory
|
|
686
854
|
if (this.nodeContext)
|
|
687
855
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
856
|
+
// First run of Matterbridge so the node storage is empty
|
|
688
857
|
if (this.globalModulesDirectory === '') {
|
|
689
858
|
try {
|
|
690
859
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -708,6 +877,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
708
877
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
709
878
|
});
|
|
710
879
|
}
|
|
880
|
+
// Create the data directory .matterbridge in the home directory
|
|
711
881
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
712
882
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
713
883
|
try {
|
|
@@ -731,6 +901,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
731
901
|
}
|
|
732
902
|
}
|
|
733
903
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
904
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
734
905
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
735
906
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
736
907
|
try {
|
|
@@ -754,19 +925,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
754
925
|
}
|
|
755
926
|
}
|
|
756
927
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
928
|
+
// Matterbridge version
|
|
757
929
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
758
930
|
this.matterbridgeVersion = packageJson.version;
|
|
759
931
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
760
932
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
933
|
+
// Matterbridge latest version
|
|
761
934
|
if (this.nodeContext)
|
|
762
935
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
763
936
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
764
937
|
this.getMatterbridgeLatestVersion();
|
|
938
|
+
// Current working directory
|
|
765
939
|
const currentDir = process.cwd();
|
|
766
940
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
941
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
767
942
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
768
943
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
769
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
|
+
*/
|
|
770
950
|
async getLatestVersion(packageName) {
|
|
771
951
|
return new Promise((resolve, reject) => {
|
|
772
952
|
this.execRunningCount++;
|
|
@@ -781,6 +961,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
781
961
|
});
|
|
782
962
|
});
|
|
783
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
|
+
*/
|
|
784
968
|
async getGlobalNodeModules() {
|
|
785
969
|
return new Promise((resolve, reject) => {
|
|
786
970
|
this.execRunningCount++;
|
|
@@ -795,6 +979,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
795
979
|
});
|
|
796
980
|
});
|
|
797
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
|
+
*/
|
|
798
987
|
async getMatterbridgeLatestVersion() {
|
|
799
988
|
this.getLatestVersion('matterbridge')
|
|
800
989
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -811,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
811
1000
|
})
|
|
812
1001
|
.catch((error) => {
|
|
813
1002
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
1003
|
+
// error.stack && this.log.debug(error.stack);
|
|
814
1004
|
});
|
|
815
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
|
+
*/
|
|
816
1016
|
async getPluginLatestVersion(plugin) {
|
|
817
1017
|
this.getLatestVersion(plugin.name)
|
|
818
1018
|
.then(async (latestVersion) => {
|
|
@@ -824,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
824
1024
|
})
|
|
825
1025
|
.catch((error) => {
|
|
826
1026
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1027
|
+
// error.stack && this.log.debug(error.stack);
|
|
827
1028
|
});
|
|
828
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
|
+
*/
|
|
829
1035
|
createMatterLogger() {
|
|
830
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1036
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
831
1037
|
return (_level, formattedLog) => {
|
|
832
1038
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
833
1039
|
const message = formattedLog.slice(65);
|
|
834
1040
|
matterLogger.logName = logger;
|
|
835
1041
|
switch (_level) {
|
|
836
1042
|
case MatterLogLevel.DEBUG:
|
|
837
|
-
matterLogger.log("debug"
|
|
1043
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
838
1044
|
break;
|
|
839
1045
|
case MatterLogLevel.INFO:
|
|
840
|
-
matterLogger.log("info"
|
|
1046
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
841
1047
|
break;
|
|
842
1048
|
case MatterLogLevel.NOTICE:
|
|
843
|
-
matterLogger.log("notice"
|
|
1049
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
844
1050
|
break;
|
|
845
1051
|
case MatterLogLevel.WARN:
|
|
846
|
-
matterLogger.log("warn"
|
|
1052
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
847
1053
|
break;
|
|
848
1054
|
case MatterLogLevel.ERROR:
|
|
849
|
-
matterLogger.log("error"
|
|
1055
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
850
1056
|
break;
|
|
851
1057
|
case MatterLogLevel.FATAL:
|
|
852
|
-
matterLogger.log("fatal"
|
|
1058
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
853
1059
|
break;
|
|
854
1060
|
default:
|
|
855
|
-
matterLogger.log("debug"
|
|
1061
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
856
1062
|
break;
|
|
857
1063
|
}
|
|
858
1064
|
};
|
|
859
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
|
+
*/
|
|
860
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
|
|
861
1075
|
let fileSize = 0;
|
|
862
1076
|
if (unlink) {
|
|
863
1077
|
try {
|
|
@@ -906,53 +1120,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
906
1120
|
}
|
|
907
1121
|
};
|
|
908
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Update matterbridge and cleanup.
|
|
1125
|
+
*/
|
|
909
1126
|
async updateProcess() {
|
|
910
1127
|
await this.cleanup('updating...', false);
|
|
911
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1131
|
+
*/
|
|
912
1132
|
async restartProcess() {
|
|
913
1133
|
await this.cleanup('restarting...', true);
|
|
914
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Shut down the process by exiting the current process.
|
|
1137
|
+
*/
|
|
915
1138
|
async shutdownProcess() {
|
|
916
1139
|
await this.cleanup('shutting down...', false);
|
|
917
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Shut down the process and reset.
|
|
1143
|
+
*/
|
|
918
1144
|
async unregisterAndShutdownProcess() {
|
|
919
1145
|
this.log.info('Unregistering all devices and shutting down...');
|
|
920
|
-
for (const plugin of this.plugins) {
|
|
921
|
-
|
|
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);
|
|
922
1151
|
}
|
|
923
1152
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
924
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Shut down the process and reset.
|
|
1156
|
+
*/
|
|
925
1157
|
async shutdownProcessAndReset() {
|
|
926
1158
|
await this.cleanup('shutting down with reset...', false);
|
|
927
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Shut down the process and factory reset.
|
|
1162
|
+
*/
|
|
928
1163
|
async shutdownProcessAndFactoryReset() {
|
|
929
1164
|
await this.cleanup('shutting down with factory reset...', false);
|
|
930
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
|
+
*/
|
|
931
1172
|
async cleanup(message, restart = false) {
|
|
932
1173
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
933
1174
|
this.hasCleanupStarted = true;
|
|
934
1175
|
this.log.info(message);
|
|
1176
|
+
// Deregisters the process handlers
|
|
935
1177
|
this.deregisterProcesslHandlers();
|
|
1178
|
+
// Clear the start matter interval
|
|
936
1179
|
if (this.startMatterInterval) {
|
|
937
1180
|
clearInterval(this.startMatterInterval);
|
|
938
1181
|
this.startMatterInterval = undefined;
|
|
939
1182
|
this.log.debug('Start matter interval cleared');
|
|
940
1183
|
}
|
|
1184
|
+
// Clear the check update interval
|
|
941
1185
|
if (this.checkUpdateInterval) {
|
|
942
1186
|
clearInterval(this.checkUpdateInterval);
|
|
943
1187
|
this.checkUpdateInterval = undefined;
|
|
944
1188
|
this.log.debug('Check update interval cleared');
|
|
945
1189
|
}
|
|
1190
|
+
// Clear the configure timeout
|
|
946
1191
|
if (this.configureTimeout) {
|
|
947
1192
|
clearTimeout(this.configureTimeout);
|
|
948
1193
|
this.configureTimeout = undefined;
|
|
949
1194
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
950
1195
|
}
|
|
1196
|
+
// Clear the reachability timeout
|
|
951
1197
|
if (this.reachabilityTimeout) {
|
|
952
1198
|
clearTimeout(this.reachabilityTimeout);
|
|
953
1199
|
this.reachabilityTimeout = undefined;
|
|
954
1200
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
955
1201
|
}
|
|
1202
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
956
1203
|
for (const plugin of this.plugins) {
|
|
957
1204
|
if (!plugin.enabled || plugin.error)
|
|
958
1205
|
continue;
|
|
@@ -963,24 +1210,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
963
1210
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
964
1211
|
}
|
|
965
1212
|
}
|
|
1213
|
+
// Convert the matter storage to the new format
|
|
1214
|
+
if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
|
|
1215
|
+
if (this.bridgeMode === 'bridge') {
|
|
1216
|
+
await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
|
|
1217
|
+
}
|
|
1218
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
1219
|
+
for (const plugin of this.plugins) {
|
|
1220
|
+
if (plugin.storageContext) {
|
|
1221
|
+
await this.convertStorage(plugin.storageContext, plugin.name);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
// Close the http server
|
|
966
1227
|
if (this.httpServer) {
|
|
967
1228
|
this.httpServer.close();
|
|
968
1229
|
this.httpServer.removeAllListeners();
|
|
969
1230
|
this.httpServer = undefined;
|
|
970
1231
|
this.log.debug('Frontend http server closed successfully');
|
|
971
1232
|
}
|
|
1233
|
+
// Close the https server
|
|
972
1234
|
if (this.httpsServer) {
|
|
973
1235
|
this.httpsServer.close();
|
|
974
1236
|
this.httpsServer.removeAllListeners();
|
|
975
1237
|
this.httpsServer = undefined;
|
|
976
1238
|
this.log.debug('Frontend https server closed successfully');
|
|
977
1239
|
}
|
|
1240
|
+
// Remove listeners from the express app
|
|
978
1241
|
if (this.expressApp) {
|
|
979
1242
|
this.expressApp.removeAllListeners();
|
|
980
1243
|
this.expressApp = undefined;
|
|
981
1244
|
this.log.debug('Frontend app closed successfully');
|
|
982
1245
|
}
|
|
1246
|
+
// Close the WebSocket server
|
|
983
1247
|
if (this.webSocketServer) {
|
|
1248
|
+
// Close all active connections
|
|
984
1249
|
this.webSocketServer.clients.forEach((client) => {
|
|
985
1250
|
if (client.readyState === WebSocket.OPEN) {
|
|
986
1251
|
client.close();
|
|
@@ -996,29 +1261,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
996
1261
|
});
|
|
997
1262
|
this.webSocketServer = undefined;
|
|
998
1263
|
}
|
|
999
|
-
|
|
1000
|
-
await this.convertStorage(this.matterbridgeContext, 'Mattebridge');
|
|
1001
|
-
}
|
|
1264
|
+
// Closing matter
|
|
1002
1265
|
await this.stopMatterServer();
|
|
1266
|
+
// Closing matter storage
|
|
1003
1267
|
await this.stopMatterStorage();
|
|
1268
|
+
// Remove the matterfilelogger
|
|
1004
1269
|
try {
|
|
1005
1270
|
Logger.removeLogger('matterfilelogger');
|
|
1271
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1006
1272
|
}
|
|
1007
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}`);
|
|
1008
1275
|
}
|
|
1276
|
+
// Serialize registeredDevices
|
|
1009
1277
|
if (this.nodeStorage && this.nodeContext) {
|
|
1010
1278
|
this.log.info('Saving registered devices...');
|
|
1011
1279
|
const serializedRegisteredDevices = [];
|
|
1012
1280
|
this.devices.forEach(async (device) => {
|
|
1013
1281
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1282
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1014
1283
|
if (serializedMatterbridgeDevice)
|
|
1015
1284
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1016
1285
|
});
|
|
1017
1286
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1018
1287
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1288
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1019
1289
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1020
1290
|
await this.nodeContext.close();
|
|
1021
1291
|
this.nodeContext = undefined;
|
|
1292
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1022
1293
|
for (const plugin of this.plugins) {
|
|
1023
1294
|
if (plugin.nodeContext) {
|
|
1024
1295
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1048,17 +1319,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1048
1319
|
}
|
|
1049
1320
|
}
|
|
1050
1321
|
else {
|
|
1051
|
-
if (message === 'shutting down with reset...') {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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.');
|
|
1055
1342
|
}
|
|
1056
1343
|
if (message === 'shutting down with factory reset...') {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
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.');
|
|
1062
1353
|
}
|
|
1063
1354
|
this.log.notice('Cleanup completed. Shutting down...');
|
|
1064
1355
|
Matterbridge.instance = undefined;
|
|
@@ -1068,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1068
1359
|
this.initialized = false;
|
|
1069
1360
|
}
|
|
1070
1361
|
}
|
|
1362
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1071
1363
|
async addBridgedEndpoint(pluginName, device) {
|
|
1364
|
+
// Nothing to do here
|
|
1072
1365
|
}
|
|
1366
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1073
1367
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1368
|
+
// Nothing to do here
|
|
1074
1369
|
}
|
|
1370
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1075
1371
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1372
|
+
// Nothing to do here
|
|
1076
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
|
+
*/
|
|
1077
1380
|
async addBridgedDevice(pluginName, device) {
|
|
1078
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
|
|
1079
1383
|
const plugin = this.plugins.get(pluginName);
|
|
1080
1384
|
if (!plugin) {
|
|
1081
1385
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1082
1386
|
return;
|
|
1083
1387
|
}
|
|
1388
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1084
1389
|
if (this.bridgeMode === 'bridge') {
|
|
1085
1390
|
if (!this.matterAggregator) {
|
|
1086
1391
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1088,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1088
1393
|
}
|
|
1089
1394
|
this.matterAggregator.addBridgedDevice(device);
|
|
1090
1395
|
}
|
|
1396
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1397
|
+
// Register and add the device in childbridge mode
|
|
1091
1398
|
if (this.bridgeMode === 'childbridge') {
|
|
1092
1399
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1400
|
+
// Check if the plugin is locked with the commissioning server
|
|
1093
1401
|
if (!plugin.locked) {
|
|
1094
1402
|
plugin.locked = true;
|
|
1095
1403
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1103,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1103
1411
|
}
|
|
1104
1412
|
}
|
|
1105
1413
|
if (plugin.type === 'DynamicPlatform') {
|
|
1414
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1106
1415
|
if (!plugin.locked) {
|
|
1107
1416
|
plugin.locked = true;
|
|
1108
1417
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1123,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1123
1432
|
plugin.registeredDevices++;
|
|
1124
1433
|
if (plugin.addedDevices !== undefined)
|
|
1125
1434
|
plugin.addedDevices++;
|
|
1435
|
+
// Add the device to the DeviceManager
|
|
1126
1436
|
this.devices.set(device);
|
|
1127
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}`);
|
|
1128
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
|
+
*/
|
|
1129
1445
|
async removeBridgedDevice(pluginName, device) {
|
|
1130
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
|
|
1131
1448
|
const plugin = this.plugins.get(pluginName);
|
|
1132
1449
|
if (!plugin) {
|
|
1133
1450
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1134
1451
|
return;
|
|
1135
1452
|
}
|
|
1453
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1136
1454
|
if (this.bridgeMode === 'bridge') {
|
|
1137
1455
|
if (!this.matterAggregator) {
|
|
1138
1456
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1142,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1142
1460
|
device.setBridgedDeviceReachability(false);
|
|
1143
1461
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1144
1462
|
}
|
|
1463
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1464
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1145
1465
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1146
1466
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1147
1467
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1149,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1149
1469
|
if (plugin.addedDevices !== undefined)
|
|
1150
1470
|
plugin.addedDevices--;
|
|
1151
1471
|
}
|
|
1472
|
+
// Remove the device in childbridge mode
|
|
1152
1473
|
if (this.bridgeMode === 'childbridge') {
|
|
1153
1474
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1154
1475
|
if (!plugin.commissioningServer) {
|
|
@@ -1172,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1172
1493
|
plugin.registeredDevices--;
|
|
1173
1494
|
if (plugin.addedDevices !== undefined)
|
|
1174
1495
|
plugin.addedDevices--;
|
|
1496
|
+
// Remove the commissioning server
|
|
1175
1497
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1176
1498
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1177
1499
|
plugin.commissioningServer = undefined;
|
|
1178
1500
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1179
1501
|
}
|
|
1180
1502
|
}
|
|
1503
|
+
// Remove the device from the DeviceManager
|
|
1181
1504
|
this.devices.remove(device);
|
|
1182
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
|
+
*/
|
|
1183
1512
|
async removeAllBridgedDevices(pluginName) {
|
|
1184
1513
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1185
1514
|
this.devices.forEach(async (device) => {
|
|
@@ -1188,12 +1517,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1188
1517
|
}
|
|
1189
1518
|
});
|
|
1190
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
|
+
*/
|
|
1191
1525
|
async startBridge() {
|
|
1526
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1192
1527
|
if (!this.storageManager)
|
|
1193
1528
|
throw new Error('No storage manager initialized');
|
|
1194
1529
|
if (!this.matterbridgeContext)
|
|
1195
1530
|
throw new Error('No storage context initialized');
|
|
1196
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1531
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1197
1532
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
1198
1533
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
1199
1534
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -1207,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1207
1542
|
let failCount = 0;
|
|
1208
1543
|
this.startMatterInterval = setInterval(async () => {
|
|
1209
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
|
|
1210
1546
|
if (!plugin.enabled)
|
|
1211
1547
|
continue;
|
|
1212
1548
|
if (plugin.error) {
|
|
@@ -1231,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1231
1567
|
clearInterval(this.startMatterInterval);
|
|
1232
1568
|
this.startMatterInterval = undefined;
|
|
1233
1569
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
+
// Start the Matter server
|
|
1234
1571
|
await this.startMatterServer();
|
|
1235
1572
|
this.log.notice('Matter server started');
|
|
1573
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1236
1574
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1575
|
+
// Configure the plugins
|
|
1237
1576
|
this.configureTimeout = setTimeout(async () => {
|
|
1238
1577
|
for (const plugin of this.plugins) {
|
|
1239
1578
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1240
1579
|
continue;
|
|
1241
1580
|
try {
|
|
1242
|
-
await this.plugins.configure(plugin);
|
|
1581
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1243
1582
|
}
|
|
1244
1583
|
catch (error) {
|
|
1245
1584
|
plugin.error = true;
|
|
@@ -1248,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1248
1587
|
}
|
|
1249
1588
|
this.wssSendRefreshRequired();
|
|
1250
1589
|
}, 30 * 1000);
|
|
1590
|
+
// Setting reachability to true
|
|
1251
1591
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1252
1592
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1253
1593
|
if (this.commissioningServer)
|
|
@@ -1257,16 +1597,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1257
1597
|
}, 60 * 1000);
|
|
1258
1598
|
}, 1000);
|
|
1259
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
|
+
*/
|
|
1260
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
|
|
1261
1608
|
if (!this.storageManager)
|
|
1262
1609
|
throw new Error('No storage manager initialized');
|
|
1263
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1610
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1264
1611
|
await this.startPlugins();
|
|
1265
1612
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1266
1613
|
let failCount = 0;
|
|
1267
1614
|
this.startMatterInterval = setInterval(async () => {
|
|
1268
1615
|
let allStarted = true;
|
|
1269
1616
|
for (const plugin of this.plugins) {
|
|
1617
|
+
// Prevents to start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1270
1618
|
if (!plugin.enabled)
|
|
1271
1619
|
continue;
|
|
1272
1620
|
if (plugin.error) {
|
|
@@ -1294,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1294
1642
|
clearInterval(this.startMatterInterval);
|
|
1295
1643
|
this.startMatterInterval = undefined;
|
|
1296
1644
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1645
|
+
// Start the Matter server
|
|
1297
1646
|
await this.startMatterServer();
|
|
1298
1647
|
this.log.notice('Matter server started');
|
|
1648
|
+
// Configure the plugins
|
|
1299
1649
|
this.configureTimeout = setTimeout(async () => {
|
|
1300
1650
|
for (const plugin of this.plugins) {
|
|
1301
1651
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1302
1652
|
continue;
|
|
1303
1653
|
try {
|
|
1304
|
-
await this.plugins.configure(plugin);
|
|
1654
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1305
1655
|
}
|
|
1306
1656
|
catch (error) {
|
|
1307
1657
|
plugin.error = true;
|
|
@@ -1330,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1330
1680
|
continue;
|
|
1331
1681
|
}
|
|
1332
1682
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1683
|
+
// Setting reachability to true
|
|
1333
1684
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1334
1685
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1335
1686
|
if (plugin.commissioningServer)
|
|
@@ -1342,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1342
1693
|
}
|
|
1343
1694
|
}, 1000);
|
|
1344
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Starts the Matterbridge controller.
|
|
1698
|
+
* @private
|
|
1699
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
|
+
*/
|
|
1345
1701
|
async startController() {
|
|
1346
1702
|
if (!this.storageManager) {
|
|
1347
1703
|
this.log.error('No storage manager initialized');
|
|
@@ -1356,7 +1712,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1356
1712
|
return;
|
|
1357
1713
|
}
|
|
1358
1714
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1359
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1715
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1360
1716
|
this.log.info('Creating matter commissioning controller');
|
|
1361
1717
|
this.commissioningController = new CommissioningController({
|
|
1362
1718
|
autoConnect: false,
|
|
@@ -1404,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1404
1760
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1405
1761
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1406
1762
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1407
|
-
}
|
|
1763
|
+
} // (hasParameter('pairingcode'))
|
|
1408
1764
|
if (hasParameter('unpairall')) {
|
|
1409
1765
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1410
1766
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1415,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1415
1771
|
return;
|
|
1416
1772
|
}
|
|
1417
1773
|
if (hasParameter('discover')) {
|
|
1774
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1775
|
+
// console.log(discover);
|
|
1418
1776
|
}
|
|
1419
1777
|
if (!this.commissioningController.isCommissioned()) {
|
|
1420
1778
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1455,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1455
1813
|
},
|
|
1456
1814
|
});
|
|
1457
1815
|
node.logStructure();
|
|
1816
|
+
// Get the interaction client
|
|
1458
1817
|
this.log.info('Getting the interaction client');
|
|
1459
1818
|
const interactionClient = await node.getInteractionClient();
|
|
1460
1819
|
let cluster;
|
|
1461
1820
|
let attributes;
|
|
1821
|
+
// Log BasicInformationCluster
|
|
1462
1822
|
cluster = BasicInformationCluster;
|
|
1463
1823
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1464
1824
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1468,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1468
1828
|
attributes.forEach((attribute) => {
|
|
1469
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}`);
|
|
1470
1830
|
});
|
|
1831
|
+
// Log PowerSourceCluster
|
|
1471
1832
|
cluster = PowerSourceCluster;
|
|
1472
1833
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1473
1834
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1477,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1477
1838
|
attributes.forEach((attribute) => {
|
|
1478
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}`);
|
|
1479
1840
|
});
|
|
1841
|
+
// Log ThreadNetworkDiagnostics
|
|
1480
1842
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1481
1843
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1482
1844
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1486,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1486
1848
|
attributes.forEach((attribute) => {
|
|
1487
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}`);
|
|
1488
1850
|
});
|
|
1851
|
+
// Log SwitchCluster
|
|
1489
1852
|
cluster = SwitchCluster;
|
|
1490
1853
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1491
1854
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1506,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1506
1869
|
this.log.info('Subscribed to all attributes and events');
|
|
1507
1870
|
}
|
|
1508
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
|
+
*/
|
|
1509
1881
|
async startMatterStorage(storageType, storageName) {
|
|
1510
1882
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1511
1883
|
if (storageType === 'disk') {
|
|
@@ -1554,11 +1926,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1554
1926
|
await this.matterbridgeContext.set('passcode', this.passcode);
|
|
1555
1927
|
await this.matterbridgeContext.set('discriminator', this.discriminator);
|
|
1556
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
|
+
*/
|
|
1557
1935
|
async convertStorage(context, pluginName) {
|
|
1936
|
+
if (this.edge !== false)
|
|
1937
|
+
return;
|
|
1558
1938
|
try {
|
|
1559
1939
|
const storageService = Environment.default.get(StorageService);
|
|
1560
1940
|
Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
|
|
1561
|
-
const nodeStorage = await storageService.open(
|
|
1941
|
+
const nodeStorage = await storageService.open(pluginName);
|
|
1562
1942
|
if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
|
|
1563
1943
|
this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
|
|
1564
1944
|
return;
|
|
@@ -1566,13 +1946,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1566
1946
|
else {
|
|
1567
1947
|
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1568
1948
|
}
|
|
1949
|
+
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1569
1950
|
const fabricManagerContext = context.createContext('FabricManager');
|
|
1570
1951
|
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1571
1952
|
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
|
|
1953
|
+
// Read EventHandler from the old storage
|
|
1572
1954
|
const eventHandlerContext = context.createContext('EventHandler');
|
|
1955
|
+
// Read SessionManager from the old storage
|
|
1573
1956
|
const sessionManagerContext = context.createContext('SessionManager');
|
|
1957
|
+
// Read EndpointStructure from the old storage
|
|
1574
1958
|
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1959
|
+
// Read generalCommissioning from the old storage
|
|
1575
1960
|
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1961
|
+
// Read basicInformation from the old storage
|
|
1576
1962
|
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1577
1963
|
const fabricInfo = {};
|
|
1578
1964
|
const fabricInfoArray = [];
|
|
@@ -1581,7 +1967,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1581
1967
|
const aclArray = [];
|
|
1582
1968
|
this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
|
|
1583
1969
|
if (fabrics.length === 0 || nextFabricIndex === 0) {
|
|
1584
|
-
this.log.notice(`If you want to try out matterbridge edge
|
|
1970
|
+
this.log.notice(`If you want to try out matterbridge edge add -edge to the command line and pair it to your controller(s).`);
|
|
1585
1971
|
return;
|
|
1586
1972
|
}
|
|
1587
1973
|
for (const fabric of fabrics) {
|
|
@@ -1603,7 +1989,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1603
1989
|
label: fabric.label,
|
|
1604
1990
|
});
|
|
1605
1991
|
nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
|
|
1606
|
-
trcArray.push(
|
|
1992
|
+
trcArray.push(fabric.rootCert);
|
|
1607
1993
|
this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
|
|
1608
1994
|
const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
|
|
1609
1995
|
if (acl && acl.value.length > 0) {
|
|
@@ -1621,14 +2007,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1621
2007
|
await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
|
|
1622
2008
|
await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
|
|
1623
2009
|
await nodeStorage.createContext('root').set('__number__', 0);
|
|
1624
|
-
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
|
|
1625
2010
|
await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
|
|
1626
2011
|
await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
|
|
1627
2012
|
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
1628
2013
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
1629
2014
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
2015
|
+
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
1630
2016
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
2017
|
+
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
1631
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}]
|
|
1632
2022
|
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
1633
2023
|
await nodeStorage
|
|
1634
2024
|
.createContext('root')
|
|
@@ -1639,9 +2029,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1639
2029
|
.createContext('basicInformation')
|
|
1640
2030
|
.set('location', await basicInformationContext.get('location', 'XX'));
|
|
1641
2031
|
await nodeStorage.createContext('root').createContext('network').set('ble', false);
|
|
1642
|
-
await nodeStorage
|
|
1643
|
-
|
|
1644
|
-
|
|
2032
|
+
await nodeStorage
|
|
2033
|
+
.createContext('root')
|
|
2034
|
+
.createContext('network')
|
|
2035
|
+
.set('operationalPort', await context.get('port', 5540));
|
|
2036
|
+
await nodeStorage
|
|
2037
|
+
.createContext('root')
|
|
2038
|
+
.createContext('productDescription')
|
|
2039
|
+
.set('productId', await context.get('productId', 0x8000));
|
|
2040
|
+
await nodeStorage
|
|
2041
|
+
.createContext('root')
|
|
2042
|
+
.createContext('productDescription')
|
|
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
|
+
*/
|
|
2063
|
+
const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
|
|
2064
|
+
this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
|
|
1645
2065
|
for (const key of await endpointStructureContext.keys()) {
|
|
1646
2066
|
if (key === 'nextEndpointId') {
|
|
1647
2067
|
await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
|
|
@@ -1650,41 +2070,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
1650
2070
|
const parts = key.split('-');
|
|
1651
2071
|
const number = await endpointStructureContext.get(key);
|
|
1652
2072
|
if (parts.length === 2) {
|
|
1653
|
-
this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.
|
|
1654
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(
|
|
1655
|
-
}
|
|
1656
|
-
else if (parts.length === 3 && parts[2].startsWith('custom_')) {
|
|
1657
|
-
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${number}`);
|
|
1658
|
-
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
|
|
2073
|
+
this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.__number__:${CYAN}${number}${db}`);
|
|
2074
|
+
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).set('__number__', number);
|
|
1659
2075
|
}
|
|
1660
2076
|
else if (parts.length === 3 && parts[2].startsWith('unique_')) {
|
|
1661
2077
|
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
1662
2078
|
if (device && device.deviceName && device.maybeNumber) {
|
|
1663
|
-
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.
|
|
1664
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(
|
|
2079
|
+
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${CYAN}${device.maybeNumber}${db}`);
|
|
2080
|
+
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
|
|
1665
2081
|
}
|
|
1666
2082
|
}
|
|
1667
2083
|
else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
|
|
1668
2084
|
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
1669
2085
|
if (device && device.deviceName && device.maybeNumber) {
|
|
1670
|
-
|
|
2086
|
+
const childEndpointName = parts[3].replace('custom_', '');
|
|
2087
|
+
const childEndpoint = device.getChildEndpointByName(childEndpointName);
|
|
2088
|
+
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${childEndpoint?.number}${db}`);
|
|
1671
2089
|
await nodeStorage
|
|
1672
2090
|
.createContext('root')
|
|
1673
2091
|
.createContext('parts')
|
|
1674
|
-
.createContext(
|
|
2092
|
+
.createContext(rootDeviceName)
|
|
1675
2093
|
.createContext('parts')
|
|
1676
2094
|
.createContext(device.deviceName.replace(/[ .]/g, ''))
|
|
1677
2095
|
.createContext('parts')
|
|
1678
2096
|
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
1679
|
-
.set('__number__',
|
|
2097
|
+
.set('__number__', childEndpoint?.number);
|
|
1680
2098
|
}
|
|
1681
2099
|
}
|
|
2100
|
+
else if (parts.length === 3 && parts[2].startsWith('custom_')) {
|
|
2101
|
+
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
|
|
2102
|
+
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
|
|
2103
|
+
}
|
|
1682
2104
|
else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
|
|
1683
|
-
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.
|
|
2105
|
+
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
|
|
1684
2106
|
await nodeStorage
|
|
1685
2107
|
.createContext('root')
|
|
1686
2108
|
.createContext('parts')
|
|
1687
|
-
.createContext(
|
|
2109
|
+
.createContext(rootDeviceName)
|
|
1688
2110
|
.createContext('parts')
|
|
1689
2111
|
.createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
|
|
1690
2112
|
.createContext('parts')
|
|
@@ -1709,12 +2131,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1709
2131
|
await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
|
|
1710
2132
|
await context.set('converted', true);
|
|
1711
2133
|
this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
|
|
1712
|
-
this.log.notice(`If you want to try out matterbridge edge
|
|
2134
|
+
this.log.notice(`If you want to try out matterbridge edge add -edge to the command line.`);
|
|
2135
|
+
this.log.notice(`All fabrics have been converted to the new storage format.`);
|
|
1713
2136
|
}
|
|
1714
2137
|
catch (error) {
|
|
1715
2138
|
this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
|
|
1716
2139
|
}
|
|
1717
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
|
+
*/
|
|
1718
2147
|
async backupMatterStorage(storageName, backupName) {
|
|
1719
2148
|
try {
|
|
1720
2149
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1735,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1735
2164
|
}
|
|
1736
2165
|
}
|
|
1737
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
|
+
*/
|
|
1738
2173
|
async restoreMatterStorage(backupName, storageName) {
|
|
1739
2174
|
try {
|
|
1740
2175
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1755,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1755
2190
|
}
|
|
1756
2191
|
}
|
|
1757
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Stops the matter storage.
|
|
2195
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2196
|
+
*/
|
|
1758
2197
|
async stopMatterStorage() {
|
|
1759
2198
|
this.log.debug('Stopping storage');
|
|
1760
2199
|
await this.storageManager?.close();
|
|
@@ -1763,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1763
2202
|
this.matterbridgeContext = undefined;
|
|
1764
2203
|
this.mattercontrollerContext = undefined;
|
|
1765
2204
|
}
|
|
1766
|
-
|
|
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
|
+
*/
|
|
2210
|
+
async createMatterServer(storageManager) {
|
|
1767
2211
|
this.log.debug('Creating matter server');
|
|
2212
|
+
// Validate mdnsInterface
|
|
1768
2213
|
if (this.mdnsInterface) {
|
|
1769
2214
|
const networkInterfaces = os.networkInterfaces();
|
|
1770
2215
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1780,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1780
2225
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1781
2226
|
return matterServer;
|
|
1782
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Starts the Matter server.
|
|
2230
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2231
|
+
*/
|
|
1783
2232
|
async startMatterServer() {
|
|
1784
2233
|
if (!this.matterServer) {
|
|
1785
2234
|
this.log.error('No matter server initialized');
|
|
@@ -1789,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1789
2238
|
this.log.debug('Starting matter server...');
|
|
1790
2239
|
await this.matterServer.start();
|
|
1791
2240
|
this.log.debug('Started matter server');
|
|
2241
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1792
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2245
|
+
*/
|
|
1793
2246
|
async stopMatterServer() {
|
|
1794
2247
|
this.log.debug('Stopping matter commissioningServer');
|
|
1795
2248
|
await this.commissioningServer?.close();
|
|
@@ -1803,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1803
2256
|
this.matterAggregator = undefined;
|
|
1804
2257
|
this.matterServer = undefined;
|
|
1805
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Creates a Matter Aggregator.
|
|
2261
|
+
* @param {StorageContext} context - The storage context.
|
|
2262
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2263
|
+
*/
|
|
1806
2264
|
async createMatterAggregator(context, pluginName) {
|
|
1807
2265
|
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
1808
2266
|
const matterAggregator = new Aggregator();
|
|
1809
2267
|
return matterAggregator;
|
|
1810
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
|
+
*/
|
|
1811
2276
|
async createCommisioningServer(context, pluginName) {
|
|
1812
2277
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1813
2278
|
const deviceName = await context.get('deviceName');
|
|
1814
2279
|
const deviceType = await context.get('deviceType');
|
|
1815
2280
|
const vendorId = await context.get('vendorId');
|
|
1816
|
-
const vendorName = await context.get('vendorName');
|
|
2281
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1817
2282
|
const productId = await context.get('productId');
|
|
1818
|
-
const productName = await context.get('productName');
|
|
2283
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1819
2284
|
const serialNumber = await context.get('serialNumber');
|
|
1820
2285
|
const uniqueId = await context.get('uniqueId');
|
|
1821
2286
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1822
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2287
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1823
2288
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1824
2289
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1825
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')})`);
|
|
@@ -1827,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1827
2292
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1828
2293
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1829
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
|
|
1830
2296
|
if (this.ipv4address) {
|
|
1831
2297
|
const networkInterfaces = os.networkInterfaces();
|
|
1832
2298
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1841,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1841
2307
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1842
2308
|
}
|
|
1843
2309
|
}
|
|
2310
|
+
// Validate ipv6address
|
|
1844
2311
|
if (this.ipv6address) {
|
|
1845
2312
|
const networkInterfaces = os.networkInterfaces();
|
|
1846
2313
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1871,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1871
2338
|
nodeLabel: productName,
|
|
1872
2339
|
productLabel: productName,
|
|
1873
2340
|
softwareVersion,
|
|
1874
|
-
softwareVersionString,
|
|
2341
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1875
2342
|
hardwareVersion,
|
|
1876
2343
|
hardwareVersionString,
|
|
1877
2344
|
uniqueId,
|
|
@@ -1967,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1967
2434
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1968
2435
|
return commissioningServer;
|
|
1969
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
|
+
*/
|
|
1970
2455
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
1971
2456
|
if (!this.storageManager)
|
|
1972
2457
|
throw new Error('No storage manager initialized');
|
|
@@ -1994,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1994
2479
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1995
2480
|
return storageContext;
|
|
1996
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
|
+
*/
|
|
1997
2489
|
async importCommissioningServerContext(pluginName, device) {
|
|
1998
2490
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
1999
2491
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -2028,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2028
2520
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2029
2521
|
return storageContext;
|
|
2030
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
|
+
*/
|
|
2031
2531
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
2032
2532
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
2033
2533
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -2038,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2038
2538
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
2039
2539
|
const QrCode = new QrCodeSchema();
|
|
2040
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`);
|
|
2041
|
-
|
|
2541
|
+
// eslint-disable-next-line no-console
|
|
2542
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
2042
2543
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
2043
2544
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
2044
2545
|
if (pluginName === 'Matterbridge') {
|
|
@@ -2085,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2085
2586
|
}
|
|
2086
2587
|
this.wssSendRefreshRequired();
|
|
2087
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
|
+
*/
|
|
2088
2595
|
sanitizeFabricInformations(fabricInfo) {
|
|
2089
2596
|
return fabricInfo.map((info) => {
|
|
2090
2597
|
return {
|
|
@@ -2098,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2098
2605
|
};
|
|
2099
2606
|
});
|
|
2100
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
|
+
*/
|
|
2101
2614
|
sanitizeSessionInformation(sessionInfo) {
|
|
2102
2615
|
return sessionInfo
|
|
2103
2616
|
.filter((session) => session.isPeerActive)
|
|
@@ -2125,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2125
2638
|
};
|
|
2126
2639
|
});
|
|
2127
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
|
+
*/
|
|
2128
2647
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2129
2648
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2130
2649
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2132,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2132
2651
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2133
2652
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2134
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
|
+
*/
|
|
2135
2659
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
2136
2660
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2137
2661
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2144,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2144
2668
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2145
2669
|
});
|
|
2146
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
|
+
*/
|
|
2147
2677
|
setDeviceReachability(device, reachable) {
|
|
2148
2678
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2149
2679
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2192,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2192
2722
|
}
|
|
2193
2723
|
return vendorName;
|
|
2194
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
|
+
*/
|
|
2195
2729
|
async getBaseRegisteredPlugins() {
|
|
2196
2730
|
const baseRegisteredPlugins = [];
|
|
2197
2731
|
for (const plugin of this.plugins) {
|
|
@@ -2223,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2223
2757
|
}
|
|
2224
2758
|
return baseRegisteredPlugins;
|
|
2225
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
|
+
*/
|
|
2226
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
|
+
*/
|
|
2227
2780
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2228
2781
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2782
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2229
2783
|
const argstring = 'npm ' + args.join(' ');
|
|
2230
2784
|
args.splice(0, args.length, '/c', argstring);
|
|
2231
2785
|
command = 'cmd.exe';
|
|
2232
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
|
|
2233
2790
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2234
2791
|
args.unshift(command);
|
|
2235
2792
|
command = 'sudo';
|
|
@@ -2287,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2287
2844
|
}
|
|
2288
2845
|
});
|
|
2289
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
|
+
*/
|
|
2290
2855
|
wssSendMessage(level, time, name, message) {
|
|
2291
2856
|
if (!level || !time || !name || !message)
|
|
2292
2857
|
return;
|
|
2858
|
+
// Remove ANSI escape codes from the message
|
|
2859
|
+
// eslint-disable-next-line no-control-regex
|
|
2293
2860
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2861
|
+
// Remove leading asterisks from the message
|
|
2294
2862
|
message = message.replace(/^\*+/, '');
|
|
2863
|
+
// Replace all occurrences of \t and \n
|
|
2295
2864
|
message = message.replace(/[\t\n]/g, '');
|
|
2865
|
+
// Remove non-printable characters
|
|
2866
|
+
// eslint-disable-next-line no-control-regex
|
|
2296
2867
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2868
|
+
// Replace all occurrences of \" with "
|
|
2297
2869
|
message = message.replace(/\\"/g, '"');
|
|
2870
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2298
2871
|
const maxContinuousLength = 100;
|
|
2299
2872
|
const keepStartLength = 20;
|
|
2300
2873
|
const keepEndLength = 20;
|
|
2874
|
+
// Split the message into words
|
|
2301
2875
|
message = message
|
|
2302
2876
|
.split(' ')
|
|
2303
2877
|
.map((word) => {
|
|
2878
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2304
2879
|
if (word.length > maxContinuousLength) {
|
|
2305
2880
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2306
2881
|
}
|
|
2307
2882
|
return word;
|
|
2308
2883
|
})
|
|
2309
2884
|
.join(' ');
|
|
2885
|
+
// Send the message to all connected clients
|
|
2310
2886
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2311
2887
|
if (client.readyState === WebSocket.OPEN) {
|
|
2312
2888
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2313
2889
|
}
|
|
2314
2890
|
});
|
|
2315
2891
|
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2894
|
+
*
|
|
2895
|
+
*/
|
|
2316
2896
|
wssSendRefreshRequired() {
|
|
2317
2897
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2898
|
+
// Send the message to all connected clients
|
|
2318
2899
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2319
2900
|
if (client.readyState === WebSocket.OPEN) {
|
|
2320
|
-
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: '
|
|
2901
|
+
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
2321
2902
|
}
|
|
2322
2903
|
});
|
|
2323
2904
|
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2907
|
+
*
|
|
2908
|
+
*/
|
|
2324
2909
|
wssSendRestartRequired() {
|
|
2325
2910
|
this.matterbridgeInformation.restartRequired = true;
|
|
2911
|
+
// Send the message to all connected clients
|
|
2326
2912
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2327
2913
|
if (client.readyState === WebSocket.OPEN) {
|
|
2328
|
-
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: '
|
|
2914
|
+
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
2329
2915
|
}
|
|
2330
2916
|
});
|
|
2331
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
|
+
*/
|
|
2332
2923
|
async initializeFrontend(port = 8283) {
|
|
2333
2924
|
let initializeError = false;
|
|
2334
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
|
|
2335
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
|
|
2336
2938
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2337
2939
|
if (!hasParameter('ssl')) {
|
|
2940
|
+
// Create an HTTP server and attach the express app
|
|
2338
2941
|
this.httpServer = createServer(this.expressApp);
|
|
2942
|
+
// Listen on the specified port
|
|
2339
2943
|
if (hasParameter('ingress')) {
|
|
2340
2944
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2341
2945
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2349,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2349
2953
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2350
2954
|
});
|
|
2351
2955
|
}
|
|
2956
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2352
2957
|
this.httpServer.on('error', (error) => {
|
|
2353
2958
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2354
2959
|
switch (error.code) {
|
|
@@ -2364,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2364
2969
|
});
|
|
2365
2970
|
}
|
|
2366
2971
|
else {
|
|
2972
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2367
2973
|
let cert;
|
|
2368
2974
|
try {
|
|
2369
2975
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2391,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2391
2997
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2392
2998
|
}
|
|
2393
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
|
|
2394
3001
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
3002
|
+
// Listen on the specified port
|
|
2395
3003
|
if (hasParameter('ingress')) {
|
|
2396
3004
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2397
3005
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2405,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2405
3013
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2406
3014
|
});
|
|
2407
3015
|
}
|
|
3016
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2408
3017
|
this.httpsServer.on('error', (error) => {
|
|
2409
3018
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2410
3019
|
switch (error.code) {
|
|
@@ -2421,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2421
3030
|
}
|
|
2422
3031
|
if (initializeError)
|
|
2423
3032
|
return;
|
|
3033
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2424
3034
|
const wssPort = port;
|
|
2425
3035
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2426
3036
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2427
3037
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2428
3038
|
const clientIp = request.socket.remoteAddress;
|
|
2429
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
3039
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2430
3040
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2431
3041
|
ws.on('message', (message) => {
|
|
2432
3042
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2459,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2459
3069
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2460
3070
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2461
3071
|
});
|
|
3072
|
+
// Endpoint to validate login code
|
|
2462
3073
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2463
3074
|
const { password } = req.body;
|
|
2464
3075
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2477,12 +3088,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2477
3088
|
this.log.warn('/api/login error wrong password');
|
|
2478
3089
|
res.json({ valid: false });
|
|
2479
3090
|
}
|
|
3091
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2480
3092
|
}
|
|
2481
3093
|
catch (error) {
|
|
2482
3094
|
this.log.error('/api/login error getting password');
|
|
2483
3095
|
res.json({ valid: false });
|
|
2484
3096
|
}
|
|
2485
3097
|
});
|
|
3098
|
+
// Endpoint to provide settings
|
|
2486
3099
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2487
3100
|
this.log.debug('The frontend sent /api/settings');
|
|
2488
3101
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2502,14 +3115,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
2502
3115
|
this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
|
|
2503
3116
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2504
3117
|
this.matterbridgeInformation.profile = this.profile;
|
|
2505
|
-
const response = {
|
|
3118
|
+
const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
3119
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2506
3120
|
res.json(response);
|
|
2507
3121
|
});
|
|
3122
|
+
// Endpoint to provide plugins
|
|
2508
3123
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2509
3124
|
this.log.debug('The frontend sent /api/plugins');
|
|
2510
3125
|
const response = await this.getBaseRegisteredPlugins();
|
|
3126
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2511
3127
|
res.json(response);
|
|
2512
3128
|
});
|
|
3129
|
+
// Endpoint to provide devices
|
|
2513
3130
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2514
3131
|
this.log.debug('The frontend sent /api/devices');
|
|
2515
3132
|
const devices = [];
|
|
@@ -2542,8 +3159,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2542
3159
|
cluster: cluster,
|
|
2543
3160
|
});
|
|
2544
3161
|
});
|
|
3162
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2545
3163
|
res.json(devices);
|
|
2546
3164
|
});
|
|
3165
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2547
3166
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2548
3167
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2549
3168
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2563,6 +3182,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2563
3182
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2564
3183
|
if (clusterServer.name === 'EveHistory')
|
|
2565
3184
|
return;
|
|
3185
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2566
3186
|
let attributeValue;
|
|
2567
3187
|
try {
|
|
2568
3188
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2573,6 +3193,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2573
3193
|
catch (error) {
|
|
2574
3194
|
attributeValue = 'Fabric-Scoped';
|
|
2575
3195
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3196
|
+
// console.log(error);
|
|
2576
3197
|
}
|
|
2577
3198
|
data.push({
|
|
2578
3199
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2585,12 +3206,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2585
3206
|
});
|
|
2586
3207
|
});
|
|
2587
3208
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2588
3210
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2589
3211
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2590
3212
|
clusterServers.forEach((clusterServer) => {
|
|
2591
3213
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2592
3214
|
if (clusterServer.name === 'EveHistory')
|
|
2593
3215
|
return;
|
|
3216
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2594
3217
|
let attributeValue;
|
|
2595
3218
|
try {
|
|
2596
3219
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2601,6 +3224,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2601
3224
|
catch (error) {
|
|
2602
3225
|
attributeValue = 'Unavailable';
|
|
2603
3226
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3227
|
+
// console.log(error);
|
|
2604
3228
|
}
|
|
2605
3229
|
data.push({
|
|
2606
3230
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2617,6 +3241,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2617
3241
|
});
|
|
2618
3242
|
res.json(data);
|
|
2619
3243
|
});
|
|
3244
|
+
// Endpoint to view the log
|
|
2620
3245
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2621
3246
|
this.log.debug('The frontend sent /api/log');
|
|
2622
3247
|
try {
|
|
@@ -2629,10 +3254,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2629
3254
|
res.status(500).send('Error reading log file');
|
|
2630
3255
|
}
|
|
2631
3256
|
});
|
|
3257
|
+
// Endpoint to download the matterbridge log
|
|
2632
3258
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2633
3259
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2634
3260
|
try {
|
|
2635
3261
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3262
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2636
3263
|
}
|
|
2637
3264
|
catch (error) {
|
|
2638
3265
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2644,10 +3271,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2644
3271
|
}
|
|
2645
3272
|
});
|
|
2646
3273
|
});
|
|
3274
|
+
// Endpoint to download the matter log
|
|
2647
3275
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2648
3276
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2649
3277
|
try {
|
|
2650
3278
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3279
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2651
3280
|
}
|
|
2652
3281
|
catch (error) {
|
|
2653
3282
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2659,6 +3288,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2659
3288
|
}
|
|
2660
3289
|
});
|
|
2661
3290
|
});
|
|
3291
|
+
// Endpoint to download the matter storage file
|
|
2662
3292
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2663
3293
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2664
3294
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2668,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2668
3298
|
}
|
|
2669
3299
|
});
|
|
2670
3300
|
});
|
|
3301
|
+
// Endpoint to download the matterbridge storage directory
|
|
2671
3302
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2672
3303
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2673
3304
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2678,6 +3309,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2678
3309
|
}
|
|
2679
3310
|
});
|
|
2680
3311
|
});
|
|
3312
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2681
3313
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2682
3314
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2683
3315
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2688,9 +3320,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2688
3320
|
}
|
|
2689
3321
|
});
|
|
2690
3322
|
});
|
|
3323
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2691
3324
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2692
3325
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2693
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')));
|
|
2694
3328
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2695
3329
|
if (error) {
|
|
2696
3330
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2698,6 +3332,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2698
3332
|
}
|
|
2699
3333
|
});
|
|
2700
3334
|
});
|
|
3335
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2701
3336
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2702
3337
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2703
3338
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2707,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2707
3342
|
}
|
|
2708
3343
|
});
|
|
2709
3344
|
});
|
|
3345
|
+
// Endpoint to receive commands
|
|
2710
3346
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2711
3347
|
const command = req.params.command;
|
|
2712
3348
|
let param = req.params.param;
|
|
@@ -2716,13 +3352,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2716
3352
|
return;
|
|
2717
3353
|
}
|
|
2718
3354
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3355
|
+
// Handle the command setpassword from Settings
|
|
2719
3356
|
if (command === 'setpassword') {
|
|
2720
|
-
const password = param.slice(1, -1);
|
|
3357
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2721
3358
|
this.log.debug('setpassword', param, password);
|
|
2722
3359
|
await this.nodeContext?.set('password', password);
|
|
2723
3360
|
res.json({ message: 'Command received' });
|
|
2724
3361
|
return;
|
|
2725
3362
|
}
|
|
3363
|
+
// Handle the command setbridgemode from Settings
|
|
2726
3364
|
if (command === 'setbridgemode') {
|
|
2727
3365
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2728
3366
|
this.wssSendRestartRequired();
|
|
@@ -2730,6 +3368,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2730
3368
|
res.json({ message: 'Command received' });
|
|
2731
3369
|
return;
|
|
2732
3370
|
}
|
|
3371
|
+
// Handle the command backup from Settings
|
|
2733
3372
|
if (command === 'backup') {
|
|
2734
3373
|
this.log.notice(`Prepairing the backup...`);
|
|
2735
3374
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2737,25 +3376,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2737
3376
|
res.json({ message: 'Command received' });
|
|
2738
3377
|
return;
|
|
2739
3378
|
}
|
|
3379
|
+
// Handle the command setmbloglevel from Settings
|
|
2740
3380
|
if (command === 'setmbloglevel') {
|
|
2741
3381
|
this.log.debug('Matterbridge log level:', param);
|
|
2742
3382
|
if (param === 'Debug') {
|
|
2743
|
-
this.log.logLevel = "debug"
|
|
3383
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2744
3384
|
}
|
|
2745
3385
|
else if (param === 'Info') {
|
|
2746
|
-
this.log.logLevel = "info"
|
|
3386
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2747
3387
|
}
|
|
2748
3388
|
else if (param === 'Notice') {
|
|
2749
|
-
this.log.logLevel = "notice"
|
|
3389
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2750
3390
|
}
|
|
2751
3391
|
else if (param === 'Warn') {
|
|
2752
|
-
this.log.logLevel = "warn"
|
|
3392
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2753
3393
|
}
|
|
2754
3394
|
else if (param === 'Error') {
|
|
2755
|
-
this.log.logLevel = "error"
|
|
3395
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2756
3396
|
}
|
|
2757
3397
|
else if (param === 'Fatal') {
|
|
2758
|
-
this.log.logLevel = "fatal"
|
|
3398
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2759
3399
|
}
|
|
2760
3400
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2761
3401
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2763,12 +3403,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2763
3403
|
for (const plugin of this.plugins) {
|
|
2764
3404
|
if (!plugin.platform || !plugin.platform.config)
|
|
2765
3405
|
continue;
|
|
2766
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2767
|
-
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);
|
|
2768
3408
|
}
|
|
2769
3409
|
res.json({ message: 'Command received' });
|
|
2770
3410
|
return;
|
|
2771
3411
|
}
|
|
3412
|
+
// Handle the command setmbloglevel from Settings
|
|
2772
3413
|
if (command === 'setmjloglevel') {
|
|
2773
3414
|
this.log.debug('Matter.js log level:', param);
|
|
2774
3415
|
if (param === 'Debug') {
|
|
@@ -2793,30 +3434,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2793
3434
|
res.json({ message: 'Command received' });
|
|
2794
3435
|
return;
|
|
2795
3436
|
}
|
|
3437
|
+
// Handle the command setmdnsinterface from Settings
|
|
2796
3438
|
if (command === 'setmdnsinterface') {
|
|
2797
|
-
param = param.slice(1, -1);
|
|
3439
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2798
3440
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2799
3441
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2800
3442
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2801
3443
|
res.json({ message: 'Command received' });
|
|
2802
3444
|
return;
|
|
2803
3445
|
}
|
|
3446
|
+
// Handle the command setipv4address from Settings
|
|
2804
3447
|
if (command === 'setipv4address') {
|
|
2805
|
-
param = param.slice(1, -1);
|
|
3448
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2806
3449
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2807
3450
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2808
3451
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2809
3452
|
res.json({ message: 'Command received' });
|
|
2810
3453
|
return;
|
|
2811
3454
|
}
|
|
3455
|
+
// Handle the command setipv6address from Settings
|
|
2812
3456
|
if (command === 'setipv6address') {
|
|
2813
|
-
param = param.slice(1, -1);
|
|
3457
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2814
3458
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2815
3459
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2816
3460
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2817
3461
|
res.json({ message: 'Command received' });
|
|
2818
3462
|
return;
|
|
2819
3463
|
}
|
|
3464
|
+
// Handle the command setmatterport from Settings
|
|
2820
3465
|
if (command === 'setmatterport') {
|
|
2821
3466
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2822
3467
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2825,6 +3470,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2825
3470
|
res.json({ message: 'Command received' });
|
|
2826
3471
|
return;
|
|
2827
3472
|
}
|
|
3473
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2828
3474
|
if (command === 'setmatterdiscriminator') {
|
|
2829
3475
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2830
3476
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2833,6 +3479,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2833
3479
|
res.json({ message: 'Command received' });
|
|
2834
3480
|
return;
|
|
2835
3481
|
}
|
|
3482
|
+
// Handle the command setmatterpasscode from Settings
|
|
2836
3483
|
if (command === 'setmatterpasscode') {
|
|
2837
3484
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2838
3485
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2841,17 +3488,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2841
3488
|
res.json({ message: 'Command received' });
|
|
2842
3489
|
return;
|
|
2843
3490
|
}
|
|
3491
|
+
// Handle the command setmbloglevel from Settings
|
|
2844
3492
|
if (command === 'setmblogfile') {
|
|
2845
3493
|
this.log.debug('Matterbridge file log:', param);
|
|
2846
3494
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2847
3495
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3496
|
+
// Create the file logger for matterbridge
|
|
2848
3497
|
if (param === 'true')
|
|
2849
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3498
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2850
3499
|
else
|
|
2851
3500
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2852
3501
|
res.json({ message: 'Command received' });
|
|
2853
3502
|
return;
|
|
2854
3503
|
}
|
|
3504
|
+
// Handle the command setmbloglevel from Settings
|
|
2855
3505
|
if (command === 'setmjlogfile') {
|
|
2856
3506
|
this.log.debug('Matter file log:', param);
|
|
2857
3507
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2878,36 +3528,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2878
3528
|
res.json({ message: 'Command received' });
|
|
2879
3529
|
return;
|
|
2880
3530
|
}
|
|
3531
|
+
// Handle the command unregister from Settings
|
|
2881
3532
|
if (command === 'unregister') {
|
|
2882
3533
|
await this.unregisterAndShutdownProcess();
|
|
2883
3534
|
res.json({ message: 'Command received' });
|
|
2884
3535
|
return;
|
|
2885
3536
|
}
|
|
3537
|
+
// Handle the command reset from Settings
|
|
2886
3538
|
if (command === 'reset') {
|
|
2887
3539
|
await this.shutdownProcessAndReset();
|
|
2888
3540
|
res.json({ message: 'Command received' });
|
|
2889
3541
|
return;
|
|
2890
3542
|
}
|
|
3543
|
+
// Handle the command factoryreset from Settings
|
|
2891
3544
|
if (command === 'factoryreset') {
|
|
2892
3545
|
await this.shutdownProcessAndFactoryReset();
|
|
2893
3546
|
res.json({ message: 'Command received' });
|
|
2894
3547
|
return;
|
|
2895
3548
|
}
|
|
3549
|
+
// Handle the command shutdown from Header
|
|
2896
3550
|
if (command === 'shutdown') {
|
|
2897
3551
|
await this.shutdownProcess();
|
|
2898
3552
|
res.json({ message: 'Command received' });
|
|
2899
3553
|
return;
|
|
2900
3554
|
}
|
|
3555
|
+
// Handle the command restart from Header
|
|
2901
3556
|
if (command === 'restart') {
|
|
2902
3557
|
await this.restartProcess();
|
|
2903
3558
|
res.json({ message: 'Command received' });
|
|
2904
3559
|
return;
|
|
2905
3560
|
}
|
|
3561
|
+
// Handle the command update from Header
|
|
2906
3562
|
if (command === 'update') {
|
|
2907
3563
|
this.log.info('Updating matterbridge...');
|
|
2908
3564
|
try {
|
|
2909
3565
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2910
3566
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3567
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2911
3568
|
}
|
|
2912
3569
|
catch (error) {
|
|
2913
3570
|
this.log.error('Error updating matterbridge');
|
|
@@ -2917,9 +3574,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2917
3574
|
res.json({ message: 'Command received' });
|
|
2918
3575
|
return;
|
|
2919
3576
|
}
|
|
3577
|
+
// Handle the command saveconfig from Home
|
|
2920
3578
|
if (command === 'saveconfig') {
|
|
2921
3579
|
param = param.replace(/\*/g, '\\');
|
|
2922
3580
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3581
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2923
3582
|
if (!this.plugins.has(param)) {
|
|
2924
3583
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2925
3584
|
}
|
|
@@ -2933,33 +3592,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2933
3592
|
res.json({ message: 'Command received' });
|
|
2934
3593
|
return;
|
|
2935
3594
|
}
|
|
3595
|
+
// Handle the command installplugin from Home
|
|
2936
3596
|
if (command === 'installplugin') {
|
|
2937
3597
|
param = param.replace(/\*/g, '\\');
|
|
2938
3598
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2939
3599
|
try {
|
|
2940
3600
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2941
3601
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3602
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2942
3603
|
}
|
|
2943
3604
|
catch (error) {
|
|
2944
3605
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2945
3606
|
}
|
|
2946
3607
|
this.wssSendRestartRequired();
|
|
2947
3608
|
param = param.split('@')[0];
|
|
3609
|
+
// Also add the plugin to matterbridge so no return!
|
|
2948
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
|
|
2949
3612
|
res.json({ message: 'Command received' });
|
|
2950
3613
|
return;
|
|
2951
3614
|
}
|
|
2952
3615
|
}
|
|
3616
|
+
// Handle the command addplugin from Home
|
|
2953
3617
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2954
3618
|
param = param.replace(/\*/g, '\\');
|
|
2955
3619
|
const plugin = await this.plugins.add(param);
|
|
2956
3620
|
if (plugin) {
|
|
2957
|
-
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
|
|
2958
3622
|
}
|
|
2959
3623
|
res.json({ message: 'Command received' });
|
|
2960
3624
|
this.wssSendRefreshRequired();
|
|
2961
3625
|
return;
|
|
2962
3626
|
}
|
|
3627
|
+
// Handle the command removeplugin from Home
|
|
2963
3628
|
if (command === 'removeplugin') {
|
|
2964
3629
|
if (!this.plugins.has(param)) {
|
|
2965
3630
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2973,6 +3638,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2973
3638
|
this.wssSendRefreshRequired();
|
|
2974
3639
|
return;
|
|
2975
3640
|
}
|
|
3641
|
+
// Handle the command enableplugin from Home
|
|
2976
3642
|
if (command === 'enableplugin') {
|
|
2977
3643
|
if (!this.plugins.has(param)) {
|
|
2978
3644
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2990,13 +3656,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2990
3656
|
plugin.registeredDevices = undefined;
|
|
2991
3657
|
plugin.addedDevices = undefined;
|
|
2992
3658
|
await this.plugins.enable(param);
|
|
2993
|
-
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
|
|
2994
3660
|
}
|
|
2995
3661
|
}
|
|
2996
3662
|
res.json({ message: 'Command received' });
|
|
2997
3663
|
this.wssSendRefreshRequired();
|
|
2998
3664
|
return;
|
|
2999
3665
|
}
|
|
3666
|
+
// Handle the command disableplugin from Home
|
|
3000
3667
|
if (command === 'disableplugin') {
|
|
3001
3668
|
if (!this.plugins.has(param)) {
|
|
3002
3669
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3013,6 +3680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3013
3680
|
return;
|
|
3014
3681
|
}
|
|
3015
3682
|
});
|
|
3683
|
+
// Fallback for routing (must be the last route)
|
|
3016
3684
|
this.expressApp.get('*', (req, res) => {
|
|
3017
3685
|
this.log.debug('The frontend sent:', req.url);
|
|
3018
3686
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -3020,6 +3688,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3020
3688
|
});
|
|
3021
3689
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
3022
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
|
+
*/
|
|
3023
3696
|
getClusterTextFromDevice(device) {
|
|
3024
3697
|
const stringifyUserLabel = (endpoint) => {
|
|
3025
3698
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -3042,9 +3715,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3042
3715
|
return '';
|
|
3043
3716
|
};
|
|
3044
3717
|
let attributes = '';
|
|
3718
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3045
3719
|
const clusterServers = device.getAllClusterServers();
|
|
3046
3720
|
clusterServers.forEach((clusterServer) => {
|
|
3047
3721
|
try {
|
|
3722
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3048
3723
|
if (clusterServer.name === 'OnOff')
|
|
3049
3724
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
3050
3725
|
if (clusterServer.name === 'Switch')
|
|
@@ -3095,18 +3770,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
3095
3770
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
3096
3771
|
if (clusterServer.name === 'UserLabel')
|
|
3097
3772
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3773
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3098
3774
|
}
|
|
3099
3775
|
catch (error) {
|
|
3100
3776
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3101
3777
|
}
|
|
3102
3778
|
});
|
|
3779
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3103
3780
|
return attributes;
|
|
3104
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
|
+
*/
|
|
3105
3788
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3789
|
+
// Set the bridge mode
|
|
3106
3790
|
this.bridgeMode = 'bridge';
|
|
3791
|
+
// Set the first port to use
|
|
3107
3792
|
this.port = port;
|
|
3108
|
-
|
|
3793
|
+
// Set Matterbridge logger
|
|
3794
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3109
3795
|
this.log.debug('Matterbridge extension is starting...');
|
|
3796
|
+
// Initialize NodeStorage
|
|
3110
3797
|
this.matterbridgeDirectory = dataPath;
|
|
3111
3798
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3112
3799
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -3125,10 +3812,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
3125
3812
|
};
|
|
3126
3813
|
this.plugins.set(plugin);
|
|
3127
3814
|
this.plugins.saveToStorage();
|
|
3815
|
+
// Log system info and create .matterbridge directory
|
|
3128
3816
|
await this.logNodeAndSystemInfo();
|
|
3129
3817
|
this.matterbridgeDirectory = dataPath;
|
|
3818
|
+
// Set matter.js logger level and format
|
|
3130
3819
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3131
3820
|
Logger.format = MatterLogFormat.ANSI;
|
|
3821
|
+
// Start the storage and create matterbridgeContext
|
|
3132
3822
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3133
3823
|
if (!this.storageManager)
|
|
3134
3824
|
return false;
|
|
@@ -3138,8 +3828,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
3138
3828
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3139
3829
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3140
3830
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3141
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3142
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
3831
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
3832
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
3143
3833
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3144
3834
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
3145
3835
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -3151,6 +3841,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3151
3841
|
await this.startMatterServer();
|
|
3152
3842
|
this.log.info('Matter server started');
|
|
3153
3843
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3844
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
3154
3845
|
setTimeout(() => {
|
|
3155
3846
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3156
3847
|
if (this.commissioningServer)
|
|
@@ -3160,14 +3851,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
3160
3851
|
}, 60 * 1000);
|
|
3161
3852
|
return this.commissioningServer.isCommissioned();
|
|
3162
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
|
+
*/
|
|
3163
3860
|
async stopExtension() {
|
|
3861
|
+
// Closing matter
|
|
3164
3862
|
await this.stopMatterServer();
|
|
3863
|
+
// Clearing the session manager
|
|
3864
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3865
|
+
// Closing storage
|
|
3165
3866
|
await this.stopMatterStorage();
|
|
3166
3867
|
this.log.info('Matter server stopped');
|
|
3167
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
|
+
*/
|
|
3168
3875
|
isExtensionCommissioned() {
|
|
3169
3876
|
if (!this.commissioningServer)
|
|
3170
3877
|
return false;
|
|
3171
3878
|
return this.commissioningServer.isCommissioned();
|
|
3172
3879
|
}
|
|
3173
3880
|
}
|
|
3881
|
+
//# sourceMappingURL=matterbridge.js.map
|