matterbridge 1.7.2-dev.7 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -2
- 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 +717 -61
- 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 +530 -0
- package/dist/matterbridgeEdge.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +10156 -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 +168 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +124 -3
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +172 -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 +46 -0
- package/dist/matterbridgeWebsocket.js.map +1 -0
- package/dist/pluginManager.d.ts +238 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +238 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/storage/export.d.ts +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.8727aee4.css → main.cf25d33e.css} +2 -2
- package/frontend/build/static/css/main.cf25d33e.css.map +1 -0
- package/frontend/build/static/js/{main.c320863b.js → main.08241820.js} +12 -12
- package/frontend/build/static/js/main.08241820.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/frontend/build/static/css/main.8727aee4.css.map +0 -1
- package/frontend/build/static/js/main.c320863b.js.map +0 -1
- /package/frontend/build/static/js/{main.c320863b.js.LICENSE.txt → main.08241820.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 1.5.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// Node.js modules
|
|
1
24
|
import { fileURLToPath } from 'url';
|
|
2
25
|
import { promises as fs } from 'fs';
|
|
3
26
|
import { exec, spawn } from 'child_process';
|
|
@@ -6,27 +29,36 @@ import EventEmitter from 'events';
|
|
|
6
29
|
import os from 'os';
|
|
7
30
|
import path from 'path';
|
|
8
31
|
import { randomBytes } from 'crypto';
|
|
32
|
+
// Package modules
|
|
9
33
|
import https from 'https';
|
|
10
34
|
import express from 'express';
|
|
11
35
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
36
|
+
// NodeStorage and AnsiLogger modules
|
|
12
37
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
13
38
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, idn, or, hk, BLUE } from 'node-ansi-logger';
|
|
39
|
+
// Matterbridge
|
|
14
40
|
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
15
41
|
import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
|
|
16
42
|
import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
17
43
|
import { PluginManager } from './pluginManager.js';
|
|
18
44
|
import { DeviceManager } from './deviceManager.js';
|
|
19
45
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
46
|
+
// @matter
|
|
20
47
|
import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageManager, EndpointServer, StorageService, Environment } from '@matter/main';
|
|
21
48
|
import { BasicInformationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, FixedLabelCluster, GeneralCommissioning, PowerSourceCluster, SwitchCluster, ThreadNetworkDiagnosticsCluster, UserLabelCluster, } from '@matter/main/clusters';
|
|
22
49
|
import { getClusterNameById, ManualPairingCodeCodec, QrCodeSchema } from '@matter/main/types';
|
|
23
50
|
import { StorageBackendDisk, StorageBackendJsonFile } from '@matter/nodejs';
|
|
51
|
+
// @project-chip
|
|
24
52
|
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
|
|
25
53
|
import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
|
|
26
54
|
import { aggregator } from './matterbridgeDeviceTypes.js';
|
|
55
|
+
// Default colors
|
|
27
56
|
const plg = '\u001B[38;5;33m';
|
|
28
57
|
const dev = '\u001B[38;5;79m';
|
|
29
58
|
const typ = '\u001B[38;5;207m';
|
|
59
|
+
/**
|
|
60
|
+
* Represents the Matterbridge application.
|
|
61
|
+
*/
|
|
30
62
|
export class Matterbridge extends EventEmitter {
|
|
31
63
|
systemInformation = {
|
|
32
64
|
interfaceName: '',
|
|
@@ -63,7 +95,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
63
95
|
edge: hasParameter('edge'),
|
|
64
96
|
readOnly: hasParameter('readonly'),
|
|
65
97
|
profile: getParameter('profile'),
|
|
66
|
-
loggerLevel: "info"
|
|
98
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
67
99
|
fileLogger: false,
|
|
68
100
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
69
101
|
matterFileLogger: false,
|
|
@@ -102,6 +134,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
102
134
|
nodeContext;
|
|
103
135
|
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
104
136
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
|
+
// Cleanup
|
|
105
138
|
hasCleanupStarted = false;
|
|
106
139
|
initialized = false;
|
|
107
140
|
execRunningCount = 0;
|
|
@@ -113,16 +146,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
113
146
|
sigtermHandler;
|
|
114
147
|
exceptionHandler;
|
|
115
148
|
rejectionHandler;
|
|
149
|
+
// Frontend
|
|
116
150
|
expressApp;
|
|
117
151
|
httpServer;
|
|
118
152
|
httpsServer;
|
|
119
153
|
webSocketServer;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
// Matter
|
|
155
|
+
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
156
|
+
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
157
|
+
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
158
|
+
port = 5540; // first commissioning server port
|
|
159
|
+
passcode; // first commissioning server passcode
|
|
160
|
+
discriminator; // first commissioning server discriminator
|
|
126
161
|
storageManager;
|
|
127
162
|
matterbridgeContext;
|
|
128
163
|
mattercontrollerContext;
|
|
@@ -133,19 +168,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
133
168
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
134
169
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
135
170
|
static instance;
|
|
171
|
+
// We load asyncronously so is private
|
|
136
172
|
constructor() {
|
|
137
173
|
super();
|
|
174
|
+
// Bind the handler to the instance
|
|
138
175
|
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
139
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Retrieves the list of Matterbridge devices.
|
|
179
|
+
* @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
|
|
180
|
+
*/
|
|
140
181
|
getDevices() {
|
|
141
182
|
return this.devices.array();
|
|
142
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Retrieves the list of registered plugins.
|
|
186
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
187
|
+
*/
|
|
143
188
|
getPlugins() {
|
|
144
189
|
return this.plugins.array();
|
|
145
190
|
}
|
|
146
191
|
matterbridgeMessageHandler;
|
|
192
|
+
/** ***********************************************************************************************************************************/
|
|
193
|
+
/** loadInstance() and cleanup() methods */
|
|
194
|
+
/** ***********************************************************************************************************************************/
|
|
195
|
+
/**
|
|
196
|
+
* Loads an instance of the Matterbridge class.
|
|
197
|
+
* If an instance already exists, return that instance.
|
|
198
|
+
*
|
|
199
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
200
|
+
* @returns The loaded Matterbridge instance.
|
|
201
|
+
*/
|
|
147
202
|
static async loadInstance(initialize = false) {
|
|
148
203
|
if (!Matterbridge.instance) {
|
|
204
|
+
// eslint-disable-next-line no-console
|
|
149
205
|
if (hasParameter('debug'))
|
|
150
206
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
151
207
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -154,6 +210,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
154
210
|
}
|
|
155
211
|
return Matterbridge.instance;
|
|
156
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Call cleanup().
|
|
215
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
216
|
+
*
|
|
217
|
+
*/
|
|
157
218
|
async destroyInstance() {
|
|
158
219
|
await this.cleanup('destroying instance...', false);
|
|
159
220
|
await waiter('destroying instance...', () => {
|
|
@@ -161,39 +222,60 @@ export class Matterbridge extends EventEmitter {
|
|
|
161
222
|
}, false, 60000, 100, false);
|
|
162
223
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
163
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Initializes the Matterbridge application.
|
|
227
|
+
*
|
|
228
|
+
* @remarks
|
|
229
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
230
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
231
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
232
|
+
*
|
|
233
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
234
|
+
*/
|
|
164
235
|
async initialize() {
|
|
236
|
+
// Set the restart mode
|
|
165
237
|
if (hasParameter('service'))
|
|
166
238
|
this.restartMode = 'service';
|
|
167
239
|
if (hasParameter('docker'))
|
|
168
240
|
this.restartMode = 'docker';
|
|
241
|
+
// Set the matterbridge directory
|
|
169
242
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
170
243
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
171
|
-
|
|
244
|
+
// Create matterbridge logger
|
|
245
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
246
|
+
// Initialize nodeStorage and nodeContext
|
|
172
247
|
try {
|
|
173
248
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
174
249
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
175
250
|
this.log.debug('Creating node storage context for matterbridge');
|
|
176
251
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
252
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
254
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
178
255
|
for (const key of keys) {
|
|
179
256
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
258
|
await this.nodeStorage?.storage.get(key);
|
|
181
259
|
}
|
|
182
260
|
const storages = await this.nodeStorage.getStorageNames();
|
|
183
261
|
for (const storage of storages) {
|
|
184
262
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
185
263
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
264
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
266
|
const keys = (await nodeContext?.storage.keys());
|
|
187
267
|
keys.forEach(async (key) => {
|
|
188
268
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
189
269
|
await nodeContext?.get(key);
|
|
190
270
|
});
|
|
191
271
|
}
|
|
272
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
192
273
|
this.log.debug('Creating node storage backup...');
|
|
193
274
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
194
275
|
this.log.debug('Created node storage backup');
|
|
195
276
|
}
|
|
196
277
|
catch (error) {
|
|
278
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
197
279
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
198
280
|
if (hasParameter('norestore')) {
|
|
199
281
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -208,45 +290,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
208
290
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
209
291
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
210
292
|
}
|
|
293
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
211
294
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
295
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
212
296
|
this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
|
|
297
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
213
298
|
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
214
299
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
300
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
215
301
|
if (hasParameter('logger')) {
|
|
216
302
|
const level = getParameter('logger');
|
|
217
303
|
if (level === 'debug') {
|
|
218
|
-
this.log.logLevel = "debug"
|
|
304
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
219
305
|
}
|
|
220
306
|
else if (level === 'info') {
|
|
221
|
-
this.log.logLevel = "info"
|
|
307
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
222
308
|
}
|
|
223
309
|
else if (level === 'notice') {
|
|
224
|
-
this.log.logLevel = "notice"
|
|
310
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
225
311
|
}
|
|
226
312
|
else if (level === 'warn') {
|
|
227
|
-
this.log.logLevel = "warn"
|
|
313
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
228
314
|
}
|
|
229
315
|
else if (level === 'error') {
|
|
230
|
-
this.log.logLevel = "error"
|
|
316
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
231
317
|
}
|
|
232
318
|
else if (level === 'fatal') {
|
|
233
|
-
this.log.logLevel = "fatal"
|
|
319
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
234
320
|
}
|
|
235
321
|
else {
|
|
236
322
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
237
|
-
this.log.logLevel = "info"
|
|
323
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
238
324
|
}
|
|
239
325
|
}
|
|
240
326
|
else {
|
|
241
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
327
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
242
328
|
}
|
|
243
329
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
330
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
244
331
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
245
332
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
246
333
|
this.matterbridgeInformation.fileLogger = true;
|
|
247
334
|
}
|
|
248
335
|
this.log.notice('Matterbridge is starting...');
|
|
249
336
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
337
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
250
338
|
if (hasParameter('matterlogger')) {
|
|
251
339
|
const level = getParameter('matterlogger');
|
|
252
340
|
if (level === 'debug') {
|
|
@@ -277,6 +365,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
277
365
|
}
|
|
278
366
|
Logger.format = MatterLogFormat.ANSI;
|
|
279
367
|
Logger.setLogger('default', this.createMatterLogger());
|
|
368
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
280
369
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
281
370
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
282
371
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -285,6 +374,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
285
374
|
});
|
|
286
375
|
}
|
|
287
376
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
+
// Set the interface to use for the matter server mdnsInterface
|
|
288
378
|
if (hasParameter('mdnsinterface')) {
|
|
289
379
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
290
380
|
}
|
|
@@ -293,6 +383,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
293
383
|
if (this.mdnsInterface === '')
|
|
294
384
|
this.mdnsInterface = undefined;
|
|
295
385
|
}
|
|
386
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
296
387
|
if (hasParameter('ipv4address')) {
|
|
297
388
|
this.ipv4address = getParameter('ipv4address');
|
|
298
389
|
}
|
|
@@ -301,6 +392,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
301
392
|
if (this.ipv4address === '')
|
|
302
393
|
this.ipv4address = undefined;
|
|
303
394
|
}
|
|
395
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
304
396
|
if (hasParameter('ipv6address')) {
|
|
305
397
|
this.ipv6address = getParameter('ipv6address');
|
|
306
398
|
}
|
|
@@ -309,17 +401,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
309
401
|
if (this.ipv6address === '')
|
|
310
402
|
this.ipv6address = undefined;
|
|
311
403
|
}
|
|
404
|
+
// Initialize PluginManager
|
|
312
405
|
this.plugins = new PluginManager(this);
|
|
313
406
|
await this.plugins.loadFromStorage();
|
|
407
|
+
// Initialize DeviceManager
|
|
314
408
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
409
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
315
410
|
for (const plugin of this.plugins) {
|
|
316
411
|
const packageJson = await this.plugins.parse(plugin);
|
|
317
412
|
if (packageJson === null && !hasParameter('add')) {
|
|
413
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
414
|
+
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
318
415
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
319
416
|
try {
|
|
320
417
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
321
418
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
322
419
|
plugin.error = false;
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
323
421
|
}
|
|
324
422
|
catch (error) {
|
|
325
423
|
plugin.error = true;
|
|
@@ -336,6 +434,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
336
434
|
await plugin.nodeContext.set('description', plugin.description);
|
|
337
435
|
await plugin.nodeContext.set('author', plugin.author);
|
|
338
436
|
}
|
|
437
|
+
// Log system info and create .matterbridge directory
|
|
339
438
|
await this.logNodeAndSystemInfo();
|
|
340
439
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
341
440
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -343,6 +442,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
343
442
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
344
443
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
345
444
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
445
|
+
// Check node version and throw error
|
|
346
446
|
const minNodeVersion = 18;
|
|
347
447
|
const nodeVersion = process.versions.node;
|
|
348
448
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -350,10 +450,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
350
450
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
351
451
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
352
452
|
}
|
|
453
|
+
// Register process handlers
|
|
353
454
|
this.registerProcessHandlers();
|
|
455
|
+
// Parse command line
|
|
354
456
|
await this.parseCommandLine();
|
|
355
457
|
this.initialized = true;
|
|
356
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
461
|
+
* @private
|
|
462
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
463
|
+
*/
|
|
357
464
|
async parseCommandLine() {
|
|
358
465
|
if (hasParameter('help')) {
|
|
359
466
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -461,6 +568,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
461
568
|
}
|
|
462
569
|
if (hasParameter('factoryreset')) {
|
|
463
570
|
try {
|
|
571
|
+
// Delete old matter storage file
|
|
464
572
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
465
573
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
466
574
|
await fs.unlink(file);
|
|
@@ -471,6 +579,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
579
|
}
|
|
472
580
|
}
|
|
473
581
|
try {
|
|
582
|
+
// Delete matter node storage directory with its subdirectories
|
|
474
583
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
475
584
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
476
585
|
await fs.rm(dir, { recursive: true });
|
|
@@ -481,6 +590,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
481
590
|
}
|
|
482
591
|
}
|
|
483
592
|
try {
|
|
593
|
+
// Delete node storage directory with its subdirectories
|
|
484
594
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
485
595
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
486
596
|
await fs.rm(dir, { recursive: true });
|
|
@@ -498,6 +608,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
498
608
|
this.emit('shutdown');
|
|
499
609
|
return;
|
|
500
610
|
}
|
|
611
|
+
// Start the matter storage and create the matterbridge context
|
|
501
612
|
try {
|
|
502
613
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
503
614
|
}
|
|
@@ -505,6 +616,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
505
616
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
506
617
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
507
618
|
}
|
|
619
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
508
620
|
if (!this.edge && hasParameter('reset') && getParameter('reset') === undefined) {
|
|
509
621
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
510
622
|
await this.matterbridgeContext?.clearAll();
|
|
@@ -513,6 +625,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
513
625
|
this.emit('shutdown');
|
|
514
626
|
return;
|
|
515
627
|
}
|
|
628
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
516
629
|
if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
517
630
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
518
631
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -532,28 +645,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
532
645
|
this.emit('shutdown');
|
|
533
646
|
return;
|
|
534
647
|
}
|
|
648
|
+
// Initialize frontend
|
|
535
649
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
536
650
|
await this.initializeFrontend(getIntParameter('frontend'));
|
|
651
|
+
// Check each 60 minutes the latest versions
|
|
537
652
|
this.checkUpdateInterval = setInterval(() => {
|
|
538
653
|
this.getMatterbridgeLatestVersion();
|
|
539
654
|
for (const plugin of this.plugins) {
|
|
540
655
|
this.getPluginLatestVersion(plugin);
|
|
541
656
|
}
|
|
542
657
|
}, 60 * 60 * 1000);
|
|
658
|
+
// Start the matterbridge in mode test
|
|
543
659
|
if (hasParameter('test')) {
|
|
544
660
|
this.bridgeMode = 'bridge';
|
|
545
661
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
546
662
|
return;
|
|
547
663
|
}
|
|
664
|
+
// Start the matterbridge in mode controller
|
|
548
665
|
if (hasParameter('controller')) {
|
|
549
666
|
this.bridgeMode = 'controller';
|
|
550
667
|
await this.startController();
|
|
551
668
|
return;
|
|
552
669
|
}
|
|
670
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
553
671
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
554
672
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
555
673
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
556
674
|
}
|
|
675
|
+
// Start matterbridge in bridge mode
|
|
557
676
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
558
677
|
this.bridgeMode = 'bridge';
|
|
559
678
|
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
@@ -562,6 +681,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
562
681
|
await this.startBridge();
|
|
563
682
|
return;
|
|
564
683
|
}
|
|
684
|
+
// Start matterbridge in childbridge mode
|
|
565
685
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
566
686
|
this.bridgeMode = 'childbridge';
|
|
567
687
|
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
@@ -571,17 +691,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
571
691
|
return;
|
|
572
692
|
}
|
|
573
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
|
+
*/
|
|
574
702
|
async startPlugins() {
|
|
703
|
+
// Check, load and start the plugins
|
|
575
704
|
for (const plugin of this.plugins) {
|
|
576
705
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
577
706
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
707
|
+
// Check if the plugin is available
|
|
578
708
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
579
709
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
580
710
|
plugin.enabled = false;
|
|
581
711
|
plugin.error = true;
|
|
582
712
|
continue;
|
|
583
713
|
}
|
|
584
|
-
|
|
714
|
+
// Check if the plugin has a new version
|
|
715
|
+
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
585
716
|
if (!plugin.enabled) {
|
|
586
717
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
587
718
|
continue;
|
|
@@ -596,20 +727,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
596
727
|
plugin.addedDevices = undefined;
|
|
597
728
|
plugin.qrPairingCode = undefined;
|
|
598
729
|
plugin.manualPairingCode = undefined;
|
|
599
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
730
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
600
731
|
}
|
|
601
732
|
this.wssSendRefreshRequired();
|
|
602
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
|
+
*/
|
|
603
738
|
registerProcessHandlers() {
|
|
604
739
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
605
740
|
process.removeAllListeners('uncaughtException');
|
|
606
741
|
process.removeAllListeners('unhandledRejection');
|
|
607
742
|
this.exceptionHandler = async (error) => {
|
|
608
743
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
744
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
609
745
|
};
|
|
610
746
|
process.on('uncaughtException', this.exceptionHandler);
|
|
611
747
|
this.rejectionHandler = async (reason, promise) => {
|
|
612
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...');
|
|
613
750
|
};
|
|
614
751
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
615
752
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -622,6 +759,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
622
759
|
};
|
|
623
760
|
process.on('SIGTERM', this.sigtermHandler);
|
|
624
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
764
|
+
*/
|
|
625
765
|
deregisterProcesslHandlers() {
|
|
626
766
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
627
767
|
if (this.exceptionHandler)
|
|
@@ -638,7 +778,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
638
778
|
process.off('SIGTERM', this.sigtermHandler);
|
|
639
779
|
this.sigtermHandler = undefined;
|
|
640
780
|
}
|
|
781
|
+
/**
|
|
782
|
+
* Logs the node and system information.
|
|
783
|
+
*/
|
|
641
784
|
async logNodeAndSystemInfo() {
|
|
785
|
+
// IP address information
|
|
642
786
|
const networkInterfaces = os.networkInterfaces();
|
|
643
787
|
this.systemInformation.ipv4Address = '';
|
|
644
788
|
this.systemInformation.ipv6Address = '';
|
|
@@ -658,7 +802,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
658
802
|
this.systemInformation.macAddress = detail.mac;
|
|
659
803
|
}
|
|
660
804
|
}
|
|
661
|
-
if (this.systemInformation.ipv4Address !== '') {
|
|
805
|
+
if (this.systemInformation.ipv4Address !== '' /* && this.systemInformation.ipv6Address !== ''*/) {
|
|
662
806
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
663
807
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
664
808
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -666,19 +810,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
666
810
|
break;
|
|
667
811
|
}
|
|
668
812
|
}
|
|
813
|
+
// Node information
|
|
669
814
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
670
815
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
671
816
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
672
817
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
818
|
+
// Host system information
|
|
673
819
|
this.systemInformation.hostname = os.hostname();
|
|
674
820
|
this.systemInformation.user = os.userInfo().username;
|
|
675
|
-
this.systemInformation.osType = os.type();
|
|
676
|
-
this.systemInformation.osRelease = os.release();
|
|
677
|
-
this.systemInformation.osPlatform = os.platform();
|
|
678
|
-
this.systemInformation.osArch = os.arch();
|
|
679
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
680
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
681
|
-
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
|
|
682
829
|
this.log.debug('Host System Information:');
|
|
683
830
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
684
831
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -694,15 +841,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
694
841
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
695
842
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
696
843
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
844
|
+
// Home directory
|
|
697
845
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
698
846
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
699
847
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
848
|
+
// Package root directory
|
|
700
849
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
701
850
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
702
851
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
703
852
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
853
|
+
// Global node_modules directory
|
|
704
854
|
if (this.nodeContext)
|
|
705
855
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
856
|
+
// First run of Matterbridge so the node storage is empty
|
|
706
857
|
if (this.globalModulesDirectory === '') {
|
|
707
858
|
try {
|
|
708
859
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -726,6 +877,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
726
877
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
727
878
|
});
|
|
728
879
|
}
|
|
880
|
+
// Create the data directory .matterbridge in the home directory
|
|
729
881
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
730
882
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
731
883
|
try {
|
|
@@ -749,6 +901,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
749
901
|
}
|
|
750
902
|
}
|
|
751
903
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
904
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
752
905
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
753
906
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
754
907
|
try {
|
|
@@ -772,19 +925,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
772
925
|
}
|
|
773
926
|
}
|
|
774
927
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
928
|
+
// Matterbridge version
|
|
775
929
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
776
930
|
this.matterbridgeVersion = packageJson.version;
|
|
777
931
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
778
932
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
933
|
+
// Matterbridge latest version
|
|
779
934
|
if (this.nodeContext)
|
|
780
935
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
781
936
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
782
937
|
this.getMatterbridgeLatestVersion();
|
|
938
|
+
// Current working directory
|
|
783
939
|
const currentDir = process.cwd();
|
|
784
940
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
941
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
785
942
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
786
943
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
787
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
|
+
*/
|
|
788
950
|
async getLatestVersion(packageName) {
|
|
789
951
|
return new Promise((resolve, reject) => {
|
|
790
952
|
this.execRunningCount++;
|
|
@@ -799,6 +961,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
799
961
|
});
|
|
800
962
|
});
|
|
801
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
|
+
*/
|
|
802
968
|
async getGlobalNodeModules() {
|
|
803
969
|
return new Promise((resolve, reject) => {
|
|
804
970
|
this.execRunningCount++;
|
|
@@ -813,6 +979,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
813
979
|
});
|
|
814
980
|
});
|
|
815
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
|
+
*/
|
|
816
987
|
async getMatterbridgeLatestVersion() {
|
|
817
988
|
this.getLatestVersion('matterbridge')
|
|
818
989
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -829,8 +1000,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
829
1000
|
})
|
|
830
1001
|
.catch((error) => {
|
|
831
1002
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
1003
|
+
// error.stack && this.log.debug(error.stack);
|
|
832
1004
|
});
|
|
833
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
|
+
*/
|
|
834
1016
|
async getPluginLatestVersion(plugin) {
|
|
835
1017
|
this.getLatestVersion(plugin.name)
|
|
836
1018
|
.then(async (latestVersion) => {
|
|
@@ -842,40 +1024,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
842
1024
|
})
|
|
843
1025
|
.catch((error) => {
|
|
844
1026
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1027
|
+
// error.stack && this.log.debug(error.stack);
|
|
845
1028
|
});
|
|
846
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
|
+
*/
|
|
847
1035
|
createMatterLogger() {
|
|
848
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1036
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
849
1037
|
return (_level, formattedLog) => {
|
|
850
1038
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
851
1039
|
const message = formattedLog.slice(65);
|
|
852
1040
|
matterLogger.logName = logger;
|
|
853
1041
|
switch (_level) {
|
|
854
1042
|
case MatterLogLevel.DEBUG:
|
|
855
|
-
matterLogger.log("debug"
|
|
1043
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
856
1044
|
break;
|
|
857
1045
|
case MatterLogLevel.INFO:
|
|
858
|
-
matterLogger.log("info"
|
|
1046
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
859
1047
|
break;
|
|
860
1048
|
case MatterLogLevel.NOTICE:
|
|
861
|
-
matterLogger.log("notice"
|
|
1049
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
862
1050
|
break;
|
|
863
1051
|
case MatterLogLevel.WARN:
|
|
864
|
-
matterLogger.log("warn"
|
|
1052
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
865
1053
|
break;
|
|
866
1054
|
case MatterLogLevel.ERROR:
|
|
867
|
-
matterLogger.log("error"
|
|
1055
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
868
1056
|
break;
|
|
869
1057
|
case MatterLogLevel.FATAL:
|
|
870
|
-
matterLogger.log("fatal"
|
|
1058
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
871
1059
|
break;
|
|
872
1060
|
default:
|
|
873
|
-
matterLogger.log("debug"
|
|
1061
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
874
1062
|
break;
|
|
875
1063
|
}
|
|
876
1064
|
};
|
|
877
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
|
+
*/
|
|
878
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
|
|
879
1075
|
let fileSize = 0;
|
|
880
1076
|
if (unlink) {
|
|
881
1077
|
try {
|
|
@@ -924,18 +1120,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
924
1120
|
}
|
|
925
1121
|
};
|
|
926
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Update matterbridge and cleanup.
|
|
1125
|
+
*/
|
|
927
1126
|
async updateProcess() {
|
|
928
1127
|
await this.cleanup('updating...', false);
|
|
929
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Restarts the process by spawning a new process and exiting the current process.
|
|
1131
|
+
*/
|
|
930
1132
|
async restartProcess() {
|
|
931
1133
|
await this.cleanup('restarting...', true);
|
|
932
1134
|
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Shut down the process by exiting the current process.
|
|
1137
|
+
*/
|
|
933
1138
|
async shutdownProcess() {
|
|
934
1139
|
await this.cleanup('shutting down...', false);
|
|
935
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Shut down the process and reset.
|
|
1143
|
+
*/
|
|
936
1144
|
async unregisterAndShutdownProcess() {
|
|
937
1145
|
this.log.info('Unregistering all devices and shutting down...');
|
|
938
|
-
for (const plugin of this.plugins) {
|
|
1146
|
+
for (const plugin of this.plugins /* .filter((plugin) => plugin.enabled && !plugin.error))*/) {
|
|
939
1147
|
if (this.edge)
|
|
940
1148
|
await this.removeAllBridgedEndpoints(plugin.name);
|
|
941
1149
|
else
|
|
@@ -943,37 +1151,55 @@ export class Matterbridge extends EventEmitter {
|
|
|
943
1151
|
}
|
|
944
1152
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
945
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Shut down the process and reset.
|
|
1156
|
+
*/
|
|
946
1157
|
async shutdownProcessAndReset() {
|
|
947
1158
|
await this.cleanup('shutting down with reset...', false);
|
|
948
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Shut down the process and factory reset.
|
|
1162
|
+
*/
|
|
949
1163
|
async shutdownProcessAndFactoryReset() {
|
|
950
1164
|
await this.cleanup('shutting down with factory reset...', false);
|
|
951
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
|
+
*/
|
|
952
1172
|
async cleanup(message, restart = false) {
|
|
953
1173
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
954
1174
|
this.hasCleanupStarted = true;
|
|
955
1175
|
this.log.info(message);
|
|
1176
|
+
// Deregisters the process handlers
|
|
956
1177
|
this.deregisterProcesslHandlers();
|
|
1178
|
+
// Clear the start matter interval
|
|
957
1179
|
if (this.startMatterInterval) {
|
|
958
1180
|
clearInterval(this.startMatterInterval);
|
|
959
1181
|
this.startMatterInterval = undefined;
|
|
960
1182
|
this.log.debug('Start matter interval cleared');
|
|
961
1183
|
}
|
|
1184
|
+
// Clear the check update interval
|
|
962
1185
|
if (this.checkUpdateInterval) {
|
|
963
1186
|
clearInterval(this.checkUpdateInterval);
|
|
964
1187
|
this.checkUpdateInterval = undefined;
|
|
965
1188
|
this.log.debug('Check update interval cleared');
|
|
966
1189
|
}
|
|
1190
|
+
// Clear the configure timeout
|
|
967
1191
|
if (this.configureTimeout) {
|
|
968
1192
|
clearTimeout(this.configureTimeout);
|
|
969
1193
|
this.configureTimeout = undefined;
|
|
970
1194
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
971
1195
|
}
|
|
1196
|
+
// Clear the reachability timeout
|
|
972
1197
|
if (this.reachabilityTimeout) {
|
|
973
1198
|
clearTimeout(this.reachabilityTimeout);
|
|
974
1199
|
this.reachabilityTimeout = undefined;
|
|
975
1200
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
976
1201
|
}
|
|
1202
|
+
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
977
1203
|
for (const plugin of this.plugins) {
|
|
978
1204
|
if (!plugin.enabled || plugin.error)
|
|
979
1205
|
continue;
|
|
@@ -984,6 +1210,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
1210
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
985
1211
|
}
|
|
986
1212
|
}
|
|
1213
|
+
// Convert the matter storage to the new format
|
|
987
1214
|
if (!hasParameter('nostorageconversion') && this.edge === false && this.matterbridgeContext && ['updating...', 'restarting...', 'shutting down...'].includes(message)) {
|
|
988
1215
|
if (this.bridgeMode === 'bridge') {
|
|
989
1216
|
await this.convertStorage(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -996,24 +1223,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
996
1223
|
}
|
|
997
1224
|
}
|
|
998
1225
|
}
|
|
1226
|
+
// Close the http server
|
|
999
1227
|
if (this.httpServer) {
|
|
1000
1228
|
this.httpServer.close();
|
|
1001
1229
|
this.httpServer.removeAllListeners();
|
|
1002
1230
|
this.httpServer = undefined;
|
|
1003
1231
|
this.log.debug('Frontend http server closed successfully');
|
|
1004
1232
|
}
|
|
1233
|
+
// Close the https server
|
|
1005
1234
|
if (this.httpsServer) {
|
|
1006
1235
|
this.httpsServer.close();
|
|
1007
1236
|
this.httpsServer.removeAllListeners();
|
|
1008
1237
|
this.httpsServer = undefined;
|
|
1009
1238
|
this.log.debug('Frontend https server closed successfully');
|
|
1010
1239
|
}
|
|
1240
|
+
// Remove listeners from the express app
|
|
1011
1241
|
if (this.expressApp) {
|
|
1012
1242
|
this.expressApp.removeAllListeners();
|
|
1013
1243
|
this.expressApp = undefined;
|
|
1014
1244
|
this.log.debug('Frontend app closed successfully');
|
|
1015
1245
|
}
|
|
1246
|
+
// Close the WebSocket server
|
|
1016
1247
|
if (this.webSocketServer) {
|
|
1248
|
+
// Close all active connections
|
|
1017
1249
|
this.webSocketServer.clients.forEach((client) => {
|
|
1018
1250
|
if (client.readyState === WebSocket.OPEN) {
|
|
1019
1251
|
client.close();
|
|
@@ -1029,26 +1261,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1029
1261
|
});
|
|
1030
1262
|
this.webSocketServer = undefined;
|
|
1031
1263
|
}
|
|
1264
|
+
// Closing matter
|
|
1032
1265
|
await this.stopMatterServer();
|
|
1266
|
+
// Closing matter storage
|
|
1033
1267
|
await this.stopMatterStorage();
|
|
1268
|
+
// Remove the matterfilelogger
|
|
1034
1269
|
try {
|
|
1035
1270
|
Logger.removeLogger('matterfilelogger');
|
|
1271
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1036
1272
|
}
|
|
1037
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}`);
|
|
1038
1275
|
}
|
|
1276
|
+
// Serialize registeredDevices
|
|
1039
1277
|
if (this.nodeStorage && this.nodeContext) {
|
|
1040
1278
|
this.log.info('Saving registered devices...');
|
|
1041
1279
|
const serializedRegisteredDevices = [];
|
|
1042
1280
|
this.devices.forEach(async (device) => {
|
|
1043
1281
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1282
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1044
1283
|
if (serializedMatterbridgeDevice)
|
|
1045
1284
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1046
1285
|
});
|
|
1047
1286
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1048
1287
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1288
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1049
1289
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1050
1290
|
await this.nodeContext.close();
|
|
1051
1291
|
this.nodeContext = undefined;
|
|
1292
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1052
1293
|
for (const plugin of this.plugins) {
|
|
1053
1294
|
if (plugin.nodeContext) {
|
|
1054
1295
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1080,6 +1321,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1080
1321
|
else {
|
|
1081
1322
|
if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
|
|
1082
1323
|
try {
|
|
1324
|
+
// Delete old matter storage file
|
|
1083
1325
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1084
1326
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1085
1327
|
await fs.unlink(file);
|
|
@@ -1088,6 +1330,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1088
1330
|
this.log.debug(`Error resetting old matter storage file: ${error}`);
|
|
1089
1331
|
}
|
|
1090
1332
|
try {
|
|
1333
|
+
// Delete matter node storage directory with its subdirectories
|
|
1091
1334
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1092
1335
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1093
1336
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1099,6 +1342,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1099
1342
|
}
|
|
1100
1343
|
if (message === 'shutting down with factory reset...') {
|
|
1101
1344
|
try {
|
|
1345
|
+
// Delete node storage directory with its subdirectories
|
|
1102
1346
|
this.log.info('Resetting Matterbridge storage...');
|
|
1103
1347
|
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1104
1348
|
}
|
|
@@ -1115,19 +1359,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1115
1359
|
this.initialized = false;
|
|
1116
1360
|
}
|
|
1117
1361
|
}
|
|
1362
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1118
1363
|
async addBridgedEndpoint(pluginName, device) {
|
|
1364
|
+
// Nothing to do here
|
|
1119
1365
|
}
|
|
1366
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1120
1367
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1368
|
+
// Nothing to do here
|
|
1121
1369
|
}
|
|
1370
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1122
1371
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1372
|
+
// Nothing to do here
|
|
1123
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
|
+
*/
|
|
1124
1380
|
async addBridgedDevice(pluginName, device) {
|
|
1125
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
|
|
1126
1383
|
const plugin = this.plugins.get(pluginName);
|
|
1127
1384
|
if (!plugin) {
|
|
1128
1385
|
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1129
1386
|
return;
|
|
1130
1387
|
}
|
|
1388
|
+
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1131
1389
|
if (this.bridgeMode === 'bridge') {
|
|
1132
1390
|
if (!this.matterAggregator) {
|
|
1133
1391
|
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
@@ -1135,8 +1393,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1135
1393
|
}
|
|
1136
1394
|
this.matterAggregator.addBridgedDevice(device);
|
|
1137
1395
|
}
|
|
1396
|
+
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1397
|
+
// Register and add the device in childbridge mode
|
|
1138
1398
|
if (this.bridgeMode === 'childbridge') {
|
|
1139
1399
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1400
|
+
// Check if the plugin is locked with the commissioning server
|
|
1140
1401
|
if (!plugin.locked) {
|
|
1141
1402
|
plugin.locked = true;
|
|
1142
1403
|
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
@@ -1150,6 +1411,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1150
1411
|
}
|
|
1151
1412
|
}
|
|
1152
1413
|
if (plugin.type === 'DynamicPlatform') {
|
|
1414
|
+
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1153
1415
|
if (!plugin.locked) {
|
|
1154
1416
|
plugin.locked = true;
|
|
1155
1417
|
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
@@ -1170,16 +1432,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1170
1432
|
plugin.registeredDevices++;
|
|
1171
1433
|
if (plugin.addedDevices !== undefined)
|
|
1172
1434
|
plugin.addedDevices++;
|
|
1435
|
+
// Add the device to the DeviceManager
|
|
1173
1436
|
this.devices.set(device);
|
|
1174
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}`);
|
|
1175
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
|
+
*/
|
|
1176
1445
|
async removeBridgedDevice(pluginName, device) {
|
|
1177
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
|
|
1178
1448
|
const plugin = this.plugins.get(pluginName);
|
|
1179
1449
|
if (!plugin) {
|
|
1180
1450
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1181
1451
|
return;
|
|
1182
1452
|
}
|
|
1453
|
+
// Remove the device from matterbridge aggregator in bridge mode
|
|
1183
1454
|
if (this.bridgeMode === 'bridge') {
|
|
1184
1455
|
if (!this.matterAggregator) {
|
|
1185
1456
|
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
@@ -1189,6 +1460,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1189
1460
|
device.setBridgedDeviceReachability(false);
|
|
1190
1461
|
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1191
1462
|
}
|
|
1463
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1464
|
+
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1192
1465
|
this.matterAggregator?.removeBridgedDevice(device);
|
|
1193
1466
|
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1194
1467
|
if (plugin.registeredDevices !== undefined)
|
|
@@ -1196,6 +1469,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1196
1469
|
if (plugin.addedDevices !== undefined)
|
|
1197
1470
|
plugin.addedDevices--;
|
|
1198
1471
|
}
|
|
1472
|
+
// Remove the device in childbridge mode
|
|
1199
1473
|
if (this.bridgeMode === 'childbridge') {
|
|
1200
1474
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1201
1475
|
if (!plugin.commissioningServer) {
|
|
@@ -1219,14 +1493,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1219
1493
|
plugin.registeredDevices--;
|
|
1220
1494
|
if (plugin.addedDevices !== undefined)
|
|
1221
1495
|
plugin.addedDevices--;
|
|
1496
|
+
// Remove the commissioning server
|
|
1222
1497
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1223
1498
|
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1224
1499
|
plugin.commissioningServer = undefined;
|
|
1225
1500
|
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1226
1501
|
}
|
|
1227
1502
|
}
|
|
1503
|
+
// Remove the device from the DeviceManager
|
|
1228
1504
|
this.devices.remove(device);
|
|
1229
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
|
+
*/
|
|
1230
1512
|
async removeAllBridgedDevices(pluginName) {
|
|
1231
1513
|
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1232
1514
|
this.devices.forEach(async (device) => {
|
|
@@ -1235,7 +1517,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1235
1517
|
}
|
|
1236
1518
|
});
|
|
1237
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
|
+
*/
|
|
1238
1525
|
async startBridge() {
|
|
1526
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1239
1527
|
if (!this.storageManager)
|
|
1240
1528
|
throw new Error('No storage manager initialized');
|
|
1241
1529
|
if (!this.matterbridgeContext)
|
|
@@ -1254,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1254
1542
|
let failCount = 0;
|
|
1255
1543
|
this.startMatterInterval = setInterval(async () => {
|
|
1256
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
|
|
1257
1546
|
if (!plugin.enabled)
|
|
1258
1547
|
continue;
|
|
1259
1548
|
if (plugin.error) {
|
|
@@ -1278,15 +1567,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1278
1567
|
clearInterval(this.startMatterInterval);
|
|
1279
1568
|
this.startMatterInterval = undefined;
|
|
1280
1569
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
+
// Start the Matter server
|
|
1281
1571
|
await this.startMatterServer();
|
|
1282
1572
|
this.log.notice('Matter server started');
|
|
1573
|
+
// Show the QR code for commissioning or log the already commissioned message
|
|
1283
1574
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1575
|
+
// Configure the plugins
|
|
1284
1576
|
this.configureTimeout = setTimeout(async () => {
|
|
1285
1577
|
for (const plugin of this.plugins) {
|
|
1286
1578
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1287
1579
|
continue;
|
|
1288
1580
|
try {
|
|
1289
|
-
await this.plugins.configure(plugin);
|
|
1581
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1290
1582
|
}
|
|
1291
1583
|
catch (error) {
|
|
1292
1584
|
plugin.error = true;
|
|
@@ -1295,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1295
1587
|
}
|
|
1296
1588
|
this.wssSendRefreshRequired();
|
|
1297
1589
|
}, 30 * 1000);
|
|
1590
|
+
// Setting reachability to true
|
|
1298
1591
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1299
1592
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1300
1593
|
if (this.commissioningServer)
|
|
@@ -1304,7 +1597,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1304
1597
|
}, 60 * 1000);
|
|
1305
1598
|
}, 1000);
|
|
1306
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
|
+
*/
|
|
1307
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
|
|
1308
1608
|
if (!this.storageManager)
|
|
1309
1609
|
throw new Error('No storage manager initialized');
|
|
1310
1610
|
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
@@ -1314,6 +1614,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1314
1614
|
this.startMatterInterval = setInterval(async () => {
|
|
1315
1615
|
let allStarted = true;
|
|
1316
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
|
|
1317
1618
|
if (!plugin.enabled)
|
|
1318
1619
|
continue;
|
|
1319
1620
|
if (plugin.error) {
|
|
@@ -1341,14 +1642,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1341
1642
|
clearInterval(this.startMatterInterval);
|
|
1342
1643
|
this.startMatterInterval = undefined;
|
|
1343
1644
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1645
|
+
// Start the Matter server
|
|
1344
1646
|
await this.startMatterServer();
|
|
1345
1647
|
this.log.notice('Matter server started');
|
|
1648
|
+
// Configure the plugins
|
|
1346
1649
|
this.configureTimeout = setTimeout(async () => {
|
|
1347
1650
|
for (const plugin of this.plugins) {
|
|
1348
1651
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1349
1652
|
continue;
|
|
1350
1653
|
try {
|
|
1351
|
-
await this.plugins.configure(plugin);
|
|
1654
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1352
1655
|
}
|
|
1353
1656
|
catch (error) {
|
|
1354
1657
|
plugin.error = true;
|
|
@@ -1377,6 +1680,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1377
1680
|
continue;
|
|
1378
1681
|
}
|
|
1379
1682
|
await this.showCommissioningQRCode(plugin.commissioningServer, plugin.storageContext, plugin.nodeContext, plugin.name);
|
|
1683
|
+
// Setting reachability to true
|
|
1380
1684
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1381
1685
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1382
1686
|
if (plugin.commissioningServer)
|
|
@@ -1389,6 +1693,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1693
|
}
|
|
1390
1694
|
}, 1000);
|
|
1391
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Starts the Matterbridge controller.
|
|
1698
|
+
* @private
|
|
1699
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
|
+
*/
|
|
1392
1701
|
async startController() {
|
|
1393
1702
|
if (!this.storageManager) {
|
|
1394
1703
|
this.log.error('No storage manager initialized');
|
|
@@ -1451,7 +1760,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1451
1760
|
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1452
1761
|
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1453
1762
|
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1454
|
-
}
|
|
1763
|
+
} // (hasParameter('pairingcode'))
|
|
1455
1764
|
if (hasParameter('unpairall')) {
|
|
1456
1765
|
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1457
1766
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
@@ -1462,6 +1771,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1462
1771
|
return;
|
|
1463
1772
|
}
|
|
1464
1773
|
if (hasParameter('discover')) {
|
|
1774
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1775
|
+
// console.log(discover);
|
|
1465
1776
|
}
|
|
1466
1777
|
if (!this.commissioningController.isCommissioned()) {
|
|
1467
1778
|
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
@@ -1502,10 +1813,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1502
1813
|
},
|
|
1503
1814
|
});
|
|
1504
1815
|
node.logStructure();
|
|
1816
|
+
// Get the interaction client
|
|
1505
1817
|
this.log.info('Getting the interaction client');
|
|
1506
1818
|
const interactionClient = await node.getInteractionClient();
|
|
1507
1819
|
let cluster;
|
|
1508
1820
|
let attributes;
|
|
1821
|
+
// Log BasicInformationCluster
|
|
1509
1822
|
cluster = BasicInformationCluster;
|
|
1510
1823
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1511
1824
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1515,6 +1828,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1515
1828
|
attributes.forEach((attribute) => {
|
|
1516
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}`);
|
|
1517
1830
|
});
|
|
1831
|
+
// Log PowerSourceCluster
|
|
1518
1832
|
cluster = PowerSourceCluster;
|
|
1519
1833
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1520
1834
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1524,6 +1838,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1524
1838
|
attributes.forEach((attribute) => {
|
|
1525
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}`);
|
|
1526
1840
|
});
|
|
1841
|
+
// Log ThreadNetworkDiagnostics
|
|
1527
1842
|
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1528
1843
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1529
1844
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1533,6 +1848,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1533
1848
|
attributes.forEach((attribute) => {
|
|
1534
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}`);
|
|
1535
1850
|
});
|
|
1851
|
+
// Log SwitchCluster
|
|
1536
1852
|
cluster = SwitchCluster;
|
|
1537
1853
|
attributes = await interactionClient.getMultipleAttributes({
|
|
1538
1854
|
attributes: [{ clusterId: cluster.id }],
|
|
@@ -1553,6 +1869,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1553
1869
|
this.log.info('Subscribed to all attributes and events');
|
|
1554
1870
|
}
|
|
1555
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
|
+
*/
|
|
1556
1881
|
async startMatterStorage(storageType, storageName) {
|
|
1557
1882
|
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1558
1883
|
if (storageType === 'disk') {
|
|
@@ -1601,6 +1926,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1601
1926
|
await this.matterbridgeContext.set('passcode', this.passcode);
|
|
1602
1927
|
await this.matterbridgeContext.set('discriminator', this.discriminator);
|
|
1603
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
|
+
*/
|
|
1604
1935
|
async convertStorage(context, pluginName) {
|
|
1605
1936
|
if (this.edge !== false)
|
|
1606
1937
|
return;
|
|
@@ -1615,13 +1946,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
1946
|
else {
|
|
1616
1947
|
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1617
1948
|
}
|
|
1949
|
+
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1618
1950
|
const fabricManagerContext = context.createContext('FabricManager');
|
|
1619
1951
|
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1620
1952
|
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
|
|
1953
|
+
// Read EventHandler from the old storage
|
|
1621
1954
|
const eventHandlerContext = context.createContext('EventHandler');
|
|
1955
|
+
// Read SessionManager from the old storage
|
|
1622
1956
|
const sessionManagerContext = context.createContext('SessionManager');
|
|
1957
|
+
// Read EndpointStructure from the old storage
|
|
1623
1958
|
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1959
|
+
// Read generalCommissioning from the old storage
|
|
1624
1960
|
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1961
|
+
// Read basicInformation from the old storage
|
|
1625
1962
|
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1626
1963
|
const fabricInfo = {};
|
|
1627
1964
|
const fabricInfoArray = [];
|
|
@@ -1675,8 +2012,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1675
2012
|
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
1676
2013
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
1677
2014
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
2015
|
+
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
1678
2016
|
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
2017
|
+
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
1679
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}]
|
|
1680
2022
|
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
1681
2023
|
await nodeStorage
|
|
1682
2024
|
.createContext('root')
|
|
@@ -1699,6 +2041,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1699
2041
|
.createContext('root')
|
|
1700
2042
|
.createContext('productDescription')
|
|
1701
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
|
+
*/
|
|
1702
2063
|
const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
|
|
1703
2064
|
this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
|
|
1704
2065
|
for (const key of await endpointStructureContext.keys()) {
|
|
@@ -1777,6 +2138,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1777
2138
|
this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
|
|
1778
2139
|
}
|
|
1779
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
|
+
*/
|
|
1780
2147
|
async backupMatterStorage(storageName, backupName) {
|
|
1781
2148
|
try {
|
|
1782
2149
|
this.log.debug(`Making backup copy of ${storageName}`);
|
|
@@ -1797,6 +2164,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1797
2164
|
}
|
|
1798
2165
|
}
|
|
1799
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
|
+
*/
|
|
1800
2173
|
async restoreMatterStorage(backupName, storageName) {
|
|
1801
2174
|
try {
|
|
1802
2175
|
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
@@ -1817,6 +2190,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1817
2190
|
}
|
|
1818
2191
|
}
|
|
1819
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Stops the matter storage.
|
|
2195
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2196
|
+
*/
|
|
1820
2197
|
async stopMatterStorage() {
|
|
1821
2198
|
this.log.debug('Stopping storage');
|
|
1822
2199
|
await this.storageManager?.close();
|
|
@@ -1825,8 +2202,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1825
2202
|
this.matterbridgeContext = undefined;
|
|
1826
2203
|
this.mattercontrollerContext = undefined;
|
|
1827
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
2207
|
+
* @param storageManager The storage manager to be used by the Matter server.
|
|
2208
|
+
*
|
|
2209
|
+
*/
|
|
1828
2210
|
async createMatterServer(storageManager) {
|
|
1829
2211
|
this.log.debug('Creating matter server');
|
|
2212
|
+
// Validate mdnsInterface
|
|
1830
2213
|
if (this.mdnsInterface) {
|
|
1831
2214
|
const networkInterfaces = os.networkInterfaces();
|
|
1832
2215
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -1842,6 +2225,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1842
2225
|
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
1843
2226
|
return matterServer;
|
|
1844
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Starts the Matter server.
|
|
2230
|
+
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2231
|
+
*/
|
|
1845
2232
|
async startMatterServer() {
|
|
1846
2233
|
if (!this.matterServer) {
|
|
1847
2234
|
this.log.error('No matter server initialized');
|
|
@@ -1851,7 +2238,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1851
2238
|
this.log.debug('Starting matter server...');
|
|
1852
2239
|
await this.matterServer.start();
|
|
1853
2240
|
this.log.debug('Started matter server');
|
|
2241
|
+
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
1854
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2245
|
+
*/
|
|
1855
2246
|
async stopMatterServer() {
|
|
1856
2247
|
this.log.debug('Stopping matter commissioningServer');
|
|
1857
2248
|
await this.commissioningServer?.close();
|
|
@@ -1865,23 +2256,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1865
2256
|
this.matterAggregator = undefined;
|
|
1866
2257
|
this.matterServer = undefined;
|
|
1867
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Creates a Matter Aggregator.
|
|
2261
|
+
* @param {StorageContext} context - The storage context.
|
|
2262
|
+
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2263
|
+
*/
|
|
1868
2264
|
async createMatterAggregator(context, pluginName) {
|
|
1869
2265
|
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
1870
2266
|
const matterAggregator = new Aggregator();
|
|
1871
2267
|
return matterAggregator;
|
|
1872
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
|
+
*/
|
|
1873
2276
|
async createCommisioningServer(context, pluginName) {
|
|
1874
2277
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
1875
2278
|
const deviceName = await context.get('deviceName');
|
|
1876
2279
|
const deviceType = await context.get('deviceType');
|
|
1877
2280
|
const vendorId = await context.get('vendorId');
|
|
1878
|
-
const vendorName = await context.get('vendorName');
|
|
2281
|
+
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
1879
2282
|
const productId = await context.get('productId');
|
|
1880
|
-
const productName = await context.get('productName');
|
|
2283
|
+
const productName = await context.get('productName'); // Home app = Model
|
|
1881
2284
|
const serialNumber = await context.get('serialNumber');
|
|
1882
2285
|
const uniqueId = await context.get('uniqueId');
|
|
1883
2286
|
const softwareVersion = await context.get('softwareVersion', 1);
|
|
1884
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0');
|
|
2287
|
+
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
1885
2288
|
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
1886
2289
|
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
1887
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')})`);
|
|
@@ -1889,6 +2292,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1889
2292
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
1890
2293
|
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
1891
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
|
|
1892
2296
|
if (this.ipv4address) {
|
|
1893
2297
|
const networkInterfaces = os.networkInterfaces();
|
|
1894
2298
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1903,6 +2307,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1903
2307
|
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
1904
2308
|
}
|
|
1905
2309
|
}
|
|
2310
|
+
// Validate ipv6address
|
|
1906
2311
|
if (this.ipv6address) {
|
|
1907
2312
|
const networkInterfaces = os.networkInterfaces();
|
|
1908
2313
|
const availableAddresses = Object.values(networkInterfaces)
|
|
@@ -1933,7 +2338,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1933
2338
|
nodeLabel: productName,
|
|
1934
2339
|
productLabel: productName,
|
|
1935
2340
|
softwareVersion,
|
|
1936
|
-
softwareVersionString,
|
|
2341
|
+
softwareVersionString, // Home app = Firmware Revision
|
|
1937
2342
|
hardwareVersion,
|
|
1938
2343
|
hardwareVersionString,
|
|
1939
2344
|
uniqueId,
|
|
@@ -2029,6 +2434,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2029
2434
|
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
2030
2435
|
return commissioningServer;
|
|
2031
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
|
+
*/
|
|
2032
2455
|
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
2033
2456
|
if (!this.storageManager)
|
|
2034
2457
|
throw new Error('No storage manager initialized');
|
|
@@ -2056,6 +2479,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2056
2479
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2057
2480
|
return storageContext;
|
|
2058
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
|
+
*/
|
|
2059
2489
|
async importCommissioningServerContext(pluginName, device) {
|
|
2060
2490
|
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
2061
2491
|
const basic = device.getClusterServer(BasicInformationCluster);
|
|
@@ -2090,6 +2520,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2090
2520
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2091
2521
|
return storageContext;
|
|
2092
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
|
+
*/
|
|
2093
2531
|
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
2094
2532
|
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
2095
2533
|
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
@@ -2100,7 +2538,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2100
2538
|
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
2101
2539
|
const QrCode = new QrCodeSchema();
|
|
2102
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`);
|
|
2103
|
-
|
|
2541
|
+
// eslint-disable-next-line no-console
|
|
2542
|
+
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
2104
2543
|
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
2105
2544
|
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
2106
2545
|
if (pluginName === 'Matterbridge') {
|
|
@@ -2147,6 +2586,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2147
2586
|
}
|
|
2148
2587
|
this.wssSendRefreshRequired();
|
|
2149
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
|
+
*/
|
|
2150
2595
|
sanitizeFabricInformations(fabricInfo) {
|
|
2151
2596
|
return fabricInfo.map((info) => {
|
|
2152
2597
|
return {
|
|
@@ -2160,6 +2605,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2160
2605
|
};
|
|
2161
2606
|
});
|
|
2162
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
|
+
*/
|
|
2163
2614
|
sanitizeSessionInformation(sessionInfo) {
|
|
2164
2615
|
return sessionInfo
|
|
2165
2616
|
.filter((session) => session.isPeerActive)
|
|
@@ -2187,6 +2638,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2187
2638
|
};
|
|
2188
2639
|
});
|
|
2189
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
|
+
*/
|
|
2190
2647
|
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2191
2648
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2192
2649
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2194,6 +2651,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2194
2651
|
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2195
2652
|
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2196
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
|
+
*/
|
|
2197
2659
|
setAggregatorReachability(matterAggregator, reachable) {
|
|
2198
2660
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2199
2661
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2206,6 +2668,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2206
2668
|
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2207
2669
|
});
|
|
2208
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
|
+
*/
|
|
2209
2677
|
setDeviceReachability(device, reachable) {
|
|
2210
2678
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2211
2679
|
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
@@ -2254,6 +2722,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2254
2722
|
}
|
|
2255
2723
|
return vendorName;
|
|
2256
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
|
+
*/
|
|
2257
2729
|
async getBaseRegisteredPlugins() {
|
|
2258
2730
|
const baseRegisteredPlugins = [];
|
|
2259
2731
|
for (const plugin of this.plugins) {
|
|
@@ -2285,13 +2757,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
2285
2757
|
}
|
|
2286
2758
|
return baseRegisteredPlugins;
|
|
2287
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
|
+
*/
|
|
2288
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
|
+
*/
|
|
2289
2780
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2290
2781
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2782
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2291
2783
|
const argstring = 'npm ' + args.join(' ');
|
|
2292
2784
|
args.splice(0, args.length, '/c', argstring);
|
|
2293
2785
|
command = 'cmd.exe';
|
|
2294
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
|
|
2295
2790
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2296
2791
|
args.unshift(command);
|
|
2297
2792
|
command = 'sudo';
|
|
@@ -2349,55 +2844,102 @@ export class Matterbridge extends EventEmitter {
|
|
|
2349
2844
|
}
|
|
2350
2845
|
});
|
|
2351
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
|
+
*/
|
|
2352
2855
|
wssSendMessage(level, time, name, message) {
|
|
2353
2856
|
if (!level || !time || !name || !message)
|
|
2354
2857
|
return;
|
|
2858
|
+
// Remove ANSI escape codes from the message
|
|
2859
|
+
// eslint-disable-next-line no-control-regex
|
|
2355
2860
|
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2861
|
+
// Remove leading asterisks from the message
|
|
2356
2862
|
message = message.replace(/^\*+/, '');
|
|
2863
|
+
// Replace all occurrences of \t and \n
|
|
2357
2864
|
message = message.replace(/[\t\n]/g, '');
|
|
2865
|
+
// Remove non-printable characters
|
|
2866
|
+
// eslint-disable-next-line no-control-regex
|
|
2358
2867
|
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2868
|
+
// Replace all occurrences of \" with "
|
|
2359
2869
|
message = message.replace(/\\"/g, '"');
|
|
2870
|
+
// Define the maximum allowed length for continuous characters without a space
|
|
2360
2871
|
const maxContinuousLength = 100;
|
|
2361
2872
|
const keepStartLength = 20;
|
|
2362
2873
|
const keepEndLength = 20;
|
|
2874
|
+
// Split the message into words
|
|
2363
2875
|
message = message
|
|
2364
2876
|
.split(' ')
|
|
2365
2877
|
.map((word) => {
|
|
2878
|
+
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2366
2879
|
if (word.length > maxContinuousLength) {
|
|
2367
2880
|
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2368
2881
|
}
|
|
2369
2882
|
return word;
|
|
2370
2883
|
})
|
|
2371
2884
|
.join(' ');
|
|
2885
|
+
// Send the message to all connected clients
|
|
2372
2886
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2373
2887
|
if (client.readyState === WebSocket.OPEN) {
|
|
2374
2888
|
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2375
2889
|
}
|
|
2376
2890
|
});
|
|
2377
2891
|
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2894
|
+
*
|
|
2895
|
+
*/
|
|
2378
2896
|
wssSendRefreshRequired() {
|
|
2379
2897
|
this.matterbridgeInformation.refreshRequired = true;
|
|
2898
|
+
// Send the message to all connected clients
|
|
2380
2899
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2381
2900
|
if (client.readyState === WebSocket.OPEN) {
|
|
2382
2901
|
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
2383
2902
|
}
|
|
2384
2903
|
});
|
|
2385
2904
|
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Sends a need to restart WebSocket message to all connected clients.
|
|
2907
|
+
*
|
|
2908
|
+
*/
|
|
2386
2909
|
wssSendRestartRequired() {
|
|
2387
2910
|
this.matterbridgeInformation.restartRequired = true;
|
|
2911
|
+
// Send the message to all connected clients
|
|
2388
2912
|
this.webSocketServer?.clients.forEach((client) => {
|
|
2389
2913
|
if (client.readyState === WebSocket.OPEN) {
|
|
2390
2914
|
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
2391
2915
|
}
|
|
2392
2916
|
});
|
|
2393
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
|
+
*/
|
|
2394
2923
|
async initializeFrontend(port = 8283) {
|
|
2395
2924
|
let initializeError = false;
|
|
2396
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
|
|
2397
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
|
|
2398
2938
|
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2399
2939
|
if (!hasParameter('ssl')) {
|
|
2940
|
+
// Create an HTTP server and attach the express app
|
|
2400
2941
|
this.httpServer = createServer(this.expressApp);
|
|
2942
|
+
// Listen on the specified port
|
|
2401
2943
|
if (hasParameter('ingress')) {
|
|
2402
2944
|
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2403
2945
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2411,6 +2953,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2411
2953
|
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2412
2954
|
});
|
|
2413
2955
|
}
|
|
2956
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2414
2957
|
this.httpServer.on('error', (error) => {
|
|
2415
2958
|
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2416
2959
|
switch (error.code) {
|
|
@@ -2426,6 +2969,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2426
2969
|
});
|
|
2427
2970
|
}
|
|
2428
2971
|
else {
|
|
2972
|
+
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2429
2973
|
let cert;
|
|
2430
2974
|
try {
|
|
2431
2975
|
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
@@ -2453,7 +2997,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2453
2997
|
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2454
2998
|
}
|
|
2455
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
|
|
2456
3001
|
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
3002
|
+
// Listen on the specified port
|
|
2457
3003
|
if (hasParameter('ingress')) {
|
|
2458
3004
|
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
2459
3005
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
@@ -2467,6 +3013,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2467
3013
|
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2468
3014
|
});
|
|
2469
3015
|
}
|
|
3016
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2470
3017
|
this.httpsServer.on('error', (error) => {
|
|
2471
3018
|
this.log.error(`Frontend https server error listening on ${port}`);
|
|
2472
3019
|
switch (error.code) {
|
|
@@ -2483,12 +3030,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2483
3030
|
}
|
|
2484
3031
|
if (initializeError)
|
|
2485
3032
|
return;
|
|
3033
|
+
// Createe a WebSocket server and attach it to the http or https server
|
|
2486
3034
|
const wssPort = port;
|
|
2487
3035
|
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
2488
3036
|
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
2489
3037
|
this.webSocketServer.on('connection', (ws, request) => {
|
|
2490
3038
|
const clientIp = request.socket.remoteAddress;
|
|
2491
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug");
|
|
3039
|
+
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
2492
3040
|
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
2493
3041
|
ws.on('message', (message) => {
|
|
2494
3042
|
this.log.debug(`WebSocket client message: ${message}`);
|
|
@@ -2521,6 +3069,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2521
3069
|
this.webSocketServer.on('error', (ws, error) => {
|
|
2522
3070
|
this.log.error(`WebSocketServer error: ${error}`);
|
|
2523
3071
|
});
|
|
3072
|
+
// Endpoint to validate login code
|
|
2524
3073
|
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
2525
3074
|
const { password } = req.body;
|
|
2526
3075
|
this.log.debug('The frontend sent /api/login', password);
|
|
@@ -2539,12 +3088,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2539
3088
|
this.log.warn('/api/login error wrong password');
|
|
2540
3089
|
res.json({ valid: false });
|
|
2541
3090
|
}
|
|
3091
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2542
3092
|
}
|
|
2543
3093
|
catch (error) {
|
|
2544
3094
|
this.log.error('/api/login error getting password');
|
|
2545
3095
|
res.json({ valid: false });
|
|
2546
3096
|
}
|
|
2547
3097
|
});
|
|
3098
|
+
// Endpoint to provide health check
|
|
3099
|
+
this.expressApp.get('/health', (req, res) => {
|
|
3100
|
+
this.log.debug('Express received /health');
|
|
3101
|
+
const healthStatus = {
|
|
3102
|
+
status: 'ok', // Indicate service is healthy
|
|
3103
|
+
uptime: process.uptime(), // Server uptime in seconds
|
|
3104
|
+
timestamp: new Date().toISOString(), // Current timestamp
|
|
3105
|
+
};
|
|
3106
|
+
res.status(200).json(healthStatus);
|
|
3107
|
+
});
|
|
3108
|
+
// Endpoint to provide settings
|
|
2548
3109
|
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
2549
3110
|
this.log.debug('The frontend sent /api/settings');
|
|
2550
3111
|
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
@@ -2565,13 +3126,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2565
3126
|
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
2566
3127
|
this.matterbridgeInformation.profile = this.profile;
|
|
2567
3128
|
const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
3129
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2568
3130
|
res.json(response);
|
|
2569
3131
|
});
|
|
3132
|
+
// Endpoint to provide plugins
|
|
2570
3133
|
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
2571
3134
|
this.log.debug('The frontend sent /api/plugins');
|
|
2572
3135
|
const response = await this.getBaseRegisteredPlugins();
|
|
3136
|
+
// this.log.debug('Response:', debugStringify(response));
|
|
2573
3137
|
res.json(response);
|
|
2574
3138
|
});
|
|
3139
|
+
// Endpoint to provide devices
|
|
2575
3140
|
this.expressApp.get('/api/devices', (req, res) => {
|
|
2576
3141
|
this.log.debug('The frontend sent /api/devices');
|
|
2577
3142
|
const devices = [];
|
|
@@ -2604,8 +3169,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2604
3169
|
cluster: cluster,
|
|
2605
3170
|
});
|
|
2606
3171
|
});
|
|
3172
|
+
// this.log.debug('Response:', debugStringify(data));
|
|
2607
3173
|
res.json(devices);
|
|
2608
3174
|
});
|
|
3175
|
+
// Endpoint to provide the cluster servers of the devices
|
|
2609
3176
|
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
2610
3177
|
const selectedPluginName = req.params.selectedPluginName;
|
|
2611
3178
|
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
@@ -2625,6 +3192,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2625
3192
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2626
3193
|
if (clusterServer.name === 'EveHistory')
|
|
2627
3194
|
return;
|
|
3195
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2628
3196
|
let attributeValue;
|
|
2629
3197
|
try {
|
|
2630
3198
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2635,6 +3203,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2635
3203
|
catch (error) {
|
|
2636
3204
|
attributeValue = 'Fabric-Scoped';
|
|
2637
3205
|
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3206
|
+
// console.log(error);
|
|
2638
3207
|
}
|
|
2639
3208
|
data.push({
|
|
2640
3209
|
endpoint: device.number ? device.number.toString() : '...',
|
|
@@ -2647,12 +3216,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2647
3216
|
});
|
|
2648
3217
|
});
|
|
2649
3218
|
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2650
3220
|
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
2651
3221
|
const clusterServers = childEndpoint.getAllClusterServers();
|
|
2652
3222
|
clusterServers.forEach((clusterServer) => {
|
|
2653
3223
|
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
2654
3224
|
if (clusterServer.name === 'EveHistory')
|
|
2655
3225
|
return;
|
|
3226
|
+
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
2656
3227
|
let attributeValue;
|
|
2657
3228
|
try {
|
|
2658
3229
|
if (typeof value.getLocal() === 'object')
|
|
@@ -2663,6 +3234,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2663
3234
|
catch (error) {
|
|
2664
3235
|
attributeValue = 'Unavailable';
|
|
2665
3236
|
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3237
|
+
// console.log(error);
|
|
2666
3238
|
}
|
|
2667
3239
|
data.push({
|
|
2668
3240
|
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
@@ -2679,6 +3251,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2679
3251
|
});
|
|
2680
3252
|
res.json(data);
|
|
2681
3253
|
});
|
|
3254
|
+
// Endpoint to view the log
|
|
2682
3255
|
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
2683
3256
|
this.log.debug('The frontend sent /api/log');
|
|
2684
3257
|
try {
|
|
@@ -2691,10 +3264,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2691
3264
|
res.status(500).send('Error reading log file');
|
|
2692
3265
|
}
|
|
2693
3266
|
});
|
|
3267
|
+
// Endpoint to download the matterbridge log
|
|
2694
3268
|
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
2695
3269
|
this.log.debug('The frontend sent /api/download-mblog');
|
|
2696
3270
|
try {
|
|
2697
3271
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3272
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2698
3273
|
}
|
|
2699
3274
|
catch (error) {
|
|
2700
3275
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2706,10 +3281,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2706
3281
|
}
|
|
2707
3282
|
});
|
|
2708
3283
|
});
|
|
3284
|
+
// Endpoint to download the matter log
|
|
2709
3285
|
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
2710
3286
|
this.log.debug('The frontend sent /api/download-mjlog');
|
|
2711
3287
|
try {
|
|
2712
3288
|
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3289
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2713
3290
|
}
|
|
2714
3291
|
catch (error) {
|
|
2715
3292
|
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
@@ -2721,6 +3298,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2721
3298
|
}
|
|
2722
3299
|
});
|
|
2723
3300
|
});
|
|
3301
|
+
// Endpoint to download the matter storage file
|
|
2724
3302
|
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
2725
3303
|
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
2726
3304
|
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
@@ -2730,6 +3308,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2730
3308
|
}
|
|
2731
3309
|
});
|
|
2732
3310
|
});
|
|
3311
|
+
// Endpoint to download the matterbridge storage directory
|
|
2733
3312
|
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
2734
3313
|
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
2735
3314
|
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
@@ -2740,6 +3319,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2740
3319
|
}
|
|
2741
3320
|
});
|
|
2742
3321
|
});
|
|
3322
|
+
// Endpoint to download the matterbridge plugin directory
|
|
2743
3323
|
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
2744
3324
|
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
2745
3325
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
@@ -2750,9 +3330,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2750
3330
|
}
|
|
2751
3331
|
});
|
|
2752
3332
|
});
|
|
3333
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2753
3334
|
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
2754
3335
|
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
2755
3336
|
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3337
|
+
// 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')));
|
|
2756
3338
|
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
2757
3339
|
if (error) {
|
|
2758
3340
|
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
@@ -2760,6 +3342,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2760
3342
|
}
|
|
2761
3343
|
});
|
|
2762
3344
|
});
|
|
3345
|
+
// Endpoint to download the matterbridge plugin config files
|
|
2763
3346
|
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
2764
3347
|
this.log.debug('The frontend sent /api/download-backup');
|
|
2765
3348
|
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
@@ -2769,6 +3352,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2769
3352
|
}
|
|
2770
3353
|
});
|
|
2771
3354
|
});
|
|
3355
|
+
// Endpoint to receive commands
|
|
2772
3356
|
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
2773
3357
|
const command = req.params.command;
|
|
2774
3358
|
let param = req.params.param;
|
|
@@ -2778,13 +3362,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
2778
3362
|
return;
|
|
2779
3363
|
}
|
|
2780
3364
|
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3365
|
+
// Handle the command setpassword from Settings
|
|
2781
3366
|
if (command === 'setpassword') {
|
|
2782
|
-
const password = param.slice(1, -1);
|
|
3367
|
+
const password = param.slice(1, -1); // Remove the first and last characters
|
|
2783
3368
|
this.log.debug('setpassword', param, password);
|
|
2784
3369
|
await this.nodeContext?.set('password', password);
|
|
2785
3370
|
res.json({ message: 'Command received' });
|
|
2786
3371
|
return;
|
|
2787
3372
|
}
|
|
3373
|
+
// Handle the command setbridgemode from Settings
|
|
2788
3374
|
if (command === 'setbridgemode') {
|
|
2789
3375
|
this.log.debug(`setbridgemode: ${param}`);
|
|
2790
3376
|
this.wssSendRestartRequired();
|
|
@@ -2792,6 +3378,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2792
3378
|
res.json({ message: 'Command received' });
|
|
2793
3379
|
return;
|
|
2794
3380
|
}
|
|
3381
|
+
// Handle the command backup from Settings
|
|
2795
3382
|
if (command === 'backup') {
|
|
2796
3383
|
this.log.notice(`Prepairing the backup...`);
|
|
2797
3384
|
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
@@ -2799,25 +3386,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
2799
3386
|
res.json({ message: 'Command received' });
|
|
2800
3387
|
return;
|
|
2801
3388
|
}
|
|
3389
|
+
// Handle the command setmbloglevel from Settings
|
|
2802
3390
|
if (command === 'setmbloglevel') {
|
|
2803
3391
|
this.log.debug('Matterbridge log level:', param);
|
|
2804
3392
|
if (param === 'Debug') {
|
|
2805
|
-
this.log.logLevel = "debug"
|
|
3393
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
2806
3394
|
}
|
|
2807
3395
|
else if (param === 'Info') {
|
|
2808
|
-
this.log.logLevel = "info"
|
|
3396
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
2809
3397
|
}
|
|
2810
3398
|
else if (param === 'Notice') {
|
|
2811
|
-
this.log.logLevel = "notice"
|
|
3399
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
2812
3400
|
}
|
|
2813
3401
|
else if (param === 'Warn') {
|
|
2814
|
-
this.log.logLevel = "warn"
|
|
3402
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
2815
3403
|
}
|
|
2816
3404
|
else if (param === 'Error') {
|
|
2817
|
-
this.log.logLevel = "error"
|
|
3405
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
2818
3406
|
}
|
|
2819
3407
|
else if (param === 'Fatal') {
|
|
2820
|
-
this.log.logLevel = "fatal"
|
|
3408
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
2821
3409
|
}
|
|
2822
3410
|
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
2823
3411
|
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
@@ -2825,12 +3413,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2825
3413
|
for (const plugin of this.plugins) {
|
|
2826
3414
|
if (!plugin.platform || !plugin.platform.config)
|
|
2827
3415
|
continue;
|
|
2828
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" : this.log.logLevel;
|
|
2829
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" : this.log.logLevel);
|
|
3416
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3417
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
2830
3418
|
}
|
|
2831
3419
|
res.json({ message: 'Command received' });
|
|
2832
3420
|
return;
|
|
2833
3421
|
}
|
|
3422
|
+
// Handle the command setmbloglevel from Settings
|
|
2834
3423
|
if (command === 'setmjloglevel') {
|
|
2835
3424
|
this.log.debug('Matter.js log level:', param);
|
|
2836
3425
|
if (param === 'Debug') {
|
|
@@ -2855,30 +3444,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
2855
3444
|
res.json({ message: 'Command received' });
|
|
2856
3445
|
return;
|
|
2857
3446
|
}
|
|
3447
|
+
// Handle the command setmdnsinterface from Settings
|
|
2858
3448
|
if (command === 'setmdnsinterface') {
|
|
2859
|
-
param = param.slice(1, -1);
|
|
3449
|
+
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
2860
3450
|
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
2861
3451
|
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
2862
3452
|
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
2863
3453
|
res.json({ message: 'Command received' });
|
|
2864
3454
|
return;
|
|
2865
3455
|
}
|
|
3456
|
+
// Handle the command setipv4address from Settings
|
|
2866
3457
|
if (command === 'setipv4address') {
|
|
2867
|
-
param = param.slice(1, -1);
|
|
3458
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2868
3459
|
this.matterbridgeInformation.matteripv4address = param;
|
|
2869
3460
|
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
2870
3461
|
await this.nodeContext?.set('matteripv4address', param);
|
|
2871
3462
|
res.json({ message: 'Command received' });
|
|
2872
3463
|
return;
|
|
2873
3464
|
}
|
|
3465
|
+
// Handle the command setipv6address from Settings
|
|
2874
3466
|
if (command === 'setipv6address') {
|
|
2875
|
-
param = param.slice(1, -1);
|
|
3467
|
+
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
2876
3468
|
this.matterbridgeInformation.matteripv6address = param;
|
|
2877
3469
|
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
2878
3470
|
await this.nodeContext?.set('matteripv6address', param);
|
|
2879
3471
|
res.json({ message: 'Command received' });
|
|
2880
3472
|
return;
|
|
2881
3473
|
}
|
|
3474
|
+
// Handle the command setmatterport from Settings
|
|
2882
3475
|
if (command === 'setmatterport') {
|
|
2883
3476
|
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
2884
3477
|
this.matterbridgeInformation.matterPort = port;
|
|
@@ -2887,6 +3480,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2887
3480
|
res.json({ message: 'Command received' });
|
|
2888
3481
|
return;
|
|
2889
3482
|
}
|
|
3483
|
+
// Handle the command setmatterdiscriminator from Settings
|
|
2890
3484
|
if (command === 'setmatterdiscriminator') {
|
|
2891
3485
|
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
2892
3486
|
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
@@ -2895,6 +3489,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2895
3489
|
res.json({ message: 'Command received' });
|
|
2896
3490
|
return;
|
|
2897
3491
|
}
|
|
3492
|
+
// Handle the command setmatterpasscode from Settings
|
|
2898
3493
|
if (command === 'setmatterpasscode') {
|
|
2899
3494
|
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
2900
3495
|
this.matterbridgeInformation.matterPasscode = passcode;
|
|
@@ -2903,17 +3498,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2903
3498
|
res.json({ message: 'Command received' });
|
|
2904
3499
|
return;
|
|
2905
3500
|
}
|
|
3501
|
+
// Handle the command setmbloglevel from Settings
|
|
2906
3502
|
if (command === 'setmblogfile') {
|
|
2907
3503
|
this.log.debug('Matterbridge file log:', param);
|
|
2908
3504
|
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
2909
3505
|
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3506
|
+
// Create the file logger for matterbridge
|
|
2910
3507
|
if (param === 'true')
|
|
2911
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug"
|
|
3508
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
2912
3509
|
else
|
|
2913
3510
|
AnsiLogger.setGlobalLogfile(undefined);
|
|
2914
3511
|
res.json({ message: 'Command received' });
|
|
2915
3512
|
return;
|
|
2916
3513
|
}
|
|
3514
|
+
// Handle the command setmbloglevel from Settings
|
|
2917
3515
|
if (command === 'setmjlogfile') {
|
|
2918
3516
|
this.log.debug('Matter file log:', param);
|
|
2919
3517
|
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
@@ -2940,36 +3538,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
2940
3538
|
res.json({ message: 'Command received' });
|
|
2941
3539
|
return;
|
|
2942
3540
|
}
|
|
3541
|
+
// Handle the command unregister from Settings
|
|
2943
3542
|
if (command === 'unregister') {
|
|
2944
3543
|
await this.unregisterAndShutdownProcess();
|
|
2945
3544
|
res.json({ message: 'Command received' });
|
|
2946
3545
|
return;
|
|
2947
3546
|
}
|
|
3547
|
+
// Handle the command reset from Settings
|
|
2948
3548
|
if (command === 'reset') {
|
|
2949
3549
|
await this.shutdownProcessAndReset();
|
|
2950
3550
|
res.json({ message: 'Command received' });
|
|
2951
3551
|
return;
|
|
2952
3552
|
}
|
|
3553
|
+
// Handle the command factoryreset from Settings
|
|
2953
3554
|
if (command === 'factoryreset') {
|
|
2954
3555
|
await this.shutdownProcessAndFactoryReset();
|
|
2955
3556
|
res.json({ message: 'Command received' });
|
|
2956
3557
|
return;
|
|
2957
3558
|
}
|
|
3559
|
+
// Handle the command shutdown from Header
|
|
2958
3560
|
if (command === 'shutdown') {
|
|
2959
3561
|
await this.shutdownProcess();
|
|
2960
3562
|
res.json({ message: 'Command received' });
|
|
2961
3563
|
return;
|
|
2962
3564
|
}
|
|
3565
|
+
// Handle the command restart from Header
|
|
2963
3566
|
if (command === 'restart') {
|
|
2964
3567
|
await this.restartProcess();
|
|
2965
3568
|
res.json({ message: 'Command received' });
|
|
2966
3569
|
return;
|
|
2967
3570
|
}
|
|
3571
|
+
// Handle the command update from Header
|
|
2968
3572
|
if (command === 'update') {
|
|
2969
3573
|
this.log.info('Updating matterbridge...');
|
|
2970
3574
|
try {
|
|
2971
3575
|
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
2972
3576
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3577
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2973
3578
|
}
|
|
2974
3579
|
catch (error) {
|
|
2975
3580
|
this.log.error('Error updating matterbridge');
|
|
@@ -2979,9 +3584,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2979
3584
|
res.json({ message: 'Command received' });
|
|
2980
3585
|
return;
|
|
2981
3586
|
}
|
|
3587
|
+
// Handle the command saveconfig from Home
|
|
2982
3588
|
if (command === 'saveconfig') {
|
|
2983
3589
|
param = param.replace(/\*/g, '\\');
|
|
2984
3590
|
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3591
|
+
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
2985
3592
|
if (!this.plugins.has(param)) {
|
|
2986
3593
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
2987
3594
|
}
|
|
@@ -2995,33 +3602,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2995
3602
|
res.json({ message: 'Command received' });
|
|
2996
3603
|
return;
|
|
2997
3604
|
}
|
|
3605
|
+
// Handle the command installplugin from Home
|
|
2998
3606
|
if (command === 'installplugin') {
|
|
2999
3607
|
param = param.replace(/\*/g, '\\');
|
|
3000
3608
|
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
3001
3609
|
try {
|
|
3002
3610
|
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
3003
3611
|
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3612
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3004
3613
|
}
|
|
3005
3614
|
catch (error) {
|
|
3006
3615
|
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
3007
3616
|
}
|
|
3008
3617
|
this.wssSendRestartRequired();
|
|
3009
3618
|
param = param.split('@')[0];
|
|
3619
|
+
// Also add the plugin to matterbridge so no return!
|
|
3010
3620
|
if (param === 'matterbridge') {
|
|
3621
|
+
// 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
|
|
3011
3622
|
res.json({ message: 'Command received' });
|
|
3012
3623
|
return;
|
|
3013
3624
|
}
|
|
3014
3625
|
}
|
|
3626
|
+
// Handle the command addplugin from Home
|
|
3015
3627
|
if (command === 'addplugin' || command === 'installplugin') {
|
|
3016
3628
|
param = param.replace(/\*/g, '\\');
|
|
3017
3629
|
const plugin = await this.plugins.add(param);
|
|
3018
3630
|
if (plugin) {
|
|
3019
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true);
|
|
3631
|
+
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
3020
3632
|
}
|
|
3021
3633
|
res.json({ message: 'Command received' });
|
|
3022
3634
|
this.wssSendRefreshRequired();
|
|
3023
3635
|
return;
|
|
3024
3636
|
}
|
|
3637
|
+
// Handle the command removeplugin from Home
|
|
3025
3638
|
if (command === 'removeplugin') {
|
|
3026
3639
|
if (!this.plugins.has(param)) {
|
|
3027
3640
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3035,6 +3648,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3035
3648
|
this.wssSendRefreshRequired();
|
|
3036
3649
|
return;
|
|
3037
3650
|
}
|
|
3651
|
+
// Handle the command enableplugin from Home
|
|
3038
3652
|
if (command === 'enableplugin') {
|
|
3039
3653
|
if (!this.plugins.has(param)) {
|
|
3040
3654
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3052,13 +3666,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
3052
3666
|
plugin.registeredDevices = undefined;
|
|
3053
3667
|
plugin.addedDevices = undefined;
|
|
3054
3668
|
await this.plugins.enable(param);
|
|
3055
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true);
|
|
3669
|
+
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
3056
3670
|
}
|
|
3057
3671
|
}
|
|
3058
3672
|
res.json({ message: 'Command received' });
|
|
3059
3673
|
this.wssSendRefreshRequired();
|
|
3060
3674
|
return;
|
|
3061
3675
|
}
|
|
3676
|
+
// Handle the command disableplugin from Home
|
|
3062
3677
|
if (command === 'disableplugin') {
|
|
3063
3678
|
if (!this.plugins.has(param)) {
|
|
3064
3679
|
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
@@ -3075,6 +3690,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3075
3690
|
return;
|
|
3076
3691
|
}
|
|
3077
3692
|
});
|
|
3693
|
+
// Fallback for routing (must be the last route)
|
|
3078
3694
|
this.expressApp.get('*', (req, res) => {
|
|
3079
3695
|
this.log.debug('The frontend sent:', req.url);
|
|
3080
3696
|
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
@@ -3082,6 +3698,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3082
3698
|
});
|
|
3083
3699
|
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
3084
3700
|
}
|
|
3701
|
+
/**
|
|
3702
|
+
* Retrieves the cluster text description from a given device.
|
|
3703
|
+
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3704
|
+
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3705
|
+
*/
|
|
3085
3706
|
getClusterTextFromDevice(device) {
|
|
3086
3707
|
const stringifyUserLabel = (endpoint) => {
|
|
3087
3708
|
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
@@ -3104,9 +3725,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
3104
3725
|
return '';
|
|
3105
3726
|
};
|
|
3106
3727
|
let attributes = '';
|
|
3728
|
+
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3107
3729
|
const clusterServers = device.getAllClusterServers();
|
|
3108
3730
|
clusterServers.forEach((clusterServer) => {
|
|
3109
3731
|
try {
|
|
3732
|
+
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3110
3733
|
if (clusterServer.name === 'OnOff')
|
|
3111
3734
|
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
3112
3735
|
if (clusterServer.name === 'Switch')
|
|
@@ -3157,18 +3780,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
3157
3780
|
attributes += `${stringifyFixedLabel(device)} `;
|
|
3158
3781
|
if (clusterServer.name === 'UserLabel')
|
|
3159
3782
|
attributes += `${stringifyUserLabel(device)} `;
|
|
3783
|
+
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3160
3784
|
}
|
|
3161
3785
|
catch (error) {
|
|
3162
3786
|
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3163
3787
|
}
|
|
3164
3788
|
});
|
|
3789
|
+
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3165
3790
|
return attributes;
|
|
3166
3791
|
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3794
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3795
|
+
*
|
|
3796
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3797
|
+
*/
|
|
3167
3798
|
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3799
|
+
// Set the bridge mode
|
|
3168
3800
|
this.bridgeMode = 'bridge';
|
|
3801
|
+
// Set the first port to use
|
|
3169
3802
|
this.port = port;
|
|
3170
|
-
|
|
3803
|
+
// Set Matterbridge logger
|
|
3804
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3171
3805
|
this.log.debug('Matterbridge extension is starting...');
|
|
3806
|
+
// Initialize NodeStorage
|
|
3172
3807
|
this.matterbridgeDirectory = dataPath;
|
|
3173
3808
|
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3174
3809
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
@@ -3187,10 +3822,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
3187
3822
|
};
|
|
3188
3823
|
this.plugins.set(plugin);
|
|
3189
3824
|
this.plugins.saveToStorage();
|
|
3825
|
+
// Log system info and create .matterbridge directory
|
|
3190
3826
|
await this.logNodeAndSystemInfo();
|
|
3191
3827
|
this.matterbridgeDirectory = dataPath;
|
|
3828
|
+
// Set matter.js logger level and format
|
|
3192
3829
|
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3193
3830
|
Logger.format = MatterLogFormat.ANSI;
|
|
3831
|
+
// Start the storage and create matterbridgeContext
|
|
3194
3832
|
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3195
3833
|
if (!this.storageManager)
|
|
3196
3834
|
return false;
|
|
@@ -3200,7 +3838,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3200
3838
|
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3201
3839
|
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3202
3840
|
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3203
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion);
|
|
3841
|
+
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
3204
3842
|
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
3205
3843
|
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3206
3844
|
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
@@ -3213,6 +3851,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
3213
3851
|
await this.startMatterServer();
|
|
3214
3852
|
this.log.info('Matter server started');
|
|
3215
3853
|
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3854
|
+
// Set reachability to true and trigger event after 60 seconds
|
|
3216
3855
|
setTimeout(() => {
|
|
3217
3856
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3218
3857
|
if (this.commissioningServer)
|
|
@@ -3222,14 +3861,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
3222
3861
|
}, 60 * 1000);
|
|
3223
3862
|
return this.commissioningServer.isCommissioned();
|
|
3224
3863
|
}
|
|
3864
|
+
/**
|
|
3865
|
+
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3866
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3867
|
+
*
|
|
3868
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
3869
|
+
*/
|
|
3225
3870
|
async stopExtension() {
|
|
3871
|
+
// Closing matter
|
|
3226
3872
|
await this.stopMatterServer();
|
|
3873
|
+
// Clearing the session manager
|
|
3874
|
+
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3875
|
+
// Closing storage
|
|
3227
3876
|
await this.stopMatterStorage();
|
|
3228
3877
|
this.log.info('Matter server stopped');
|
|
3229
3878
|
}
|
|
3879
|
+
/**
|
|
3880
|
+
* Checks if the extension is commissioned.
|
|
3881
|
+
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3882
|
+
*
|
|
3883
|
+
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3884
|
+
*/
|
|
3230
3885
|
isExtensionCommissioned() {
|
|
3231
3886
|
if (!this.commissioningServer)
|
|
3232
3887
|
return false;
|
|
3233
3888
|
return this.commissioningServer.isCommissioned();
|
|
3234
3889
|
}
|
|
3235
3890
|
}
|
|
3891
|
+
//# sourceMappingURL=matterbridge.js.map
|