matterbridge 1.6.8-dev.8 → 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 +23 -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 +851 -106
- 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 +179 -7
- 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,13 +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
|
+
*/
|
|
181
|
+
getDevices() {
|
|
182
|
+
return this.devices.array();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Retrieves the list of registered plugins.
|
|
186
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
187
|
+
*/
|
|
188
|
+
getPlugins() {
|
|
189
|
+
return this.plugins.array();
|
|
190
|
+
}
|
|
140
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
|
+
*/
|
|
141
202
|
static async loadInstance(initialize = false) {
|
|
142
203
|
if (!Matterbridge.instance) {
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
143
205
|
if (hasParameter('debug'))
|
|
144
206
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
145
207
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -148,6 +210,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
148
210
|
}
|
|
149
211
|
return Matterbridge.instance;
|
|
150
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Call cleanup().
|
|
215
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
216
|
+
*
|
|
217
|
+
*/
|
|
151
218
|
async destroyInstance() {
|
|
152
219
|
await this.cleanup('destroying instance...', false);
|
|
153
220
|
await waiter('destroying instance...', () => {
|
|
@@ -155,39 +222,60 @@ export class Matterbridge extends EventEmitter {
|
|
|
155
222
|
}, false, 60000, 100, false);
|
|
156
223
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
157
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
|
+
*/
|
|
158
235
|
async initialize() {
|
|
236
|
+
// Set the restart mode
|
|
159
237
|
if (hasParameter('service'))
|
|
160
238
|
this.restartMode = 'service';
|
|
161
239
|
if (hasParameter('docker'))
|
|
162
240
|
this.restartMode = 'docker';
|
|
241
|
+
// Set the matterbridge directory
|
|
163
242
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
164
243
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
165
|
-
|
|
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
|
|
166
247
|
try {
|
|
167
248
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
168
249
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
169
250
|
this.log.debug('Creating node storage context for matterbridge');
|
|
170
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
|
|
171
254
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
172
255
|
for (const key of keys) {
|
|
173
256
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
258
|
await this.nodeStorage?.storage.get(key);
|
|
175
259
|
}
|
|
176
260
|
const storages = await this.nodeStorage.getStorageNames();
|
|
177
261
|
for (const storage of storages) {
|
|
178
262
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
179
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
|
|
180
266
|
const keys = (await nodeContext?.storage.keys());
|
|
181
267
|
keys.forEach(async (key) => {
|
|
182
268
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
183
269
|
await nodeContext?.get(key);
|
|
184
270
|
});
|
|
185
271
|
}
|
|
272
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
186
273
|
this.log.debug('Creating node storage backup...');
|
|
187
274
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
188
275
|
this.log.debug('Created node storage backup');
|
|
189
276
|
}
|
|
190
277
|
catch (error) {
|
|
278
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
191
279
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
192
280
|
if (hasParameter('norestore')) {
|
|
193
281
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -202,45 +290,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
202
290
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
203
291
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
204
292
|
}
|
|
293
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
205
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)
|
|
206
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)
|
|
207
298
|
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
208
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)
|
|
209
301
|
if (hasParameter('logger')) {
|
|
210
302
|
const level = getParameter('logger');
|
|
211
303
|
if (level === 'debug') {
|
|
212
|
-
this.log.logLevel = "debug"
|
|
304
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
213
305
|
}
|
|
214
306
|
else if (level === 'info') {
|
|
215
|
-
this.log.logLevel = "info"
|
|
307
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
216
308
|
}
|
|
217
309
|
else if (level === 'notice') {
|
|
218
|
-
this.log.logLevel = "notice"
|
|
310
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
219
311
|
}
|
|
220
312
|
else if (level === 'warn') {
|
|
221
|
-
this.log.logLevel = "warn"
|
|
313
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
222
314
|
}
|
|
223
315
|
else if (level === 'error') {
|
|
224
|
-
this.log.logLevel = "error"
|
|
316
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
225
317
|
}
|
|
226
318
|
else if (level === 'fatal') {
|
|
227
|
-
this.log.logLevel = "fatal"
|
|
319
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
228
320
|
}
|
|
229
321
|
else {
|
|
230
322
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
231
|
-
this.log.logLevel = "info"
|
|
323
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
232
324
|
}
|
|
233
325
|
}
|
|
234
326
|
else {
|
|
235
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
327
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
236
328
|
}
|
|
237
329
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
330
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
238
331
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
239
332
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
240
333
|
this.matterbridgeInformation.fileLogger = true;
|
|
241
334
|
}
|
|
242
335
|
this.log.notice('Matterbridge is starting...');
|
|
243
336
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
337
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
244
338
|
if (hasParameter('matterlogger')) {
|
|
245
339
|
const level = getParameter('matterlogger');
|
|
246
340
|
if (level === 'debug') {
|
|
@@ -271,6 +365,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
271
365
|
}
|
|
272
366
|
Logger.format = MatterLogFormat.ANSI;
|
|
273
367
|
Logger.setLogger('default', this.createMatterLogger());
|
|
368
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
274
369
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
275
370
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
276
371
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -279,6 +374,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
279
374
|
});
|
|
280
375
|
}
|
|
281
376
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
282
378
|
if (hasParameter('mdnsinterface')) {
|
|
283
379
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
284
380
|
}
|
|
@@ -287,6 +383,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
287
383
|
if (this.mdnsInterface === '')
|
|
288
384
|
this.mdnsInterface = undefined;
|
|
289
385
|
}
|
|
386
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
290
387
|
if (hasParameter('ipv4address')) {
|
|
291
388
|
this.ipv4address = getParameter('ipv4address');
|
|
292
389
|
}
|
|
@@ -295,6 +392,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
295
392
|
if (this.ipv4address === '')
|
|
296
393
|
this.ipv4address = undefined;
|
|
297
394
|
}
|
|
395
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
298
396
|
if (hasParameter('ipv6address')) {
|
|
299
397
|
this.ipv6address = getParameter('ipv6address');
|
|
300
398
|
}
|
|
@@ -303,17 +401,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
303
401
|
if (this.ipv6address === '')
|
|
304
402
|
this.ipv6address = undefined;
|
|
305
403
|
}
|
|
404
|
+
// Initialize PluginManager
|
|
306
405
|
this.plugins = new PluginManager(this);
|
|
307
406
|
await this.plugins.loadFromStorage();
|
|
407
|
+
// Initialize DeviceManager
|
|
308
408
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
409
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
309
410
|
for (const plugin of this.plugins) {
|
|
310
411
|
const packageJson = await this.plugins.parse(plugin);
|
|
311
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
|
|
312
415
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
313
416
|
try {
|
|
314
417
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
315
418
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
316
419
|
plugin.error = false;
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
317
421
|
}
|
|
318
422
|
catch (error) {
|
|
319
423
|
plugin.error = true;
|
|
@@ -330,6 +434,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
330
434
|
await plugin.nodeContext.set('description', plugin.description);
|
|
331
435
|
await plugin.nodeContext.set('author', plugin.author);
|
|
332
436
|
}
|
|
437
|
+
// Log system info and create .matterbridge directory
|
|
333
438
|
await this.logNodeAndSystemInfo();
|
|
334
439
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
335
440
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -337,6 +442,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
337
442
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
338
443
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
339
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
|
|
340
446
|
const minNodeVersion = 18;
|
|
341
447
|
const nodeVersion = process.versions.node;
|
|
342
448
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -344,10 +450,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
344
450
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
345
451
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
346
452
|
}
|
|
453
|
+
// Register process handlers
|
|
347
454
|
this.registerProcessHandlers();
|
|
455
|
+
// Parse command line
|
|
348
456
|
await this.parseCommandLine();
|
|
349
457
|
this.initialized = true;
|
|
350
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
|
+
*/
|
|
351
464
|
async parseCommandLine() {
|
|
352
465
|
if (hasParameter('help')) {
|
|
353
466
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -455,18 +568,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
455
568
|
}
|
|
456
569
|
if (hasParameter('factoryreset')) {
|
|
457
570
|
try {
|
|
458
|
-
|
|
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);
|
|
459
575
|
}
|
|
460
576
|
catch (err) {
|
|
461
|
-
|
|
577
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
578
|
+
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
579
|
+
}
|
|
462
580
|
}
|
|
463
581
|
try {
|
|
464
|
-
|
|
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 });
|
|
465
586
|
}
|
|
466
587
|
catch (err) {
|
|
467
|
-
|
|
588
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
589
|
+
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
590
|
+
}
|
|
468
591
|
}
|
|
469
|
-
|
|
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 });
|
|
597
|
+
}
|
|
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.');
|
|
470
604
|
this.nodeContext = undefined;
|
|
471
605
|
this.nodeStorage = undefined;
|
|
472
606
|
this.plugins.clear();
|
|
@@ -474,6 +608,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
474
608
|
this.emit('shutdown');
|
|
475
609
|
return;
|
|
476
610
|
}
|
|
611
|
+
// Start the matter storage and create the matterbridge context
|
|
477
612
|
try {
|
|
478
613
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
479
614
|
}
|
|
@@ -481,7 +616,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
481
616
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
482
617
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
483
618
|
}
|
|
484
|
-
if
|
|
619
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
620
|
+
if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
|
|
485
621
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
486
622
|
await this.matterbridgeContext?.clearAll();
|
|
487
623
|
await this.stopMatterStorage();
|
|
@@ -489,7 +625,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
489
625
|
this.emit('shutdown');
|
|
490
626
|
return;
|
|
491
627
|
}
|
|
492
|
-
if
|
|
628
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
629
|
+
if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
493
630
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
494
631
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
495
632
|
if (plugin) {
|
|
@@ -508,28 +645,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
508
645
|
this.emit('shutdown');
|
|
509
646
|
return;
|
|
510
647
|
}
|
|
648
|
+
// Initialize frontend
|
|
511
649
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
512
650
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
651
|
+
// Check each 60 minutes the latest versions
|
|
513
652
|
this.checkUpdateInterval = setInterval(() => {
|
|
514
653
|
this.getMatterbridgeLatestVersion();
|
|
515
654
|
for (const plugin of this.plugins) {
|
|
516
655
|
this.getPluginLatestVersion(plugin);
|
|
517
656
|
}
|
|
518
657
|
}, 60 * 60 * 1000);
|
|
658
|
+
// Start the matterbridge in mode test
|
|
519
659
|
if (hasParameter('test')) {
|
|
520
660
|
this.bridgeMode = 'bridge';
|
|
521
661
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
522
662
|
return;
|
|
523
663
|
}
|
|
664
|
+
// Start the matterbridge in mode controller
|
|
524
665
|
if (hasParameter('controller')) {
|
|
525
666
|
this.bridgeMode = 'controller';
|
|
526
667
|
await this.startController();
|
|
527
668
|
return;
|
|
528
669
|
}
|
|
670
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
529
671
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
530
672
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
531
673
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
532
674
|
}
|
|
675
|
+
// Start matterbridge in bridge mode
|
|
533
676
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
534
677
|
this.bridgeMode = 'bridge';
|
|
535
678
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -538,6 +681,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
538
681
|
await this.startBridge();
|
|
539
682
|
return;
|
|
540
683
|
}
|
|
684
|
+
// Start matterbridge in childbridge mode
|
|
541
685
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
542
686
|
this.bridgeMode = 'childbridge';
|
|
543
687
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -547,17 +691,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
547
691
|
return;
|
|
548
692
|
}
|
|
549
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
|
+
*/
|
|
550
702
|
async startPlugins() {
|
|
703
|
+
// Check, load and start the plugins
|
|
551
704
|
for (const plugin of this.plugins) {
|
|
552
705
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
553
706
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
707
|
+
// Check if the plugin is available
|
|
554
708
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
555
709
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
556
710
|
plugin.enabled = false;
|
|
557
711
|
plugin.error = true;
|
|
558
712
|
continue;
|
|
559
713
|
}
|
|
560
|
-
|
|
714
|
+
// Check if the plugin has a new version
|
|
715
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
561
716
|
if (!plugin.enabled) {
|
|
562
717
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
563
718
|
continue;
|
|
@@ -572,20 +727,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
572
727
|
plugin.addedDevices = undefined;
|
|
573
728
|
plugin.qrPairingCode = undefined;
|
|
574
729
|
plugin.manualPairingCode = undefined;
|
|
575
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
730
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
576
731
|
}
|
|
577
732
|
this.wssSendRefreshRequired();
|
|
578
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
|
+
*/
|
|
579
738
|
registerProcessHandlers() {
|
|
580
739
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
581
740
|
process.removeAllListeners('uncaughtException');
|
|
582
741
|
process.removeAllListeners('unhandledRejection');
|
|
583
742
|
this.exceptionHandler = async (error) => {
|
|
584
743
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
744
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
585
745
|
};
|
|
586
746
|
process.on('uncaughtException', this.exceptionHandler);
|
|
587
747
|
this.rejectionHandler = async (reason, promise) => {
|
|
588
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...');
|
|
589
750
|
};
|
|
590
751
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
591
752
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -598,6 +759,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
598
759
|
};
|
|
599
760
|
process.on('SIGTERM', this.sigtermHandler);
|
|
600
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
764
|
+
*/
|
|
601
765
|
deregisterProcesslHandlers() {
|
|
602
766
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
603
767
|
if (this.exceptionHandler)
|
|
@@ -614,7 +778,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
614
778
|
process.off('SIGTERM', this.sigtermHandler);
|
|
615
779
|
this.sigtermHandler = undefined;
|
|
616
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* Logs the node and system information.
|
|
783
|
+
*/
|
|
617
784
|
async logNodeAndSystemInfo() {
|
|
785
|
+
// IP address information
|
|
618
786
|
const networkInterfaces = os.networkInterfaces();
|
|
619
787
|
this.systemInformation.ipv4Address = '';
|
|
620
788
|
this.systemInformation.ipv6Address = '';
|
|
@@ -634,7 +802,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
634
802
|
this.systemInformation.macAddress = detail.mac;
|
|
635
803
|
}
|
|
636
804
|
}
|
|
637
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
805
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
638
806
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
639
807
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
640
808
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -642,19 +810,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
642
810
|
break;
|
|
643
811
|
}
|
|
644
812
|
}
|
|
813
|
+
// Node information
|
|
645
814
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
646
815
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
647
816
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
648
817
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
818
|
+
// Host system information
|
|
649
819
|
this.systemInformation.hostname = os.hostname();
|
|
650
820
|
this.systemInformation.user = os.userInfo().username;
|
|
651
|
-
this.systemInformation.osType = os.type();
|
|
652
|
-
this.systemInformation.osRelease = os.release();
|
|
653
|
-
this.systemInformation.osPlatform = os.platform();
|
|
654
|
-
this.systemInformation.osArch = os.arch();
|
|
655
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
656
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
657
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
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
|
|
658
829
|
this.log.debug('Host System Information:');
|
|
659
830
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
660
831
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -670,15 +841,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
670
841
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
671
842
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
672
843
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
844
|
+
// Home directory
|
|
673
845
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
674
846
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
675
847
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
848
|
+
// Package root directory
|
|
676
849
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
677
850
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
678
851
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
679
852
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
853
|
+
// Global node_modules directory
|
|
680
854
|
if (this.nodeContext)
|
|
681
855
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
856
|
+
// First run of Matterbridge so the node storage is empty
|
|
682
857
|
if (this.globalModulesDirectory === '') {
|
|
683
858
|
try {
|
|
684
859
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -702,6 +877,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
702
877
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
703
878
|
});
|
|
704
879
|
}
|
|
880
|
+
// Create the data directory .matterbridge in the home directory
|
|
705
881
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
706
882
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
707
883
|
try {
|
|
@@ -725,6 +901,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
725
901
|
}
|
|
726
902
|
}
|
|
727
903
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
904
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
728
905
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
729
906
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
730
907
|
try {
|
|
@@ -748,19 +925,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
748
925
|
}
|
|
749
926
|
}
|
|
750
927
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
928
|
+
// Matterbridge version
|
|
751
929
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
752
930
|
this.matterbridgeVersion = packageJson.version;
|
|
753
931
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
754
932
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
933
|
+
// Matterbridge latest version
|
|
755
934
|
if (this.nodeContext)
|
|
756
935
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
757
936
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
758
937
|
this.getMatterbridgeLatestVersion();
|
|
938
|
+
// Current working directory
|
|
759
939
|
const currentDir = process.cwd();
|
|
760
940
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
941
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
761
942
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
762
943
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
763
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
|
+
*/
|
|
764
950
|
async getLatestVersion(packageName) {
|
|
765
951
|
return new Promise((resolve, reject) => {
|
|
766
952
|
this.execRunningCount++;
|
|
@@ -775,6 +961,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
775
961
|
});
|
|
776
962
|
});
|
|
777
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
|
+
*/
|
|
778
968
|
async getGlobalNodeModules() {
|
|
779
969
|
return new Promise((resolve, reject) => {
|
|
780
970
|
this.execRunningCount++;
|
|
@@ -789,6 +979,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
789
979
|
});
|
|
790
980
|
});
|
|
791
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
|
+
*/
|
|
792
987
|
async getMatterbridgeLatestVersion() {
|
|
793
988
|
this.getLatestVersion('matterbridge')
|
|
794
989
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -805,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
805
1000
|
})
|
|
806
1001
|
.catch((error) => {
|
|
807
1002
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
1003
|
+
// error.stack && this.log.debug(error.stack);
|
|
808
1004
|
});
|
|
809
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
|
+
*/
|
|
810
1016
|
async getPluginLatestVersion(plugin) {
|
|
811
1017
|
this.getLatestVersion(plugin.name)
|
|
812
1018
|
.then(async (latestVersion) => {
|
|
@@ -818,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
818
1024
|
})
|
|
819
1025
|
.catch((error) => {
|
|
820
1026
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1027
|
+
// error.stack && this.log.debug(error.stack);
|
|
821
1028
|
});
|
|
822
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
|
+
*/
|
|
823
1035
|
createMatterLogger() {
|
|
824
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1036
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
825
1037
|
return (_level, formattedLog) => {
|
|
826
1038
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
827
1039
|
const message = formattedLog.slice(65);
|
|
828
1040
|
matterLogger.logName = logger;
|
|
829
1041
|
switch (_level) {
|
|
830
1042
|
case MatterLogLevel.DEBUG:
|
|
831
|
-
matterLogger.log("debug"
|
|
1043
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
832
1044
|
break;
|
|
833
1045
|
case MatterLogLevel.INFO:
|
|
834
|
-
matterLogger.log("info"
|
|
1046
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
835
1047
|
break;
|
|
836
1048
|
case MatterLogLevel.NOTICE:
|
|
837
|
-
matterLogger.log("notice"
|
|
1049
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
838
1050
|
break;
|
|
839
1051
|
case MatterLogLevel.WARN:
|
|
840
|
-
matterLogger.log("warn"
|
|
1052
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
841
1053
|
break;
|
|
842
1054
|
case MatterLogLevel.ERROR:
|
|
843
|
-
matterLogger.log("error"
|
|
1055
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
844
1056
|
break;
|
|
845
1057
|
case MatterLogLevel.FATAL:
|
|
846
|
-
matterLogger.log("fatal"
|
|
1058
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
847
1059
|
break;
|
|
848
1060
|
default:
|
|
849
|
-
matterLogger.log("debug"
|
|
1061
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
850
1062
|
break;
|
|
851
1063
|
}
|
|
852
1064
|
};
|
|
853
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
|
+
*/
|
|
854
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
|
|
855
1075
|
let fileSize = 0;
|
|
856
1076
|
if (unlink) {
|
|
857
1077
|
try {
|
|
@@ -900,53 +1120,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
900
1120
|
}
|
|
901
1121
|
};
|
|
902
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Update matterbridge and cleanup.
|
|
1125
|
+
*/
|
|
903
1126
|
async updateProcess() {
|
|
904
1127
|
await this.cleanup('updating...', false);
|
|
905
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1131
|
+
*/
|
|
906
1132
|
async restartProcess() {
|
|
907
1133
|
await this.cleanup('restarting...', true);
|
|
908
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Shut down the process by exiting the current process.
|
|
1137
|
+
*/
|
|
909
1138
|
async shutdownProcess() {
|
|
910
1139
|
await this.cleanup('shutting down...', false);
|
|
911
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Shut down the process and reset.
|
|
1143
|
+
*/
|
|
912
1144
|
async unregisterAndShutdownProcess() {
|
|
913
1145
|
this.log.info('Unregistering all devices and shutting down...');
|
|
914
|
-
for (const plugin of this.plugins) {
|
|
915
|
-
|
|
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);
|
|
916
1151
|
}
|
|
917
1152
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
918
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Shut down the process and reset.
|
|
1156
|
+
*/
|
|
919
1157
|
async shutdownProcessAndReset() {
|
|
920
1158
|
await this.cleanup('shutting down with reset...', false);
|
|
921
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Shut down the process and factory reset.
|
|
1162
|
+
*/
|
|
922
1163
|
async shutdownProcessAndFactoryReset() {
|
|
923
1164
|
await this.cleanup('shutting down with factory reset...', false);
|
|
924
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
|
+
*/
|
|
925
1172
|
async cleanup(message, restart = false) {
|
|
926
1173
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
927
1174
|
this.hasCleanupStarted = true;
|
|
928
1175
|
this.log.info(message);
|
|
1176
|
+
// Deregisters the process handlers
|
|
929
1177
|
this.deregisterProcesslHandlers();
|
|
1178
|
+
// Clear the start matter interval
|
|
930
1179
|
if (this.startMatterInterval) {
|
|
931
1180
|
clearInterval(this.startMatterInterval);
|
|
932
1181
|
this.startMatterInterval = undefined;
|
|
933
1182
|
this.log.debug('Start matter interval cleared');
|
|
934
1183
|
}
|
|
1184
|
+
// Clear the check update interval
|
|
935
1185
|
if (this.checkUpdateInterval) {
|
|
936
1186
|
clearInterval(this.checkUpdateInterval);
|
|
937
1187
|
this.checkUpdateInterval = undefined;
|
|
938
1188
|
this.log.debug('Check update interval cleared');
|
|
939
1189
|
}
|
|
1190
|
+
// Clear the configure timeout
|
|
940
1191
|
if (this.configureTimeout) {
|
|
941
1192
|
clearTimeout(this.configureTimeout);
|
|
942
1193
|
this.configureTimeout = undefined;
|
|
943
1194
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
944
1195
|
}
|
|
1196
|
+
// Clear the reachability timeout
|
|
945
1197
|
if (this.reachabilityTimeout) {
|
|
946
1198
|
clearTimeout(this.reachabilityTimeout);
|
|
947
1199
|
this.reachabilityTimeout = undefined;
|
|
948
1200
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
949
1201
|
}
|
|
1202
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
950
1203
|
for (const plugin of this.plugins) {
|
|
951
1204
|
if (!plugin.enabled || plugin.error)
|
|
952
1205
|
continue;
|
|
@@ -957,24 +1210,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
957
1210
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
958
1211
|
}
|
|
959
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
|
|
960
1227
|
if (this.httpServer) {
|
|
961
1228
|
this.httpServer.close();
|
|
962
1229
|
this.httpServer.removeAllListeners();
|
|
963
1230
|
this.httpServer = undefined;
|
|
964
1231
|
this.log.debug('Frontend http server closed successfully');
|
|
965
1232
|
}
|
|
1233
|
+
// Close the https server
|
|
966
1234
|
if (this.httpsServer) {
|
|
967
1235
|
this.httpsServer.close();
|
|
968
1236
|
this.httpsServer.removeAllListeners();
|
|
969
1237
|
this.httpsServer = undefined;
|
|
970
1238
|
this.log.debug('Frontend https server closed successfully');
|
|
971
1239
|
}
|
|
1240
|
+
// Remove listeners from the express app
|
|
972
1241
|
if (this.expressApp) {
|
|
973
1242
|
this.expressApp.removeAllListeners();
|
|
974
1243
|
this.expressApp = undefined;
|
|
975
1244
|
this.log.debug('Frontend app closed successfully');
|
|
976
1245
|
}
|
|
1246
|
+
// Close the WebSocket server
|
|
977
1247
|
if (this.webSocketServer) {
|
|
1248
|
+
// Close all active connections
|
|
978
1249
|
this.webSocketServer.clients.forEach((client) => {
|
|
979
1250
|
if (client.readyState === WebSocket.OPEN) {
|
|
980
1251
|
client.close();
|
|
@@ -990,29 +1261,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
990
1261
|
});
|
|
991
1262
|
this.webSocketServer = undefined;
|
|
992
1263
|
}
|
|
993
|
-
|
|
994
|
-
await this.convertStorage(this.matterbridgeContext, 'Mattebridge');
|
|
995
|
-
}
|
|
1264
|
+
// Closing matter
|
|
996
1265
|
await this.stopMatterServer();
|
|
1266
|
+
// Closing matter storage
|
|
997
1267
|
await this.stopMatterStorage();
|
|
1268
|
+
// Remove the matterfilelogger
|
|
998
1269
|
try {
|
|
999
1270
|
Logger.removeLogger('matterfilelogger');
|
|
1271
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1000
1272
|
}
|
|
1001
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}`);
|
|
1002
1275
|
}
|
|
1276
|
+
// Serialize registeredDevices
|
|
1003
1277
|
if (this.nodeStorage && this.nodeContext) {
|
|
1004
1278
|
this.log.info('Saving registered devices...');
|
|
1005
1279
|
const serializedRegisteredDevices = [];
|
|
1006
1280
|
this.devices.forEach(async (device) => {
|
|
1007
1281
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1282
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1008
1283
|
if (serializedMatterbridgeDevice)
|
|
1009
1284
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1010
1285
|
});
|
|
1011
1286
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1012
1287
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1288
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1013
1289
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1014
1290
|
await this.nodeContext.close();
|
|
1015
1291
|
this.nodeContext = undefined;
|
|
1292
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1016
1293
|
for (const plugin of this.plugins) {
|
|
1017
1294
|
if (plugin.nodeContext) {
|
|
1018
1295
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1042,17 +1319,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1042
1319
|
}
|
|
1043
1320
|
}
|
|
1044
1321
|
else {
|
|
1045
|
-
if (message === 'shutting down with reset...') {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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.');
|
|
1049
1342
|
}
|
|
1050
1343
|
if (message === 'shutting down with factory reset...') {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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.');
|
|
1056
1353
|
}
|
|
1057
1354
|
this.log.notice('Cleanup completed. Shutting down...');
|
|
1058
1355
|
Matterbridge.instance = undefined;
|
|
@@ -1062,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1062
1359
|
this.initialized = false;
|
|
1063
1360
|
}
|
|
1064
1361
|
}
|
|
1362
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1065
1363
|
async addBridgedEndpoint(pluginName, device) {
|
|
1364
|
+
// Nothing to do here
|
|
1066
1365
|
}
|
|
1366
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1067
1367
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1368
|
+
// Nothing to do here
|
|
1068
1369
|
}
|
|
1370
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1069
1371
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1372
|
+
// Nothing to do here
|
|
1070
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
|
+
*/
|
|
1071
1380
|
async addBridgedDevice(pluginName, device) {
|
|
1072
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
|
|
1073
1383
|
const plugin = this.plugins.get(pluginName);
|
|
1074
1384
|
if (!plugin) {
|
|
1075
1385
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1076
1386
|
return;
|
|
1077
1387
|
}
|
|
1388
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1078
1389
|
if (this.bridgeMode === 'bridge') {
|
|
1079
1390
|
if (!this.matterAggregator) {
|
|
1080
1391
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1082,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
1393
|
}
|
|
1083
1394
|
this.matterAggregator.addBridgedDevice(device);
|
|
1084
1395
|
}
|
|
1396
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1397
|
+
// Register and add the device in childbridge mode
|
|
1085
1398
|
if (this.bridgeMode === 'childbridge') {
|
|
1086
1399
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1400
|
+
// Check if the plugin is locked with the commissioning server
|
|
1087
1401
|
if (!plugin.locked) {
|
|
1088
1402
|
plugin.locked = true;
|
|
1089
1403
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1097,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1097
1411
|
}
|
|
1098
1412
|
}
|
|
1099
1413
|
if (plugin.type === 'DynamicPlatform') {
|
|
1414
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1100
1415
|
if (!plugin.locked) {
|
|
1101
1416
|
plugin.locked = true;
|
|
1102
1417
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1117,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1117
1432
|
plugin.registeredDevices++;
|
|
1118
1433
|
if (plugin.addedDevices !== undefined)
|
|
1119
1434
|
plugin.addedDevices++;
|
|
1435
|
+
// Add the device to the DeviceManager
|
|
1120
1436
|
this.devices.set(device);
|
|
1121
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}`);
|
|
1122
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
|
+
*/
|
|
1123
1445
|
async removeBridgedDevice(pluginName, device) {
|
|
1124
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
|
|
1125
1448
|
const plugin = this.plugins.get(pluginName);
|
|
1126
1449
|
if (!plugin) {
|
|
1127
1450
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1128
1451
|
return;
|
|
1129
1452
|
}
|
|
1453
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1130
1454
|
if (this.bridgeMode === 'bridge') {
|
|
1131
1455
|
if (!this.matterAggregator) {
|
|
1132
1456
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1136,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1136
1460
|
device.setBridgedDeviceReachability(false);
|
|
1137
1461
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1138
1462
|
}
|
|
1463
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1464
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1139
1465
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1140
1466
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1141
1467
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1143,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1143
1469
|
if (plugin.addedDevices !== undefined)
|
|
1144
1470
|
plugin.addedDevices--;
|
|
1145
1471
|
}
|
|
1472
|
+
// Remove the device in childbridge mode
|
|
1146
1473
|
if (this.bridgeMode === 'childbridge') {
|
|
1147
1474
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1148
1475
|
if (!plugin.commissioningServer) {
|
|
@@ -1166,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1166
1493
|
plugin.registeredDevices--;
|
|
1167
1494
|
if (plugin.addedDevices !== undefined)
|
|
1168
1495
|
plugin.addedDevices--;
|
|
1496
|
+
// Remove the commissioning server
|
|
1169
1497
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1170
1498
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1171
1499
|
plugin.commissioningServer = undefined;
|
|
1172
1500
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1173
1501
|
}
|
|
1174
1502
|
}
|
|
1503
|
+
// Remove the device from the DeviceManager
|
|
1175
1504
|
this.devices.remove(device);
|
|
1176
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
|
+
*/
|
|
1177
1512
|
async removeAllBridgedDevices(pluginName) {
|
|
1178
1513
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1179
1514
|
this.devices.forEach(async (device) => {
|
|
@@ -1182,12 +1517,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1182
1517
|
}
|
|
1183
1518
|
});
|
|
1184
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
|
+
*/
|
|
1185
1525
|
async startBridge() {
|
|
1526
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1186
1527
|
if (!this.storageManager)
|
|
1187
1528
|
throw new Error('No storage manager initialized');
|
|
1188
1529
|
if (!this.matterbridgeContext)
|
|
1189
1530
|
throw new Error('No storage context initialized');
|
|
1190
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1531
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1191
1532
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
1192
1533
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
1193
1534
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -1201,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1201
1542
|
let failCount = 0;
|
|
1202
1543
|
this.startMatterInterval = setInterval(async () => {
|
|
1203
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
|
|
1204
1546
|
if (!plugin.enabled)
|
|
1205
1547
|
continue;
|
|
1206
1548
|
if (plugin.error) {
|
|
@@ -1225,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1225
1567
|
clearInterval(this.startMatterInterval);
|
|
1226
1568
|
this.startMatterInterval = undefined;
|
|
1227
1569
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
+
// Start the Matter server
|
|
1228
1571
|
await this.startMatterServer();
|
|
1229
1572
|
this.log.notice('Matter server started');
|
|
1573
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1230
1574
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1575
|
+
// Configure the plugins
|
|
1231
1576
|
this.configureTimeout = setTimeout(async () => {
|
|
1232
1577
|
for (const plugin of this.plugins) {
|
|
1233
1578
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1234
1579
|
continue;
|
|
1235
1580
|
try {
|
|
1236
|
-
await this.plugins.configure(plugin);
|
|
1581
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1237
1582
|
}
|
|
1238
1583
|
catch (error) {
|
|
1239
1584
|
plugin.error = true;
|
|
@@ -1242,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1242
1587
|
}
|
|
1243
1588
|
this.wssSendRefreshRequired();
|
|
1244
1589
|
}, 30 * 1000);
|
|
1590
|
+
// Setting reachability to true
|
|
1245
1591
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1246
1592
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1247
1593
|
if (this.commissioningServer)
|
|
@@ -1251,16 +1597,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1251
1597
|
}, 60 * 1000);
|
|
1252
1598
|
}, 1000);
|
|
1253
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
|
+
*/
|
|
1254
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
|
|
1255
1608
|
if (!this.storageManager)
|
|
1256
1609
|
throw new Error('No storage manager initialized');
|
|
1257
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1610
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1258
1611
|
await this.startPlugins();
|
|
1259
1612
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1260
1613
|
let failCount = 0;
|
|
1261
1614
|
this.startMatterInterval = setInterval(async () => {
|
|
1262
1615
|
let allStarted = true;
|
|
1263
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
|
|
1264
1618
|
if (!plugin.enabled)
|
|
1265
1619
|
continue;
|
|
1266
1620
|
if (plugin.error) {
|
|
@@ -1288,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1288
1642
|
clearInterval(this.startMatterInterval);
|
|
1289
1643
|
this.startMatterInterval = undefined;
|
|
1290
1644
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1645
|
+
// Start the Matter server
|
|
1291
1646
|
await this.startMatterServer();
|
|
1292
1647
|
this.log.notice('Matter server started');
|
|
1648
|
+
// Configure the plugins
|
|
1293
1649
|
this.configureTimeout = setTimeout(async () => {
|
|
1294
1650
|
for (const plugin of this.plugins) {
|
|
1295
1651
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1296
1652
|
continue;
|
|
1297
1653
|
try {
|
|
1298
|
-
await this.plugins.configure(plugin);
|
|
1654
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1299
1655
|
}
|
|
1300
1656
|
catch (error) {
|
|
1301
1657
|
plugin.error = true;
|
|
@@ -1324,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1324
1680
|
continue;
|
|
1325
1681
|
}
|
|
1326
1682
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1683
|
+
// Setting reachability to true
|
|
1327
1684
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1328
1685
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1329
1686
|
if (plugin.commissioningServer)
|
|
@@ -1336,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1336
1693
|
}
|
|
1337
1694
|
}, 1000);
|
|
1338
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Starts the Matterbridge controller.
|
|
1698
|
+
* @private
|
|
1699
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
|
+
*/
|
|
1339
1701
|
async startController() {
|
|
1340
1702
|
if (!this.storageManager) {
|
|
1341
1703
|
this.log.error('No storage manager initialized');
|
|
@@ -1350,7 +1712,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1350
1712
|
return;
|
|
1351
1713
|
}
|
|
1352
1714
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1353
|
-
this.matterServer = this.createMatterServer(this.storageManager);
|
|
1715
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1354
1716
|
this.log.info('Creating matter commissioning controller');
|
|
1355
1717
|
this.commissioningController = new CommissioningController({
|
|
1356
1718
|
autoConnect: false,
|
|
@@ -1398,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1398
1760
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1399
1761
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1400
1762
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1401
|
-
}
|
|
1763
|
+
} // (hasParameter('pairingcode'))
|
|
1402
1764
|
if (hasParameter('unpairall')) {
|
|
1403
1765
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1404
1766
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1409,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1409
1771
|
return;
|
|
1410
1772
|
}
|
|
1411
1773
|
if (hasParameter('discover')) {
|
|
1774
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1775
|
+
// console.log(discover);
|
|
1412
1776
|
}
|
|
1413
1777
|
if (!this.commissioningController.isCommissioned()) {
|
|
1414
1778
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1449,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1449
1813
|
},
|
|
1450
1814
|
});
|
|
1451
1815
|
node.logStructure();
|
|
1816
|
+
// Get the interaction client
|
|
1452
1817
|
this.log.info('Getting the interaction client');
|
|
1453
1818
|
const interactionClient = await node.getInteractionClient();
|
|
1454
1819
|
let cluster;
|
|
1455
1820
|
let attributes;
|
|
1821
|
+
// Log BasicInformationCluster
|
|
1456
1822
|
cluster = BasicInformationCluster;
|
|
1457
1823
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1458
1824
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1462,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1462
1828
|
attributes.forEach((attribute) => {
|
|
1463
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}`);
|
|
1464
1830
|
});
|
|
1831
|
+
// Log PowerSourceCluster
|
|
1465
1832
|
cluster = PowerSourceCluster;
|
|
1466
1833
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1467
1834
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1471,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1471
1838
|
attributes.forEach((attribute) => {
|
|
1472
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}`);
|
|
1473
1840
|
});
|
|
1841
|
+
// Log ThreadNetworkDiagnostics
|
|
1474
1842
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1475
1843
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1476
1844
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1480,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1480
1848
|
attributes.forEach((attribute) => {
|
|
1481
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}`);
|
|
1482
1850
|
});
|
|
1851
|
+
// Log SwitchCluster
|
|
1483
1852
|
cluster = SwitchCluster;
|
|
1484
1853
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1485
1854
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1500,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1500
1869
|
this.log.info('Subscribed to all attributes and events');
|
|
1501
1870
|
}
|
|
1502
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
|
+
*/
|
|
1503
1881
|
async startMatterStorage(storageType, storageName) {
|
|
1504
1882
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1505
1883
|
if (storageType === 'disk') {
|
|
@@ -1548,25 +1926,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1548
1926
|
await this.matterbridgeContext.set('passcode', this.passcode);
|
|
1549
1927
|
await this.matterbridgeContext.set('discriminator', this.discriminator);
|
|
1550
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
|
+
*/
|
|
1551
1935
|
async convertStorage(context, pluginName) {
|
|
1936
|
+
if (this.edge !== false)
|
|
1937
|
+
return;
|
|
1552
1938
|
try {
|
|
1553
1939
|
const storageService = Environment.default.get(StorageService);
|
|
1554
1940
|
Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
|
|
1555
|
-
const nodeStorage = await storageService.open(
|
|
1556
|
-
if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount',
|
|
1941
|
+
const nodeStorage = await storageService.open(pluginName);
|
|
1942
|
+
if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
|
|
1557
1943
|
this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
|
|
1558
1944
|
return;
|
|
1559
1945
|
}
|
|
1560
1946
|
else {
|
|
1561
1947
|
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1562
1948
|
}
|
|
1949
|
+
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1563
1950
|
const fabricManagerContext = context.createContext('FabricManager');
|
|
1564
1951
|
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1565
|
-
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex',
|
|
1952
|
+
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
|
|
1953
|
+
// Read EventHandler from the old storage
|
|
1566
1954
|
const eventHandlerContext = context.createContext('EventHandler');
|
|
1955
|
+
// Read SessionManager from the old storage
|
|
1567
1956
|
const sessionManagerContext = context.createContext('SessionManager');
|
|
1957
|
+
// Read EndpointStructure from the old storage
|
|
1568
1958
|
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1959
|
+
// Read generalCommissioning from the old storage
|
|
1569
1960
|
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1961
|
+
// Read basicInformation from the old storage
|
|
1570
1962
|
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1571
1963
|
const fabricInfo = {};
|
|
1572
1964
|
const fabricInfoArray = [];
|
|
@@ -1574,6 +1966,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1574
1966
|
const trcArray = [];
|
|
1575
1967
|
const aclArray = [];
|
|
1576
1968
|
this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
|
|
1969
|
+
if (fabrics.length === 0 || nextFabricIndex === 0) {
|
|
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).`);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1577
1973
|
for (const fabric of fabrics) {
|
|
1578
1974
|
this.log.info(`- fabricIndex ${CYAN}${fabric.fabricIndex}${nf} fabricId ${CYAN}${fabric.fabricId}${nf} nodeId ${CYAN}${fabric.nodeId}${nf} rootNodeId ${CYAN}${fabric.rootNodeId}${nf} rootVendorId ${CYAN}${fabric.rootVendorId}${nf} label ${CYAN}${fabric.label}${nf}`);
|
|
1579
1975
|
fabricInfo[fabric.fabricIndex] = {
|
|
@@ -1593,7 +1989,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1593
1989
|
label: fabric.label,
|
|
1594
1990
|
});
|
|
1595
1991
|
nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
|
|
1596
|
-
trcArray.push(
|
|
1992
|
+
trcArray.push(fabric.rootCert);
|
|
1597
1993
|
this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
|
|
1598
1994
|
const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
|
|
1599
1995
|
if (acl && acl.value.length > 0) {
|
|
@@ -1611,14 +2007,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1611
2007
|
await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
|
|
1612
2008
|
await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
|
|
1613
2009
|
await nodeStorage.createContext('root').set('__number__', 0);
|
|
1614
|
-
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').set('__number__', 1);
|
|
1615
2010
|
await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
|
|
1616
2011
|
await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
|
|
1617
2012
|
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
1618
2013
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
1619
2014
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
2015
|
+
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
1620
2016
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
2017
|
+
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
1621
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}]
|
|
1622
2022
|
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
1623
2023
|
await nodeStorage
|
|
1624
2024
|
.createContext('root')
|
|
@@ -1629,9 +2029,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1629
2029
|
.createContext('basicInformation')
|
|
1630
2030
|
.set('location', await basicInformationContext.get('location', 'XX'));
|
|
1631
2031
|
await nodeStorage.createContext('root').createContext('network').set('ble', false);
|
|
1632
|
-
await nodeStorage
|
|
1633
|
-
|
|
1634
|
-
|
|
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}...`);
|
|
1635
2065
|
for (const key of await endpointStructureContext.keys()) {
|
|
1636
2066
|
if (key === 'nextEndpointId') {
|
|
1637
2067
|
await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
|
|
@@ -1640,20 +2070,49 @@ export class Matterbridge extends EventEmitter {
|
|
|
1640
2070
|
const parts = key.split('-');
|
|
1641
2071
|
const number = await endpointStructureContext.get(key);
|
|
1642
2072
|
if (parts.length === 2) {
|
|
1643
|
-
this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.
|
|
1644
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(
|
|
1645
|
-
}
|
|
1646
|
-
else if (parts.length === 3 && parts[2].startsWith('custom_')) {
|
|
1647
|
-
this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.Matterbridge.parts.${parts[2].replace('custom_', '')}.__number__:${number}`);
|
|
1648
|
-
await nodeStorage.createContext('root').createContext('parts').createContext('Matterbridge').createContext('parts').createContext(parts[2].replace('custom_', '')).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);
|
|
1649
2075
|
}
|
|
1650
2076
|
else if (parts.length === 3 && parts[2].startsWith('unique_')) {
|
|
1651
2077
|
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
1652
2078
|
if (device && device.deviceName && device.maybeNumber) {
|
|
1653
|
-
this.log.debug(`Converting Matterbridge.EndpointStructure:${key}:${number} to root.parts.
|
|
1654
|
-
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);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
|
|
2084
|
+
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
2085
|
+
if (device && device.deviceName && device.maybeNumber) {
|
|
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}`);
|
|
2089
|
+
await nodeStorage
|
|
2090
|
+
.createContext('root')
|
|
2091
|
+
.createContext('parts')
|
|
2092
|
+
.createContext(rootDeviceName)
|
|
2093
|
+
.createContext('parts')
|
|
2094
|
+
.createContext(device.deviceName.replace(/[ .]/g, ''))
|
|
2095
|
+
.createContext('parts')
|
|
2096
|
+
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2097
|
+
.set('__number__', childEndpoint?.number);
|
|
1655
2098
|
}
|
|
1656
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
|
+
}
|
|
2104
|
+
else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
|
|
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}`);
|
|
2106
|
+
await nodeStorage
|
|
2107
|
+
.createContext('root')
|
|
2108
|
+
.createContext('parts')
|
|
2109
|
+
.createContext(rootDeviceName)
|
|
2110
|
+
.createContext('parts')
|
|
2111
|
+
.createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2112
|
+
.createContext('parts')
|
|
2113
|
+
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2114
|
+
.set('__number__', number);
|
|
2115
|
+
}
|
|
1657
2116
|
}
|
|
1658
2117
|
await nodeStorage.createContext('persist').set('converted', true);
|
|
1659
2118
|
await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
|
|
@@ -1672,12 +2131,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1672
2131
|
await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
|
|
1673
2132
|
await context.set('converted', true);
|
|
1674
2133
|
this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
|
|
1675
|
-
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.`);
|
|
1676
2136
|
}
|
|
1677
2137
|
catch (error) {
|
|
1678
2138
|
this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
|
|
1679
2139
|
}
|
|
1680
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
|
+
*/
|
|
1681
2147
|
async backupMatterStorage(storageName, backupName) {
|
|
1682
2148
|
try {
|
|
1683
2149
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1698,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1698
2164
|
}
|
|
1699
2165
|
}
|
|
1700
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
|
+
*/
|
|
1701
2173
|
async restoreMatterStorage(backupName, storageName) {
|
|
1702
2174
|
try {
|
|
1703
2175
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1718,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1718
2190
|
}
|
|
1719
2191
|
}
|
|
1720
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Stops the matter storage.
|
|
2195
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2196
|
+
*/
|
|
1721
2197
|
async stopMatterStorage() {
|
|
1722
2198
|
this.log.debug('Stopping storage');
|
|
1723
2199
|
await this.storageManager?.close();
|
|
@@ -1726,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1726
2202
|
this.matterbridgeContext = undefined;
|
|
1727
2203
|
this.mattercontrollerContext = undefined;
|
|
1728
2204
|
}
|
|
1729
|
-
|
|
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) {
|
|
1730
2211
|
this.log.debug('Creating matter server');
|
|
2212
|
+
// Validate mdnsInterface
|
|
1731
2213
|
if (this.mdnsInterface) {
|
|
1732
2214
|
const networkInterfaces = os.networkInterfaces();
|
|
1733
2215
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1743,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1743
2225
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1744
2226
|
return matterServer;
|
|
1745
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Starts the Matter server.
|
|
2230
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2231
|
+
*/
|
|
1746
2232
|
async startMatterServer() {
|
|
1747
2233
|
if (!this.matterServer) {
|
|
1748
2234
|
this.log.error('No matter server initialized');
|
|
@@ -1752,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1752
2238
|
this.log.debug('Starting matter server...');
|
|
1753
2239
|
await this.matterServer.start();
|
|
1754
2240
|
this.log.debug('Started matter server');
|
|
2241
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1755
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2245
|
+
*/
|
|
1756
2246
|
async stopMatterServer() {
|
|
1757
2247
|
this.log.debug('Stopping matter commissioningServer');
|
|
1758
2248
|
await this.commissioningServer?.close();
|
|
@@ -1766,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1766
2256
|
this.matterAggregator = undefined;
|
|
1767
2257
|
this.matterServer = undefined;
|
|
1768
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Creates a Matter Aggregator.
|
|
2261
|
+
* @param {StorageContext} context - The storage context.
|
|
2262
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2263
|
+
*/
|
|
1769
2264
|
async createMatterAggregator(context, pluginName) {
|
|
1770
2265
|
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
1771
2266
|
const matterAggregator = new Aggregator();
|
|
1772
2267
|
return matterAggregator;
|
|
1773
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
|
+
*/
|
|
1774
2276
|
async createCommisioningServer(context, pluginName) {
|
|
1775
2277
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1776
2278
|
const deviceName = await context.get('deviceName');
|
|
1777
2279
|
const deviceType = await context.get('deviceType');
|
|
1778
2280
|
const vendorId = await context.get('vendorId');
|
|
1779
|
-
const vendorName = await context.get('vendorName');
|
|
2281
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1780
2282
|
const productId = await context.get('productId');
|
|
1781
|
-
const productName = await context.get('productName');
|
|
2283
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1782
2284
|
const serialNumber = await context.get('serialNumber');
|
|
1783
2285
|
const uniqueId = await context.get('uniqueId');
|
|
1784
2286
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1785
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2287
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1786
2288
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1787
2289
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1788
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')})`);
|
|
@@ -1790,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1790
2292
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1791
2293
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1792
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
|
|
1793
2296
|
if (this.ipv4address) {
|
|
1794
2297
|
const networkInterfaces = os.networkInterfaces();
|
|
1795
2298
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1804,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1804
2307
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1805
2308
|
}
|
|
1806
2309
|
}
|
|
2310
|
+
// Validate ipv6address
|
|
1807
2311
|
if (this.ipv6address) {
|
|
1808
2312
|
const networkInterfaces = os.networkInterfaces();
|
|
1809
2313
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1834,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1834
2338
|
nodeLabel: productName,
|
|
1835
2339
|
productLabel: productName,
|
|
1836
2340
|
softwareVersion,
|
|
1837
|
-
softwareVersionString,
|
|
2341
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1838
2342
|
hardwareVersion,
|
|
1839
2343
|
hardwareVersionString,
|
|
1840
2344
|
uniqueId,
|
|
@@ -1930,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1930
2434
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
1931
2435
|
return commissioningServer;
|
|
1932
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
|
+
*/
|
|
1933
2455
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
1934
2456
|
if (!this.storageManager)
|
|
1935
2457
|
throw new Error('No storage manager initialized');
|
|
@@ -1957,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1957
2479
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1958
2480
|
return storageContext;
|
|
1959
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
|
+
*/
|
|
1960
2489
|
async importCommissioningServerContext(pluginName, device) {
|
|
1961
2490
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
1962
2491
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -1991,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1991
2520
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1992
2521
|
return storageContext;
|
|
1993
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
|
+
*/
|
|
1994
2531
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
1995
2532
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
1996
2533
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -2001,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2001
2538
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
2002
2539
|
const QrCode = new QrCodeSchema();
|
|
2003
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`);
|
|
2004
|
-
|
|
2541
|
+
// eslint-disable-next-line no-console
|
|
2542
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
2005
2543
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
2006
2544
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
2007
2545
|
if (pluginName === 'Matterbridge') {
|
|
@@ -2048,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2048
2586
|
}
|
|
2049
2587
|
this.wssSendRefreshRequired();
|
|
2050
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
|
+
*/
|
|
2051
2595
|
sanitizeFabricInformations(fabricInfo) {
|
|
2052
2596
|
return fabricInfo.map((info) => {
|
|
2053
2597
|
return {
|
|
@@ -2061,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2061
2605
|
};
|
|
2062
2606
|
});
|
|
2063
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
|
+
*/
|
|
2064
2614
|
sanitizeSessionInformation(sessionInfo) {
|
|
2065
2615
|
return sessionInfo
|
|
2066
2616
|
.filter((session) => session.isPeerActive)
|
|
@@ -2088,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2088
2638
|
};
|
|
2089
2639
|
});
|
|
2090
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
|
+
*/
|
|
2091
2647
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2092
2648
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2093
2649
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2095,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2095
2651
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2096
2652
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2097
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
|
+
*/
|
|
2098
2659
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
2099
2660
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2100
2661
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2107,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2107
2668
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2108
2669
|
});
|
|
2109
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
|
+
*/
|
|
2110
2677
|
setDeviceReachability(device, reachable) {
|
|
2111
2678
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2112
2679
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2155,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2155
2722
|
}
|
|
2156
2723
|
return vendorName;
|
|
2157
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
|
+
*/
|
|
2158
2729
|
async getBaseRegisteredPlugins() {
|
|
2159
2730
|
const baseRegisteredPlugins = [];
|
|
2160
2731
|
for (const plugin of this.plugins) {
|
|
@@ -2186,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2186
2757
|
}
|
|
2187
2758
|
return baseRegisteredPlugins;
|
|
2188
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
|
+
*/
|
|
2189
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
|
+
*/
|
|
2190
2780
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2191
2781
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2782
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2192
2783
|
const argstring = 'npm ' + args.join(' ');
|
|
2193
2784
|
args.splice(0, args.length, '/c', argstring);
|
|
2194
2785
|
command = 'cmd.exe';
|
|
2195
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
|
|
2196
2790
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2197
2791
|
args.unshift(command);
|
|
2198
2792
|
command = 'sudo';
|
|
@@ -2250,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2250
2844
|
}
|
|
2251
2845
|
});
|
|
2252
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
|
+
*/
|
|
2253
2855
|
wssSendMessage(level, time, name, message) {
|
|
2254
2856
|
if (!level || !time || !name || !message)
|
|
2255
2857
|
return;
|
|
2858
|
+
// Remove ANSI escape codes from the message
|
|
2859
|
+
// eslint-disable-next-line no-control-regex
|
|
2256
2860
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2861
|
+
// Remove leading asterisks from the message
|
|
2257
2862
|
message = message.replace(/^\*+/, '');
|
|
2863
|
+
// Replace all occurrences of \t and \n
|
|
2258
2864
|
message = message.replace(/[\t\n]/g, '');
|
|
2865
|
+
// Remove non-printable characters
|
|
2866
|
+
// eslint-disable-next-line no-control-regex
|
|
2259
2867
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2868
|
+
// Replace all occurrences of \" with "
|
|
2260
2869
|
message = message.replace(/\\"/g, '"');
|
|
2870
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2261
2871
|
const maxContinuousLength = 100;
|
|
2262
2872
|
const keepStartLength = 20;
|
|
2263
2873
|
const keepEndLength = 20;
|
|
2874
|
+
// Split the message into words
|
|
2264
2875
|
message = message
|
|
2265
2876
|
.split(' ')
|
|
2266
2877
|
.map((word) => {
|
|
2878
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2267
2879
|
if (word.length > maxContinuousLength) {
|
|
2268
2880
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2269
2881
|
}
|
|
2270
2882
|
return word;
|
|
2271
2883
|
})
|
|
2272
2884
|
.join(' ');
|
|
2885
|
+
// Send the message to all connected clients
|
|
2273
2886
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2274
2887
|
if (client.readyState === WebSocket.OPEN) {
|
|
2275
2888
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2276
2889
|
}
|
|
2277
2890
|
});
|
|
2278
2891
|
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2894
|
+
*
|
|
2895
|
+
*/
|
|
2279
2896
|
wssSendRefreshRequired() {
|
|
2280
2897
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2898
|
+
// Send the message to all connected clients
|
|
2281
2899
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2282
2900
|
if (client.readyState === WebSocket.OPEN) {
|
|
2283
|
-
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: {} }));
|
|
2284
2902
|
}
|
|
2285
2903
|
});
|
|
2286
2904
|
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2907
|
+
*
|
|
2908
|
+
*/
|
|
2287
2909
|
wssSendRestartRequired() {
|
|
2288
2910
|
this.matterbridgeInformation.restartRequired = true;
|
|
2911
|
+
// Send the message to all connected clients
|
|
2289
2912
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2290
2913
|
if (client.readyState === WebSocket.OPEN) {
|
|
2291
|
-
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: {} }));
|
|
2292
2915
|
}
|
|
2293
2916
|
});
|
|
2294
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
|
+
*/
|
|
2295
2923
|
async initializeFrontend(port = 8283) {
|
|
2296
2924
|
let initializeError = false;
|
|
2297
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
|
|
2298
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
|
|
2299
2938
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2300
2939
|
if (!hasParameter('ssl')) {
|
|
2940
|
+
// Create an HTTP server and attach the express app
|
|
2301
2941
|
this.httpServer = createServer(this.expressApp);
|
|
2942
|
+
// Listen on the specified port
|
|
2302
2943
|
if (hasParameter('ingress')) {
|
|
2303
2944
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2304
2945
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2312,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2312
2953
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2313
2954
|
});
|
|
2314
2955
|
}
|
|
2956
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2315
2957
|
this.httpServer.on('error', (error) => {
|
|
2316
2958
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2317
2959
|
switch (error.code) {
|
|
@@ -2327,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2327
2969
|
});
|
|
2328
2970
|
}
|
|
2329
2971
|
else {
|
|
2972
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2330
2973
|
let cert;
|
|
2331
2974
|
try {
|
|
2332
2975
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2354,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2354
2997
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2355
2998
|
}
|
|
2356
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
|
|
2357
3001
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
3002
|
+
// Listen on the specified port
|
|
2358
3003
|
if (hasParameter('ingress')) {
|
|
2359
3004
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2360
3005
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2368,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2368
3013
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2369
3014
|
});
|
|
2370
3015
|
}
|
|
3016
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2371
3017
|
this.httpsServer.on('error', (error) => {
|
|
2372
3018
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2373
3019
|
switch (error.code) {
|
|
@@ -2384,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2384
3030
|
}
|
|
2385
3031
|
if (initializeError)
|
|
2386
3032
|
return;
|
|
3033
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2387
3034
|
const wssPort = port;
|
|
2388
3035
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2389
3036
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2390
3037
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2391
3038
|
const clientIp = request.socket.remoteAddress;
|
|
2392
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
3039
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2393
3040
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2394
3041
|
ws.on('message', (message) => {
|
|
2395
3042
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2422,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2422
3069
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2423
3070
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2424
3071
|
});
|
|
3072
|
+
// Endpoint to validate login code
|
|
2425
3073
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2426
3074
|
const { password } = req.body;
|
|
2427
3075
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2440,12 +3088,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2440
3088
|
this.log.warn('/api/login error wrong password');
|
|
2441
3089
|
res.json({ valid: false });
|
|
2442
3090
|
}
|
|
3091
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2443
3092
|
}
|
|
2444
3093
|
catch (error) {
|
|
2445
3094
|
this.log.error('/api/login error getting password');
|
|
2446
3095
|
res.json({ valid: false });
|
|
2447
3096
|
}
|
|
2448
3097
|
});
|
|
3098
|
+
// Endpoint to provide settings
|
|
2449
3099
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2450
3100
|
this.log.debug('The frontend sent /api/settings');
|
|
2451
3101
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2465,14 +3115,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
2465
3115
|
this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
|
|
2466
3116
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2467
3117
|
this.matterbridgeInformation.profile = this.profile;
|
|
2468
|
-
const response = {
|
|
3118
|
+
const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
3119
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2469
3120
|
res.json(response);
|
|
2470
3121
|
});
|
|
3122
|
+
// Endpoint to provide plugins
|
|
2471
3123
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2472
3124
|
this.log.debug('The frontend sent /api/plugins');
|
|
2473
3125
|
const response = await this.getBaseRegisteredPlugins();
|
|
3126
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2474
3127
|
res.json(response);
|
|
2475
3128
|
});
|
|
3129
|
+
// Endpoint to provide devices
|
|
2476
3130
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2477
3131
|
this.log.debug('The frontend sent /api/devices');
|
|
2478
3132
|
const devices = [];
|
|
@@ -2505,8 +3159,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2505
3159
|
cluster: cluster,
|
|
2506
3160
|
});
|
|
2507
3161
|
});
|
|
3162
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2508
3163
|
res.json(devices);
|
|
2509
3164
|
});
|
|
3165
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2510
3166
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2511
3167
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2512
3168
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2526,6 +3182,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2526
3182
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2527
3183
|
if (clusterServer.name === 'EveHistory')
|
|
2528
3184
|
return;
|
|
3185
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2529
3186
|
let attributeValue;
|
|
2530
3187
|
try {
|
|
2531
3188
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2536,6 +3193,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2536
3193
|
catch (error) {
|
|
2537
3194
|
attributeValue = 'Fabric-Scoped';
|
|
2538
3195
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3196
|
+
// console.log(error);
|
|
2539
3197
|
}
|
|
2540
3198
|
data.push({
|
|
2541
3199
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2548,12 +3206,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2548
3206
|
});
|
|
2549
3207
|
});
|
|
2550
3208
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2551
3210
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2552
3211
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2553
3212
|
clusterServers.forEach((clusterServer) => {
|
|
2554
3213
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2555
3214
|
if (clusterServer.name === 'EveHistory')
|
|
2556
3215
|
return;
|
|
3216
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2557
3217
|
let attributeValue;
|
|
2558
3218
|
try {
|
|
2559
3219
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2564,6 +3224,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2564
3224
|
catch (error) {
|
|
2565
3225
|
attributeValue = 'Unavailable';
|
|
2566
3226
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3227
|
+
// console.log(error);
|
|
2567
3228
|
}
|
|
2568
3229
|
data.push({
|
|
2569
3230
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2580,6 +3241,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2580
3241
|
});
|
|
2581
3242
|
res.json(data);
|
|
2582
3243
|
});
|
|
3244
|
+
// Endpoint to view the log
|
|
2583
3245
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2584
3246
|
this.log.debug('The frontend sent /api/log');
|
|
2585
3247
|
try {
|
|
@@ -2592,10 +3254,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2592
3254
|
res.status(500).send('Error reading log file');
|
|
2593
3255
|
}
|
|
2594
3256
|
});
|
|
3257
|
+
// Endpoint to download the matterbridge log
|
|
2595
3258
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2596
3259
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2597
3260
|
try {
|
|
2598
3261
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3262
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2599
3263
|
}
|
|
2600
3264
|
catch (error) {
|
|
2601
3265
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2607,10 +3271,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2607
3271
|
}
|
|
2608
3272
|
});
|
|
2609
3273
|
});
|
|
3274
|
+
// Endpoint to download the matter log
|
|
2610
3275
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2611
3276
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2612
3277
|
try {
|
|
2613
3278
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3279
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2614
3280
|
}
|
|
2615
3281
|
catch (error) {
|
|
2616
3282
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2622,6 +3288,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2622
3288
|
}
|
|
2623
3289
|
});
|
|
2624
3290
|
});
|
|
3291
|
+
// Endpoint to download the matter storage file
|
|
2625
3292
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2626
3293
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2627
3294
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2631,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2631
3298
|
}
|
|
2632
3299
|
});
|
|
2633
3300
|
});
|
|
3301
|
+
// Endpoint to download the matterbridge storage directory
|
|
2634
3302
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2635
3303
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2636
3304
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2641,6 +3309,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2641
3309
|
}
|
|
2642
3310
|
});
|
|
2643
3311
|
});
|
|
3312
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2644
3313
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2645
3314
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2646
3315
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2651,9 +3320,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2651
3320
|
}
|
|
2652
3321
|
});
|
|
2653
3322
|
});
|
|
3323
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2654
3324
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2655
3325
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2656
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')));
|
|
2657
3328
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2658
3329
|
if (error) {
|
|
2659
3330
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2661,6 +3332,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2661
3332
|
}
|
|
2662
3333
|
});
|
|
2663
3334
|
});
|
|
3335
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2664
3336
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2665
3337
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2666
3338
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2670,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2670
3342
|
}
|
|
2671
3343
|
});
|
|
2672
3344
|
});
|
|
3345
|
+
// Endpoint to receive commands
|
|
2673
3346
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2674
3347
|
const command = req.params.command;
|
|
2675
3348
|
let param = req.params.param;
|
|
@@ -2679,13 +3352,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2679
3352
|
return;
|
|
2680
3353
|
}
|
|
2681
3354
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3355
|
+
// Handle the command setpassword from Settings
|
|
2682
3356
|
if (command === 'setpassword') {
|
|
2683
|
-
const password = param.slice(1, -1);
|
|
3357
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2684
3358
|
this.log.debug('setpassword', param, password);
|
|
2685
3359
|
await this.nodeContext?.set('password', password);
|
|
2686
3360
|
res.json({ message: 'Command received' });
|
|
2687
3361
|
return;
|
|
2688
3362
|
}
|
|
3363
|
+
// Handle the command setbridgemode from Settings
|
|
2689
3364
|
if (command === 'setbridgemode') {
|
|
2690
3365
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2691
3366
|
this.wssSendRestartRequired();
|
|
@@ -2693,6 +3368,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2693
3368
|
res.json({ message: 'Command received' });
|
|
2694
3369
|
return;
|
|
2695
3370
|
}
|
|
3371
|
+
// Handle the command backup from Settings
|
|
2696
3372
|
if (command === 'backup') {
|
|
2697
3373
|
this.log.notice(`Prepairing the backup...`);
|
|
2698
3374
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2700,25 +3376,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2700
3376
|
res.json({ message: 'Command received' });
|
|
2701
3377
|
return;
|
|
2702
3378
|
}
|
|
3379
|
+
// Handle the command setmbloglevel from Settings
|
|
2703
3380
|
if (command === 'setmbloglevel') {
|
|
2704
3381
|
this.log.debug('Matterbridge log level:', param);
|
|
2705
3382
|
if (param === 'Debug') {
|
|
2706
|
-
this.log.logLevel = "debug"
|
|
3383
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2707
3384
|
}
|
|
2708
3385
|
else if (param === 'Info') {
|
|
2709
|
-
this.log.logLevel = "info"
|
|
3386
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2710
3387
|
}
|
|
2711
3388
|
else if (param === 'Notice') {
|
|
2712
|
-
this.log.logLevel = "notice"
|
|
3389
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2713
3390
|
}
|
|
2714
3391
|
else if (param === 'Warn') {
|
|
2715
|
-
this.log.logLevel = "warn"
|
|
3392
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2716
3393
|
}
|
|
2717
3394
|
else if (param === 'Error') {
|
|
2718
|
-
this.log.logLevel = "error"
|
|
3395
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2719
3396
|
}
|
|
2720
3397
|
else if (param === 'Fatal') {
|
|
2721
|
-
this.log.logLevel = "fatal"
|
|
3398
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2722
3399
|
}
|
|
2723
3400
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2724
3401
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2726,12 +3403,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2726
3403
|
for (const plugin of this.plugins) {
|
|
2727
3404
|
if (!plugin.platform || !plugin.platform.config)
|
|
2728
3405
|
continue;
|
|
2729
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2730
|
-
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);
|
|
2731
3408
|
}
|
|
2732
3409
|
res.json({ message: 'Command received' });
|
|
2733
3410
|
return;
|
|
2734
3411
|
}
|
|
3412
|
+
// Handle the command setmbloglevel from Settings
|
|
2735
3413
|
if (command === 'setmjloglevel') {
|
|
2736
3414
|
this.log.debug('Matter.js log level:', param);
|
|
2737
3415
|
if (param === 'Debug') {
|
|
@@ -2756,30 +3434,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2756
3434
|
res.json({ message: 'Command received' });
|
|
2757
3435
|
return;
|
|
2758
3436
|
}
|
|
3437
|
+
// Handle the command setmdnsinterface from Settings
|
|
2759
3438
|
if (command === 'setmdnsinterface') {
|
|
2760
|
-
param = param.slice(1, -1);
|
|
3439
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2761
3440
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2762
3441
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2763
3442
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2764
3443
|
res.json({ message: 'Command received' });
|
|
2765
3444
|
return;
|
|
2766
3445
|
}
|
|
3446
|
+
// Handle the command setipv4address from Settings
|
|
2767
3447
|
if (command === 'setipv4address') {
|
|
2768
|
-
param = param.slice(1, -1);
|
|
3448
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2769
3449
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2770
3450
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2771
3451
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2772
3452
|
res.json({ message: 'Command received' });
|
|
2773
3453
|
return;
|
|
2774
3454
|
}
|
|
3455
|
+
// Handle the command setipv6address from Settings
|
|
2775
3456
|
if (command === 'setipv6address') {
|
|
2776
|
-
param = param.slice(1, -1);
|
|
3457
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2777
3458
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2778
3459
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2779
3460
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2780
3461
|
res.json({ message: 'Command received' });
|
|
2781
3462
|
return;
|
|
2782
3463
|
}
|
|
3464
|
+
// Handle the command setmatterport from Settings
|
|
2783
3465
|
if (command === 'setmatterport') {
|
|
2784
3466
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2785
3467
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2788,6 +3470,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2788
3470
|
res.json({ message: 'Command received' });
|
|
2789
3471
|
return;
|
|
2790
3472
|
}
|
|
3473
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2791
3474
|
if (command === 'setmatterdiscriminator') {
|
|
2792
3475
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2793
3476
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2796,6 +3479,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2796
3479
|
res.json({ message: 'Command received' });
|
|
2797
3480
|
return;
|
|
2798
3481
|
}
|
|
3482
|
+
// Handle the command setmatterpasscode from Settings
|
|
2799
3483
|
if (command === 'setmatterpasscode') {
|
|
2800
3484
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2801
3485
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2804,17 +3488,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2804
3488
|
res.json({ message: 'Command received' });
|
|
2805
3489
|
return;
|
|
2806
3490
|
}
|
|
3491
|
+
// Handle the command setmbloglevel from Settings
|
|
2807
3492
|
if (command === 'setmblogfile') {
|
|
2808
3493
|
this.log.debug('Matterbridge file log:', param);
|
|
2809
3494
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2810
3495
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3496
|
+
// Create the file logger for matterbridge
|
|
2811
3497
|
if (param === 'true')
|
|
2812
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3498
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2813
3499
|
else
|
|
2814
3500
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2815
3501
|
res.json({ message: 'Command received' });
|
|
2816
3502
|
return;
|
|
2817
3503
|
}
|
|
3504
|
+
// Handle the command setmbloglevel from Settings
|
|
2818
3505
|
if (command === 'setmjlogfile') {
|
|
2819
3506
|
this.log.debug('Matter file log:', param);
|
|
2820
3507
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2841,36 +3528,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2841
3528
|
res.json({ message: 'Command received' });
|
|
2842
3529
|
return;
|
|
2843
3530
|
}
|
|
3531
|
+
// Handle the command unregister from Settings
|
|
2844
3532
|
if (command === 'unregister') {
|
|
2845
3533
|
await this.unregisterAndShutdownProcess();
|
|
2846
3534
|
res.json({ message: 'Command received' });
|
|
2847
3535
|
return;
|
|
2848
3536
|
}
|
|
3537
|
+
// Handle the command reset from Settings
|
|
2849
3538
|
if (command === 'reset') {
|
|
2850
3539
|
await this.shutdownProcessAndReset();
|
|
2851
3540
|
res.json({ message: 'Command received' });
|
|
2852
3541
|
return;
|
|
2853
3542
|
}
|
|
3543
|
+
// Handle the command factoryreset from Settings
|
|
2854
3544
|
if (command === 'factoryreset') {
|
|
2855
3545
|
await this.shutdownProcessAndFactoryReset();
|
|
2856
3546
|
res.json({ message: 'Command received' });
|
|
2857
3547
|
return;
|
|
2858
3548
|
}
|
|
3549
|
+
// Handle the command shutdown from Header
|
|
2859
3550
|
if (command === 'shutdown') {
|
|
2860
3551
|
await this.shutdownProcess();
|
|
2861
3552
|
res.json({ message: 'Command received' });
|
|
2862
3553
|
return;
|
|
2863
3554
|
}
|
|
3555
|
+
// Handle the command restart from Header
|
|
2864
3556
|
if (command === 'restart') {
|
|
2865
3557
|
await this.restartProcess();
|
|
2866
3558
|
res.json({ message: 'Command received' });
|
|
2867
3559
|
return;
|
|
2868
3560
|
}
|
|
3561
|
+
// Handle the command update from Header
|
|
2869
3562
|
if (command === 'update') {
|
|
2870
3563
|
this.log.info('Updating matterbridge...');
|
|
2871
3564
|
try {
|
|
2872
3565
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2873
3566
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3567
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2874
3568
|
}
|
|
2875
3569
|
catch (error) {
|
|
2876
3570
|
this.log.error('Error updating matterbridge');
|
|
@@ -2880,9 +3574,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2880
3574
|
res.json({ message: 'Command received' });
|
|
2881
3575
|
return;
|
|
2882
3576
|
}
|
|
3577
|
+
// Handle the command saveconfig from Home
|
|
2883
3578
|
if (command === 'saveconfig') {
|
|
2884
3579
|
param = param.replace(/\*/g, '\\');
|
|
2885
3580
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3581
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2886
3582
|
if (!this.plugins.has(param)) {
|
|
2887
3583
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2888
3584
|
}
|
|
@@ -2896,33 +3592,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2896
3592
|
res.json({ message: 'Command received' });
|
|
2897
3593
|
return;
|
|
2898
3594
|
}
|
|
3595
|
+
// Handle the command installplugin from Home
|
|
2899
3596
|
if (command === 'installplugin') {
|
|
2900
3597
|
param = param.replace(/\*/g, '\\');
|
|
2901
3598
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
2902
3599
|
try {
|
|
2903
3600
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
2904
3601
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3602
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2905
3603
|
}
|
|
2906
3604
|
catch (error) {
|
|
2907
3605
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
2908
3606
|
}
|
|
2909
3607
|
this.wssSendRestartRequired();
|
|
2910
3608
|
param = param.split('@')[0];
|
|
3609
|
+
// Also add the plugin to matterbridge so no return!
|
|
2911
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
|
|
2912
3612
|
res.json({ message: 'Command received' });
|
|
2913
3613
|
return;
|
|
2914
3614
|
}
|
|
2915
3615
|
}
|
|
3616
|
+
// Handle the command addplugin from Home
|
|
2916
3617
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
2917
3618
|
param = param.replace(/\*/g, '\\');
|
|
2918
3619
|
const plugin = await this.plugins.add(param);
|
|
2919
3620
|
if (plugin) {
|
|
2920
|
-
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
|
|
2921
3622
|
}
|
|
2922
3623
|
res.json({ message: 'Command received' });
|
|
2923
3624
|
this.wssSendRefreshRequired();
|
|
2924
3625
|
return;
|
|
2925
3626
|
}
|
|
3627
|
+
// Handle the command removeplugin from Home
|
|
2926
3628
|
if (command === 'removeplugin') {
|
|
2927
3629
|
if (!this.plugins.has(param)) {
|
|
2928
3630
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2936,6 +3638,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2936
3638
|
this.wssSendRefreshRequired();
|
|
2937
3639
|
return;
|
|
2938
3640
|
}
|
|
3641
|
+
// Handle the command enableplugin from Home
|
|
2939
3642
|
if (command === 'enableplugin') {
|
|
2940
3643
|
if (!this.plugins.has(param)) {
|
|
2941
3644
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2953,13 +3656,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2953
3656
|
plugin.registeredDevices = undefined;
|
|
2954
3657
|
plugin.addedDevices = undefined;
|
|
2955
3658
|
await this.plugins.enable(param);
|
|
2956
|
-
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
|
|
2957
3660
|
}
|
|
2958
3661
|
}
|
|
2959
3662
|
res.json({ message: 'Command received' });
|
|
2960
3663
|
this.wssSendRefreshRequired();
|
|
2961
3664
|
return;
|
|
2962
3665
|
}
|
|
3666
|
+
// Handle the command disableplugin from Home
|
|
2963
3667
|
if (command === 'disableplugin') {
|
|
2964
3668
|
if (!this.plugins.has(param)) {
|
|
2965
3669
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -2976,6 +3680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2976
3680
|
return;
|
|
2977
3681
|
}
|
|
2978
3682
|
});
|
|
3683
|
+
// Fallback for routing (must be the last route)
|
|
2979
3684
|
this.expressApp.get('*', (req, res) => {
|
|
2980
3685
|
this.log.debug('The frontend sent:', req.url);
|
|
2981
3686
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -2983,6 +3688,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2983
3688
|
});
|
|
2984
3689
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
2985
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
|
+
*/
|
|
2986
3696
|
getClusterTextFromDevice(device) {
|
|
2987
3697
|
const stringifyUserLabel = (endpoint) => {
|
|
2988
3698
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -3005,9 +3715,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3005
3715
|
return '';
|
|
3006
3716
|
};
|
|
3007
3717
|
let attributes = '';
|
|
3718
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3008
3719
|
const clusterServers = device.getAllClusterServers();
|
|
3009
3720
|
clusterServers.forEach((clusterServer) => {
|
|
3010
3721
|
try {
|
|
3722
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3011
3723
|
if (clusterServer.name === 'OnOff')
|
|
3012
3724
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
3013
3725
|
if (clusterServer.name === 'Switch')
|
|
@@ -3058,18 +3770,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
3058
3770
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
3059
3771
|
if (clusterServer.name === 'UserLabel')
|
|
3060
3772
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3773
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3061
3774
|
}
|
|
3062
3775
|
catch (error) {
|
|
3063
3776
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3064
3777
|
}
|
|
3065
3778
|
});
|
|
3779
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3066
3780
|
return attributes;
|
|
3067
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
|
+
*/
|
|
3068
3788
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3789
|
+
// Set the bridge mode
|
|
3069
3790
|
this.bridgeMode = 'bridge';
|
|
3791
|
+
// Set the first port to use
|
|
3070
3792
|
this.port = port;
|
|
3071
|
-
|
|
3793
|
+
// Set Matterbridge logger
|
|
3794
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3072
3795
|
this.log.debug('Matterbridge extension is starting...');
|
|
3796
|
+
// Initialize NodeStorage
|
|
3073
3797
|
this.matterbridgeDirectory = dataPath;
|
|
3074
3798
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3075
3799
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -3088,10 +3812,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
3088
3812
|
};
|
|
3089
3813
|
this.plugins.set(plugin);
|
|
3090
3814
|
this.plugins.saveToStorage();
|
|
3815
|
+
// Log system info and create .matterbridge directory
|
|
3091
3816
|
await this.logNodeAndSystemInfo();
|
|
3092
3817
|
this.matterbridgeDirectory = dataPath;
|
|
3818
|
+
// Set matter.js logger level and format
|
|
3093
3819
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3094
3820
|
Logger.format = MatterLogFormat.ANSI;
|
|
3821
|
+
// Start the storage and create matterbridgeContext
|
|
3095
3822
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3096
3823
|
if (!this.storageManager)
|
|
3097
3824
|
return false;
|
|
@@ -3101,8 +3828,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
3101
3828
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3102
3829
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3103
3830
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3104
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3105
|
-
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);
|
|
3106
3833
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3107
3834
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
3108
3835
|
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
@@ -3114,6 +3841,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3114
3841
|
await this.startMatterServer();
|
|
3115
3842
|
this.log.info('Matter server started');
|
|
3116
3843
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3844
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
3117
3845
|
setTimeout(() => {
|
|
3118
3846
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3119
3847
|
if (this.commissioningServer)
|
|
@@ -3123,14 +3851,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
3123
3851
|
}, 60 * 1000);
|
|
3124
3852
|
return this.commissioningServer.isCommissioned();
|
|
3125
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
|
+
*/
|
|
3126
3860
|
async stopExtension() {
|
|
3861
|
+
// Closing matter
|
|
3127
3862
|
await this.stopMatterServer();
|
|
3863
|
+
// Clearing the session manager
|
|
3864
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3865
|
+
// Closing storage
|
|
3128
3866
|
await this.stopMatterStorage();
|
|
3129
3867
|
this.log.info('Matter server stopped');
|
|
3130
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
|
+
*/
|
|
3131
3875
|
isExtensionCommissioned() {
|
|
3132
3876
|
if (!this.commissioningServer)
|
|
3133
3877
|
return false;
|
|
3134
3878
|
return this.commissioningServer.isCommissioned();
|
|
3135
3879
|
}
|
|
3136
3880
|
}
|
|
3881
|
+
//# sourceMappingURL=matterbridge.js.map
|