matterbridge 1.7.2 → 2.0.0-edge1
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 +35 -1
- package/dist/cli.js +3 -13
- package/dist/cli.js.map +1 -1
- package/dist/deviceManager.d.ts +7 -7
- package/dist/deviceManager.d.ts.map +1 -1
- package/dist/deviceManager.js +2 -2
- package/dist/deviceManager.js.map +1 -1
- package/dist/frontend.d.ts +98 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +1377 -0
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/matter/export.d.ts.map +1 -1
- package/dist/matter/export.js +0 -1
- package/dist/matter/export.js.map +1 -1
- package/dist/matterbridge.d.ts +82 -208
- package/dist/matterbridge.d.ts.map +1 -1
- package/dist/matterbridge.js +777 -2310
- package/dist/matterbridge.js.map +1 -1
- package/dist/matterbridgeBehaviors.d.ts +32 -851
- package/dist/matterbridgeBehaviors.d.ts.map +1 -1
- package/dist/matterbridgeBehaviors.js +22 -2
- package/dist/matterbridgeBehaviors.js.map +1 -1
- package/dist/matterbridgeEndpoint.d.ts +100 -9089
- package/dist/matterbridgeEndpoint.d.ts.map +1 -1
- package/dist/matterbridgeEndpoint.js +59 -31
- package/dist/matterbridgeEndpoint.js.map +1 -1
- package/dist/matterbridgePlatform.d.ts +14 -28
- package/dist/matterbridgePlatform.d.ts.map +1 -1
- package/dist/matterbridgePlatform.js +7 -23
- package/dist/matterbridgePlatform.js.map +1 -1
- package/dist/matterbridgeTypes.d.ts.map +1 -1
- package/dist/matterbridgeTypes.js +4 -0
- package/dist/matterbridgeTypes.js.map +1 -1
- package/dist/pluginManager.d.ts +1 -1
- package/dist/pluginManager.d.ts.map +1 -1
- package/dist/pluginManager.js +5 -11
- package/dist/pluginManager.js.map +1 -1
- package/dist/utils/utils.d.ts +1 -1
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +6 -6
- package/dist/utils/utils.js.map +1 -1
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.08241820.js → main.ea28015b.js} +3 -3
- package/frontend/build/static/js/main.ea28015b.js.map +1 -0
- package/npm-shrinkwrap.json +9 -9
- package/package.json +2 -3
- package/dist/cli.d.ts +0 -25
- package/dist/cluster/export.d.ts +0 -2
- package/dist/defaultConfigSchema.d.ts +0 -27
- package/dist/index.d.ts +0 -40
- package/dist/logger/export.d.ts +0 -2
- package/dist/matter/export.d.ts +0 -11
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
- package/dist/matterbridgeDevice.d.ts +0 -7077
- package/dist/matterbridgeDevice.d.ts.map +0 -1
- package/dist/matterbridgeDevice.js +0 -2736
- package/dist/matterbridgeDevice.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
- package/dist/matterbridgeEdge.d.ts +0 -91
- package/dist/matterbridgeEdge.d.ts.map +0 -1
- package/dist/matterbridgeEdge.js +0 -1077
- package/dist/matterbridgeEdge.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -172
- package/dist/matterbridgeWebsocket.d.ts +0 -49
- package/dist/matterbridgeWebsocket.d.ts.map +0 -1
- package/dist/matterbridgeWebsocket.js +0 -325
- package/dist/matterbridgeWebsocket.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/utils/colorUtils.d.ts +0 -61
- package/dist/utils/export.d.ts +0 -3
- package/frontend/build/static/js/main.08241820.js.map +0 -1
- /package/frontend/build/static/js/{main.08241820.js.LICENSE.txt → main.ea28015b.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -24,34 +24,24 @@
|
|
|
24
24
|
import { fileURLToPath } from 'url';
|
|
25
25
|
import { promises as fs } from 'fs';
|
|
26
26
|
import { exec, spawn } from 'child_process';
|
|
27
|
-
import { createServer } from 'http';
|
|
28
27
|
import EventEmitter from 'events';
|
|
29
28
|
import os from 'os';
|
|
30
29
|
import path from 'path';
|
|
31
30
|
import { randomBytes } from 'crypto';
|
|
32
|
-
// Package modules
|
|
33
|
-
import https from 'https';
|
|
34
|
-
import express from 'express';
|
|
35
|
-
import WebSocket, { WebSocketServer } from 'ws';
|
|
36
31
|
// NodeStorage and AnsiLogger modules
|
|
37
32
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
38
|
-
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify,
|
|
33
|
+
import { AnsiLogger, TimestampFormat, LogLevel, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
|
|
39
34
|
// Matterbridge
|
|
40
|
-
import {
|
|
41
|
-
import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
|
|
42
|
-
import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
35
|
+
import { logInterfaces, wait, waiter, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
43
36
|
import { PluginManager } from './pluginManager.js';
|
|
44
37
|
import { DeviceManager } from './deviceManager.js';
|
|
45
38
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
39
|
+
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
46
40
|
// @matter
|
|
47
|
-
import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId,
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
// @project-chip
|
|
52
|
-
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
|
|
53
|
-
import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
|
|
54
|
-
import { aggregator } from './matterbridgeDeviceTypes.js';
|
|
41
|
+
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
42
|
+
import { FabricAction, PaseClient } from '@matter/main/protocol';
|
|
43
|
+
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
44
|
+
import { Frontend } from './frontend.js';
|
|
55
45
|
// Default colors
|
|
56
46
|
const plg = '\u001B[38;5;33m';
|
|
57
47
|
const dev = '\u001B[38;5;79m';
|
|
@@ -92,10 +82,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
92
82
|
matterbridgeConnected: false,
|
|
93
83
|
bridgeMode: '',
|
|
94
84
|
restartMode: '',
|
|
95
|
-
edge: hasParameter('edge'),
|
|
96
85
|
readOnly: hasParameter('readonly'),
|
|
97
86
|
profile: getParameter('profile'),
|
|
98
|
-
loggerLevel:
|
|
87
|
+
loggerLevel: LogLevel.INFO,
|
|
99
88
|
fileLogger: false,
|
|
100
89
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
101
90
|
matterFileLogger: false,
|
|
@@ -124,15 +113,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
124
113
|
bridgeMode = '';
|
|
125
114
|
restartMode = '';
|
|
126
115
|
profile = getParameter('profile');
|
|
127
|
-
edge =
|
|
116
|
+
edge = true;
|
|
128
117
|
log;
|
|
129
118
|
matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
130
119
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
131
120
|
plugins;
|
|
132
121
|
devices;
|
|
122
|
+
frontend = new Frontend(this);
|
|
123
|
+
// Matterbridge storage
|
|
133
124
|
nodeStorage;
|
|
134
125
|
nodeContext;
|
|
135
|
-
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
136
126
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
127
|
// Cleanup
|
|
138
128
|
hasCleanupStarted = false;
|
|
@@ -146,37 +136,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
146
136
|
sigtermHandler;
|
|
147
137
|
exceptionHandler;
|
|
148
138
|
rejectionHandler;
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
139
|
+
// Matter environment
|
|
140
|
+
environment = Environment.default;
|
|
141
|
+
// Matter storage
|
|
142
|
+
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
143
|
+
matterStorageService;
|
|
144
|
+
matterStorageManager;
|
|
145
|
+
matterbridgeContext;
|
|
146
|
+
mattercontrollerContext;
|
|
147
|
+
// Matter parameters
|
|
155
148
|
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
156
149
|
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
157
150
|
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
158
|
-
port
|
|
151
|
+
port; // first commissioning server port
|
|
159
152
|
passcode; // first commissioning server passcode
|
|
160
153
|
discriminator; // first commissioning server discriminator
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
mattercontrollerContext;
|
|
164
|
-
matterServer;
|
|
165
|
-
matterAggregator;
|
|
166
|
-
commissioningServer;
|
|
167
|
-
commissioningController;
|
|
154
|
+
serverNode;
|
|
155
|
+
aggregatorNode;
|
|
168
156
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
169
157
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
170
158
|
static instance;
|
|
171
159
|
// We load asyncronously so is private
|
|
172
160
|
constructor() {
|
|
173
161
|
super();
|
|
174
|
-
// Bind the handler to the instance
|
|
175
|
-
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
176
162
|
}
|
|
177
163
|
/**
|
|
178
164
|
* Retrieves the list of Matterbridge devices.
|
|
179
|
-
* @returns {
|
|
165
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
180
166
|
*/
|
|
181
167
|
getDevices() {
|
|
182
168
|
return this.devices.array();
|
|
@@ -188,7 +174,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
188
174
|
getPlugins() {
|
|
189
175
|
return this.plugins.array();
|
|
190
176
|
}
|
|
191
|
-
matterbridgeMessageHandler;
|
|
192
177
|
/** ***********************************************************************************************************************************/
|
|
193
178
|
/** loadInstance() and cleanup() methods */
|
|
194
179
|
/** ***********************************************************************************************************************************/
|
|
@@ -241,8 +226,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
241
226
|
// Set the matterbridge directory
|
|
242
227
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
243
228
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
229
|
+
// Setup matter environment
|
|
230
|
+
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
231
|
+
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
232
|
+
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
233
|
+
this.environment.vars.set('runtime.signals', false);
|
|
234
|
+
this.environment.vars.set('runtime.exitcode', false);
|
|
244
235
|
// Create matterbridge logger
|
|
245
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat:
|
|
236
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: TimestampFormat.TIME_MILLIS, logLevel: hasParameter('debug') ? LogLevel.DEBUG : LogLevel.INFO });
|
|
237
|
+
// Register process handlers
|
|
238
|
+
this.registerProcessHandlers();
|
|
246
239
|
// Initialize nodeStorage and nodeContext
|
|
247
240
|
try {
|
|
248
241
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
@@ -293,40 +286,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
293
286
|
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
294
287
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
295
288
|
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
296
|
-
this.passcode =
|
|
289
|
+
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
297
290
|
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
298
|
-
this.discriminator =
|
|
291
|
+
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
299
292
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
300
293
|
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
301
294
|
if (hasParameter('logger')) {
|
|
302
295
|
const level = getParameter('logger');
|
|
303
296
|
if (level === 'debug') {
|
|
304
|
-
this.log.logLevel =
|
|
297
|
+
this.log.logLevel = LogLevel.DEBUG;
|
|
305
298
|
}
|
|
306
299
|
else if (level === 'info') {
|
|
307
|
-
this.log.logLevel =
|
|
300
|
+
this.log.logLevel = LogLevel.INFO;
|
|
308
301
|
}
|
|
309
302
|
else if (level === 'notice') {
|
|
310
|
-
this.log.logLevel =
|
|
303
|
+
this.log.logLevel = LogLevel.NOTICE;
|
|
311
304
|
}
|
|
312
305
|
else if (level === 'warn') {
|
|
313
|
-
this.log.logLevel =
|
|
306
|
+
this.log.logLevel = LogLevel.WARN;
|
|
314
307
|
}
|
|
315
308
|
else if (level === 'error') {
|
|
316
|
-
this.log.logLevel =
|
|
309
|
+
this.log.logLevel = LogLevel.ERROR;
|
|
317
310
|
}
|
|
318
311
|
else if (level === 'fatal') {
|
|
319
|
-
this.log.logLevel =
|
|
312
|
+
this.log.logLevel = LogLevel.FATAL;
|
|
320
313
|
}
|
|
321
314
|
else {
|
|
322
315
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
323
|
-
this.log.logLevel =
|
|
316
|
+
this.log.logLevel = LogLevel.INFO;
|
|
324
317
|
}
|
|
325
318
|
}
|
|
326
319
|
else {
|
|
327
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel',
|
|
320
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', LogLevel.INFO);
|
|
328
321
|
}
|
|
329
|
-
|
|
322
|
+
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
330
323
|
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
331
324
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
332
325
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
@@ -374,21 +367,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
374
367
|
});
|
|
375
368
|
}
|
|
376
369
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
-
// Set the interface to use for
|
|
370
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
378
371
|
if (hasParameter('mdnsinterface')) {
|
|
379
372
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
380
373
|
}
|
|
381
374
|
else {
|
|
382
|
-
this.mdnsInterface = await this.nodeContext
|
|
375
|
+
this.mdnsInterface = await this.nodeContext.get('mattermdnsinterface', undefined);
|
|
383
376
|
if (this.mdnsInterface === '')
|
|
384
377
|
this.mdnsInterface = undefined;
|
|
385
378
|
}
|
|
379
|
+
// Validate mdnsInterface
|
|
380
|
+
if (this.mdnsInterface) {
|
|
381
|
+
const networkInterfaces = os.networkInterfaces();
|
|
382
|
+
const availableInterfaces = Object.keys(networkInterfaces);
|
|
383
|
+
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
384
|
+
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
385
|
+
this.mdnsInterface = undefined;
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (this.mdnsInterface)
|
|
392
|
+
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
386
393
|
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
387
394
|
if (hasParameter('ipv4address')) {
|
|
388
395
|
this.ipv4address = getParameter('ipv4address');
|
|
389
396
|
}
|
|
390
397
|
else {
|
|
391
|
-
this.ipv4address = await this.nodeContext
|
|
398
|
+
this.ipv4address = await this.nodeContext.get('matteripv4address', undefined);
|
|
392
399
|
if (this.ipv4address === '')
|
|
393
400
|
this.ipv4address = undefined;
|
|
394
401
|
}
|
|
@@ -409,20 +416,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
409
416
|
// Get the plugins from node storage and create the plugins node storage contexts
|
|
410
417
|
for (const plugin of this.plugins) {
|
|
411
418
|
const packageJson = await this.plugins.parse(plugin);
|
|
412
|
-
if (packageJson === null && !hasParameter('add')) {
|
|
419
|
+
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
413
420
|
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
414
|
-
// We don't do this when the add
|
|
421
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
415
422
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
416
423
|
try {
|
|
417
424
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
418
425
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
419
426
|
plugin.error = false;
|
|
420
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
421
427
|
}
|
|
422
428
|
catch (error) {
|
|
423
429
|
plugin.error = true;
|
|
424
430
|
plugin.enabled = false;
|
|
425
|
-
this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled
|
|
431
|
+
this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`, error instanceof Error ? error.message : error);
|
|
426
432
|
}
|
|
427
433
|
}
|
|
428
434
|
this.log.debug(`Creating node storage context for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -450,8 +456,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
450
456
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
451
457
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
452
458
|
}
|
|
453
|
-
// Register process handlers
|
|
454
|
-
this.registerProcessHandlers();
|
|
455
459
|
// Parse command line
|
|
456
460
|
await this.parseCommandLine();
|
|
457
461
|
this.initialized = true;
|
|
@@ -567,75 +571,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
567
571
|
return;
|
|
568
572
|
}
|
|
569
573
|
if (hasParameter('factoryreset')) {
|
|
570
|
-
|
|
571
|
-
// Delete old matter storage file
|
|
572
|
-
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
573
|
-
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
574
|
-
await fs.unlink(file);
|
|
575
|
-
}
|
|
576
|
-
catch (err) {
|
|
577
|
-
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
578
|
-
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
try {
|
|
582
|
-
// Delete matter node storage directory with its subdirectories
|
|
583
|
-
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
584
|
-
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
585
|
-
await fs.rm(dir, { recursive: true });
|
|
586
|
-
}
|
|
587
|
-
catch (err) {
|
|
588
|
-
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
589
|
-
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
try {
|
|
593
|
-
// Delete node storage directory with its subdirectories
|
|
594
|
-
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
595
|
-
this.log.info(`Removing storage directory: ${dir}`);
|
|
596
|
-
await fs.rm(dir, { recursive: true });
|
|
597
|
-
}
|
|
598
|
-
catch (err) {
|
|
599
|
-
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
600
|
-
this.log.error(`Error removing storage directory: ${err}`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
604
|
-
this.nodeContext = undefined;
|
|
605
|
-
this.nodeStorage = undefined;
|
|
606
|
-
this.plugins.clear();
|
|
607
|
-
this.devices.clear();
|
|
608
|
-
this.emit('shutdown');
|
|
574
|
+
await this.shutdownProcessAndFactoryReset();
|
|
609
575
|
return;
|
|
610
576
|
}
|
|
611
577
|
// Start the matter storage and create the matterbridge context
|
|
612
578
|
try {
|
|
613
|
-
await this.startMatterStorage(
|
|
579
|
+
await this.startMatterStorage();
|
|
614
580
|
}
|
|
615
581
|
catch (error) {
|
|
616
582
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
617
583
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
618
584
|
}
|
|
619
585
|
// Clear the matterbridge context if the reset parameter is set
|
|
620
|
-
if (
|
|
621
|
-
this.
|
|
622
|
-
await this.matterbridgeContext?.clearAll();
|
|
623
|
-
await this.stopMatterStorage();
|
|
624
|
-
this.log.info('Reset done! Remove the device from the controller.');
|
|
625
|
-
this.emit('shutdown');
|
|
586
|
+
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
587
|
+
await this.shutdownProcessAndReset();
|
|
626
588
|
return;
|
|
627
589
|
}
|
|
628
590
|
// Clear matterbridge plugin context if the reset parameter is set
|
|
629
|
-
if (
|
|
591
|
+
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
630
592
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
631
593
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
632
594
|
if (plugin) {
|
|
633
|
-
|
|
595
|
+
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
596
|
+
if (!matterStorageManager)
|
|
634
597
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
await
|
|
598
|
+
await matterStorageManager?.createContext('events')?.clearAll();
|
|
599
|
+
await matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
600
|
+
await matterStorageManager?.createContext('root')?.clearAll();
|
|
601
|
+
await matterStorageManager?.createContext('sessions')?.clearAll();
|
|
602
|
+
await matterStorageManager?.createContext('persist')?.clearAll();
|
|
639
603
|
this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
|
|
640
604
|
}
|
|
641
605
|
else {
|
|
@@ -647,18 +611,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
647
611
|
}
|
|
648
612
|
// Initialize frontend
|
|
649
613
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
650
|
-
await this.
|
|
614
|
+
await this.frontend.start(getIntParameter('frontend'));
|
|
651
615
|
// Check each 60 minutes the latest versions
|
|
652
616
|
this.checkUpdateInterval = setInterval(() => {
|
|
653
617
|
this.getMatterbridgeLatestVersion();
|
|
654
618
|
for (const plugin of this.plugins) {
|
|
655
619
|
this.getPluginLatestVersion(plugin);
|
|
656
620
|
}
|
|
621
|
+
this.frontend.wssSendRefreshRequired();
|
|
657
622
|
}, 60 * 60 * 1000);
|
|
658
623
|
// Start the matterbridge in mode test
|
|
659
624
|
if (hasParameter('test')) {
|
|
660
625
|
this.bridgeMode = 'bridge';
|
|
661
|
-
|
|
626
|
+
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
662
627
|
return;
|
|
663
628
|
}
|
|
664
629
|
// Start the matterbridge in mode controller
|
|
@@ -675,7 +640,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
675
640
|
// Start matterbridge in bridge mode
|
|
676
641
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
677
642
|
this.bridgeMode = 'bridge';
|
|
678
|
-
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
679
643
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
680
644
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
681
645
|
await this.startBridge();
|
|
@@ -684,7 +648,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
684
648
|
// Start matterbridge in childbridge mode
|
|
685
649
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
686
650
|
this.bridgeMode = 'childbridge';
|
|
687
|
-
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
688
651
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
689
652
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
690
653
|
await this.startChildbridge();
|
|
@@ -695,7 +658,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
695
658
|
* Asynchronously loads and starts the registered plugins.
|
|
696
659
|
*
|
|
697
660
|
* This method is responsible for initializing and staarting all enabled plugins.
|
|
698
|
-
* It ensures that each plugin is properly loaded and started before the
|
|
661
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
699
662
|
*
|
|
700
663
|
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
701
664
|
*/
|
|
@@ -729,7 +692,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
729
692
|
plugin.manualPairingCode = undefined;
|
|
730
693
|
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
731
694
|
}
|
|
732
|
-
this.wssSendRefreshRequired();
|
|
695
|
+
this.frontend.wssSendRefreshRequired();
|
|
733
696
|
}
|
|
734
697
|
/**
|
|
735
698
|
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
@@ -784,9 +747,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
784
747
|
async logNodeAndSystemInfo() {
|
|
785
748
|
// IP address information
|
|
786
749
|
const networkInterfaces = os.networkInterfaces();
|
|
750
|
+
this.systemInformation.interfaceName = '';
|
|
787
751
|
this.systemInformation.ipv4Address = '';
|
|
788
752
|
this.systemInformation.ipv6Address = '';
|
|
789
753
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
754
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
755
|
+
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
756
|
+
continue;
|
|
790
757
|
if (!interfaceDetails) {
|
|
791
758
|
break;
|
|
792
759
|
}
|
|
@@ -802,7 +769,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
802
769
|
this.systemInformation.macAddress = detail.mac;
|
|
803
770
|
}
|
|
804
771
|
}
|
|
805
|
-
if (this.systemInformation.ipv4Address !== ''
|
|
772
|
+
if (this.systemInformation.ipv4Address !== '' || this.systemInformation.ipv6Address !== '') {
|
|
806
773
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
807
774
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
808
775
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -1033,32 +1000,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1033
1000
|
* @returns {Function} The MatterLogger function.
|
|
1034
1001
|
*/
|
|
1035
1002
|
createMatterLogger() {
|
|
1036
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat:
|
|
1003
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: TimestampFormat.TIME_MILLIS, logLevel: LogLevel.DEBUG });
|
|
1037
1004
|
return (_level, formattedLog) => {
|
|
1038
1005
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1039
1006
|
const message = formattedLog.slice(65);
|
|
1040
1007
|
matterLogger.logName = logger;
|
|
1041
1008
|
switch (_level) {
|
|
1042
1009
|
case MatterLogLevel.DEBUG:
|
|
1043
|
-
matterLogger.log(
|
|
1010
|
+
matterLogger.log(LogLevel.DEBUG, message);
|
|
1044
1011
|
break;
|
|
1045
1012
|
case MatterLogLevel.INFO:
|
|
1046
|
-
matterLogger.log(
|
|
1013
|
+
matterLogger.log(LogLevel.INFO, message);
|
|
1047
1014
|
break;
|
|
1048
1015
|
case MatterLogLevel.NOTICE:
|
|
1049
|
-
matterLogger.log(
|
|
1016
|
+
matterLogger.log(LogLevel.NOTICE, message);
|
|
1050
1017
|
break;
|
|
1051
1018
|
case MatterLogLevel.WARN:
|
|
1052
|
-
matterLogger.log(
|
|
1019
|
+
matterLogger.log(LogLevel.WARN, message);
|
|
1053
1020
|
break;
|
|
1054
1021
|
case MatterLogLevel.ERROR:
|
|
1055
|
-
matterLogger.log(
|
|
1022
|
+
matterLogger.log(LogLevel.ERROR, message);
|
|
1056
1023
|
break;
|
|
1057
1024
|
case MatterLogLevel.FATAL:
|
|
1058
|
-
matterLogger.log(
|
|
1025
|
+
matterLogger.log(LogLevel.FATAL, message);
|
|
1059
1026
|
break;
|
|
1060
1027
|
default:
|
|
1061
|
-
matterLogger.log(
|
|
1028
|
+
matterLogger.log(LogLevel.DEBUG, message);
|
|
1062
1029
|
break;
|
|
1063
1030
|
}
|
|
1064
1031
|
};
|
|
@@ -1121,13 +1088,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1121
1088
|
};
|
|
1122
1089
|
}
|
|
1123
1090
|
/**
|
|
1124
|
-
*
|
|
1125
|
-
*/
|
|
1126
|
-
async updateProcess() {
|
|
1127
|
-
await this.cleanup('updating...', false);
|
|
1128
|
-
}
|
|
1129
|
-
/**
|
|
1130
|
-
* Restarts the process by spawning a new process and exiting the current process.
|
|
1091
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1131
1092
|
*/
|
|
1132
1093
|
async restartProcess() {
|
|
1133
1094
|
await this.cleanup('restarting...', true);
|
|
@@ -1139,28 +1100,95 @@ export class Matterbridge extends EventEmitter {
|
|
|
1139
1100
|
await this.cleanup('shutting down...', false);
|
|
1140
1101
|
}
|
|
1141
1102
|
/**
|
|
1142
|
-
*
|
|
1103
|
+
* Update matterbridge and and shut down the process.
|
|
1104
|
+
*/
|
|
1105
|
+
async updateProcess() {
|
|
1106
|
+
this.log.info('Updating matterbridge...');
|
|
1107
|
+
try {
|
|
1108
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
1109
|
+
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
1110
|
+
}
|
|
1111
|
+
catch (error) {
|
|
1112
|
+
this.log.error('Error updating matterbridge:', error instanceof Error ? error.message : error);
|
|
1113
|
+
}
|
|
1114
|
+
this.frontend.wssSendRestartRequired();
|
|
1115
|
+
await this.cleanup('updating...', false);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Unregister all devices and shut down the process.
|
|
1143
1119
|
*/
|
|
1144
1120
|
async unregisterAndShutdownProcess() {
|
|
1145
1121
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1146
|
-
for (const plugin of this.plugins
|
|
1147
|
-
|
|
1148
|
-
await this.removeAllBridgedEndpoints(plugin.name);
|
|
1149
|
-
else
|
|
1150
|
-
await this.removeAllBridgedDevices(plugin.name);
|
|
1122
|
+
for (const plugin of this.plugins) {
|
|
1123
|
+
await this.removeAllBridgedEndpoints(plugin.name);
|
|
1151
1124
|
}
|
|
1152
1125
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1153
1126
|
}
|
|
1154
1127
|
/**
|
|
1155
|
-
*
|
|
1128
|
+
* Reset commissioning and shut down the process.
|
|
1156
1129
|
*/
|
|
1157
1130
|
async shutdownProcessAndReset() {
|
|
1131
|
+
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1132
|
+
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
1133
|
+
await this.matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
1134
|
+
await this.matterStorageManager?.createContext('root')?.clearAll();
|
|
1135
|
+
await this.matterStorageManager?.createContext('sessions')?.clearAll();
|
|
1136
|
+
await this.matterbridgeContext?.clearAll();
|
|
1137
|
+
await this.stopMatterStorage();
|
|
1138
|
+
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1158
1139
|
await this.cleanup('shutting down with reset...', false);
|
|
1159
1140
|
}
|
|
1160
1141
|
/**
|
|
1161
|
-
*
|
|
1142
|
+
* Factory reset and shut down the process.
|
|
1162
1143
|
*/
|
|
1163
1144
|
async shutdownProcessAndFactoryReset() {
|
|
1145
|
+
try {
|
|
1146
|
+
// Delete old matter storage file and backup
|
|
1147
|
+
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1148
|
+
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1149
|
+
await fs.unlink(file);
|
|
1150
|
+
const backup = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup.json');
|
|
1151
|
+
this.log.info(`Unlinking old matter storage backup file: ${backup}`);
|
|
1152
|
+
await fs.unlink(backup);
|
|
1153
|
+
}
|
|
1154
|
+
catch (err) {
|
|
1155
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
1156
|
+
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
try {
|
|
1160
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1161
|
+
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1162
|
+
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1163
|
+
await fs.rm(dir, { recursive: true });
|
|
1164
|
+
const backup = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
|
|
1165
|
+
this.log.info(`Removing matter node storage backup directory: ${backup}`);
|
|
1166
|
+
await fs.rm(backup, { recursive: true });
|
|
1167
|
+
}
|
|
1168
|
+
catch (err) {
|
|
1169
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
1170
|
+
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1175
|
+
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1176
|
+
this.log.info(`Removing storage directory: ${dir}`);
|
|
1177
|
+
await fs.rm(dir, { recursive: true });
|
|
1178
|
+
const backup = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
|
|
1179
|
+
this.log.info(`Removing storage backup directory: ${backup}`);
|
|
1180
|
+
await fs.rm(backup, { recursive: true });
|
|
1181
|
+
}
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
1184
|
+
this.log.error(`Error removing storage directory: ${err}`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1188
|
+
this.nodeContext = undefined;
|
|
1189
|
+
this.nodeStorage = undefined;
|
|
1190
|
+
this.plugins.clear();
|
|
1191
|
+
this.devices.clear();
|
|
1164
1192
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1165
1193
|
}
|
|
1166
1194
|
/**
|
|
@@ -1173,8 +1201,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1173
1201
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1174
1202
|
this.hasCleanupStarted = true;
|
|
1175
1203
|
this.log.info(message);
|
|
1176
|
-
// Deregisters the process handlers
|
|
1177
|
-
this.deregisterProcesslHandlers();
|
|
1178
1204
|
// Clear the start matter interval
|
|
1179
1205
|
if (this.startMatterInterval) {
|
|
1180
1206
|
clearInterval(this.startMatterInterval);
|
|
@@ -1199,7 +1225,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1199
1225
|
this.reachabilityTimeout = undefined;
|
|
1200
1226
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1201
1227
|
}
|
|
1202
|
-
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
1228
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1203
1229
|
for (const plugin of this.plugins) {
|
|
1204
1230
|
if (!plugin.enabled || plugin.error)
|
|
1205
1231
|
continue;
|
|
@@ -1210,60 +1236,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
1210
1236
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1211
1237
|
}
|
|
1212
1238
|
}
|
|
1213
|
-
//
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
await this.convertStorage(plugin.storageContext, plugin.name);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1239
|
+
// Stop the frontend
|
|
1240
|
+
this.frontend.stop();
|
|
1241
|
+
// Stopping matter server nodes
|
|
1242
|
+
this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1243
|
+
if (this.bridgeMode === 'bridge') {
|
|
1244
|
+
if (this.serverNode) {
|
|
1245
|
+
await this.stopServerNode(this.serverNode);
|
|
1246
|
+
this.log.info(`Stopped matter server node for Matterbridge`);
|
|
1224
1247
|
}
|
|
1225
1248
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
this.log.debug('Frontend http server closed successfully');
|
|
1232
|
-
}
|
|
1233
|
-
// Close the https server
|
|
1234
|
-
if (this.httpsServer) {
|
|
1235
|
-
this.httpsServer.close();
|
|
1236
|
-
this.httpsServer.removeAllListeners();
|
|
1237
|
-
this.httpsServer = undefined;
|
|
1238
|
-
this.log.debug('Frontend https server closed successfully');
|
|
1239
|
-
}
|
|
1240
|
-
// Remove listeners from the express app
|
|
1241
|
-
if (this.expressApp) {
|
|
1242
|
-
this.expressApp.removeAllListeners();
|
|
1243
|
-
this.expressApp = undefined;
|
|
1244
|
-
this.log.debug('Frontend app closed successfully');
|
|
1245
|
-
}
|
|
1246
|
-
// Close the WebSocket server
|
|
1247
|
-
if (this.webSocketServer) {
|
|
1248
|
-
// Close all active connections
|
|
1249
|
-
this.webSocketServer.clients.forEach((client) => {
|
|
1250
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
1251
|
-
client.close();
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
this.webSocketServer.close((error) => {
|
|
1255
|
-
if (error) {
|
|
1256
|
-
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
1257
|
-
}
|
|
1258
|
-
else {
|
|
1259
|
-
this.log.debug('WebSocket server closed successfully');
|
|
1249
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1250
|
+
for (const plugin of this.plugins.array()) {
|
|
1251
|
+
if (plugin.serverNode) {
|
|
1252
|
+
await this.stopServerNode(plugin.serverNode);
|
|
1253
|
+
this.log.info(`Stopped matter server node for ${plugin.name}`);
|
|
1260
1254
|
}
|
|
1261
|
-
}
|
|
1262
|
-
this.webSocketServer = undefined;
|
|
1255
|
+
}
|
|
1263
1256
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
//
|
|
1257
|
+
this.log.info('Stopped matter server nodes');
|
|
1258
|
+
// Stop matter MdnsService
|
|
1259
|
+
// await this.environment.get(MdnsService)[Symbol.asyncDispose]();
|
|
1260
|
+
// this.log.info('Stopped MdnsService');
|
|
1261
|
+
// Stop matter storage
|
|
1267
1262
|
await this.stopMatterStorage();
|
|
1268
1263
|
// Remove the matterfilelogger
|
|
1269
1264
|
try {
|
|
@@ -1306,11 +1301,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1306
1301
|
}
|
|
1307
1302
|
this.plugins.clear();
|
|
1308
1303
|
this.devices.clear();
|
|
1304
|
+
// Deregisters the process handlers
|
|
1305
|
+
this.deregisterProcesslHandlers();
|
|
1309
1306
|
if (restart) {
|
|
1310
1307
|
if (message === 'updating...') {
|
|
1311
1308
|
this.log.info('Cleanup completed. Updating...');
|
|
1312
1309
|
Matterbridge.instance = undefined;
|
|
1313
|
-
this.emit('update');
|
|
1310
|
+
this.emit('update'); // Restart the process but the update has been done before
|
|
1314
1311
|
}
|
|
1315
1312
|
else if (message === 'restarting...') {
|
|
1316
1313
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1319,38 +1316,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1319
1316
|
}
|
|
1320
1317
|
}
|
|
1321
1318
|
else {
|
|
1322
|
-
if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
|
|
1323
|
-
try {
|
|
1324
|
-
// Delete old matter storage file
|
|
1325
|
-
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1326
|
-
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1327
|
-
await fs.unlink(file);
|
|
1328
|
-
}
|
|
1329
|
-
catch (error) {
|
|
1330
|
-
this.log.debug(`Error resetting old matter storage file: ${error}`);
|
|
1331
|
-
}
|
|
1332
|
-
try {
|
|
1333
|
-
// Delete matter node storage directory with its subdirectories
|
|
1334
|
-
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1335
|
-
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1336
|
-
await fs.rm(dir, { recursive: true });
|
|
1337
|
-
}
|
|
1338
|
-
catch (error) {
|
|
1339
|
-
this.log.debug(`Error resetting matter node storage file: ${error}`);
|
|
1340
|
-
}
|
|
1341
|
-
this.log.info('Reset done! Remove all paired fabrics from the controllers.');
|
|
1342
|
-
}
|
|
1343
|
-
if (message === 'shutting down with factory reset...') {
|
|
1344
|
-
try {
|
|
1345
|
-
// Delete node storage directory with its subdirectories
|
|
1346
|
-
this.log.info('Resetting Matterbridge storage...');
|
|
1347
|
-
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1348
|
-
}
|
|
1349
|
-
catch (error) {
|
|
1350
|
-
this.log.debug(`Error resetting Matterbridge storage: ${error}`);
|
|
1351
|
-
}
|
|
1352
|
-
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1353
|
-
}
|
|
1354
1319
|
this.log.notice('Cleanup completed. Shutting down...');
|
|
1355
1320
|
Matterbridge.instance = undefined;
|
|
1356
1321
|
this.emit('shutdown');
|
|
@@ -1359,163 +1324,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1359
1324
|
this.initialized = false;
|
|
1360
1325
|
}
|
|
1361
1326
|
}
|
|
1362
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1363
|
-
async addBridgedEndpoint(pluginName, device) {
|
|
1364
|
-
// Nothing to do here
|
|
1365
|
-
}
|
|
1366
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1367
|
-
async removeBridgedEndpoint(pluginName, device) {
|
|
1368
|
-
// Nothing to do here
|
|
1369
|
-
}
|
|
1370
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1371
|
-
async removeAllBridgedEndpoints(pluginName) {
|
|
1372
|
-
// Nothing to do here
|
|
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
|
-
*/
|
|
1380
|
-
async addBridgedDevice(pluginName, device) {
|
|
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
|
|
1383
|
-
const plugin = this.plugins.get(pluginName);
|
|
1384
|
-
if (!plugin) {
|
|
1385
|
-
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1389
|
-
if (this.bridgeMode === 'bridge') {
|
|
1390
|
-
if (!this.matterAggregator) {
|
|
1391
|
-
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
this.matterAggregator.addBridgedDevice(device);
|
|
1395
|
-
}
|
|
1396
|
-
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1397
|
-
// Register and add the device in childbridge mode
|
|
1398
|
-
if (this.bridgeMode === 'childbridge') {
|
|
1399
|
-
if (plugin.type === 'AccessoryPlatform') {
|
|
1400
|
-
// Check if the plugin is locked with the commissioning server
|
|
1401
|
-
if (!plugin.locked) {
|
|
1402
|
-
plugin.locked = true;
|
|
1403
|
-
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
1404
|
-
this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
|
|
1405
|
-
plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
1406
|
-
this.log.debug(`Adding device ${dev}${device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
1407
|
-
plugin.commissioningServer.addDevice(device);
|
|
1408
|
-
plugin.device = device;
|
|
1409
|
-
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
1410
|
-
await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
if (plugin.type === 'DynamicPlatform') {
|
|
1414
|
-
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1415
|
-
if (!plugin.locked) {
|
|
1416
|
-
plugin.locked = true;
|
|
1417
|
-
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
1418
|
-
plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, plugin.description);
|
|
1419
|
-
this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
|
|
1420
|
-
plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
1421
|
-
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1422
|
-
plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
|
|
1423
|
-
this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
1424
|
-
plugin.commissioningServer.addDevice(plugin.aggregator);
|
|
1425
|
-
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
1426
|
-
await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
1427
|
-
}
|
|
1428
|
-
plugin.aggregator?.addBridgedDevice(device);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
if (plugin.registeredDevices !== undefined)
|
|
1432
|
-
plugin.registeredDevices++;
|
|
1433
|
-
if (plugin.addedDevices !== undefined)
|
|
1434
|
-
plugin.addedDevices++;
|
|
1435
|
-
// Add the device to the DeviceManager
|
|
1436
|
-
this.devices.set(device);
|
|
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}`);
|
|
1438
|
-
}
|
|
1439
1327
|
/**
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1442
|
-
* @param
|
|
1443
|
-
* @
|
|
1328
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1329
|
+
*
|
|
1330
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1331
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1332
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1333
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1444
1334
|
*/
|
|
1445
|
-
async
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
this.log.
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
if (this.bridgeMode === 'bridge') {
|
|
1455
|
-
if (!this.matterAggregator) {
|
|
1456
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
if (device.number !== undefined) {
|
|
1460
|
-
device.setBridgedDeviceReachability(false);
|
|
1461
|
-
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1462
|
-
}
|
|
1463
|
-
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1464
|
-
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1465
|
-
this.matterAggregator?.removeBridgedDevice(device);
|
|
1466
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1467
|
-
if (plugin.registeredDevices !== undefined)
|
|
1468
|
-
plugin.registeredDevices--;
|
|
1469
|
-
if (plugin.addedDevices !== undefined)
|
|
1470
|
-
plugin.addedDevices--;
|
|
1335
|
+
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1336
|
+
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1337
|
+
plugin.locked = true;
|
|
1338
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
1339
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1340
|
+
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
|
|
1341
|
+
await plugin.serverNode.add(device);
|
|
1342
|
+
if (start)
|
|
1343
|
+
await this.startServerNode(plugin.serverNode);
|
|
1471
1344
|
}
|
|
1472
|
-
// Remove the device in childbridge mode
|
|
1473
|
-
if (this.bridgeMode === 'childbridge') {
|
|
1474
|
-
if (plugin.type === 'AccessoryPlatform') {
|
|
1475
|
-
if (!plugin.commissioningServer) {
|
|
1476
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
|
|
1477
|
-
return;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
else if (plugin.type === 'DynamicPlatform') {
|
|
1481
|
-
if (!plugin.aggregator) {
|
|
1482
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
if (device.number !== undefined) {
|
|
1486
|
-
device.setBridgedDeviceReachability(false);
|
|
1487
|
-
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1488
|
-
}
|
|
1489
|
-
plugin.aggregator.removeBridgedDevice(device);
|
|
1490
|
-
}
|
|
1491
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1492
|
-
if (plugin.registeredDevices !== undefined)
|
|
1493
|
-
plugin.registeredDevices--;
|
|
1494
|
-
if (plugin.addedDevices !== undefined)
|
|
1495
|
-
plugin.addedDevices--;
|
|
1496
|
-
// Remove the commissioning server
|
|
1497
|
-
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1498
|
-
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1499
|
-
plugin.commissioningServer = undefined;
|
|
1500
|
-
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
// Remove the device from the DeviceManager
|
|
1504
|
-
this.devices.remove(device);
|
|
1505
1345
|
}
|
|
1506
1346
|
/**
|
|
1507
|
-
*
|
|
1347
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1508
1348
|
*
|
|
1509
|
-
* @param
|
|
1510
|
-
* @
|
|
1349
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1350
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1351
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1511
1352
|
*/
|
|
1512
|
-
async
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1353
|
+
async createDynamicPlugin(plugin, start = false) {
|
|
1354
|
+
if (!plugin.locked) {
|
|
1355
|
+
plugin.locked = true;
|
|
1356
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1357
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1358
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1359
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1360
|
+
if (start)
|
|
1361
|
+
await this.startServerNode(plugin.serverNode);
|
|
1362
|
+
}
|
|
1519
1363
|
}
|
|
1520
1364
|
/**
|
|
1521
1365
|
* Starts the Matterbridge in bridge mode.
|
|
@@ -1524,25 +1368,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1524
1368
|
*/
|
|
1525
1369
|
async startBridge() {
|
|
1526
1370
|
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1527
|
-
if (!this.
|
|
1371
|
+
if (!this.matterStorageManager)
|
|
1528
1372
|
throw new Error('No storage manager initialized');
|
|
1529
1373
|
if (!this.matterbridgeContext)
|
|
1530
1374
|
throw new Error('No storage context initialized');
|
|
1531
|
-
this.
|
|
1532
|
-
this.
|
|
1533
|
-
|
|
1534
|
-
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
1535
|
-
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
|
|
1536
|
-
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
1537
|
-
this.commissioningServer.addDevice(this.matterAggregator);
|
|
1538
|
-
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
1539
|
-
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
1375
|
+
this.serverNode = await this.createServerNode(this.matterbridgeContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1376
|
+
this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
|
|
1377
|
+
await this.serverNode.add(this.aggregatorNode);
|
|
1540
1378
|
await this.startPlugins();
|
|
1541
1379
|
this.log.debug('Starting start matter interval in bridge mode');
|
|
1542
1380
|
let failCount = 0;
|
|
1543
1381
|
this.startMatterInterval = setInterval(async () => {
|
|
1544
1382
|
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
|
|
1546
1383
|
if (!plugin.enabled)
|
|
1547
1384
|
continue;
|
|
1548
1385
|
if (plugin.error) {
|
|
@@ -1567,11 +1404,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1567
1404
|
clearInterval(this.startMatterInterval);
|
|
1568
1405
|
this.startMatterInterval = undefined;
|
|
1569
1406
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
-
// Start the Matter server
|
|
1571
|
-
|
|
1572
|
-
this.log.notice('Matter server started');
|
|
1573
|
-
// Show the QR code for commissioning or log the already commissioned message
|
|
1574
|
-
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1407
|
+
// Start the Matter server node
|
|
1408
|
+
this.startServerNode(this.serverNode);
|
|
1575
1409
|
// Configure the plugins
|
|
1576
1410
|
this.configureTimeout = setTimeout(async () => {
|
|
1577
1411
|
for (const plugin of this.plugins) {
|
|
@@ -1585,15 +1419,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1585
1419
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1586
1420
|
}
|
|
1587
1421
|
}
|
|
1588
|
-
this.wssSendRefreshRequired();
|
|
1422
|
+
this.frontend.wssSendRefreshRequired();
|
|
1589
1423
|
}, 30 * 1000);
|
|
1590
1424
|
// Setting reachability to true
|
|
1591
1425
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1592
1426
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1593
|
-
if (this.
|
|
1594
|
-
this.
|
|
1595
|
-
if (this.
|
|
1596
|
-
this.setAggregatorReachability(this.
|
|
1427
|
+
if (this.serverNode)
|
|
1428
|
+
this.setServerNodeReachability(this.serverNode, true);
|
|
1429
|
+
if (this.aggregatorNode)
|
|
1430
|
+
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1431
|
+
this.frontend.wssSendRefreshRequired();
|
|
1597
1432
|
}, 60 * 1000);
|
|
1598
1433
|
}, 1000);
|
|
1599
1434
|
}
|
|
@@ -1605,16 +1440,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1605
1440
|
async startChildbridge() {
|
|
1606
1441
|
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1607
1442
|
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1608
|
-
if (!this.
|
|
1443
|
+
if (!this.matterStorageManager)
|
|
1609
1444
|
throw new Error('No storage manager initialized');
|
|
1610
|
-
|
|
1445
|
+
for (const plugin of this.plugins) {
|
|
1446
|
+
if (!plugin.enabled)
|
|
1447
|
+
continue;
|
|
1448
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
1449
|
+
plugin.locked = true;
|
|
1450
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1451
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1452
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1453
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1611
1456
|
await this.startPlugins();
|
|
1612
1457
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1613
1458
|
let failCount = 0;
|
|
1614
1459
|
this.startMatterInterval = setInterval(async () => {
|
|
1615
1460
|
let allStarted = true;
|
|
1616
1461
|
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
|
|
1618
1462
|
if (!plugin.enabled)
|
|
1619
1463
|
continue;
|
|
1620
1464
|
if (plugin.error) {
|
|
@@ -1642,9 +1486,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
1486
|
clearInterval(this.startMatterInterval);
|
|
1643
1487
|
this.startMatterInterval = undefined;
|
|
1644
1488
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1645
|
-
// Start the Matter server
|
|
1646
|
-
await this.startMatterServer();
|
|
1647
|
-
this.log.notice('Matter server started');
|
|
1648
1489
|
// Configure the plugins
|
|
1649
1490
|
this.configureTimeout = setTimeout(async () => {
|
|
1650
1491
|
for (const plugin of this.plugins) {
|
|
@@ -1658,7 +1499,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1658
1499
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1659
1500
|
}
|
|
1660
1501
|
}
|
|
1661
|
-
this.wssSendRefreshRequired();
|
|
1502
|
+
this.frontend.wssSendRefreshRequired();
|
|
1662
1503
|
}, 30 * 1000);
|
|
1663
1504
|
for (const plugin of this.plugins) {
|
|
1664
1505
|
if (!plugin.enabled || plugin.error)
|
|
@@ -1667,8 +1508,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1667
1508
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1668
1509
|
continue;
|
|
1669
1510
|
}
|
|
1670
|
-
if (!plugin.
|
|
1671
|
-
this.log.error(`
|
|
1511
|
+
if (!plugin.serverNode) {
|
|
1512
|
+
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1672
1513
|
continue;
|
|
1673
1514
|
}
|
|
1674
1515
|
if (!plugin.storageContext) {
|
|
@@ -1679,16 +1520,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1679
1520
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1680
1521
|
continue;
|
|
1681
1522
|
}
|
|
1682
|
-
|
|
1523
|
+
// Start the Matter server node
|
|
1524
|
+
this.startServerNode(plugin.serverNode);
|
|
1683
1525
|
// Setting reachability to true
|
|
1684
1526
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1685
1527
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1686
|
-
if (plugin.
|
|
1687
|
-
this.
|
|
1528
|
+
if (plugin.serverNode)
|
|
1529
|
+
this.setServerNodeReachability(plugin.serverNode, true);
|
|
1688
1530
|
if (plugin.type === 'AccessoryPlatform' && plugin.device)
|
|
1689
1531
|
this.setDeviceReachability(plugin.device, true);
|
|
1690
|
-
if (plugin.type === 'DynamicPlatform' && plugin.
|
|
1691
|
-
this.setAggregatorReachability(plugin.
|
|
1532
|
+
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1533
|
+
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1534
|
+
this.frontend.wssSendRefreshRequired();
|
|
1692
1535
|
}, 60 * 1000);
|
|
1693
1536
|
}
|
|
1694
1537
|
}, 1000);
|
|
@@ -1699,747 +1542,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1699
1542
|
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
1543
|
*/
|
|
1701
1544
|
async startController() {
|
|
1545
|
+
/*
|
|
1702
1546
|
if (!this.storageManager) {
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1547
|
+
this.log.error('No storage manager initialized');
|
|
1548
|
+
await this.cleanup('No storage manager initialized');
|
|
1549
|
+
return;
|
|
1706
1550
|
}
|
|
1707
1551
|
this.log.info('Creating context: mattercontrollerContext');
|
|
1708
1552
|
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1709
1553
|
if (!this.mattercontrollerContext) {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1554
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1555
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1556
|
+
return;
|
|
1713
1557
|
}
|
|
1558
|
+
|
|
1714
1559
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1715
1560
|
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1716
1561
|
this.log.info('Creating matter commissioning controller');
|
|
1717
1562
|
this.commissioningController = new CommissioningController({
|
|
1718
|
-
|
|
1563
|
+
autoConnect: false,
|
|
1719
1564
|
});
|
|
1720
1565
|
this.log.info('Adding matter commissioning controller to matter server');
|
|
1721
1566
|
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1567
|
+
|
|
1722
1568
|
this.log.info('Starting matter server');
|
|
1723
1569
|
await this.matterServer.start();
|
|
1724
1570
|
this.log.info('Matter server started');
|
|
1571
|
+
|
|
1725
1572
|
if (hasParameter('pairingcode')) {
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1573
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1574
|
+
const pairingCode = getParameter('pairingcode');
|
|
1575
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1576
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1577
|
+
|
|
1578
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1579
|
+
if (pairingCode !== undefined) {
|
|
1580
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1581
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1582
|
+
longDiscriminator = undefined;
|
|
1583
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1584
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1585
|
+
} else {
|
|
1586
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1587
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1588
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1589
|
+
}
|
|
1590
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1591
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1595
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1596
|
+
regulatoryCountryCode: 'XX',
|
|
1597
|
+
};
|
|
1598
|
+
const options = {
|
|
1599
|
+
commissioning: commissioningOptions,
|
|
1600
|
+
discovery: {
|
|
1601
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1602
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1603
|
+
},
|
|
1604
|
+
passcode: setupPin,
|
|
1605
|
+
} as NodeCommissioningOptions;
|
|
1606
|
+
this.log.info('Commissioning with options:', options);
|
|
1607
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1608
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1609
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1763
1610
|
} // (hasParameter('pairingcode'))
|
|
1611
|
+
|
|
1764
1612
|
if (hasParameter('unpairall')) {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1613
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1614
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1615
|
+
for (const nodeId of nodeIds) {
|
|
1616
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1617
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1618
|
+
}
|
|
1619
|
+
return;
|
|
1772
1620
|
}
|
|
1621
|
+
|
|
1773
1622
|
if (hasParameter('discover')) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1623
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1624
|
+
// console.log(discover);
|
|
1776
1625
|
}
|
|
1626
|
+
|
|
1777
1627
|
if (!this.commissioningController.isCommissioned()) {
|
|
1778
|
-
|
|
1779
|
-
|
|
1628
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1629
|
+
return;
|
|
1780
1630
|
}
|
|
1631
|
+
|
|
1781
1632
|
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1782
1633
|
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1783
1634
|
for (const nodeId of nodeIds) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
cluster
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1635
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1636
|
+
|
|
1637
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1638
|
+
autoSubscribe: false,
|
|
1639
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1640
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1641
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1642
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1643
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1644
|
+
switch (info) {
|
|
1645
|
+
case NodeStateInformation.Connected:
|
|
1646
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1647
|
+
break;
|
|
1648
|
+
case NodeStateInformation.Disconnected:
|
|
1649
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1650
|
+
break;
|
|
1651
|
+
case NodeStateInformation.Reconnecting:
|
|
1652
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1653
|
+
break;
|
|
1654
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1655
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1656
|
+
break;
|
|
1657
|
+
case NodeStateInformation.StructureChanged:
|
|
1658
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1659
|
+
break;
|
|
1660
|
+
case NodeStateInformation.Decommissioned:
|
|
1661
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1662
|
+
break;
|
|
1663
|
+
default:
|
|
1664
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
node.logStructure();
|
|
1671
|
+
|
|
1672
|
+
// Get the interaction client
|
|
1673
|
+
this.log.info('Getting the interaction client');
|
|
1674
|
+
const interactionClient = await node.getInteractionClient();
|
|
1675
|
+
let cluster;
|
|
1676
|
+
let attributes;
|
|
1677
|
+
|
|
1678
|
+
// Log BasicInformationCluster
|
|
1679
|
+
cluster = BasicInformationCluster;
|
|
1680
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1681
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1682
|
+
});
|
|
1683
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1684
|
+
attributes.forEach((attribute) => {
|
|
1685
|
+
this.log.info(
|
|
1686
|
+
`- 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}`,
|
|
1687
|
+
);
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
// Log PowerSourceCluster
|
|
1691
|
+
cluster = PowerSourceCluster;
|
|
1692
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1693
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1694
|
+
});
|
|
1695
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1696
|
+
attributes.forEach((attribute) => {
|
|
1697
|
+
this.log.info(
|
|
1698
|
+
`- 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}`,
|
|
1699
|
+
);
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
// Log ThreadNetworkDiagnostics
|
|
1703
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1704
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1705
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1706
|
+
});
|
|
1707
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1708
|
+
attributes.forEach((attribute) => {
|
|
1709
|
+
this.log.info(
|
|
1710
|
+
`- 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}`,
|
|
1711
|
+
);
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
// Log SwitchCluster
|
|
1715
|
+
cluster = SwitchCluster;
|
|
1716
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1717
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1718
|
+
});
|
|
1719
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1720
|
+
attributes.forEach((attribute) => {
|
|
1721
|
+
this.log.info(
|
|
1722
|
+
`- 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}`,
|
|
1723
|
+
);
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1727
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1728
|
+
ignoreInitialTriggers: false,
|
|
1729
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1730
|
+
this.log.info(
|
|
1731
|
+
`***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
|
|
1732
|
+
),
|
|
1733
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1734
|
+
this.log.info(
|
|
1735
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1736
|
+
);
|
|
1737
|
+
},
|
|
1738
|
+
});
|
|
1739
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1870
1740
|
}
|
|
1741
|
+
*/
|
|
1871
1742
|
}
|
|
1872
1743
|
/** ***********************************************************************************************************************************/
|
|
1873
1744
|
/** Matter.js methods */
|
|
1874
1745
|
/** ***********************************************************************************************************************************/
|
|
1875
1746
|
/**
|
|
1876
1747
|
* 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
1748
|
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1880
1749
|
*/
|
|
1881
|
-
async startMatterStorage(
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
1893
|
-
else {
|
|
1894
|
-
this.log.error(`Unsupported matter storage type ${storageType}`);
|
|
1895
|
-
await this.cleanup('Unsupported matter storage type');
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
try {
|
|
1899
|
-
await this.storageManager.initialize();
|
|
1900
|
-
this.log.debug('Matter storage initialized');
|
|
1901
|
-
if (storageType === 'json') {
|
|
1902
|
-
await this.backupMatterStorage(storageName, storageName.replace('.json', '') + '.backup.json');
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
catch (error) {
|
|
1906
|
-
this.log.error(`Matter storage initialize error! The file .matterbridge/${storageName} may be corrupted: ${error instanceof Error ? error.message : error}`);
|
|
1907
|
-
if (hasParameter('norestore')) {
|
|
1908
|
-
this.log.fatal(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
|
|
1909
|
-
await this.cleanup('Matter storage initialize error and -norestore parameter found!');
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
await this.restoreMatterStorage(storageName.replace('.json', '') + '.backup.json', storageName);
|
|
1913
|
-
try {
|
|
1914
|
-
await this.storageManager.initialize();
|
|
1915
|
-
this.log.notice('Matter storage initialized from the backup file');
|
|
1916
|
-
}
|
|
1917
|
-
catch (error) {
|
|
1918
|
-
this.log.error(`Matter storage initialize error! The backup file for .matterbridge/${storageName} may be corrupted too:`, error instanceof Error ? error.message : error);
|
|
1919
|
-
await this.cleanup('Matter storage initialize error from backup!');
|
|
1920
|
-
return;
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
this.log.debug(`Creating commissioning server context for ${plg}Matterbridge${db}`);
|
|
1924
|
-
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge', aggregator.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge aggregator');
|
|
1925
|
-
await this.matterbridgeContext.set('port', this.port);
|
|
1926
|
-
await this.matterbridgeContext.set('passcode', this.passcode);
|
|
1927
|
-
await this.matterbridgeContext.set('discriminator', this.discriminator);
|
|
1750
|
+
async startMatterStorage() {
|
|
1751
|
+
// Setup Matter storage
|
|
1752
|
+
this.log.info(`Starting matter node storage...`);
|
|
1753
|
+
this.matterStorageService = this.environment.get(StorageService);
|
|
1754
|
+
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
1755
|
+
this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
|
|
1756
|
+
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1757
|
+
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1758
|
+
this.log.info('Matter node storage started');
|
|
1759
|
+
// Backup matter storage since it is created/opened correctly
|
|
1760
|
+
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1928
1761
|
}
|
|
1929
1762
|
/**
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
* @param
|
|
1933
|
-
* @
|
|
1934
|
-
*/
|
|
1935
|
-
async convertStorage(context, pluginName) {
|
|
1936
|
-
if (this.edge !== false)
|
|
1937
|
-
return;
|
|
1938
|
-
try {
|
|
1939
|
-
const storageService = Environment.default.get(StorageService);
|
|
1940
|
-
Environment.default.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage' + (this.profile ? '.' + this.profile : '')));
|
|
1941
|
-
const nodeStorage = await storageService.open(pluginName);
|
|
1942
|
-
if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
|
|
1943
|
-
this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
else {
|
|
1947
|
-
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1948
|
-
}
|
|
1949
|
-
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1950
|
-
const fabricManagerContext = context.createContext('FabricManager');
|
|
1951
|
-
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1952
|
-
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
|
|
1953
|
-
// Read EventHandler from the old storage
|
|
1954
|
-
const eventHandlerContext = context.createContext('EventHandler');
|
|
1955
|
-
// Read SessionManager from the old storage
|
|
1956
|
-
const sessionManagerContext = context.createContext('SessionManager');
|
|
1957
|
-
// Read EndpointStructure from the old storage
|
|
1958
|
-
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1959
|
-
// Read generalCommissioning from the old storage
|
|
1960
|
-
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1961
|
-
// Read basicInformation from the old storage
|
|
1962
|
-
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1963
|
-
const fabricInfo = {};
|
|
1964
|
-
const fabricInfoArray = [];
|
|
1965
|
-
const nocArray = [];
|
|
1966
|
-
const trcArray = [];
|
|
1967
|
-
const aclArray = [];
|
|
1968
|
-
this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
|
|
1969
|
-
if (fabrics.length === 0 || nextFabricIndex === 0) {
|
|
1970
|
-
this.log.notice(`If you want to try out matterbridge edge add -edge to the command line and pair it to your controller(s).`);
|
|
1971
|
-
return;
|
|
1972
|
-
}
|
|
1973
|
-
for (const fabric of fabrics) {
|
|
1974
|
-
this.log.info(`- fabricIndex ${CYAN}${fabric.fabricIndex}${nf} fabricId ${CYAN}${fabric.fabricId}${nf} nodeId ${CYAN}${fabric.nodeId}${nf} rootNodeId ${CYAN}${fabric.rootNodeId}${nf} rootVendorId ${CYAN}${fabric.rootVendorId}${nf} label ${CYAN}${fabric.label}${nf}`);
|
|
1975
|
-
fabricInfo[fabric.fabricIndex] = {
|
|
1976
|
-
fabricIndex: fabric.fabricIndex,
|
|
1977
|
-
fabricId: fabric.fabricId,
|
|
1978
|
-
nodeId: fabric.nodeId,
|
|
1979
|
-
rootNodeId: fabric.rootNodeId,
|
|
1980
|
-
rootVendorId: fabric.rootVendorId,
|
|
1981
|
-
label: fabric.label,
|
|
1982
|
-
};
|
|
1983
|
-
fabricInfoArray.push({
|
|
1984
|
-
fabricIndex: fabric.fabricIndex,
|
|
1985
|
-
fabricId: fabric.fabricId,
|
|
1986
|
-
nodeId: fabric.nodeId,
|
|
1987
|
-
vendorId: fabric.rootVendorId,
|
|
1988
|
-
rootPublicKey: fabric.rootPublicKey,
|
|
1989
|
-
label: fabric.label,
|
|
1990
|
-
});
|
|
1991
|
-
nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
|
|
1992
|
-
trcArray.push(fabric.rootCert);
|
|
1993
|
-
this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
|
|
1994
|
-
const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
|
|
1995
|
-
if (acl && acl.value.length > 0) {
|
|
1996
|
-
aclArray.push(acl.value[0]);
|
|
1997
|
-
this.log.info(`- ACL updated to ${debugStringify(acl.value)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
|
|
1998
|
-
}
|
|
1999
|
-
else {
|
|
2000
|
-
const defaultAcl = { fabricIndex: fabric.fabricIndex, privilege: 5, authMode: 2, subjects: [fabric.rootNodeId], targets: null };
|
|
2001
|
-
aclArray.push(defaultAcl);
|
|
2002
|
-
this.log.info(`- ACL updated to default ${debugStringify(defaultAcl)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
await nodeStorage.createContext('fabrics').set('fabrics', fabrics);
|
|
2006
|
-
await nodeStorage.createContext('fabrics').set('nextFabricIndex', nextFabricIndex);
|
|
2007
|
-
await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
|
|
2008
|
-
await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
|
|
2009
|
-
await nodeStorage.createContext('root').set('__number__', 0);
|
|
2010
|
-
await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
|
|
2011
|
-
await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
|
|
2012
|
-
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
2013
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
2014
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
2015
|
-
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
2016
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
2017
|
-
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
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}]
|
|
2022
|
-
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
2023
|
-
await nodeStorage
|
|
2024
|
-
.createContext('root')
|
|
2025
|
-
.createContext('generalCommissioning')
|
|
2026
|
-
.set('breadcrumb', await generalCommissioningContext.get('breadcrumb', BigInt(0)));
|
|
2027
|
-
await nodeStorage
|
|
2028
|
-
.createContext('root')
|
|
2029
|
-
.createContext('basicInformation')
|
|
2030
|
-
.set('location', await basicInformationContext.get('location', 'XX'));
|
|
2031
|
-
await nodeStorage.createContext('root').createContext('network').set('ble', false);
|
|
2032
|
-
await nodeStorage
|
|
2033
|
-
.createContext('root')
|
|
2034
|
-
.createContext('network')
|
|
2035
|
-
.set('operationalPort', await context.get('port', 5540));
|
|
2036
|
-
await nodeStorage
|
|
2037
|
-
.createContext('root')
|
|
2038
|
-
.createContext('productDescription')
|
|
2039
|
-
.set('productId', await context.get('productId', 0x8000));
|
|
2040
|
-
await nodeStorage
|
|
2041
|
-
.createContext('root')
|
|
2042
|
-
.createContext('productDescription')
|
|
2043
|
-
.set('vendorId', await context.get('vendorId', 0xfff1));
|
|
2044
|
-
/*
|
|
2045
|
-
"Matterbridge.EndpointStructure": {
|
|
2046
|
-
"unique_d60ca095a002f160-index_0": 1,
|
|
2047
|
-
"unique_d60ca095a002f160-index_0-custom_Switch0": 2,
|
|
2048
|
-
"unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
|
|
2049
|
-
"unique_d60ca095a002f160-index_0-custom_Light0": 4,
|
|
2050
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
|
|
2051
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
|
|
2052
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
|
|
2053
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
|
|
2054
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
|
|
2055
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
|
|
2056
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
|
|
2057
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
|
|
2058
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
|
|
2059
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
|
|
2060
|
-
"nextEndpointId": 5
|
|
2061
|
-
},
|
|
2062
|
-
*/
|
|
2063
|
-
const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
|
|
2064
|
-
this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
|
|
2065
|
-
for (const key of await endpointStructureContext.keys()) {
|
|
2066
|
-
if (key === 'nextEndpointId') {
|
|
2067
|
-
await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
|
|
2068
|
-
continue;
|
|
2069
|
-
}
|
|
2070
|
-
const parts = key.split('-');
|
|
2071
|
-
const number = await endpointStructureContext.get(key);
|
|
2072
|
-
if (parts.length === 2) {
|
|
2073
|
-
this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.__number__:${CYAN}${number}${db}`);
|
|
2074
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).set('__number__', number);
|
|
2075
|
-
}
|
|
2076
|
-
else if (parts.length === 3 && parts[2].startsWith('unique_')) {
|
|
2077
|
-
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
2078
|
-
if (device && device.deviceName && device.maybeNumber) {
|
|
2079
|
-
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${CYAN}${device.maybeNumber}${db}`);
|
|
2080
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
|
|
2084
|
-
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
2085
|
-
if (device && device.deviceName && device.maybeNumber) {
|
|
2086
|
-
const childEndpointName = parts[3].replace('custom_', '');
|
|
2087
|
-
const childEndpoint = device.getChildEndpointByName(childEndpointName);
|
|
2088
|
-
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${childEndpoint?.number}${db}`);
|
|
2089
|
-
await nodeStorage
|
|
2090
|
-
.createContext('root')
|
|
2091
|
-
.createContext('parts')
|
|
2092
|
-
.createContext(rootDeviceName)
|
|
2093
|
-
.createContext('parts')
|
|
2094
|
-
.createContext(device.deviceName.replace(/[ .]/g, ''))
|
|
2095
|
-
.createContext('parts')
|
|
2096
|
-
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2097
|
-
.set('__number__', childEndpoint?.number);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
else if (parts.length === 3 && parts[2].startsWith('custom_')) {
|
|
2101
|
-
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
|
|
2102
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
|
|
2103
|
-
}
|
|
2104
|
-
else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
|
|
2105
|
-
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
|
|
2106
|
-
await nodeStorage
|
|
2107
|
-
.createContext('root')
|
|
2108
|
-
.createContext('parts')
|
|
2109
|
-
.createContext(rootDeviceName)
|
|
2110
|
-
.createContext('parts')
|
|
2111
|
-
.createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2112
|
-
.createContext('parts')
|
|
2113
|
-
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2114
|
-
.set('__number__', number);
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
await nodeStorage.createContext('persist').set('converted', true);
|
|
2118
|
-
await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
|
|
2119
|
-
await nodeStorage.createContext('persist').set('deviceType', await context.get('deviceType'));
|
|
2120
|
-
await nodeStorage.createContext('persist').set('vendorId', await context.get('vendorId'));
|
|
2121
|
-
await nodeStorage.createContext('persist').set('vendorName', await context.get('vendorName'));
|
|
2122
|
-
await nodeStorage.createContext('persist').set('productId', await context.get('productId'));
|
|
2123
|
-
await nodeStorage.createContext('persist').set('productName', await context.get('productName'));
|
|
2124
|
-
await nodeStorage.createContext('persist').set('nodeLabel', await context.get('nodeLabel'));
|
|
2125
|
-
await nodeStorage.createContext('persist').set('productLabel', await context.get('productLabel'));
|
|
2126
|
-
await nodeStorage.createContext('persist').set('serialNumber', 'SN' + (await context.get('serialNumber')));
|
|
2127
|
-
await nodeStorage.createContext('persist').set('uniqueId', await context.get('uniqueId'));
|
|
2128
|
-
await nodeStorage.createContext('persist').set('softwareVersion', await context.get('softwareVersion'));
|
|
2129
|
-
await nodeStorage.createContext('persist').set('softwareVersionString', await context.get('softwareVersionString'));
|
|
2130
|
-
await nodeStorage.createContext('persist').set('hardwareVersion', await context.get('hardwareVersion'));
|
|
2131
|
-
await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
|
|
2132
|
-
await context.set('converted', true);
|
|
2133
|
-
this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
|
|
2134
|
-
this.log.notice(`If you want to try out matterbridge edge add -edge to the command line.`);
|
|
2135
|
-
this.log.notice(`All fabrics have been converted to the new storage format.`);
|
|
2136
|
-
}
|
|
2137
|
-
catch (error) {
|
|
2138
|
-
this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
|
|
2139
|
-
}
|
|
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.
|
|
1763
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1764
|
+
*
|
|
1765
|
+
* @param storageName - The name of the storage file to be backed up.
|
|
1766
|
+
* @param backupName - The name of the backup file to be created.
|
|
2146
1767
|
*/
|
|
2147
1768
|
async backupMatterStorage(storageName, backupName) {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
this.log.debug(`Successfully backed up ${storageName} to ${backupName}`);
|
|
2152
|
-
}
|
|
2153
|
-
catch (err) {
|
|
2154
|
-
if (err instanceof Error && 'code' in err) {
|
|
2155
|
-
if (err.code === 'ENOENT') {
|
|
2156
|
-
this.log.debug(`No existing file to back up for ${storageName}. This is expected on the first run.`);
|
|
2157
|
-
}
|
|
2158
|
-
else {
|
|
2159
|
-
this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
else {
|
|
2163
|
-
this.log.error(`An unexpected error occurred during the backup of ${storageName}: ${String(err)}`);
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
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
|
-
*/
|
|
2173
|
-
async restoreMatterStorage(backupName, storageName) {
|
|
2174
|
-
try {
|
|
2175
|
-
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
2176
|
-
await fs.copyFile(backupName, storageName);
|
|
2177
|
-
this.log.notice(`Successfully restored ${backupName} to ${storageName}`);
|
|
2178
|
-
}
|
|
2179
|
-
catch (err) {
|
|
2180
|
-
if (err instanceof Error && 'code' in err) {
|
|
2181
|
-
if (err.code === 'ENOENT') {
|
|
2182
|
-
this.log.info(`No existing file to restore: ${backupName}.`);
|
|
2183
|
-
}
|
|
2184
|
-
else {
|
|
2185
|
-
this.log.error(`Error restoring ${backupName}: ${err.message}`);
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
else {
|
|
2189
|
-
this.log.error(`An unexpected error occurred during the restore of ${backupName}: ${String(err)}`);
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
1769
|
+
this.log.info('Creating matter node storage backup...');
|
|
1770
|
+
await copyDirectory(storageName, backupName);
|
|
1771
|
+
this.log.info('Created matter node storage backup');
|
|
2192
1772
|
}
|
|
2193
1773
|
/**
|
|
2194
1774
|
* Stops the matter storage.
|
|
2195
1775
|
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2196
1776
|
*/
|
|
2197
1777
|
async stopMatterStorage() {
|
|
2198
|
-
this.log.
|
|
2199
|
-
|
|
2200
|
-
this.
|
|
2201
|
-
this.
|
|
1778
|
+
this.log.info('Closing matter node storage...');
|
|
1779
|
+
this.matterStorageManager?.close();
|
|
1780
|
+
this.matterStorageService = undefined;
|
|
1781
|
+
this.matterStorageManager = undefined;
|
|
2202
1782
|
this.matterbridgeContext = undefined;
|
|
2203
|
-
this.
|
|
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
|
-
*/
|
|
2210
|
-
async createMatterServer(storageManager) {
|
|
2211
|
-
this.log.debug('Creating matter server');
|
|
2212
|
-
// Validate mdnsInterface
|
|
2213
|
-
if (this.mdnsInterface) {
|
|
2214
|
-
const networkInterfaces = os.networkInterfaces();
|
|
2215
|
-
const availableInterfaces = Object.keys(networkInterfaces);
|
|
2216
|
-
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
2217
|
-
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
2218
|
-
this.mdnsInterface = undefined;
|
|
2219
|
-
}
|
|
2220
|
-
else {
|
|
2221
|
-
this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
const matterServer = new MatterServer(storageManager, { mdnsInterface: this.mdnsInterface });
|
|
2225
|
-
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
2226
|
-
return matterServer;
|
|
2227
|
-
}
|
|
2228
|
-
/**
|
|
2229
|
-
* Starts the Matter server.
|
|
2230
|
-
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2231
|
-
*/
|
|
2232
|
-
async startMatterServer() {
|
|
2233
|
-
if (!this.matterServer) {
|
|
2234
|
-
this.log.error('No matter server initialized');
|
|
2235
|
-
await this.cleanup('No matter server initialized');
|
|
2236
|
-
return;
|
|
2237
|
-
}
|
|
2238
|
-
this.log.debug('Starting matter server...');
|
|
2239
|
-
await this.matterServer.start();
|
|
2240
|
-
this.log.debug('Started matter server');
|
|
2241
|
-
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
2242
|
-
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2245
|
-
*/
|
|
2246
|
-
async stopMatterServer() {
|
|
2247
|
-
this.log.debug('Stopping matter commissioningServer');
|
|
2248
|
-
await this.commissioningServer?.close();
|
|
2249
|
-
this.log.debug('Stopping matter commissioningController');
|
|
2250
|
-
await this.commissioningController?.close();
|
|
2251
|
-
this.log.debug('Stopping matter server');
|
|
2252
|
-
await this.matterServer?.close();
|
|
2253
|
-
this.log.debug('Matter server closed');
|
|
2254
|
-
this.commissioningController = undefined;
|
|
2255
|
-
this.commissioningServer = undefined;
|
|
2256
|
-
this.matterAggregator = undefined;
|
|
2257
|
-
this.matterServer = undefined;
|
|
2258
|
-
}
|
|
2259
|
-
/**
|
|
2260
|
-
* Creates a Matter Aggregator.
|
|
2261
|
-
* @param {StorageContext} context - The storage context.
|
|
2262
|
-
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2263
|
-
*/
|
|
2264
|
-
async createMatterAggregator(context, pluginName) {
|
|
2265
|
-
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
2266
|
-
const matterAggregator = new Aggregator();
|
|
2267
|
-
return matterAggregator;
|
|
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
|
-
*/
|
|
2276
|
-
async createCommisioningServer(context, pluginName) {
|
|
2277
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
2278
|
-
const deviceName = await context.get('deviceName');
|
|
2279
|
-
const deviceType = await context.get('deviceType');
|
|
2280
|
-
const vendorId = await context.get('vendorId');
|
|
2281
|
-
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
2282
|
-
const productId = await context.get('productId');
|
|
2283
|
-
const productName = await context.get('productName'); // Home app = Model
|
|
2284
|
-
const serialNumber = await context.get('serialNumber');
|
|
2285
|
-
const uniqueId = await context.get('uniqueId');
|
|
2286
|
-
const softwareVersion = await context.get('softwareVersion', 1);
|
|
2287
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
2288
|
-
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
2289
|
-
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
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')})`);
|
|
2291
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
|
|
2292
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
2293
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
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
|
|
2296
|
-
if (this.ipv4address) {
|
|
2297
|
-
const networkInterfaces = os.networkInterfaces();
|
|
2298
|
-
const availableAddresses = Object.values(networkInterfaces)
|
|
2299
|
-
.flat()
|
|
2300
|
-
.filter((iface) => iface !== undefined && iface.family === 'IPv4' && !iface.internal)
|
|
2301
|
-
.map((iface) => iface.address);
|
|
2302
|
-
if (!availableAddresses.includes(this.ipv4address)) {
|
|
2303
|
-
this.log.error(`Invalid ipv4address: ${this.ipv4address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
|
|
2304
|
-
this.mdnsInterface = undefined;
|
|
2305
|
-
}
|
|
2306
|
-
else {
|
|
2307
|
-
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
// Validate ipv6address
|
|
2311
|
-
if (this.ipv6address) {
|
|
2312
|
-
const networkInterfaces = os.networkInterfaces();
|
|
2313
|
-
const availableAddresses = Object.values(networkInterfaces)
|
|
2314
|
-
.flat()
|
|
2315
|
-
.filter((iface) => iface !== undefined && iface.family === 'IPv6' && !iface.internal)
|
|
2316
|
-
.map((iface) => iface.address);
|
|
2317
|
-
if (!availableAddresses.includes(this.ipv6address)) {
|
|
2318
|
-
this.log.error(`Invalid ipv6address: ${this.ipv6address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
|
|
2319
|
-
this.mdnsInterface = undefined;
|
|
2320
|
-
}
|
|
2321
|
-
else {
|
|
2322
|
-
this.log.info(`Using ipv6address '${this.ipv6address}' for the Matter commissioning server.`);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
const commissioningServer = new CommissioningServer({
|
|
2326
|
-
port: this.port++,
|
|
2327
|
-
listeningAddressIpv4: this.ipv4address,
|
|
2328
|
-
listeningAddressIpv6: this.ipv6address,
|
|
2329
|
-
passcode: this.passcode,
|
|
2330
|
-
discriminator: this.discriminator,
|
|
2331
|
-
deviceName,
|
|
2332
|
-
deviceType,
|
|
2333
|
-
basicInformation: {
|
|
2334
|
-
vendorId: VendorId(vendorId),
|
|
2335
|
-
vendorName,
|
|
2336
|
-
productId,
|
|
2337
|
-
productName,
|
|
2338
|
-
nodeLabel: productName,
|
|
2339
|
-
productLabel: productName,
|
|
2340
|
-
softwareVersion,
|
|
2341
|
-
softwareVersionString, // Home app = Firmware Revision
|
|
2342
|
-
hardwareVersion,
|
|
2343
|
-
hardwareVersionString,
|
|
2344
|
-
uniqueId,
|
|
2345
|
-
serialNumber,
|
|
2346
|
-
reachable: true,
|
|
2347
|
-
},
|
|
2348
|
-
activeSessionsChangedCallback: (fabricIndex) => {
|
|
2349
|
-
const sessionInformations = commissioningServer.getActiveSessionInformation(fabricIndex);
|
|
2350
|
-
let connected = false;
|
|
2351
|
-
sessionInformations.forEach((session) => {
|
|
2352
|
-
this.log.info(`Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
|
|
2353
|
-
if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
|
|
2354
|
-
this.log.notice(`Controller ${zb}${session.fabric?.rootVendorId}${nt} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nt} on session ${session.name}`);
|
|
2355
|
-
connected = true;
|
|
2356
|
-
}
|
|
2357
|
-
});
|
|
2358
|
-
if (connected) {
|
|
2359
|
-
if (this.bridgeMode === 'bridge') {
|
|
2360
|
-
this.matterbridgePaired = true;
|
|
2361
|
-
this.matterbridgeConnected = true;
|
|
2362
|
-
this.matterbridgeSessionInformations = this.sanitizeSessionInformation(sessionInformations);
|
|
2363
|
-
}
|
|
2364
|
-
if (this.bridgeMode === 'childbridge') {
|
|
2365
|
-
const plugin = this.plugins.get(pluginName);
|
|
2366
|
-
if (plugin) {
|
|
2367
|
-
plugin.paired = true;
|
|
2368
|
-
plugin.connected = true;
|
|
2369
|
-
plugin.sessionInformations = this.sanitizeSessionInformation(sessionInformations);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
else {
|
|
2374
|
-
if (this.bridgeMode === 'bridge') {
|
|
2375
|
-
this.matterbridgeSessionInformations = [];
|
|
2376
|
-
}
|
|
2377
|
-
if (this.bridgeMode === 'childbridge') {
|
|
2378
|
-
const plugin = this.plugins.get(pluginName);
|
|
2379
|
-
if (plugin) {
|
|
2380
|
-
plugin.sessionInformations = [];
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
this.wssSendRefreshRequired();
|
|
2385
|
-
},
|
|
2386
|
-
commissioningChangedCallback: async (fabricIndex) => {
|
|
2387
|
-
const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
|
|
2388
|
-
this.log.debug(`Commissioning changed on fabric ${zb}${fabricIndex}${db} for ${plg}${pluginName}${db}`, debugStringify(fabricInfo));
|
|
2389
|
-
if (commissioningServer.getCommissionedFabricInformation().length === 0) {
|
|
2390
|
-
this.log.warn(`Commissioning removed from fabric ${zb}${fabricIndex}${wr} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
|
|
2391
|
-
await commissioningServer.factoryReset();
|
|
2392
|
-
if (pluginName === 'Matterbridge') {
|
|
2393
|
-
await this.matterbridgeContext?.clearAll();
|
|
2394
|
-
this.matterbridgeFabricInformations = [];
|
|
2395
|
-
this.matterbridgeSessionInformations = [];
|
|
2396
|
-
this.matterbridgePaired = false;
|
|
2397
|
-
this.matterbridgeConnected = false;
|
|
2398
|
-
}
|
|
2399
|
-
else {
|
|
2400
|
-
for (const plugin of this.plugins) {
|
|
2401
|
-
if (plugin.name === pluginName) {
|
|
2402
|
-
await plugin.platform?.onShutdown('Commissioning removed by the controller');
|
|
2403
|
-
plugin.fabricInformations = [];
|
|
2404
|
-
plugin.sessionInformations = [];
|
|
2405
|
-
plugin.paired = false;
|
|
2406
|
-
plugin.connected = false;
|
|
2407
|
-
await plugin.storageContext?.clearAll();
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
this.log.warn(`Restart to activate the pairing for ${plg}${pluginName}${wr}.`);
|
|
2412
|
-
}
|
|
2413
|
-
else {
|
|
2414
|
-
const fabricInfo = commissioningServer.getCommissionedFabricInformation();
|
|
2415
|
-
if (pluginName === 'Matterbridge') {
|
|
2416
|
-
this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2417
|
-
this.matterbridgePaired = true;
|
|
2418
|
-
}
|
|
2419
|
-
else {
|
|
2420
|
-
const plugin = this.plugins.get(pluginName);
|
|
2421
|
-
if (plugin) {
|
|
2422
|
-
plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2423
|
-
plugin.paired = true;
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
this.wssSendRefreshRequired();
|
|
2428
|
-
},
|
|
2429
|
-
});
|
|
2430
|
-
if (this.passcode !== undefined)
|
|
2431
|
-
this.passcode++;
|
|
2432
|
-
if (this.discriminator !== undefined)
|
|
2433
|
-
this.discriminator++;
|
|
2434
|
-
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
2435
|
-
return commissioningServer;
|
|
1783
|
+
this.log.info('Matter node storage closed');
|
|
2436
1784
|
}
|
|
2437
1785
|
/**
|
|
2438
|
-
* Creates a
|
|
1786
|
+
* Creates a server node storage context.
|
|
2439
1787
|
*
|
|
2440
1788
|
* @param pluginName - The name of the plugin.
|
|
2441
1789
|
* @param deviceName - The name of the device.
|
|
2442
|
-
* @param deviceType - The
|
|
1790
|
+
* @param deviceType - The deviceType of the device.
|
|
2443
1791
|
* @param vendorId - The vendor ID.
|
|
2444
1792
|
* @param vendorName - The vendor name.
|
|
2445
1793
|
* @param productId - The product ID.
|
|
@@ -2452,12 +1800,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2452
1800
|
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2453
1801
|
* @returns The storage context for the commissioning server.
|
|
2454
1802
|
*/
|
|
2455
|
-
async
|
|
2456
|
-
if (!this.
|
|
2457
|
-
throw new Error('No storage
|
|
2458
|
-
this.log.
|
|
1803
|
+
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1804
|
+
if (!this.matterStorageService)
|
|
1805
|
+
throw new Error('No storage service initialized');
|
|
1806
|
+
this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
|
|
1807
|
+
const storageManager = await this.matterStorageService.open(pluginName);
|
|
1808
|
+
const storageContext = storageManager.createContext('persist');
|
|
2459
1809
|
const random = randomBytes(8).toString('hex');
|
|
2460
|
-
|
|
1810
|
+
await storageContext.set('storeId', pluginName);
|
|
2461
1811
|
await storageContext.set('deviceName', deviceName);
|
|
2462
1812
|
await storageContext.set('deviceType', deviceType);
|
|
2463
1813
|
await storageContext.set('vendorId', vendorId);
|
|
@@ -2466,125 +1816,317 @@ export class Matterbridge extends EventEmitter {
|
|
|
2466
1816
|
await storageContext.set('productName', productName.slice(0, 32));
|
|
2467
1817
|
await storageContext.set('nodeLabel', productName.slice(0, 32));
|
|
2468
1818
|
await storageContext.set('productLabel', productName.slice(0, 32));
|
|
2469
|
-
await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
|
|
2470
|
-
await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
|
|
2471
|
-
await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
|
|
2472
|
-
await storageContext.set('softwareVersionString', this.matterbridgeVersion
|
|
2473
|
-
await storageContext.set('hardwareVersion', this.systemInformation.osRelease && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
|
|
2474
|
-
await storageContext.set('hardwareVersionString', this.systemInformation.osRelease
|
|
2475
|
-
this.log.debug(`Created
|
|
2476
|
-
this.log.debug(`-
|
|
2477
|
-
this.log.debug(`-
|
|
1819
|
+
await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
|
|
1820
|
+
await storageContext.set('uniqueId', await storageContext.get('uniqueId', 'UI' + random));
|
|
1821
|
+
await storageContext.set('softwareVersion', this.matterbridgeVersion !== '' && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
|
|
1822
|
+
await storageContext.set('softwareVersionString', this.matterbridgeVersion !== '' ? this.matterbridgeVersion : '1.0.0');
|
|
1823
|
+
await storageContext.set('hardwareVersion', this.systemInformation.osRelease !== '' && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
|
|
1824
|
+
await storageContext.set('hardwareVersionString', this.systemInformation.osRelease !== '' ? this.systemInformation.osRelease : '1.0.0');
|
|
1825
|
+
this.log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`);
|
|
1826
|
+
this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
|
|
1827
|
+
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
|
|
1828
|
+
this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
1829
|
+
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
|
|
1830
|
+
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2478
1831
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2479
1832
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2480
1833
|
return storageContext;
|
|
2481
1834
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
async importCommissioningServerContext(pluginName, device) {
|
|
2490
|
-
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
2491
|
-
const basic = device.getClusterServer(BasicInformationCluster);
|
|
2492
|
-
if (!basic) {
|
|
2493
|
-
this.log.error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
|
|
2494
|
-
process.exit(1);
|
|
2495
|
-
}
|
|
2496
|
-
if (!this.storageManager) {
|
|
2497
|
-
this.log.error('importCommissioningServerContext error: no storage manager initialized');
|
|
2498
|
-
process.exit(1);
|
|
2499
|
-
}
|
|
2500
|
-
this.log.debug(`Importing commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2501
|
-
const storageContext = this.storageManager.createContext(pluginName);
|
|
2502
|
-
await storageContext.set('deviceName', basic.getNodeLabelAttribute());
|
|
2503
|
-
await storageContext.set('deviceType', DeviceTypeId(device.deviceType));
|
|
2504
|
-
await storageContext.set('vendorId', basic.getVendorIdAttribute());
|
|
2505
|
-
await storageContext.set('vendorName', basic.getVendorNameAttribute());
|
|
2506
|
-
await storageContext.set('productId', basic.getProductIdAttribute());
|
|
2507
|
-
await storageContext.set('productName', basic.getProductNameAttribute());
|
|
2508
|
-
await storageContext.set('nodeLabel', basic.getNodeLabelAttribute());
|
|
2509
|
-
await storageContext.set('productLabel', basic.getNodeLabelAttribute());
|
|
2510
|
-
await storageContext.set('serialNumber', basic.attributes.serialNumber?.getLocal());
|
|
2511
|
-
await storageContext.set('uniqueId', basic.attributes.uniqueId?.getLocal());
|
|
2512
|
-
await storageContext.set('softwareVersion', basic.getSoftwareVersionAttribute());
|
|
2513
|
-
await storageContext.set('softwareVersionString', basic.getSoftwareVersionStringAttribute());
|
|
2514
|
-
await storageContext.set('hardwareVersion', basic.getHardwareVersionAttribute());
|
|
2515
|
-
await storageContext.set('hardwareVersionString', basic.getHardwareVersionStringAttribute());
|
|
2516
|
-
this.log.debug(`Imported commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2517
|
-
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
2518
|
-
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1835
|
+
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1836
|
+
const storeId = await storageContext.get('storeId');
|
|
1837
|
+
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
1838
|
+
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
|
|
1839
|
+
this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
1840
|
+
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
|
|
1841
|
+
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2519
1842
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2520
1843
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
//
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
1844
|
+
/**
|
|
1845
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1846
|
+
*/
|
|
1847
|
+
const serverNode = await ServerNode.create({
|
|
1848
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1849
|
+
id: storeId,
|
|
1850
|
+
// Provide Network relevant configuration like the port
|
|
1851
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1852
|
+
network: {
|
|
1853
|
+
listeningAddressIpv4: this.ipv4address,
|
|
1854
|
+
listeningAddressIpv6: this.ipv6address,
|
|
1855
|
+
port,
|
|
1856
|
+
},
|
|
1857
|
+
// Provide Commissioning relevant settings
|
|
1858
|
+
// Optional for development/testing purposes
|
|
1859
|
+
commissioning: {
|
|
1860
|
+
passcode,
|
|
1861
|
+
discriminator,
|
|
1862
|
+
},
|
|
1863
|
+
// Provide Node announcement settings
|
|
1864
|
+
// Optional: If Ommitted some development defaults are used
|
|
1865
|
+
productDescription: {
|
|
1866
|
+
name: await storageContext.get('deviceName'),
|
|
1867
|
+
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1868
|
+
},
|
|
1869
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1870
|
+
// Optional: If Omitted some development defaults are used
|
|
1871
|
+
basicInformation: {
|
|
1872
|
+
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1873
|
+
vendorName: await storageContext.get('vendorName'),
|
|
1874
|
+
productId: await storageContext.get('productId'),
|
|
1875
|
+
productName: await storageContext.get('productName'),
|
|
1876
|
+
productLabel: await storageContext.get('productName'),
|
|
1877
|
+
nodeLabel: await storageContext.get('productName'),
|
|
1878
|
+
serialNumber: await storageContext.get('serialNumber'),
|
|
1879
|
+
uniqueId: await storageContext.get('uniqueId'),
|
|
1880
|
+
softwareVersion: await storageContext.get('softwareVersion'),
|
|
1881
|
+
softwareVersionString: await storageContext.get('softwareVersionString'),
|
|
1882
|
+
hardwareVersion: await storageContext.get('hardwareVersion'),
|
|
1883
|
+
hardwareVersionString: await storageContext.get('hardwareVersionString'),
|
|
1884
|
+
},
|
|
1885
|
+
});
|
|
1886
|
+
const sanitizeFabrics = (fabrics) => {
|
|
1887
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1888
|
+
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1889
|
+
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1890
|
+
if (this.bridgeMode === 'bridge') {
|
|
1891
|
+
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1892
|
+
this.matterbridgeSessionInformations = [];
|
|
1893
|
+
this.matterbridgePaired = true;
|
|
1894
|
+
}
|
|
1895
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1896
|
+
const plugin = this.plugins.get(storeId);
|
|
1897
|
+
if (plugin) {
|
|
1898
|
+
plugin.fabricInformations = sanitizedFabrics;
|
|
1899
|
+
plugin.sessionInformations = [];
|
|
1900
|
+
plugin.paired = true;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
/**
|
|
1905
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1906
|
+
* This means: It is added to the first fabric.
|
|
1907
|
+
*/
|
|
1908
|
+
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1909
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1910
|
+
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1911
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1912
|
+
serverNode.lifecycle.online.on(() => {
|
|
1913
|
+
this.log.notice(`Server node for ${storeId} is online`);
|
|
1914
|
+
if (!serverNode.lifecycle.isCommissioned) {
|
|
1915
|
+
this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
|
|
1916
|
+
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
1917
|
+
if (this.bridgeMode === 'bridge') {
|
|
1918
|
+
this.matterbridgeQrPairingCode = qrPairingCode;
|
|
1919
|
+
this.matterbridgeManualPairingCode = manualPairingCode;
|
|
1920
|
+
this.matterbridgeFabricInformations = [];
|
|
1921
|
+
this.matterbridgeSessionInformations = [];
|
|
1922
|
+
this.matterbridgePaired = false;
|
|
1923
|
+
this.matterbridgeConnected = false;
|
|
1924
|
+
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1925
|
+
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1926
|
+
}
|
|
1927
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1928
|
+
const plugin = this.plugins.get(storeId);
|
|
1929
|
+
if (plugin) {
|
|
1930
|
+
plugin.qrPairingCode = qrPairingCode;
|
|
1931
|
+
plugin.manualPairingCode = manualPairingCode;
|
|
1932
|
+
plugin.fabricInformations = [];
|
|
1933
|
+
plugin.sessionInformations = [];
|
|
1934
|
+
plugin.paired = false;
|
|
1935
|
+
plugin.connected = false;
|
|
1936
|
+
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1937
|
+
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
1943
|
+
sanitizeFabrics(serverNode.state.commissioning.fabrics);
|
|
1944
|
+
}
|
|
1945
|
+
this.frontend.wssSendRefreshRequired();
|
|
1946
|
+
});
|
|
1947
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1948
|
+
serverNode.lifecycle.offline.on(() => {
|
|
1949
|
+
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1950
|
+
if (this.bridgeMode === 'bridge') {
|
|
1951
|
+
this.matterbridgeQrPairingCode = undefined;
|
|
1952
|
+
this.matterbridgeManualPairingCode = undefined;
|
|
2548
1953
|
this.matterbridgeFabricInformations = [];
|
|
2549
1954
|
this.matterbridgeSessionInformations = [];
|
|
2550
1955
|
this.matterbridgePaired = false;
|
|
2551
1956
|
this.matterbridgeConnected = false;
|
|
2552
1957
|
}
|
|
2553
|
-
|
|
2554
|
-
|
|
1958
|
+
this.frontend.wssSendRefreshRequired();
|
|
1959
|
+
});
|
|
1960
|
+
/**
|
|
1961
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
1962
|
+
* information is needed.
|
|
1963
|
+
*/
|
|
1964
|
+
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1965
|
+
let action = '';
|
|
1966
|
+
switch (fabricAction) {
|
|
1967
|
+
case FabricAction.Added:
|
|
1968
|
+
action = 'added';
|
|
1969
|
+
break;
|
|
1970
|
+
case FabricAction.Removed:
|
|
1971
|
+
action = 'removed';
|
|
1972
|
+
break;
|
|
1973
|
+
case FabricAction.Updated:
|
|
1974
|
+
action = 'updated';
|
|
1975
|
+
break;
|
|
1976
|
+
}
|
|
1977
|
+
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1978
|
+
sanitizeFabrics(serverNode.state.commissioning.fabrics);
|
|
1979
|
+
this.frontend.wssSendRefreshRequired();
|
|
1980
|
+
});
|
|
1981
|
+
const sanitizeSessions = (sessions) => {
|
|
1982
|
+
const sanitizedSessions = this.sanitizeSessionInformation(sessions.map((session) => ({
|
|
1983
|
+
...session,
|
|
1984
|
+
secure: session.name.startsWith('secure'),
|
|
1985
|
+
})));
|
|
1986
|
+
this.log.debug(`Sessions: ${debugStringify(sanitizedSessions)}`);
|
|
1987
|
+
if (this.bridgeMode === 'bridge') {
|
|
1988
|
+
this.matterbridgeSessionInformations = sanitizedSessions;
|
|
1989
|
+
}
|
|
1990
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1991
|
+
const plugin = this.plugins.get(storeId);
|
|
2555
1992
|
if (plugin) {
|
|
2556
|
-
plugin.
|
|
2557
|
-
plugin.manualPairingCode = manualPairingCode;
|
|
2558
|
-
plugin.fabricInformations = [];
|
|
2559
|
-
plugin.sessionInformations = [];
|
|
2560
|
-
plugin.paired = false;
|
|
2561
|
-
plugin.connected = false;
|
|
1993
|
+
plugin.sessionInformations = sanitizedSessions;
|
|
2562
1994
|
}
|
|
2563
1995
|
}
|
|
1996
|
+
};
|
|
1997
|
+
/**
|
|
1998
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
1999
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2000
|
+
*/
|
|
2001
|
+
serverNode.events.sessions.opened.on((session) => {
|
|
2002
|
+
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2003
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2004
|
+
this.frontend.wssSendRefreshRequired();
|
|
2005
|
+
});
|
|
2006
|
+
/**
|
|
2007
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2008
|
+
*/
|
|
2009
|
+
serverNode.events.sessions.closed.on((session) => {
|
|
2010
|
+
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2011
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2012
|
+
this.frontend.wssSendRefreshRequired();
|
|
2013
|
+
});
|
|
2014
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2015
|
+
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2016
|
+
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2017
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2018
|
+
this.frontend.wssSendRefreshRequired();
|
|
2019
|
+
});
|
|
2020
|
+
this.log.info(`Created server node for ${storeId}`);
|
|
2021
|
+
return serverNode;
|
|
2022
|
+
}
|
|
2023
|
+
async startServerNode(matterServerNode) {
|
|
2024
|
+
if (!matterServerNode)
|
|
2025
|
+
return;
|
|
2026
|
+
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2027
|
+
await matterServerNode.start();
|
|
2028
|
+
}
|
|
2029
|
+
async stopServerNode(matterServerNode) {
|
|
2030
|
+
if (!matterServerNode)
|
|
2031
|
+
return;
|
|
2032
|
+
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2033
|
+
await matterServerNode.close();
|
|
2034
|
+
}
|
|
2035
|
+
async createAggregatorNode(storageContext) {
|
|
2036
|
+
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
2037
|
+
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2038
|
+
return aggregatorNode;
|
|
2039
|
+
}
|
|
2040
|
+
async addBridgedEndpoint(pluginName, device) {
|
|
2041
|
+
// Check if the plugin is registered
|
|
2042
|
+
const plugin = this.plugins.get(pluginName);
|
|
2043
|
+
if (!plugin) {
|
|
2044
|
+
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
2045
|
+
return;
|
|
2564
2046
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
if (
|
|
2569
|
-
this.log.
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
if (
|
|
2574
|
-
this.
|
|
2575
|
-
this.matterbridgeSessionInformations = [];
|
|
2576
|
-
this.matterbridgePaired = true;
|
|
2047
|
+
// Register and add the device to the matterbridge aggregator node
|
|
2048
|
+
if (this.bridgeMode === 'bridge') {
|
|
2049
|
+
this.log.debug(`Adding ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2050
|
+
if (!this.aggregatorNode)
|
|
2051
|
+
this.log.error('Aggregator node not found for Matterbridge');
|
|
2052
|
+
await this.aggregatorNode?.add(device);
|
|
2053
|
+
}
|
|
2054
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
2055
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
2056
|
+
this.createAccessoryPlugin(plugin, device);
|
|
2577
2057
|
}
|
|
2578
|
-
if (
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2058
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
2059
|
+
plugin.locked = true;
|
|
2060
|
+
this.log.debug(`Adding ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} aggregator node`);
|
|
2061
|
+
if (!plugin.aggregatorNode)
|
|
2062
|
+
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${db}`);
|
|
2063
|
+
await plugin.aggregatorNode?.add(device);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
if (plugin.registeredDevices !== undefined)
|
|
2067
|
+
plugin.registeredDevices++;
|
|
2068
|
+
if (plugin.addedDevices !== undefined)
|
|
2069
|
+
plugin.addedDevices++;
|
|
2070
|
+
// Add the device to the DeviceManager
|
|
2071
|
+
this.devices.set(device);
|
|
2072
|
+
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
2073
|
+
}
|
|
2074
|
+
async removeBridgedEndpoint(pluginName, device) {
|
|
2075
|
+
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2076
|
+
// Check if the plugin is registered
|
|
2077
|
+
const plugin = this.plugins.get(pluginName);
|
|
2078
|
+
if (!plugin) {
|
|
2079
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
// Register and add the device to the matterbridge aggregator node
|
|
2083
|
+
if (this.bridgeMode === 'bridge') {
|
|
2084
|
+
if (!this.aggregatorNode) {
|
|
2085
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
await device.delete();
|
|
2089
|
+
this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
2090
|
+
if (plugin.registeredDevices !== undefined)
|
|
2091
|
+
plugin.registeredDevices--;
|
|
2092
|
+
if (plugin.addedDevices !== undefined)
|
|
2093
|
+
plugin.addedDevices--;
|
|
2094
|
+
}
|
|
2095
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
2096
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
2097
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2098
|
+
}
|
|
2099
|
+
else if (plugin.type === 'DynamicPlatform') {
|
|
2100
|
+
if (!plugin.aggregatorNode) {
|
|
2101
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
await device.delete();
|
|
2105
|
+
}
|
|
2106
|
+
this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
2107
|
+
if (plugin.registeredDevices !== undefined)
|
|
2108
|
+
plugin.registeredDevices--;
|
|
2109
|
+
if (plugin.addedDevices !== undefined)
|
|
2110
|
+
plugin.addedDevices--;
|
|
2111
|
+
// Close the server node TODO check if this is correct
|
|
2112
|
+
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
2113
|
+
if (plugin.serverNode) {
|
|
2114
|
+
await this.stopServerNode(plugin.serverNode);
|
|
2115
|
+
plugin.locked = false;
|
|
2116
|
+
plugin.aggregatorNode = undefined;
|
|
2117
|
+
plugin.serverNode = undefined;
|
|
2118
|
+
this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
|
|
2584
2119
|
}
|
|
2585
2120
|
}
|
|
2586
2121
|
}
|
|
2587
|
-
|
|
2122
|
+
// Remove the device from the DeviceManager
|
|
2123
|
+
this.devices.remove(device);
|
|
2124
|
+
}
|
|
2125
|
+
async removeAllBridgedEndpoints(pluginName) {
|
|
2126
|
+
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
2127
|
+
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
2128
|
+
await this.removeBridgedEndpoint(pluginName, device);
|
|
2129
|
+
}
|
|
2588
2130
|
}
|
|
2589
2131
|
/**
|
|
2590
2132
|
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
@@ -2639,47 +2181,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
2639
2181
|
});
|
|
2640
2182
|
}
|
|
2641
2183
|
/**
|
|
2642
|
-
* Sets the reachability of a
|
|
2184
|
+
* Sets the reachability of a matter server node and trigger ReachableChanged event.
|
|
2643
2185
|
*
|
|
2644
|
-
* @param {
|
|
2186
|
+
* @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
|
|
2645
2187
|
* @param {boolean} reachable - The new reachability status.
|
|
2646
2188
|
*/
|
|
2647
|
-
|
|
2189
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2190
|
+
setServerNodeReachability(serverNode, reachable) {
|
|
2191
|
+
/*
|
|
2648
2192
|
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2649
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2193
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2194
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2195
|
+
*/
|
|
2653
2196
|
}
|
|
2654
2197
|
/**
|
|
2655
2198
|
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2656
|
-
* @param {
|
|
2199
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
|
|
2657
2200
|
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2658
2201
|
*/
|
|
2659
|
-
|
|
2202
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2203
|
+
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2204
|
+
/*
|
|
2660
2205
|
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2661
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2662
|
-
|
|
2663
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2664
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2206
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2207
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2665
2208
|
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2209
|
+
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2210
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2211
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2669
2212
|
});
|
|
2213
|
+
*/
|
|
2670
2214
|
}
|
|
2671
2215
|
/**
|
|
2672
2216
|
* Sets the reachability of a device and trigger.
|
|
2673
2217
|
*
|
|
2674
|
-
* @param {
|
|
2218
|
+
* @param {MatterbridgeEndpoint} device - The device to set the reachability for.
|
|
2675
2219
|
* @param {boolean} reachable - The new reachability status of the device.
|
|
2676
2220
|
*/
|
|
2221
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2677
2222
|
setDeviceReachability(device, reachable) {
|
|
2223
|
+
/*
|
|
2678
2224
|
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2679
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2225
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2226
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2227
|
+
*/
|
|
2683
2228
|
}
|
|
2684
2229
|
getVendorIdName = (vendorId) => {
|
|
2685
2230
|
if (!vendorId)
|
|
@@ -2722,41 +2267,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2722
2267
|
}
|
|
2723
2268
|
return vendorName;
|
|
2724
2269
|
};
|
|
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
|
-
*/
|
|
2729
|
-
async getBaseRegisteredPlugins() {
|
|
2730
|
-
const baseRegisteredPlugins = [];
|
|
2731
|
-
for (const plugin of this.plugins) {
|
|
2732
|
-
baseRegisteredPlugins.push({
|
|
2733
|
-
path: plugin.path,
|
|
2734
|
-
type: plugin.type,
|
|
2735
|
-
name: plugin.name,
|
|
2736
|
-
version: plugin.version,
|
|
2737
|
-
description: plugin.description,
|
|
2738
|
-
author: plugin.author,
|
|
2739
|
-
latestVersion: plugin.latestVersion,
|
|
2740
|
-
locked: plugin.locked,
|
|
2741
|
-
error: plugin.error,
|
|
2742
|
-
enabled: plugin.enabled,
|
|
2743
|
-
loaded: plugin.loaded,
|
|
2744
|
-
started: plugin.started,
|
|
2745
|
-
configured: plugin.configured,
|
|
2746
|
-
paired: plugin.paired,
|
|
2747
|
-
connected: plugin.connected,
|
|
2748
|
-
fabricInformations: plugin.fabricInformations,
|
|
2749
|
-
sessionInformations: plugin.sessionInformations,
|
|
2750
|
-
registeredDevices: plugin.registeredDevices,
|
|
2751
|
-
addedDevices: plugin.addedDevices,
|
|
2752
|
-
qrPairingCode: plugin.qrPairingCode,
|
|
2753
|
-
manualPairingCode: plugin.manualPairingCode,
|
|
2754
|
-
configJson: plugin.configJson,
|
|
2755
|
-
schemaJson: plugin.schemaJson,
|
|
2756
|
-
});
|
|
2757
|
-
}
|
|
2758
|
-
return baseRegisteredPlugins;
|
|
2759
|
-
}
|
|
2760
2270
|
/**
|
|
2761
2271
|
* Spawns a child process with the given command and arguments.
|
|
2762
2272
|
* @param {string} command - The command to execute.
|
|
@@ -2801,7 +2311,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2801
2311
|
reject(err);
|
|
2802
2312
|
});
|
|
2803
2313
|
childProcess.on('close', (code, signal) => {
|
|
2804
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
|
|
2314
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
|
|
2805
2315
|
if (code === 0) {
|
|
2806
2316
|
if (cmdLine.startsWith('npm install -g'))
|
|
2807
2317
|
this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
|
|
@@ -2814,7 +2324,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2814
2324
|
}
|
|
2815
2325
|
});
|
|
2816
2326
|
childProcess.on('exit', (code, signal) => {
|
|
2817
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
2327
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
2818
2328
|
if (code === 0) {
|
|
2819
2329
|
this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
|
|
2820
2330
|
resolve(true);
|
|
@@ -2832,1060 +2342,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2832
2342
|
childProcess.stdout.on('data', (data) => {
|
|
2833
2343
|
const message = data.toString().trim();
|
|
2834
2344
|
this.log.debug(`Spawn output (stdout): ${message}`);
|
|
2835
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
2345
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
2836
2346
|
});
|
|
2837
2347
|
}
|
|
2838
2348
|
if (childProcess.stderr) {
|
|
2839
2349
|
childProcess.stderr.on('data', (data) => {
|
|
2840
2350
|
const message = data.toString().trim();
|
|
2841
2351
|
this.log.debug(`Spawn verbose (stderr): ${message}`);
|
|
2842
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
});
|
|
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
|
-
*/
|
|
2855
|
-
wssSendMessage(level, time, name, message) {
|
|
2856
|
-
if (!level || !time || !name || !message)
|
|
2857
|
-
return;
|
|
2858
|
-
// Remove ANSI escape codes from the message
|
|
2859
|
-
// eslint-disable-next-line no-control-regex
|
|
2860
|
-
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2861
|
-
// Remove leading asterisks from the message
|
|
2862
|
-
message = message.replace(/^\*+/, '');
|
|
2863
|
-
// Replace all occurrences of \t and \n
|
|
2864
|
-
message = message.replace(/[\t\n]/g, '');
|
|
2865
|
-
// Remove non-printable characters
|
|
2866
|
-
// eslint-disable-next-line no-control-regex
|
|
2867
|
-
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2868
|
-
// Replace all occurrences of \" with "
|
|
2869
|
-
message = message.replace(/\\"/g, '"');
|
|
2870
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2871
|
-
const maxContinuousLength = 100;
|
|
2872
|
-
const keepStartLength = 20;
|
|
2873
|
-
const keepEndLength = 20;
|
|
2874
|
-
// Split the message into words
|
|
2875
|
-
message = message
|
|
2876
|
-
.split(' ')
|
|
2877
|
-
.map((word) => {
|
|
2878
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2879
|
-
if (word.length > maxContinuousLength) {
|
|
2880
|
-
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2881
|
-
}
|
|
2882
|
-
return word;
|
|
2883
|
-
})
|
|
2884
|
-
.join(' ');
|
|
2885
|
-
// Send the message to all connected clients
|
|
2886
|
-
this.webSocketServer?.clients.forEach((client) => {
|
|
2887
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
2888
|
-
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2889
|
-
}
|
|
2890
|
-
});
|
|
2891
|
-
}
|
|
2892
|
-
/**
|
|
2893
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2894
|
-
*
|
|
2895
|
-
*/
|
|
2896
|
-
wssSendRefreshRequired() {
|
|
2897
|
-
this.matterbridgeInformation.refreshRequired = true;
|
|
2898
|
-
// Send the message to all connected clients
|
|
2899
|
-
this.webSocketServer?.clients.forEach((client) => {
|
|
2900
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
2901
|
-
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
2902
|
-
}
|
|
2903
|
-
});
|
|
2904
|
-
}
|
|
2905
|
-
/**
|
|
2906
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2907
|
-
*
|
|
2908
|
-
*/
|
|
2909
|
-
wssSendRestartRequired() {
|
|
2910
|
-
this.matterbridgeInformation.restartRequired = true;
|
|
2911
|
-
// Send the message to all connected clients
|
|
2912
|
-
this.webSocketServer?.clients.forEach((client) => {
|
|
2913
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
2914
|
-
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
2915
|
-
}
|
|
2916
|
-
});
|
|
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
|
-
*/
|
|
2923
|
-
async initializeFrontend(port = 8283) {
|
|
2924
|
-
let initializeError = false;
|
|
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
|
|
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
|
|
2938
|
-
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2939
|
-
if (!hasParameter('ssl')) {
|
|
2940
|
-
// Create an HTTP server and attach the express app
|
|
2941
|
-
this.httpServer = createServer(this.expressApp);
|
|
2942
|
-
// Listen on the specified port
|
|
2943
|
-
if (hasParameter('ingress')) {
|
|
2944
|
-
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2945
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
2946
|
-
});
|
|
2947
|
-
}
|
|
2948
|
-
else {
|
|
2949
|
-
this.httpServer.listen(port, () => {
|
|
2950
|
-
if (this.systemInformation.ipv4Address !== '')
|
|
2951
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
2952
|
-
if (this.systemInformation.ipv6Address !== '')
|
|
2953
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2954
|
-
});
|
|
2955
|
-
}
|
|
2956
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2957
|
-
this.httpServer.on('error', (error) => {
|
|
2958
|
-
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2959
|
-
switch (error.code) {
|
|
2960
|
-
case 'EACCES':
|
|
2961
|
-
this.log.error(`Port ${port} requires elevated privileges`);
|
|
2962
|
-
break;
|
|
2963
|
-
case 'EADDRINUSE':
|
|
2964
|
-
this.log.error(`Port ${port} is already in use`);
|
|
2965
|
-
break;
|
|
2966
|
-
}
|
|
2967
|
-
initializeError = true;
|
|
2968
|
-
return;
|
|
2969
|
-
});
|
|
2970
|
-
}
|
|
2971
|
-
else {
|
|
2972
|
-
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2973
|
-
let cert;
|
|
2974
|
-
try {
|
|
2975
|
-
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
2976
|
-
this.log.info(`Loaded certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
2977
|
-
}
|
|
2978
|
-
catch (error) {
|
|
2979
|
-
this.log.error(`Error reading certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
|
|
2980
|
-
return;
|
|
2981
|
-
}
|
|
2982
|
-
let key;
|
|
2983
|
-
try {
|
|
2984
|
-
key = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
2985
|
-
this.log.info(`Loaded key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}`);
|
|
2986
|
-
}
|
|
2987
|
-
catch (error) {
|
|
2988
|
-
this.log.error(`Error reading key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
|
|
2989
|
-
return;
|
|
2990
|
-
}
|
|
2991
|
-
let ca;
|
|
2992
|
-
try {
|
|
2993
|
-
ca = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
|
|
2994
|
-
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')}`);
|
|
2995
|
-
}
|
|
2996
|
-
catch (error) {
|
|
2997
|
-
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2998
|
-
}
|
|
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
|
|
3001
|
-
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
3002
|
-
// Listen on the specified port
|
|
3003
|
-
if (hasParameter('ingress')) {
|
|
3004
|
-
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
3005
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
3006
|
-
});
|
|
3007
|
-
}
|
|
3008
|
-
else {
|
|
3009
|
-
this.httpsServer.listen(port, () => {
|
|
3010
|
-
if (this.systemInformation.ipv4Address !== '')
|
|
3011
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
3012
|
-
if (this.systemInformation.ipv6Address !== '')
|
|
3013
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
3014
|
-
});
|
|
3015
|
-
}
|
|
3016
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3017
|
-
this.httpsServer.on('error', (error) => {
|
|
3018
|
-
this.log.error(`Frontend https server error listening on ${port}`);
|
|
3019
|
-
switch (error.code) {
|
|
3020
|
-
case 'EACCES':
|
|
3021
|
-
this.log.error(`Port ${port} requires elevated privileges`);
|
|
3022
|
-
break;
|
|
3023
|
-
case 'EADDRINUSE':
|
|
3024
|
-
this.log.error(`Port ${port} is already in use`);
|
|
3025
|
-
break;
|
|
3026
|
-
}
|
|
3027
|
-
initializeError = true;
|
|
3028
|
-
return;
|
|
3029
|
-
});
|
|
3030
|
-
}
|
|
3031
|
-
if (initializeError)
|
|
3032
|
-
return;
|
|
3033
|
-
// Createe a WebSocket server and attach it to the http or https server
|
|
3034
|
-
const wssPort = port;
|
|
3035
|
-
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
3036
|
-
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
3037
|
-
this.webSocketServer.on('connection', (ws, request) => {
|
|
3038
|
-
const clientIp = request.socket.remoteAddress;
|
|
3039
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
3040
|
-
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
3041
|
-
ws.on('message', (message) => {
|
|
3042
|
-
this.log.debug(`WebSocket client message: ${message}`);
|
|
3043
|
-
this.matterbridgeMessageHandler(ws, message);
|
|
3044
|
-
});
|
|
3045
|
-
ws.on('ping', () => {
|
|
3046
|
-
this.log.debug('WebSocket client ping');
|
|
3047
|
-
ws.pong();
|
|
3048
|
-
});
|
|
3049
|
-
ws.on('pong', () => {
|
|
3050
|
-
this.log.debug('WebSocket client pong');
|
|
3051
|
-
});
|
|
3052
|
-
ws.on('close', () => {
|
|
3053
|
-
this.log.info('WebSocket client disconnected');
|
|
3054
|
-
if (this.webSocketServer?.clients.size === 0) {
|
|
3055
|
-
AnsiLogger.setGlobalCallback(undefined);
|
|
3056
|
-
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
3057
|
-
}
|
|
3058
|
-
});
|
|
3059
|
-
ws.on('error', (error) => {
|
|
3060
|
-
this.log.error(`WebSocket client error: ${error}`);
|
|
3061
|
-
});
|
|
3062
|
-
});
|
|
3063
|
-
this.webSocketServer.on('close', () => {
|
|
3064
|
-
this.log.debug(`WebSocketServer closed`);
|
|
3065
|
-
});
|
|
3066
|
-
this.webSocketServer.on('listening', () => {
|
|
3067
|
-
this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
|
|
3068
|
-
});
|
|
3069
|
-
this.webSocketServer.on('error', (ws, error) => {
|
|
3070
|
-
this.log.error(`WebSocketServer error: ${error}`);
|
|
3071
|
-
});
|
|
3072
|
-
// Endpoint to validate login code
|
|
3073
|
-
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
3074
|
-
const { password } = req.body;
|
|
3075
|
-
this.log.debug('The frontend sent /api/login', password);
|
|
3076
|
-
if (!this.nodeContext) {
|
|
3077
|
-
this.log.error('/api/login nodeContext not found');
|
|
3078
|
-
res.json({ valid: false });
|
|
3079
|
-
return;
|
|
3080
|
-
}
|
|
3081
|
-
try {
|
|
3082
|
-
const storedPassword = await this.nodeContext.get('password', '');
|
|
3083
|
-
if (storedPassword === '' || password === storedPassword) {
|
|
3084
|
-
this.log.debug('/api/login password valid');
|
|
3085
|
-
res.json({ valid: true });
|
|
3086
|
-
}
|
|
3087
|
-
else {
|
|
3088
|
-
this.log.warn('/api/login error wrong password');
|
|
3089
|
-
res.json({ valid: false });
|
|
3090
|
-
}
|
|
3091
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3092
|
-
}
|
|
3093
|
-
catch (error) {
|
|
3094
|
-
this.log.error('/api/login error getting password');
|
|
3095
|
-
res.json({ valid: false });
|
|
3096
|
-
}
|
|
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
|
|
3109
|
-
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
3110
|
-
this.log.debug('The frontend sent /api/settings');
|
|
3111
|
-
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
3112
|
-
this.matterbridgeInformation.restartMode = this.restartMode;
|
|
3113
|
-
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
3114
|
-
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
3115
|
-
this.matterbridgeInformation.mattermdnsinterface = (await this.nodeContext?.get('mattermdnsinterface', '')) || '';
|
|
3116
|
-
this.matterbridgeInformation.matteripv4address = (await this.nodeContext?.get('matteripv4address', '')) || '';
|
|
3117
|
-
this.matterbridgeInformation.matteripv6address = (await this.nodeContext?.get('matteripv6address', '')) || '';
|
|
3118
|
-
this.matterbridgeInformation.matterPort = (await this.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
3119
|
-
this.matterbridgeInformation.matterDiscriminator = await this.nodeContext?.get('matterdiscriminator');
|
|
3120
|
-
this.matterbridgeInformation.matterPasscode = await this.nodeContext?.get('matterpasscode');
|
|
3121
|
-
this.matterbridgeInformation.matterbridgePaired = this.matterbridgePaired;
|
|
3122
|
-
this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
|
|
3123
|
-
this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
|
|
3124
|
-
this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
|
|
3125
|
-
this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
|
|
3126
|
-
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
3127
|
-
this.matterbridgeInformation.profile = this.profile;
|
|
3128
|
-
const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
3129
|
-
// this.log.debug('Response:', debugStringify(response));
|
|
3130
|
-
res.json(response);
|
|
3131
|
-
});
|
|
3132
|
-
// Endpoint to provide plugins
|
|
3133
|
-
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
3134
|
-
this.log.debug('The frontend sent /api/plugins');
|
|
3135
|
-
const response = await this.getBaseRegisteredPlugins();
|
|
3136
|
-
// this.log.debug('Response:', debugStringify(response));
|
|
3137
|
-
res.json(response);
|
|
3138
|
-
});
|
|
3139
|
-
// Endpoint to provide devices
|
|
3140
|
-
this.expressApp.get('/api/devices', (req, res) => {
|
|
3141
|
-
this.log.debug('The frontend sent /api/devices');
|
|
3142
|
-
const devices = [];
|
|
3143
|
-
this.devices.forEach(async (device) => {
|
|
3144
|
-
const pluginName = device.plugin ?? 'Unknown';
|
|
3145
|
-
if (this.edge)
|
|
3146
|
-
device = EndpointServer.forEndpoint(device);
|
|
3147
|
-
let name = device.getClusterServer(BasicInformationCluster)?.attributes.nodeLabel?.getLocal();
|
|
3148
|
-
if (!name)
|
|
3149
|
-
name = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.nodeLabel?.getLocal() ?? 'Unknown';
|
|
3150
|
-
let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
|
|
3151
|
-
if (!serial)
|
|
3152
|
-
serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
|
|
3153
|
-
let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
|
|
3154
|
-
if (!productUrl)
|
|
3155
|
-
productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
|
|
3156
|
-
let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
|
|
3157
|
-
if (!uniqueId)
|
|
3158
|
-
uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
|
|
3159
|
-
const cluster = this.getClusterTextFromDevice(device);
|
|
3160
|
-
devices.push({
|
|
3161
|
-
pluginName,
|
|
3162
|
-
type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
|
|
3163
|
-
endpoint: device.number,
|
|
3164
|
-
name,
|
|
3165
|
-
serial,
|
|
3166
|
-
productUrl,
|
|
3167
|
-
configUrl: device.configUrl,
|
|
3168
|
-
uniqueId,
|
|
3169
|
-
cluster: cluster,
|
|
2352
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
3170
2353
|
});
|
|
3171
|
-
});
|
|
3172
|
-
// this.log.debug('Response:', debugStringify(data));
|
|
3173
|
-
res.json(devices);
|
|
3174
|
-
});
|
|
3175
|
-
// Endpoint to provide the cluster servers of the devices
|
|
3176
|
-
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
3177
|
-
const selectedPluginName = req.params.selectedPluginName;
|
|
3178
|
-
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
3179
|
-
this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
|
|
3180
|
-
if (selectedPluginName === 'none') {
|
|
3181
|
-
res.json([]);
|
|
3182
|
-
return;
|
|
3183
|
-
}
|
|
3184
|
-
const data = [];
|
|
3185
|
-
this.devices.forEach(async (device) => {
|
|
3186
|
-
const pluginName = device.plugin;
|
|
3187
|
-
if (this.edge)
|
|
3188
|
-
device = EndpointServer.forEndpoint(device);
|
|
3189
|
-
if (pluginName === selectedPluginName && device.number === selectedDeviceEndpoint) {
|
|
3190
|
-
const clusterServers = device.getAllClusterServers();
|
|
3191
|
-
clusterServers.forEach((clusterServer) => {
|
|
3192
|
-
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
3193
|
-
if (clusterServer.name === 'EveHistory')
|
|
3194
|
-
return;
|
|
3195
|
-
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
3196
|
-
let attributeValue;
|
|
3197
|
-
try {
|
|
3198
|
-
if (typeof value.getLocal() === 'object')
|
|
3199
|
-
attributeValue = stringify(value.getLocal());
|
|
3200
|
-
else
|
|
3201
|
-
attributeValue = value.getLocal().toString();
|
|
3202
|
-
}
|
|
3203
|
-
catch (error) {
|
|
3204
|
-
attributeValue = 'Fabric-Scoped';
|
|
3205
|
-
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3206
|
-
// console.log(error);
|
|
3207
|
-
}
|
|
3208
|
-
data.push({
|
|
3209
|
-
endpoint: device.number ? device.number.toString() : '...',
|
|
3210
|
-
clusterName: clusterServer.name,
|
|
3211
|
-
clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
|
|
3212
|
-
attributeName: key,
|
|
3213
|
-
attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
|
|
3214
|
-
attributeValue,
|
|
3215
|
-
});
|
|
3216
|
-
});
|
|
3217
|
-
});
|
|
3218
|
-
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3219
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3220
|
-
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
3221
|
-
const clusterServers = childEndpoint.getAllClusterServers();
|
|
3222
|
-
clusterServers.forEach((clusterServer) => {
|
|
3223
|
-
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
3224
|
-
if (clusterServer.name === 'EveHistory')
|
|
3225
|
-
return;
|
|
3226
|
-
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
3227
|
-
let attributeValue;
|
|
3228
|
-
try {
|
|
3229
|
-
if (typeof value.getLocal() === 'object')
|
|
3230
|
-
attributeValue = stringify(value.getLocal());
|
|
3231
|
-
else
|
|
3232
|
-
attributeValue = value.getLocal().toString();
|
|
3233
|
-
}
|
|
3234
|
-
catch (error) {
|
|
3235
|
-
attributeValue = 'Unavailable';
|
|
3236
|
-
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3237
|
-
// console.log(error);
|
|
3238
|
-
}
|
|
3239
|
-
data.push({
|
|
3240
|
-
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
3241
|
-
clusterName: clusterServer.name,
|
|
3242
|
-
clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
|
|
3243
|
-
attributeName: key,
|
|
3244
|
-
attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
|
|
3245
|
-
attributeValue,
|
|
3246
|
-
});
|
|
3247
|
-
});
|
|
3248
|
-
});
|
|
3249
|
-
});
|
|
3250
|
-
}
|
|
3251
|
-
});
|
|
3252
|
-
res.json(data);
|
|
3253
|
-
});
|
|
3254
|
-
// Endpoint to view the log
|
|
3255
|
-
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
3256
|
-
this.log.debug('The frontend sent /api/log');
|
|
3257
|
-
try {
|
|
3258
|
-
const data = await fs.readFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'utf8');
|
|
3259
|
-
res.type('text/plain');
|
|
3260
|
-
res.send(data);
|
|
3261
|
-
}
|
|
3262
|
-
catch (error) {
|
|
3263
|
-
this.log.error(`Error reading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
3264
|
-
res.status(500).send('Error reading log file');
|
|
3265
|
-
}
|
|
3266
|
-
});
|
|
3267
|
-
// Endpoint to download the matterbridge log
|
|
3268
|
-
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
3269
|
-
this.log.debug('The frontend sent /api/download-mblog');
|
|
3270
|
-
try {
|
|
3271
|
-
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3272
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3273
|
-
}
|
|
3274
|
-
catch (error) {
|
|
3275
|
-
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
3276
|
-
}
|
|
3277
|
-
res.download(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
3278
|
-
if (error) {
|
|
3279
|
-
this.log.error(`Error downloading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
3280
|
-
res.status(500).send('Error downloading the matterbridge log file');
|
|
3281
|
-
}
|
|
3282
|
-
});
|
|
3283
|
-
});
|
|
3284
|
-
// Endpoint to download the matter log
|
|
3285
|
-
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
3286
|
-
this.log.debug('The frontend sent /api/download-mjlog');
|
|
3287
|
-
try {
|
|
3288
|
-
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3289
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3290
|
-
}
|
|
3291
|
-
catch (error) {
|
|
3292
|
-
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
3293
|
-
}
|
|
3294
|
-
res.download(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'matter.log', (error) => {
|
|
3295
|
-
if (error) {
|
|
3296
|
-
this.log.error(`Error downloading log file ${this.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
3297
|
-
res.status(500).send('Error downloading the matter log file');
|
|
3298
|
-
}
|
|
3299
|
-
});
|
|
3300
|
-
});
|
|
3301
|
-
// Endpoint to download the matter storage file
|
|
3302
|
-
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
3303
|
-
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
3304
|
-
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
3305
|
-
if (error) {
|
|
3306
|
-
this.log.error(`Error downloading log file ${this.matterStorageName}: ${error instanceof Error ? error.message : error}`);
|
|
3307
|
-
res.status(500).send('Error downloading the matter storage file');
|
|
3308
|
-
}
|
|
3309
|
-
});
|
|
3310
|
-
});
|
|
3311
|
-
// Endpoint to download the matterbridge storage directory
|
|
3312
|
-
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
3313
|
-
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
3314
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
3315
|
-
res.download(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), `matterbridge.${this.nodeStorageName}.zip`, (error) => {
|
|
3316
|
-
if (error) {
|
|
3317
|
-
this.log.error(`Error downloading file ${`matterbridge.${this.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
3318
|
-
res.status(500).send('Error downloading the matterbridge storage file');
|
|
3319
|
-
}
|
|
3320
|
-
});
|
|
3321
|
-
});
|
|
3322
|
-
// Endpoint to download the matterbridge plugin directory
|
|
3323
|
-
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
3324
|
-
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
3325
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
3326
|
-
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
3327
|
-
if (error) {
|
|
3328
|
-
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
3329
|
-
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
3330
|
-
}
|
|
3331
|
-
});
|
|
3332
|
-
});
|
|
3333
|
-
// Endpoint to download the matterbridge plugin config files
|
|
3334
|
-
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
3335
|
-
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
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')));
|
|
3338
|
-
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
3339
|
-
if (error) {
|
|
3340
|
-
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
3341
|
-
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
3342
|
-
}
|
|
3343
|
-
});
|
|
3344
|
-
});
|
|
3345
|
-
// Endpoint to download the matterbridge plugin config files
|
|
3346
|
-
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
3347
|
-
this.log.debug('The frontend sent /api/download-backup');
|
|
3348
|
-
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
3349
|
-
if (error) {
|
|
3350
|
-
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
3351
|
-
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
3352
|
-
}
|
|
3353
|
-
});
|
|
3354
|
-
});
|
|
3355
|
-
// Endpoint to receive commands
|
|
3356
|
-
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
3357
|
-
const command = req.params.command;
|
|
3358
|
-
let param = req.params.param;
|
|
3359
|
-
this.log.debug(`The frontend sent /api/command/${command}/${param}`);
|
|
3360
|
-
if (!command) {
|
|
3361
|
-
res.status(400).json({ error: 'No command provided' });
|
|
3362
|
-
return;
|
|
3363
|
-
}
|
|
3364
|
-
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3365
|
-
// Handle the command setpassword from Settings
|
|
3366
|
-
if (command === 'setpassword') {
|
|
3367
|
-
const password = param.slice(1, -1); // Remove the first and last characters
|
|
3368
|
-
this.log.debug('setpassword', param, password);
|
|
3369
|
-
await this.nodeContext?.set('password', password);
|
|
3370
|
-
res.json({ message: 'Command received' });
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
// Handle the command setbridgemode from Settings
|
|
3374
|
-
if (command === 'setbridgemode') {
|
|
3375
|
-
this.log.debug(`setbridgemode: ${param}`);
|
|
3376
|
-
this.wssSendRestartRequired();
|
|
3377
|
-
await this.nodeContext?.set('bridgeMode', param);
|
|
3378
|
-
res.json({ message: 'Command received' });
|
|
3379
|
-
return;
|
|
3380
|
-
}
|
|
3381
|
-
// Handle the command backup from Settings
|
|
3382
|
-
if (command === 'backup') {
|
|
3383
|
-
this.log.notice(`Prepairing the backup...`);
|
|
3384
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
3385
|
-
this.log.notice(`Backup ready to be downloaded.`);
|
|
3386
|
-
res.json({ message: 'Command received' });
|
|
3387
|
-
return;
|
|
3388
|
-
}
|
|
3389
|
-
// Handle the command setmbloglevel from Settings
|
|
3390
|
-
if (command === 'setmbloglevel') {
|
|
3391
|
-
this.log.debug('Matterbridge log level:', param);
|
|
3392
|
-
if (param === 'Debug') {
|
|
3393
|
-
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
3394
|
-
}
|
|
3395
|
-
else if (param === 'Info') {
|
|
3396
|
-
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
3397
|
-
}
|
|
3398
|
-
else if (param === 'Notice') {
|
|
3399
|
-
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
3400
|
-
}
|
|
3401
|
-
else if (param === 'Warn') {
|
|
3402
|
-
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
3403
|
-
}
|
|
3404
|
-
else if (param === 'Error') {
|
|
3405
|
-
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
3406
|
-
}
|
|
3407
|
-
else if (param === 'Fatal') {
|
|
3408
|
-
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
3409
|
-
}
|
|
3410
|
-
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
3411
|
-
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
3412
|
-
this.plugins.logLevel = this.log.logLevel;
|
|
3413
|
-
for (const plugin of this.plugins) {
|
|
3414
|
-
if (!plugin.platform || !plugin.platform.config)
|
|
3415
|
-
continue;
|
|
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);
|
|
3418
|
-
}
|
|
3419
|
-
res.json({ message: 'Command received' });
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
// Handle the command setmbloglevel from Settings
|
|
3423
|
-
if (command === 'setmjloglevel') {
|
|
3424
|
-
this.log.debug('Matter.js log level:', param);
|
|
3425
|
-
if (param === 'Debug') {
|
|
3426
|
-
Logger.defaultLogLevel = MatterLogLevel.DEBUG;
|
|
3427
|
-
}
|
|
3428
|
-
else if (param === 'Info') {
|
|
3429
|
-
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3430
|
-
}
|
|
3431
|
-
else if (param === 'Notice') {
|
|
3432
|
-
Logger.defaultLogLevel = MatterLogLevel.NOTICE;
|
|
3433
|
-
}
|
|
3434
|
-
else if (param === 'Warn') {
|
|
3435
|
-
Logger.defaultLogLevel = MatterLogLevel.WARN;
|
|
3436
|
-
}
|
|
3437
|
-
else if (param === 'Error') {
|
|
3438
|
-
Logger.defaultLogLevel = MatterLogLevel.ERROR;
|
|
3439
|
-
}
|
|
3440
|
-
else if (param === 'Fatal') {
|
|
3441
|
-
Logger.defaultLogLevel = MatterLogLevel.FATAL;
|
|
3442
|
-
}
|
|
3443
|
-
await this.nodeContext?.set('matterLogLevel', Logger.defaultLogLevel);
|
|
3444
|
-
res.json({ message: 'Command received' });
|
|
3445
|
-
return;
|
|
3446
|
-
}
|
|
3447
|
-
// Handle the command setmdnsinterface from Settings
|
|
3448
|
-
if (command === 'setmdnsinterface') {
|
|
3449
|
-
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
3450
|
-
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
3451
|
-
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
3452
|
-
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
3453
|
-
res.json({ message: 'Command received' });
|
|
3454
|
-
return;
|
|
3455
|
-
}
|
|
3456
|
-
// Handle the command setipv4address from Settings
|
|
3457
|
-
if (command === 'setipv4address') {
|
|
3458
|
-
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
3459
|
-
this.matterbridgeInformation.matteripv4address = param;
|
|
3460
|
-
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
3461
|
-
await this.nodeContext?.set('matteripv4address', param);
|
|
3462
|
-
res.json({ message: 'Command received' });
|
|
3463
|
-
return;
|
|
3464
|
-
}
|
|
3465
|
-
// Handle the command setipv6address from Settings
|
|
3466
|
-
if (command === 'setipv6address') {
|
|
3467
|
-
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
3468
|
-
this.matterbridgeInformation.matteripv6address = param;
|
|
3469
|
-
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
3470
|
-
await this.nodeContext?.set('matteripv6address', param);
|
|
3471
|
-
res.json({ message: 'Command received' });
|
|
3472
|
-
return;
|
|
3473
|
-
}
|
|
3474
|
-
// Handle the command setmatterport from Settings
|
|
3475
|
-
if (command === 'setmatterport') {
|
|
3476
|
-
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
3477
|
-
this.matterbridgeInformation.matterPort = port;
|
|
3478
|
-
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
3479
|
-
await this.nodeContext?.set('matterport', port);
|
|
3480
|
-
res.json({ message: 'Command received' });
|
|
3481
|
-
return;
|
|
3482
|
-
}
|
|
3483
|
-
// Handle the command setmatterdiscriminator from Settings
|
|
3484
|
-
if (command === 'setmatterdiscriminator') {
|
|
3485
|
-
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
3486
|
-
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
3487
|
-
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
3488
|
-
await this.nodeContext?.set('matterdiscriminator', discriminator);
|
|
3489
|
-
res.json({ message: 'Command received' });
|
|
3490
|
-
return;
|
|
3491
|
-
}
|
|
3492
|
-
// Handle the command setmatterpasscode from Settings
|
|
3493
|
-
if (command === 'setmatterpasscode') {
|
|
3494
|
-
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
3495
|
-
this.matterbridgeInformation.matterPasscode = passcode;
|
|
3496
|
-
this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
|
|
3497
|
-
await this.nodeContext?.set('matterpasscode', passcode);
|
|
3498
|
-
res.json({ message: 'Command received' });
|
|
3499
|
-
return;
|
|
3500
|
-
}
|
|
3501
|
-
// Handle the command setmbloglevel from Settings
|
|
3502
|
-
if (command === 'setmblogfile') {
|
|
3503
|
-
this.log.debug('Matterbridge file log:', param);
|
|
3504
|
-
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
3505
|
-
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3506
|
-
// Create the file logger for matterbridge
|
|
3507
|
-
if (param === 'true')
|
|
3508
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
3509
|
-
else
|
|
3510
|
-
AnsiLogger.setGlobalLogfile(undefined);
|
|
3511
|
-
res.json({ message: 'Command received' });
|
|
3512
|
-
return;
|
|
3513
|
-
}
|
|
3514
|
-
// Handle the command setmbloglevel from Settings
|
|
3515
|
-
if (command === 'setmjlogfile') {
|
|
3516
|
-
this.log.debug('Matter file log:', param);
|
|
3517
|
-
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
3518
|
-
await this.nodeContext?.set('matterFileLog', param === 'true');
|
|
3519
|
-
if (param === 'true') {
|
|
3520
|
-
try {
|
|
3521
|
-
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
3522
|
-
defaultLogLevel: MatterLogLevel.DEBUG,
|
|
3523
|
-
logFormat: MatterLogFormat.PLAIN,
|
|
3524
|
-
});
|
|
3525
|
-
}
|
|
3526
|
-
catch (error) {
|
|
3527
|
-
this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
else {
|
|
3531
|
-
try {
|
|
3532
|
-
Logger.removeLogger('matterfilelogger');
|
|
3533
|
-
}
|
|
3534
|
-
catch (error) {
|
|
3535
|
-
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
3536
|
-
}
|
|
3537
|
-
}
|
|
3538
|
-
res.json({ message: 'Command received' });
|
|
3539
|
-
return;
|
|
3540
|
-
}
|
|
3541
|
-
// Handle the command unregister from Settings
|
|
3542
|
-
if (command === 'unregister') {
|
|
3543
|
-
await this.unregisterAndShutdownProcess();
|
|
3544
|
-
res.json({ message: 'Command received' });
|
|
3545
|
-
return;
|
|
3546
|
-
}
|
|
3547
|
-
// Handle the command reset from Settings
|
|
3548
|
-
if (command === 'reset') {
|
|
3549
|
-
await this.shutdownProcessAndReset();
|
|
3550
|
-
res.json({ message: 'Command received' });
|
|
3551
|
-
return;
|
|
3552
|
-
}
|
|
3553
|
-
// Handle the command factoryreset from Settings
|
|
3554
|
-
if (command === 'factoryreset') {
|
|
3555
|
-
await this.shutdownProcessAndFactoryReset();
|
|
3556
|
-
res.json({ message: 'Command received' });
|
|
3557
|
-
return;
|
|
3558
|
-
}
|
|
3559
|
-
// Handle the command shutdown from Header
|
|
3560
|
-
if (command === 'shutdown') {
|
|
3561
|
-
await this.shutdownProcess();
|
|
3562
|
-
res.json({ message: 'Command received' });
|
|
3563
|
-
return;
|
|
3564
|
-
}
|
|
3565
|
-
// Handle the command restart from Header
|
|
3566
|
-
if (command === 'restart') {
|
|
3567
|
-
await this.restartProcess();
|
|
3568
|
-
res.json({ message: 'Command received' });
|
|
3569
|
-
return;
|
|
3570
|
-
}
|
|
3571
|
-
// Handle the command update from Header
|
|
3572
|
-
if (command === 'update') {
|
|
3573
|
-
this.log.info('Updating matterbridge...');
|
|
3574
|
-
try {
|
|
3575
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
3576
|
-
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3577
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3578
|
-
}
|
|
3579
|
-
catch (error) {
|
|
3580
|
-
this.log.error('Error updating matterbridge');
|
|
3581
|
-
}
|
|
3582
|
-
await this.updateProcess();
|
|
3583
|
-
this.wssSendRestartRequired();
|
|
3584
|
-
res.json({ message: 'Command received' });
|
|
3585
|
-
return;
|
|
3586
|
-
}
|
|
3587
|
-
// Handle the command saveconfig from Home
|
|
3588
|
-
if (command === 'saveconfig') {
|
|
3589
|
-
param = param.replace(/\*/g, '\\');
|
|
3590
|
-
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3591
|
-
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
3592
|
-
if (!this.plugins.has(param)) {
|
|
3593
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3594
|
-
}
|
|
3595
|
-
else {
|
|
3596
|
-
const plugin = this.plugins.get(param);
|
|
3597
|
-
if (!plugin)
|
|
3598
|
-
return;
|
|
3599
|
-
this.plugins.saveConfigFromJson(plugin, req.body);
|
|
3600
|
-
}
|
|
3601
|
-
this.wssSendRestartRequired();
|
|
3602
|
-
res.json({ message: 'Command received' });
|
|
3603
|
-
return;
|
|
3604
|
-
}
|
|
3605
|
-
// Handle the command installplugin from Home
|
|
3606
|
-
if (command === 'installplugin') {
|
|
3607
|
-
param = param.replace(/\*/g, '\\');
|
|
3608
|
-
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
3609
|
-
try {
|
|
3610
|
-
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
3611
|
-
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3612
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3613
|
-
}
|
|
3614
|
-
catch (error) {
|
|
3615
|
-
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
3616
|
-
}
|
|
3617
|
-
this.wssSendRestartRequired();
|
|
3618
|
-
param = param.split('@')[0];
|
|
3619
|
-
// Also add the plugin to matterbridge so no return!
|
|
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
|
|
3622
|
-
res.json({ message: 'Command received' });
|
|
3623
|
-
return;
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
// Handle the command addplugin from Home
|
|
3627
|
-
if (command === 'addplugin' || command === 'installplugin') {
|
|
3628
|
-
param = param.replace(/\*/g, '\\');
|
|
3629
|
-
const plugin = await this.plugins.add(param);
|
|
3630
|
-
if (plugin) {
|
|
3631
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
3632
|
-
}
|
|
3633
|
-
res.json({ message: 'Command received' });
|
|
3634
|
-
this.wssSendRefreshRequired();
|
|
3635
|
-
return;
|
|
3636
|
-
}
|
|
3637
|
-
// Handle the command removeplugin from Home
|
|
3638
|
-
if (command === 'removeplugin') {
|
|
3639
|
-
if (!this.plugins.has(param)) {
|
|
3640
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3641
|
-
}
|
|
3642
|
-
else {
|
|
3643
|
-
const plugin = this.plugins.get(param);
|
|
3644
|
-
await this.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
3645
|
-
await this.plugins.remove(param);
|
|
3646
|
-
}
|
|
3647
|
-
res.json({ message: 'Command received' });
|
|
3648
|
-
this.wssSendRefreshRequired();
|
|
3649
|
-
return;
|
|
3650
|
-
}
|
|
3651
|
-
// Handle the command enableplugin from Home
|
|
3652
|
-
if (command === 'enableplugin') {
|
|
3653
|
-
if (!this.plugins.has(param)) {
|
|
3654
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3655
|
-
}
|
|
3656
|
-
else {
|
|
3657
|
-
const plugin = this.plugins.get(param);
|
|
3658
|
-
if (plugin && !plugin.enabled) {
|
|
3659
|
-
plugin.locked = undefined;
|
|
3660
|
-
plugin.error = undefined;
|
|
3661
|
-
plugin.loaded = undefined;
|
|
3662
|
-
plugin.started = undefined;
|
|
3663
|
-
plugin.configured = undefined;
|
|
3664
|
-
plugin.connected = undefined;
|
|
3665
|
-
plugin.platform = undefined;
|
|
3666
|
-
plugin.registeredDevices = undefined;
|
|
3667
|
-
plugin.addedDevices = undefined;
|
|
3668
|
-
await this.plugins.enable(param);
|
|
3669
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
res.json({ message: 'Command received' });
|
|
3673
|
-
this.wssSendRefreshRequired();
|
|
3674
|
-
return;
|
|
3675
|
-
}
|
|
3676
|
-
// Handle the command disableplugin from Home
|
|
3677
|
-
if (command === 'disableplugin') {
|
|
3678
|
-
if (!this.plugins.has(param)) {
|
|
3679
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3680
|
-
}
|
|
3681
|
-
else {
|
|
3682
|
-
const plugin = this.plugins.get(param);
|
|
3683
|
-
if (plugin && plugin.enabled) {
|
|
3684
|
-
await this.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
3685
|
-
await this.plugins.disable(param);
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
res.json({ message: 'Command received' });
|
|
3689
|
-
this.wssSendRefreshRequired();
|
|
3690
|
-
return;
|
|
3691
|
-
}
|
|
3692
|
-
});
|
|
3693
|
-
// Fallback for routing (must be the last route)
|
|
3694
|
-
this.expressApp.get('*', (req, res) => {
|
|
3695
|
-
this.log.debug('The frontend sent:', req.url);
|
|
3696
|
-
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
3697
|
-
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
3698
|
-
});
|
|
3699
|
-
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
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
|
-
*/
|
|
3706
|
-
getClusterTextFromDevice(device) {
|
|
3707
|
-
const stringifyUserLabel = (endpoint) => {
|
|
3708
|
-
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
3709
|
-
if (!labelList)
|
|
3710
|
-
return;
|
|
3711
|
-
const composed = labelList.find((entry) => entry.label === 'composed');
|
|
3712
|
-
if (composed)
|
|
3713
|
-
return 'Composed: ' + composed.value;
|
|
3714
|
-
else
|
|
3715
|
-
return '';
|
|
3716
|
-
};
|
|
3717
|
-
const stringifyFixedLabel = (endpoint) => {
|
|
3718
|
-
const labelList = endpoint.getClusterServer(FixedLabelCluster)?.attributes.labelList.getLocal();
|
|
3719
|
-
if (!labelList)
|
|
3720
|
-
return;
|
|
3721
|
-
const composed = labelList.find((entry) => entry.label === 'composed');
|
|
3722
|
-
if (composed)
|
|
3723
|
-
return 'Composed: ' + composed.value;
|
|
3724
|
-
else
|
|
3725
|
-
return '';
|
|
3726
|
-
};
|
|
3727
|
-
let attributes = '';
|
|
3728
|
-
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3729
|
-
const clusterServers = device.getAllClusterServers();
|
|
3730
|
-
clusterServers.forEach((clusterServer) => {
|
|
3731
|
-
try {
|
|
3732
|
-
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3733
|
-
if (clusterServer.name === 'OnOff')
|
|
3734
|
-
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
3735
|
-
if (clusterServer.name === 'Switch')
|
|
3736
|
-
attributes += `Position: ${clusterServer.attributes.currentPosition.getLocal()} `;
|
|
3737
|
-
if (clusterServer.name === 'WindowCovering')
|
|
3738
|
-
attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
|
|
3739
|
-
if (clusterServer.name === 'DoorLock')
|
|
3740
|
-
attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
|
|
3741
|
-
if (clusterServer.name === 'Thermostat')
|
|
3742
|
-
attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
|
|
3743
|
-
if (clusterServer.name === 'LevelControl')
|
|
3744
|
-
attributes += `Level: ${clusterServer.attributes.currentLevel.getLocal()}% `;
|
|
3745
|
-
if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentX)
|
|
3746
|
-
attributes += `X: ${Math.round(clusterServer.attributes.currentX.getLocal())} Y: ${Math.round(clusterServer.attributes.currentY.getLocal())} `;
|
|
3747
|
-
if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentHue)
|
|
3748
|
-
attributes += `Hue: ${Math.round(clusterServer.attributes.currentHue.getLocal())} Saturation: ${Math.round(clusterServer.attributes.currentSaturation.getLocal())}% `;
|
|
3749
|
-
if (clusterServer.name === 'ColorControl' && clusterServer.attributes.colorTemperatureMireds)
|
|
3750
|
-
attributes += `ColorTemp: ${Math.round(clusterServer.attributes.colorTemperatureMireds.getLocal())} `;
|
|
3751
|
-
if (clusterServer.name === 'BooleanState')
|
|
3752
|
-
attributes += `Contact: ${clusterServer.attributes.stateValue.getLocal()} `;
|
|
3753
|
-
if (clusterServer.name === 'BooleanStateConfiguration' && clusterServer.attributes.alarmsActive)
|
|
3754
|
-
attributes += `Active alarms: ${stringify(clusterServer.attributes.alarmsActive.getLocal())} `;
|
|
3755
|
-
if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.smokeState)
|
|
3756
|
-
attributes += `Smoke: ${clusterServer.attributes.smokeState.getLocal()} `;
|
|
3757
|
-
if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.coState)
|
|
3758
|
-
attributes += `Co: ${clusterServer.attributes.coState.getLocal()} `;
|
|
3759
|
-
if (clusterServer.name === 'FanControl')
|
|
3760
|
-
attributes += `Mode: ${clusterServer.attributes.fanMode.getLocal()} Speed: ${clusterServer.attributes.percentCurrent.getLocal()} `;
|
|
3761
|
-
if (clusterServer.name === 'FanControl' && clusterServer.attributes.speedCurrent)
|
|
3762
|
-
attributes += `MultiSpeed: ${clusterServer.attributes.speedCurrent.getLocal()} `;
|
|
3763
|
-
if (clusterServer.name === 'OccupancySensing')
|
|
3764
|
-
attributes += `Occupancy: ${clusterServer.attributes.occupancy.getLocal().occupied} `;
|
|
3765
|
-
if (clusterServer.name === 'IlluminanceMeasurement')
|
|
3766
|
-
attributes += `Illuminance: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3767
|
-
if (clusterServer.name === 'AirQuality')
|
|
3768
|
-
attributes += `Air quality: ${clusterServer.attributes.airQuality.getLocal()} `;
|
|
3769
|
-
if (clusterServer.name === 'TvocMeasurement')
|
|
3770
|
-
attributes += `Voc: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3771
|
-
if (clusterServer.name === 'TemperatureMeasurement')
|
|
3772
|
-
attributes += `Temperature: ${clusterServer.attributes.measuredValue.getLocal() / 100}°C `;
|
|
3773
|
-
if (clusterServer.name === 'RelativeHumidityMeasurement')
|
|
3774
|
-
attributes += `Humidity: ${clusterServer.attributes.measuredValue.getLocal() / 100}% `;
|
|
3775
|
-
if (clusterServer.name === 'PressureMeasurement')
|
|
3776
|
-
attributes += `Pressure: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3777
|
-
if (clusterServer.name === 'FlowMeasurement')
|
|
3778
|
-
attributes += `Flow: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3779
|
-
if (clusterServer.name === 'FixedLabel')
|
|
3780
|
-
attributes += `${stringifyFixedLabel(device)} `;
|
|
3781
|
-
if (clusterServer.name === 'UserLabel')
|
|
3782
|
-
attributes += `${stringifyUserLabel(device)} `;
|
|
3783
|
-
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3784
|
-
}
|
|
3785
|
-
catch (error) {
|
|
3786
|
-
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3787
2354
|
}
|
|
3788
2355
|
});
|
|
3789
|
-
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3790
|
-
return attributes;
|
|
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
|
-
*/
|
|
3798
|
-
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3799
|
-
// Set the bridge mode
|
|
3800
|
-
this.bridgeMode = 'bridge';
|
|
3801
|
-
// Set the first port to use
|
|
3802
|
-
this.port = port;
|
|
3803
|
-
// Set Matterbridge logger
|
|
3804
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3805
|
-
this.log.debug('Matterbridge extension is starting...');
|
|
3806
|
-
// Initialize NodeStorage
|
|
3807
|
-
this.matterbridgeDirectory = dataPath;
|
|
3808
|
-
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3809
|
-
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
3810
|
-
this.log.debug('Creating node storage context for matterbridge: matterbridge');
|
|
3811
|
-
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
3812
|
-
const plugin = {
|
|
3813
|
-
path: '',
|
|
3814
|
-
type: 'DynamicPlatform',
|
|
3815
|
-
name: 'MatterbridgeExtension',
|
|
3816
|
-
version: '1.0.0',
|
|
3817
|
-
description: 'Matterbridge extension',
|
|
3818
|
-
author: 'https://github.com/Luligu',
|
|
3819
|
-
enabled: false,
|
|
3820
|
-
registeredDevices: 0,
|
|
3821
|
-
addedDevices: 0,
|
|
3822
|
-
};
|
|
3823
|
-
this.plugins.set(plugin);
|
|
3824
|
-
this.plugins.saveToStorage();
|
|
3825
|
-
// Log system info and create .matterbridge directory
|
|
3826
|
-
await this.logNodeAndSystemInfo();
|
|
3827
|
-
this.matterbridgeDirectory = dataPath;
|
|
3828
|
-
// Set matter.js logger level and format
|
|
3829
|
-
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3830
|
-
Logger.format = MatterLogFormat.ANSI;
|
|
3831
|
-
// Start the storage and create matterbridgeContext
|
|
3832
|
-
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3833
|
-
if (!this.storageManager)
|
|
3834
|
-
return false;
|
|
3835
|
-
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge zigbee2MQTT', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'zigbee2MQTT Matter extension');
|
|
3836
|
-
if (!this.matterbridgeContext)
|
|
3837
|
-
return false;
|
|
3838
|
-
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3839
|
-
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3840
|
-
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3841
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
3842
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
3843
|
-
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3844
|
-
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
3845
|
-
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
3846
|
-
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
|
|
3847
|
-
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
3848
|
-
this.commissioningServer.addDevice(this.matterAggregator);
|
|
3849
|
-
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
3850
|
-
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
3851
|
-
await this.startMatterServer();
|
|
3852
|
-
this.log.info('Matter server started');
|
|
3853
|
-
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3854
|
-
// Set reachability to true and trigger event after 60 seconds
|
|
3855
|
-
setTimeout(() => {
|
|
3856
|
-
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3857
|
-
if (this.commissioningServer)
|
|
3858
|
-
this.setCommissioningServerReachability(this.commissioningServer, true);
|
|
3859
|
-
if (this.matterAggregator)
|
|
3860
|
-
this.setAggregatorReachability(this.matterAggregator, true);
|
|
3861
|
-
}, 60 * 1000);
|
|
3862
|
-
return this.commissioningServer.isCommissioned();
|
|
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
|
-
*/
|
|
3870
|
-
async stopExtension() {
|
|
3871
|
-
// Closing matter
|
|
3872
|
-
await this.stopMatterServer();
|
|
3873
|
-
// Clearing the session manager
|
|
3874
|
-
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3875
|
-
// Closing storage
|
|
3876
|
-
await this.stopMatterStorage();
|
|
3877
|
-
this.log.info('Matter server stopped');
|
|
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
|
-
*/
|
|
3885
|
-
isExtensionCommissioned() {
|
|
3886
|
-
if (!this.commissioningServer)
|
|
3887
|
-
return false;
|
|
3888
|
-
return this.commissioningServer.isCommissioned();
|
|
3889
2356
|
}
|
|
3890
2357
|
}
|
|
3891
2358
|
//# sourceMappingURL=matterbridge.js.map
|