matterbridge 1.7.2 → 2.0.0-edge.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -1
- package/dist/cli.js +3 -39
- package/dist/cluster/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -23
- package/dist/deviceManager.js +1 -26
- package/dist/frontend.js +1203 -0
- package/dist/index.js +1 -36
- package/dist/logger/export.js +0 -1
- package/dist/matter/export.js +0 -5
- package/dist/matterbridge.js +516 -2678
- package/dist/matterbridgeAccessoryPlatform.js +0 -33
- package/dist/matterbridgeBehaviors.js +23 -31
- package/dist/matterbridgeDeviceTypes.js +11 -82
- package/dist/matterbridgeDynamicPlatform.js +0 -33
- package/dist/matterbridgeEndpoint.js +45 -1145
- package/dist/matterbridgePlatform.js +14 -145
- package/dist/matterbridgeTypes.js +3 -24
- package/dist/pluginManager.js +5 -246
- package/dist/storage/export.js +0 -1
- package/dist/utils/colorUtils.js +2 -205
- package/dist/utils/export.js +0 -1
- package/dist/utils/utils.js +7 -252
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.08241820.js → main.6204ae54.js} +3 -3
- package/frontend/build/static/js/main.6204ae54.js.map +1 -0
- package/npm-shrinkwrap.json +9 -9
- package/package.json +2 -3
- package/dist/cli.d.ts +0 -25
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cluster/export.d.ts +0 -2
- package/dist/cluster/export.d.ts.map +0 -1
- package/dist/cluster/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -27
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -46
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/index.d.ts +0 -40
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/export.d.ts +0 -11
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -483
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -942
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- 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/matterbridgeDeviceTypes.d.ts +0 -109
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- 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/matterbridgeEndpoint.d.ts +0 -10156
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -168
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -172
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- 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/pluginManager.d.ts +0 -238
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -61
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/export.d.ts +0 -3
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/utils.d.ts +0 -221
- package/dist/utils/utils.d.ts.map +0 -1
- package/dist/utils/utils.js.map +0 -1
- package/frontend/build/static/js/main.08241820.js.map +0 -1
- /package/frontend/build/static/js/{main.08241820.js.LICENSE.txt → main.6204ae54.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,64 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file contains the class Matterbridge.
|
|
3
|
-
*
|
|
4
|
-
* @file matterbridge.ts
|
|
5
|
-
* @author Luca Liguori
|
|
6
|
-
* @date 2023-12-29
|
|
7
|
-
* @version 1.5.2
|
|
8
|
-
*
|
|
9
|
-
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
-
*
|
|
11
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
-
* you may not use this file except in compliance with the License.
|
|
13
|
-
* You may obtain a copy of the License at
|
|
14
|
-
*
|
|
15
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
-
*
|
|
17
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
-
* See the License for the specific language governing permissions and
|
|
21
|
-
* limitations under the License. *
|
|
22
|
-
*/
|
|
23
|
-
// Node.js modules
|
|
24
1
|
import { fileURLToPath } from 'url';
|
|
25
2
|
import { promises as fs } from 'fs';
|
|
26
3
|
import { exec, spawn } from 'child_process';
|
|
27
|
-
import { createServer } from 'http';
|
|
28
4
|
import EventEmitter from 'events';
|
|
29
5
|
import os from 'os';
|
|
30
6
|
import path from 'path';
|
|
31
7
|
import { randomBytes } from 'crypto';
|
|
32
|
-
// Package modules
|
|
33
|
-
import https from 'https';
|
|
34
|
-
import express from 'express';
|
|
35
|
-
import WebSocket, { WebSocketServer } from 'ws';
|
|
36
|
-
// NodeStorage and AnsiLogger modules
|
|
37
8
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
38
|
-
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify,
|
|
39
|
-
|
|
40
|
-
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
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';
|
|
9
|
+
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
|
|
10
|
+
import { logInterfaces, wait, waiter, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
43
11
|
import { PluginManager } from './pluginManager.js';
|
|
44
12
|
import { DeviceManager } from './deviceManager.js';
|
|
45
13
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
46
|
-
|
|
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';
|
|
55
|
-
// Default colors
|
|
14
|
+
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
15
|
+
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
16
|
+
import { FabricAction, PaseClient } from '@matter/main/protocol';
|
|
17
|
+
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
18
|
+
import { Frontend } from './frontend.js';
|
|
56
19
|
const plg = '\u001B[38;5;33m';
|
|
57
20
|
const dev = '\u001B[38;5;79m';
|
|
58
21
|
const typ = '\u001B[38;5;207m';
|
|
59
|
-
/**
|
|
60
|
-
* Represents the Matterbridge application.
|
|
61
|
-
*/
|
|
62
22
|
export class Matterbridge extends EventEmitter {
|
|
63
23
|
systemInformation = {
|
|
64
24
|
interfaceName: '',
|
|
@@ -92,10 +52,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
92
52
|
matterbridgeConnected: false,
|
|
93
53
|
bridgeMode: '',
|
|
94
54
|
restartMode: '',
|
|
95
|
-
edge: hasParameter('edge'),
|
|
96
55
|
readOnly: hasParameter('readonly'),
|
|
97
56
|
profile: getParameter('profile'),
|
|
98
|
-
loggerLevel: "info"
|
|
57
|
+
loggerLevel: "info",
|
|
99
58
|
fileLogger: false,
|
|
100
59
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
101
60
|
matterFileLogger: false,
|
|
@@ -124,17 +83,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
124
83
|
bridgeMode = '';
|
|
125
84
|
restartMode = '';
|
|
126
85
|
profile = getParameter('profile');
|
|
127
|
-
edge =
|
|
86
|
+
edge = true;
|
|
128
87
|
log;
|
|
129
88
|
matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
130
89
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
131
90
|
plugins;
|
|
132
91
|
devices;
|
|
92
|
+
frontend = new Frontend(this);
|
|
133
93
|
nodeStorage;
|
|
134
94
|
nodeContext;
|
|
135
|
-
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
136
95
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
|
-
// Cleanup
|
|
138
96
|
hasCleanupStarted = false;
|
|
139
97
|
initialized = false;
|
|
140
98
|
execRunningCount = 0;
|
|
@@ -146,62 +104,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
146
104
|
sigtermHandler;
|
|
147
105
|
exceptionHandler;
|
|
148
106
|
rejectionHandler;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
webSocketServer;
|
|
154
|
-
// Matter
|
|
155
|
-
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
156
|
-
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
157
|
-
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
158
|
-
port = 5540; // first commissioning server port
|
|
159
|
-
passcode; // first commissioning server passcode
|
|
160
|
-
discriminator; // first commissioning server discriminator
|
|
161
|
-
storageManager;
|
|
107
|
+
environment = Environment.default;
|
|
108
|
+
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
109
|
+
matterStorageService;
|
|
110
|
+
matterStorageManager;
|
|
162
111
|
matterbridgeContext;
|
|
163
112
|
mattercontrollerContext;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
113
|
+
mdnsInterface;
|
|
114
|
+
ipv4address;
|
|
115
|
+
ipv6address;
|
|
116
|
+
port;
|
|
117
|
+
passcode;
|
|
118
|
+
discriminator;
|
|
119
|
+
serverNode;
|
|
120
|
+
aggregatorNode;
|
|
168
121
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
169
122
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
170
123
|
static instance;
|
|
171
|
-
// We load asyncronously so is private
|
|
172
124
|
constructor() {
|
|
173
125
|
super();
|
|
174
|
-
// Bind the handler to the instance
|
|
175
|
-
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
176
126
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Retrieves the list of Matterbridge devices.
|
|
179
|
-
* @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
|
|
180
|
-
*/
|
|
181
127
|
getDevices() {
|
|
182
128
|
return this.devices.array();
|
|
183
129
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Retrieves the list of registered plugins.
|
|
186
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
187
|
-
*/
|
|
188
130
|
getPlugins() {
|
|
189
131
|
return this.plugins.array();
|
|
190
132
|
}
|
|
191
|
-
matterbridgeMessageHandler;
|
|
192
|
-
/** ***********************************************************************************************************************************/
|
|
193
|
-
/** loadInstance() and cleanup() methods */
|
|
194
|
-
/** ***********************************************************************************************************************************/
|
|
195
|
-
/**
|
|
196
|
-
* Loads an instance of the Matterbridge class.
|
|
197
|
-
* If an instance already exists, return that instance.
|
|
198
|
-
*
|
|
199
|
-
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
200
|
-
* @returns The loaded Matterbridge instance.
|
|
201
|
-
*/
|
|
202
133
|
static async loadInstance(initialize = false) {
|
|
203
134
|
if (!Matterbridge.instance) {
|
|
204
|
-
// eslint-disable-next-line no-console
|
|
205
135
|
if (hasParameter('debug'))
|
|
206
136
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
207
137
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -210,11 +140,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
210
140
|
}
|
|
211
141
|
return Matterbridge.instance;
|
|
212
142
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Call cleanup().
|
|
215
|
-
* @deprecated This method is deprecated and is only used for jest tests.
|
|
216
|
-
*
|
|
217
|
-
*/
|
|
218
143
|
async destroyInstance() {
|
|
219
144
|
await this.cleanup('destroying instance...', false);
|
|
220
145
|
await waiter('destroying instance...', () => {
|
|
@@ -222,60 +147,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
222
147
|
}, false, 60000, 100, false);
|
|
223
148
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
224
149
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Initializes the Matterbridge application.
|
|
227
|
-
*
|
|
228
|
-
* @remarks
|
|
229
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
230
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
231
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
232
|
-
*
|
|
233
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
234
|
-
*/
|
|
235
150
|
async initialize() {
|
|
236
|
-
// Set the restart mode
|
|
237
151
|
if (hasParameter('service'))
|
|
238
152
|
this.restartMode = 'service';
|
|
239
153
|
if (hasParameter('docker'))
|
|
240
154
|
this.restartMode = 'docker';
|
|
241
|
-
// Set the matterbridge directory
|
|
242
155
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
243
156
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
244
|
-
|
|
245
|
-
this.
|
|
246
|
-
|
|
157
|
+
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
158
|
+
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
159
|
+
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
160
|
+
this.environment.vars.set('runtime.signals', false);
|
|
161
|
+
this.environment.vars.set('runtime.exitcode', false);
|
|
162
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
163
|
+
this.registerProcessHandlers();
|
|
247
164
|
try {
|
|
248
165
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
249
166
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
250
167
|
this.log.debug('Creating node storage context for matterbridge');
|
|
251
168
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
252
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
253
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
169
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
255
170
|
for (const key of keys) {
|
|
256
171
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
257
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
172
|
await this.nodeStorage?.storage.get(key);
|
|
259
173
|
}
|
|
260
174
|
const storages = await this.nodeStorage.getStorageNames();
|
|
261
175
|
for (const storage of storages) {
|
|
262
176
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
263
177
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
264
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
265
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
266
178
|
const keys = (await nodeContext?.storage.keys());
|
|
267
179
|
keys.forEach(async (key) => {
|
|
268
180
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
269
181
|
await nodeContext?.get(key);
|
|
270
182
|
});
|
|
271
183
|
}
|
|
272
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
273
184
|
this.log.debug('Creating node storage backup...');
|
|
274
185
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
275
186
|
this.log.debug('Created node storage backup');
|
|
276
187
|
}
|
|
277
188
|
catch (error) {
|
|
278
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
279
189
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
280
190
|
if (hasParameter('norestore')) {
|
|
281
191
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -290,51 +200,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
290
200
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
291
201
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
292
202
|
}
|
|
293
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
294
203
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
295
|
-
|
|
296
|
-
this.
|
|
297
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
298
|
-
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
204
|
+
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
205
|
+
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
299
206
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
300
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
301
207
|
if (hasParameter('logger')) {
|
|
302
208
|
const level = getParameter('logger');
|
|
303
209
|
if (level === 'debug') {
|
|
304
|
-
this.log.logLevel = "debug"
|
|
210
|
+
this.log.logLevel = "debug";
|
|
305
211
|
}
|
|
306
212
|
else if (level === 'info') {
|
|
307
|
-
this.log.logLevel = "info"
|
|
213
|
+
this.log.logLevel = "info";
|
|
308
214
|
}
|
|
309
215
|
else if (level === 'notice') {
|
|
310
|
-
this.log.logLevel = "notice"
|
|
216
|
+
this.log.logLevel = "notice";
|
|
311
217
|
}
|
|
312
218
|
else if (level === 'warn') {
|
|
313
|
-
this.log.logLevel = "warn"
|
|
219
|
+
this.log.logLevel = "warn";
|
|
314
220
|
}
|
|
315
221
|
else if (level === 'error') {
|
|
316
|
-
this.log.logLevel = "error"
|
|
222
|
+
this.log.logLevel = "error";
|
|
317
223
|
}
|
|
318
224
|
else if (level === 'fatal') {
|
|
319
|
-
this.log.logLevel = "fatal"
|
|
225
|
+
this.log.logLevel = "fatal";
|
|
320
226
|
}
|
|
321
227
|
else {
|
|
322
228
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
323
|
-
this.log.logLevel = "info"
|
|
229
|
+
this.log.logLevel = "info";
|
|
324
230
|
}
|
|
325
231
|
}
|
|
326
232
|
else {
|
|
327
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info"
|
|
233
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
328
234
|
}
|
|
329
|
-
|
|
330
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
235
|
+
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
331
236
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
332
237
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
333
238
|
this.matterbridgeInformation.fileLogger = true;
|
|
334
239
|
}
|
|
335
240
|
this.log.notice('Matterbridge is starting...');
|
|
336
241
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
337
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
338
242
|
if (hasParameter('matterlogger')) {
|
|
339
243
|
const level = getParameter('matterlogger');
|
|
340
244
|
if (level === 'debug') {
|
|
@@ -365,7 +269,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
365
269
|
}
|
|
366
270
|
Logger.format = MatterLogFormat.ANSI;
|
|
367
271
|
Logger.setLogger('default', this.createMatterLogger());
|
|
368
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
369
272
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
370
273
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
371
274
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -374,25 +277,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
374
277
|
});
|
|
375
278
|
}
|
|
376
279
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
-
// Set the interface to use for the matter server mdnsInterface
|
|
378
280
|
if (hasParameter('mdnsinterface')) {
|
|
379
281
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
380
282
|
}
|
|
381
283
|
else {
|
|
382
|
-
this.mdnsInterface = await this.nodeContext
|
|
284
|
+
this.mdnsInterface = await this.nodeContext.get('mattermdnsinterface', undefined);
|
|
383
285
|
if (this.mdnsInterface === '')
|
|
384
286
|
this.mdnsInterface = undefined;
|
|
385
287
|
}
|
|
386
|
-
|
|
288
|
+
if (this.mdnsInterface) {
|
|
289
|
+
const networkInterfaces = os.networkInterfaces();
|
|
290
|
+
const availableInterfaces = Object.keys(networkInterfaces);
|
|
291
|
+
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
292
|
+
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
293
|
+
this.mdnsInterface = undefined;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (this.mdnsInterface)
|
|
300
|
+
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
387
301
|
if (hasParameter('ipv4address')) {
|
|
388
302
|
this.ipv4address = getParameter('ipv4address');
|
|
389
303
|
}
|
|
390
304
|
else {
|
|
391
|
-
this.ipv4address = await this.nodeContext
|
|
305
|
+
this.ipv4address = await this.nodeContext.get('matteripv4address', undefined);
|
|
392
306
|
if (this.ipv4address === '')
|
|
393
307
|
this.ipv4address = undefined;
|
|
394
308
|
}
|
|
395
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
396
309
|
if (hasParameter('ipv6address')) {
|
|
397
310
|
this.ipv6address = getParameter('ipv6address');
|
|
398
311
|
}
|
|
@@ -401,28 +314,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
401
314
|
if (this.ipv6address === '')
|
|
402
315
|
this.ipv6address = undefined;
|
|
403
316
|
}
|
|
404
|
-
// Initialize PluginManager
|
|
405
317
|
this.plugins = new PluginManager(this);
|
|
406
318
|
await this.plugins.loadFromStorage();
|
|
407
|
-
// Initialize DeviceManager
|
|
408
319
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
409
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
410
320
|
for (const plugin of this.plugins) {
|
|
411
321
|
const packageJson = await this.plugins.parse(plugin);
|
|
412
|
-
if (packageJson === null && !hasParameter('add')) {
|
|
413
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
414
|
-
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
322
|
+
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
415
323
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
416
324
|
try {
|
|
417
325
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
418
326
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
419
327
|
plugin.error = false;
|
|
420
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
421
328
|
}
|
|
422
329
|
catch (error) {
|
|
423
330
|
plugin.error = true;
|
|
424
331
|
plugin.enabled = false;
|
|
425
|
-
this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled
|
|
332
|
+
this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`, error instanceof Error ? error.message : error);
|
|
426
333
|
}
|
|
427
334
|
}
|
|
428
335
|
this.log.debug(`Creating node storage context for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -434,7 +341,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
434
341
|
await plugin.nodeContext.set('description', plugin.description);
|
|
435
342
|
await plugin.nodeContext.set('author', plugin.author);
|
|
436
343
|
}
|
|
437
|
-
// Log system info and create .matterbridge directory
|
|
438
344
|
await this.logNodeAndSystemInfo();
|
|
439
345
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
440
346
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -442,7 +348,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
442
348
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
443
349
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
444
350
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
445
|
-
// Check node version and throw error
|
|
446
351
|
const minNodeVersion = 18;
|
|
447
352
|
const nodeVersion = process.versions.node;
|
|
448
353
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -450,17 +355,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
450
355
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
451
356
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
452
357
|
}
|
|
453
|
-
// Register process handlers
|
|
454
|
-
this.registerProcessHandlers();
|
|
455
|
-
// Parse command line
|
|
456
358
|
await this.parseCommandLine();
|
|
457
359
|
this.initialized = true;
|
|
458
360
|
}
|
|
459
|
-
/**
|
|
460
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
461
|
-
* @private
|
|
462
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
463
|
-
*/
|
|
464
361
|
async parseCommandLine() {
|
|
465
362
|
if (hasParameter('help')) {
|
|
466
363
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -567,75 +464,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
567
464
|
return;
|
|
568
465
|
}
|
|
569
466
|
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');
|
|
467
|
+
await this.shutdownProcessAndFactoryReset();
|
|
609
468
|
return;
|
|
610
469
|
}
|
|
611
|
-
// Start the matter storage and create the matterbridge context
|
|
612
470
|
try {
|
|
613
|
-
await this.startMatterStorage(
|
|
471
|
+
await this.startMatterStorage();
|
|
614
472
|
}
|
|
615
473
|
catch (error) {
|
|
616
474
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
617
475
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
618
476
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.log.info('Resetting Matterbridge commissioning information...');
|
|
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');
|
|
477
|
+
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
478
|
+
await this.shutdownProcessAndReset();
|
|
626
479
|
return;
|
|
627
480
|
}
|
|
628
|
-
|
|
629
|
-
if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
481
|
+
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
630
482
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
631
483
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
632
484
|
if (plugin) {
|
|
633
|
-
|
|
485
|
+
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
486
|
+
if (!matterStorageManager)
|
|
634
487
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
await
|
|
488
|
+
await matterStorageManager?.createContext('events')?.clearAll();
|
|
489
|
+
await matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
490
|
+
await matterStorageManager?.createContext('root')?.clearAll();
|
|
491
|
+
await matterStorageManager?.createContext('sessions')?.clearAll();
|
|
492
|
+
await matterStorageManager?.createContext('persist')?.clearAll();
|
|
639
493
|
this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
|
|
640
494
|
}
|
|
641
495
|
else {
|
|
@@ -645,74 +499,55 @@ export class Matterbridge extends EventEmitter {
|
|
|
645
499
|
this.emit('shutdown');
|
|
646
500
|
return;
|
|
647
501
|
}
|
|
648
|
-
// Initialize frontend
|
|
649
502
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
650
|
-
await this.
|
|
651
|
-
// Check each 60 minutes the latest versions
|
|
503
|
+
await this.frontend.start(getIntParameter('frontend'));
|
|
652
504
|
this.checkUpdateInterval = setInterval(() => {
|
|
653
505
|
this.getMatterbridgeLatestVersion();
|
|
654
506
|
for (const plugin of this.plugins) {
|
|
655
507
|
this.getPluginLatestVersion(plugin);
|
|
656
508
|
}
|
|
509
|
+
this.frontend.wssSendRefreshRequired();
|
|
657
510
|
}, 60 * 60 * 1000);
|
|
658
|
-
// Start the matterbridge in mode test
|
|
659
511
|
if (hasParameter('test')) {
|
|
660
512
|
this.bridgeMode = 'bridge';
|
|
661
|
-
|
|
513
|
+
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
662
514
|
return;
|
|
663
515
|
}
|
|
664
|
-
// Start the matterbridge in mode controller
|
|
665
516
|
if (hasParameter('controller')) {
|
|
666
517
|
this.bridgeMode = 'controller';
|
|
667
518
|
await this.startController();
|
|
668
519
|
return;
|
|
669
520
|
}
|
|
670
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
671
521
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
672
522
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
673
523
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
674
524
|
}
|
|
675
|
-
// Start matterbridge in bridge mode
|
|
676
525
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
677
526
|
this.bridgeMode = 'bridge';
|
|
678
|
-
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
679
527
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
680
528
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
681
529
|
await this.startBridge();
|
|
682
530
|
return;
|
|
683
531
|
}
|
|
684
|
-
// Start matterbridge in childbridge mode
|
|
685
532
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
686
533
|
this.bridgeMode = 'childbridge';
|
|
687
|
-
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
688
534
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
689
535
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
690
536
|
await this.startChildbridge();
|
|
691
537
|
return;
|
|
692
538
|
}
|
|
693
539
|
}
|
|
694
|
-
/**
|
|
695
|
-
* Asynchronously loads and starts the registered plugins.
|
|
696
|
-
*
|
|
697
|
-
* This method is responsible for initializing and staarting all enabled plugins.
|
|
698
|
-
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
699
|
-
*
|
|
700
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
701
|
-
*/
|
|
702
540
|
async startPlugins() {
|
|
703
|
-
// Check, load and start the plugins
|
|
704
541
|
for (const plugin of this.plugins) {
|
|
705
542
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
706
543
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
707
|
-
// Check if the plugin is available
|
|
708
544
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
709
545
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
710
546
|
plugin.enabled = false;
|
|
711
547
|
plugin.error = true;
|
|
712
548
|
continue;
|
|
713
549
|
}
|
|
714
|
-
|
|
715
|
-
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
550
|
+
this.getPluginLatestVersion(plugin);
|
|
716
551
|
if (!plugin.enabled) {
|
|
717
552
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
718
553
|
continue;
|
|
@@ -727,26 +562,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
727
562
|
plugin.addedDevices = undefined;
|
|
728
563
|
plugin.qrPairingCode = undefined;
|
|
729
564
|
plugin.manualPairingCode = undefined;
|
|
730
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
565
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
731
566
|
}
|
|
732
|
-
this.wssSendRefreshRequired();
|
|
567
|
+
this.frontend.wssSendRefreshRequired();
|
|
733
568
|
}
|
|
734
|
-
/**
|
|
735
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
736
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
737
|
-
*/
|
|
738
569
|
registerProcessHandlers() {
|
|
739
570
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
740
571
|
process.removeAllListeners('uncaughtException');
|
|
741
572
|
process.removeAllListeners('unhandledRejection');
|
|
742
573
|
this.exceptionHandler = async (error) => {
|
|
743
574
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
744
|
-
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
745
575
|
};
|
|
746
576
|
process.on('uncaughtException', this.exceptionHandler);
|
|
747
577
|
this.rejectionHandler = async (reason, promise) => {
|
|
748
578
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
749
|
-
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
750
579
|
};
|
|
751
580
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
752
581
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -759,9 +588,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
759
588
|
};
|
|
760
589
|
process.on('SIGTERM', this.sigtermHandler);
|
|
761
590
|
}
|
|
762
|
-
/**
|
|
763
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
764
|
-
*/
|
|
765
591
|
deregisterProcesslHandlers() {
|
|
766
592
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
767
593
|
if (this.exceptionHandler)
|
|
@@ -778,15 +604,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
778
604
|
process.off('SIGTERM', this.sigtermHandler);
|
|
779
605
|
this.sigtermHandler = undefined;
|
|
780
606
|
}
|
|
781
|
-
/**
|
|
782
|
-
* Logs the node and system information.
|
|
783
|
-
*/
|
|
784
607
|
async logNodeAndSystemInfo() {
|
|
785
|
-
// IP address information
|
|
786
608
|
const networkInterfaces = os.networkInterfaces();
|
|
609
|
+
this.systemInformation.interfaceName = '';
|
|
787
610
|
this.systemInformation.ipv4Address = '';
|
|
788
611
|
this.systemInformation.ipv6Address = '';
|
|
789
612
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
613
|
+
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
614
|
+
continue;
|
|
790
615
|
if (!interfaceDetails) {
|
|
791
616
|
break;
|
|
792
617
|
}
|
|
@@ -802,7 +627,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
802
627
|
this.systemInformation.macAddress = detail.mac;
|
|
803
628
|
}
|
|
804
629
|
}
|
|
805
|
-
if (this.systemInformation.ipv4Address !== ''
|
|
630
|
+
if (this.systemInformation.ipv4Address !== '' || this.systemInformation.ipv6Address !== '') {
|
|
806
631
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
807
632
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
808
633
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -810,22 +635,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
810
635
|
break;
|
|
811
636
|
}
|
|
812
637
|
}
|
|
813
|
-
// Node information
|
|
814
638
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
815
639
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
816
640
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
817
641
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
818
|
-
// Host system information
|
|
819
642
|
this.systemInformation.hostname = os.hostname();
|
|
820
643
|
this.systemInformation.user = os.userInfo().username;
|
|
821
|
-
this.systemInformation.osType = os.type();
|
|
822
|
-
this.systemInformation.osRelease = os.release();
|
|
823
|
-
this.systemInformation.osPlatform = os.platform();
|
|
824
|
-
this.systemInformation.osArch = os.arch();
|
|
825
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
826
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
827
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
828
|
-
// Log the system information
|
|
644
|
+
this.systemInformation.osType = os.type();
|
|
645
|
+
this.systemInformation.osRelease = os.release();
|
|
646
|
+
this.systemInformation.osPlatform = os.platform();
|
|
647
|
+
this.systemInformation.osArch = os.arch();
|
|
648
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
649
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
650
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
829
651
|
this.log.debug('Host System Information:');
|
|
830
652
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
831
653
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -841,19 +663,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
841
663
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
842
664
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
843
665
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
844
|
-
// Home directory
|
|
845
666
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
846
667
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
847
668
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
848
|
-
// Package root directory
|
|
849
669
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
850
670
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
851
671
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
852
672
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
853
|
-
// Global node_modules directory
|
|
854
673
|
if (this.nodeContext)
|
|
855
674
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
856
|
-
// First run of Matterbridge so the node storage is empty
|
|
857
675
|
if (this.globalModulesDirectory === '') {
|
|
858
676
|
try {
|
|
859
677
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -877,7 +695,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
877
695
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
878
696
|
});
|
|
879
697
|
}
|
|
880
|
-
// Create the data directory .matterbridge in the home directory
|
|
881
698
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
882
699
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
883
700
|
try {
|
|
@@ -901,7 +718,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
901
718
|
}
|
|
902
719
|
}
|
|
903
720
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
904
|
-
// Create the plugin directory Matterbridge in the home directory
|
|
905
721
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
906
722
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
907
723
|
try {
|
|
@@ -925,28 +741,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
925
741
|
}
|
|
926
742
|
}
|
|
927
743
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
928
|
-
// Matterbridge version
|
|
929
744
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
930
745
|
this.matterbridgeVersion = packageJson.version;
|
|
931
746
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
932
747
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
933
|
-
// Matterbridge latest version
|
|
934
748
|
if (this.nodeContext)
|
|
935
749
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
936
750
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
937
751
|
this.getMatterbridgeLatestVersion();
|
|
938
|
-
// Current working directory
|
|
939
752
|
const currentDir = process.cwd();
|
|
940
753
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
941
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
942
754
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
943
755
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
944
756
|
}
|
|
945
|
-
/**
|
|
946
|
-
* Retrieves the latest version of a package from the npm registry.
|
|
947
|
-
* @param packageName - The name of the package.
|
|
948
|
-
* @returns A Promise that resolves to the latest version of the package.
|
|
949
|
-
*/
|
|
950
757
|
async getLatestVersion(packageName) {
|
|
951
758
|
return new Promise((resolve, reject) => {
|
|
952
759
|
this.execRunningCount++;
|
|
@@ -961,10 +768,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
961
768
|
});
|
|
962
769
|
});
|
|
963
770
|
}
|
|
964
|
-
/**
|
|
965
|
-
* Retrieves the path to the global Node.js modules directory.
|
|
966
|
-
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
967
|
-
*/
|
|
968
771
|
async getGlobalNodeModules() {
|
|
969
772
|
return new Promise((resolve, reject) => {
|
|
970
773
|
this.execRunningCount++;
|
|
@@ -979,11 +782,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
979
782
|
});
|
|
980
783
|
});
|
|
981
784
|
}
|
|
982
|
-
/**
|
|
983
|
-
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
984
|
-
* @private
|
|
985
|
-
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
986
|
-
*/
|
|
987
785
|
async getMatterbridgeLatestVersion() {
|
|
988
786
|
this.getLatestVersion('matterbridge')
|
|
989
787
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -1000,19 +798,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1000
798
|
})
|
|
1001
799
|
.catch((error) => {
|
|
1002
800
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
1003
|
-
// error.stack && this.log.debug(error.stack);
|
|
1004
801
|
});
|
|
1005
802
|
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
1008
|
-
* If the plugin's version is different from the latest version, logs a warning message.
|
|
1009
|
-
* If the plugin's version is the same as the latest version, logs an info message.
|
|
1010
|
-
* If there is an error retrieving the latest version, logs an error message.
|
|
1011
|
-
*
|
|
1012
|
-
* @private
|
|
1013
|
-
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
1014
|
-
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
1015
|
-
*/
|
|
1016
803
|
async getPluginLatestVersion(plugin) {
|
|
1017
804
|
this.getLatestVersion(plugin.name)
|
|
1018
805
|
.then(async (latestVersion) => {
|
|
@@ -1024,54 +811,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1024
811
|
})
|
|
1025
812
|
.catch((error) => {
|
|
1026
813
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1027
|
-
// error.stack && this.log.debug(error.stack);
|
|
1028
814
|
});
|
|
1029
815
|
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1032
|
-
*
|
|
1033
|
-
* @returns {Function} The MatterLogger function.
|
|
1034
|
-
*/
|
|
1035
816
|
createMatterLogger() {
|
|
1036
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
817
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1037
818
|
return (_level, formattedLog) => {
|
|
1038
819
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1039
820
|
const message = formattedLog.slice(65);
|
|
1040
821
|
matterLogger.logName = logger;
|
|
1041
822
|
switch (_level) {
|
|
1042
823
|
case MatterLogLevel.DEBUG:
|
|
1043
|
-
matterLogger.log("debug"
|
|
824
|
+
matterLogger.log("debug", message);
|
|
1044
825
|
break;
|
|
1045
826
|
case MatterLogLevel.INFO:
|
|
1046
|
-
matterLogger.log("info"
|
|
827
|
+
matterLogger.log("info", message);
|
|
1047
828
|
break;
|
|
1048
829
|
case MatterLogLevel.NOTICE:
|
|
1049
|
-
matterLogger.log("notice"
|
|
830
|
+
matterLogger.log("notice", message);
|
|
1050
831
|
break;
|
|
1051
832
|
case MatterLogLevel.WARN:
|
|
1052
|
-
matterLogger.log("warn"
|
|
833
|
+
matterLogger.log("warn", message);
|
|
1053
834
|
break;
|
|
1054
835
|
case MatterLogLevel.ERROR:
|
|
1055
|
-
matterLogger.log("error"
|
|
836
|
+
matterLogger.log("error", message);
|
|
1056
837
|
break;
|
|
1057
838
|
case MatterLogLevel.FATAL:
|
|
1058
|
-
matterLogger.log("fatal"
|
|
839
|
+
matterLogger.log("fatal", message);
|
|
1059
840
|
break;
|
|
1060
841
|
default:
|
|
1061
|
-
matterLogger.log("debug"
|
|
842
|
+
matterLogger.log("debug", message);
|
|
1062
843
|
break;
|
|
1063
844
|
}
|
|
1064
845
|
};
|
|
1065
846
|
}
|
|
1066
|
-
/**
|
|
1067
|
-
* Creates a Matter File Logger.
|
|
1068
|
-
*
|
|
1069
|
-
* @param {string} filePath - The path to the log file.
|
|
1070
|
-
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1071
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1072
|
-
*/
|
|
1073
847
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1074
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1075
848
|
let fileSize = 0;
|
|
1076
849
|
if (unlink) {
|
|
1077
850
|
try {
|
|
@@ -1120,86 +893,113 @@ export class Matterbridge extends EventEmitter {
|
|
|
1120
893
|
}
|
|
1121
894
|
};
|
|
1122
895
|
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Update matterbridge and cleanup.
|
|
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.
|
|
1131
|
-
*/
|
|
1132
896
|
async restartProcess() {
|
|
1133
897
|
await this.cleanup('restarting...', true);
|
|
1134
898
|
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Shut down the process by exiting the current process.
|
|
1137
|
-
*/
|
|
1138
899
|
async shutdownProcess() {
|
|
1139
900
|
await this.cleanup('shutting down...', false);
|
|
1140
901
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
902
|
+
async updateProcess() {
|
|
903
|
+
this.log.info('Updating matterbridge...');
|
|
904
|
+
try {
|
|
905
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
906
|
+
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
907
|
+
}
|
|
908
|
+
catch (error) {
|
|
909
|
+
this.log.error('Error updating matterbridge:', error instanceof Error ? error.message : error);
|
|
910
|
+
}
|
|
911
|
+
this.frontend.wssSendRestartRequired();
|
|
912
|
+
await this.cleanup('updating...', false);
|
|
913
|
+
}
|
|
1144
914
|
async unregisterAndShutdownProcess() {
|
|
1145
915
|
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);
|
|
916
|
+
for (const plugin of this.plugins) {
|
|
917
|
+
await this.removeAllBridgedEndpoints(plugin.name);
|
|
1151
918
|
}
|
|
1152
919
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1153
920
|
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Shut down the process and reset.
|
|
1156
|
-
*/
|
|
1157
921
|
async shutdownProcessAndReset() {
|
|
922
|
+
this.log.info('Resetting Matterbridge commissioning information...');
|
|
923
|
+
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
924
|
+
await this.matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
925
|
+
await this.matterStorageManager?.createContext('root')?.clearAll();
|
|
926
|
+
await this.matterStorageManager?.createContext('sessions')?.clearAll();
|
|
927
|
+
await this.matterbridgeContext?.clearAll();
|
|
928
|
+
await this.stopMatterStorage();
|
|
929
|
+
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1158
930
|
await this.cleanup('shutting down with reset...', false);
|
|
1159
931
|
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Shut down the process and factory reset.
|
|
1162
|
-
*/
|
|
1163
932
|
async shutdownProcessAndFactoryReset() {
|
|
933
|
+
try {
|
|
934
|
+
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
935
|
+
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
936
|
+
await fs.unlink(file);
|
|
937
|
+
const backup = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup.json');
|
|
938
|
+
this.log.info(`Unlinking old matter storage backup file: ${backup}`);
|
|
939
|
+
await fs.unlink(backup);
|
|
940
|
+
}
|
|
941
|
+
catch (err) {
|
|
942
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
943
|
+
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
948
|
+
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
949
|
+
await fs.rm(dir, { recursive: true });
|
|
950
|
+
const backup = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
|
|
951
|
+
this.log.info(`Removing matter node storage backup directory: ${backup}`);
|
|
952
|
+
await fs.rm(backup, { recursive: true });
|
|
953
|
+
}
|
|
954
|
+
catch (err) {
|
|
955
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
956
|
+
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
961
|
+
this.log.info(`Removing storage directory: ${dir}`);
|
|
962
|
+
await fs.rm(dir, { recursive: true });
|
|
963
|
+
const backup = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
|
|
964
|
+
this.log.info(`Removing storage backup directory: ${backup}`);
|
|
965
|
+
await fs.rm(backup, { recursive: true });
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
969
|
+
this.log.error(`Error removing storage directory: ${err}`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
973
|
+
this.nodeContext = undefined;
|
|
974
|
+
this.nodeStorage = undefined;
|
|
975
|
+
this.plugins.clear();
|
|
976
|
+
this.devices.clear();
|
|
1164
977
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1165
978
|
}
|
|
1166
|
-
/**
|
|
1167
|
-
* Cleans up the Matterbridge instance.
|
|
1168
|
-
* @param message - The cleanup message.
|
|
1169
|
-
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1170
|
-
* @returns A promise that resolves when the cleanup is completed.
|
|
1171
|
-
*/
|
|
1172
979
|
async cleanup(message, restart = false) {
|
|
1173
980
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1174
981
|
this.hasCleanupStarted = true;
|
|
1175
982
|
this.log.info(message);
|
|
1176
|
-
// Deregisters the process handlers
|
|
1177
|
-
this.deregisterProcesslHandlers();
|
|
1178
|
-
// Clear the start matter interval
|
|
1179
983
|
if (this.startMatterInterval) {
|
|
1180
984
|
clearInterval(this.startMatterInterval);
|
|
1181
985
|
this.startMatterInterval = undefined;
|
|
1182
986
|
this.log.debug('Start matter interval cleared');
|
|
1183
987
|
}
|
|
1184
|
-
// Clear the check update interval
|
|
1185
988
|
if (this.checkUpdateInterval) {
|
|
1186
989
|
clearInterval(this.checkUpdateInterval);
|
|
1187
990
|
this.checkUpdateInterval = undefined;
|
|
1188
991
|
this.log.debug('Check update interval cleared');
|
|
1189
992
|
}
|
|
1190
|
-
// Clear the configure timeout
|
|
1191
993
|
if (this.configureTimeout) {
|
|
1192
994
|
clearTimeout(this.configureTimeout);
|
|
1193
995
|
this.configureTimeout = undefined;
|
|
1194
996
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1195
997
|
}
|
|
1196
|
-
// Clear the reachability timeout
|
|
1197
998
|
if (this.reachabilityTimeout) {
|
|
1198
999
|
clearTimeout(this.reachabilityTimeout);
|
|
1199
1000
|
this.reachabilityTimeout = undefined;
|
|
1200
1001
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1201
1002
|
}
|
|
1202
|
-
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
1203
1003
|
for (const plugin of this.plugins) {
|
|
1204
1004
|
if (!plugin.enabled || plugin.error)
|
|
1205
1005
|
continue;
|
|
@@ -1210,86 +1010,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1210
1010
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1211
1011
|
}
|
|
1212
1012
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
for (const plugin of this.plugins) {
|
|
1220
|
-
if (plugin.storageContext) {
|
|
1221
|
-
await this.convertStorage(plugin.storageContext, plugin.name);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1013
|
+
this.frontend.stop();
|
|
1014
|
+
this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1015
|
+
if (this.bridgeMode === 'bridge') {
|
|
1016
|
+
if (this.serverNode) {
|
|
1017
|
+
await this.stopServerNode(this.serverNode);
|
|
1018
|
+
this.log.info(`Stopped matter server node for Matterbridge`);
|
|
1224
1019
|
}
|
|
1225
1020
|
}
|
|
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');
|
|
1021
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1022
|
+
for (const plugin of this.plugins.array()) {
|
|
1023
|
+
if (plugin.serverNode) {
|
|
1024
|
+
await this.stopServerNode(plugin.serverNode);
|
|
1025
|
+
this.log.info(`Stopped matter server node for ${plugin.name}`);
|
|
1260
1026
|
}
|
|
1261
|
-
}
|
|
1262
|
-
this.webSocketServer = undefined;
|
|
1027
|
+
}
|
|
1263
1028
|
}
|
|
1264
|
-
|
|
1265
|
-
await this.stopMatterServer();
|
|
1266
|
-
// Closing matter storage
|
|
1029
|
+
this.log.info('Stopped matter server nodes');
|
|
1267
1030
|
await this.stopMatterStorage();
|
|
1268
|
-
// Remove the matterfilelogger
|
|
1269
1031
|
try {
|
|
1270
1032
|
Logger.removeLogger('matterfilelogger');
|
|
1271
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1272
1033
|
}
|
|
1273
1034
|
catch (error) {
|
|
1274
|
-
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1275
1035
|
}
|
|
1276
|
-
// Serialize registeredDevices
|
|
1277
1036
|
if (this.nodeStorage && this.nodeContext) {
|
|
1278
1037
|
this.log.info('Saving registered devices...');
|
|
1279
1038
|
const serializedRegisteredDevices = [];
|
|
1280
1039
|
this.devices.forEach(async (device) => {
|
|
1281
1040
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1282
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1283
1041
|
if (serializedMatterbridgeDevice)
|
|
1284
1042
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1285
1043
|
});
|
|
1286
1044
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1287
1045
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1288
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1289
1046
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1290
1047
|
await this.nodeContext.close();
|
|
1291
1048
|
this.nodeContext = undefined;
|
|
1292
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1293
1049
|
for (const plugin of this.plugins) {
|
|
1294
1050
|
if (plugin.nodeContext) {
|
|
1295
1051
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1306,6 +1062,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1306
1062
|
}
|
|
1307
1063
|
this.plugins.clear();
|
|
1308
1064
|
this.devices.clear();
|
|
1065
|
+
this.deregisterProcesslHandlers();
|
|
1309
1066
|
if (restart) {
|
|
1310
1067
|
if (message === 'updating...') {
|
|
1311
1068
|
this.log.info('Cleanup completed. Updating...');
|
|
@@ -1319,38 +1076,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1319
1076
|
}
|
|
1320
1077
|
}
|
|
1321
1078
|
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
1079
|
this.log.notice('Cleanup completed. Shutting down...');
|
|
1355
1080
|
Matterbridge.instance = undefined;
|
|
1356
1081
|
this.emit('shutdown');
|
|
@@ -1359,190 +1084,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
1359
1084
|
this.initialized = false;
|
|
1360
1085
|
}
|
|
1361
1086
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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
|
-
}
|
|
1087
|
+
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1088
|
+
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1089
|
+
plugin.locked = true;
|
|
1090
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
1091
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1092
|
+
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
|
|
1093
|
+
await plugin.serverNode.add(device);
|
|
1094
|
+
if (start)
|
|
1095
|
+
await this.startServerNode(plugin.serverNode);
|
|
1430
1096
|
}
|
|
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
1097
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const plugin = this.plugins.get(pluginName);
|
|
1449
|
-
if (!plugin) {
|
|
1450
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1453
|
-
// Remove the device from matterbridge aggregator in bridge mode
|
|
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--;
|
|
1471
|
-
}
|
|
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
|
-
}
|
|
1098
|
+
async createDynamicPlugin(plugin, start = false) {
|
|
1099
|
+
if (!plugin.locked) {
|
|
1100
|
+
plugin.locked = true;
|
|
1101
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1102
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1103
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1104
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1105
|
+
if (start)
|
|
1106
|
+
await this.startServerNode(plugin.serverNode);
|
|
1502
1107
|
}
|
|
1503
|
-
// Remove the device from the DeviceManager
|
|
1504
|
-
this.devices.remove(device);
|
|
1505
|
-
}
|
|
1506
|
-
/**
|
|
1507
|
-
* Removes all bridged devices associated with a specific plugin.
|
|
1508
|
-
*
|
|
1509
|
-
* @param pluginName - The name of the plugin.
|
|
1510
|
-
* @returns A promise that resolves when all devices have been removed.
|
|
1511
|
-
*/
|
|
1512
|
-
async removeAllBridgedDevices(pluginName) {
|
|
1513
|
-
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1514
|
-
this.devices.forEach(async (device) => {
|
|
1515
|
-
if (device.plugin === pluginName) {
|
|
1516
|
-
await this.removeBridgedDevice(pluginName, device);
|
|
1517
|
-
}
|
|
1518
|
-
});
|
|
1519
1108
|
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Starts the Matterbridge in bridge mode.
|
|
1522
|
-
* @private
|
|
1523
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1524
|
-
*/
|
|
1525
1109
|
async startBridge() {
|
|
1526
|
-
|
|
1527
|
-
if (!this.storageManager)
|
|
1110
|
+
if (!this.matterStorageManager)
|
|
1528
1111
|
throw new Error('No storage manager initialized');
|
|
1529
1112
|
if (!this.matterbridgeContext)
|
|
1530
1113
|
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' });
|
|
1114
|
+
this.serverNode = await this.createServerNode(this.matterbridgeContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1115
|
+
this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
|
|
1116
|
+
await this.serverNode.add(this.aggregatorNode);
|
|
1540
1117
|
await this.startPlugins();
|
|
1541
1118
|
this.log.debug('Starting start matter interval in bridge mode');
|
|
1542
1119
|
let failCount = 0;
|
|
1543
1120
|
this.startMatterInterval = setInterval(async () => {
|
|
1544
1121
|
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
1122
|
if (!plugin.enabled)
|
|
1547
1123
|
continue;
|
|
1548
1124
|
if (plugin.error) {
|
|
@@ -1567,54 +1143,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1567
1143
|
clearInterval(this.startMatterInterval);
|
|
1568
1144
|
this.startMatterInterval = undefined;
|
|
1569
1145
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
-
|
|
1571
|
-
await this.startMatterServer();
|
|
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');
|
|
1575
|
-
// Configure the plugins
|
|
1146
|
+
this.startServerNode(this.serverNode);
|
|
1576
1147
|
this.configureTimeout = setTimeout(async () => {
|
|
1577
1148
|
for (const plugin of this.plugins) {
|
|
1578
1149
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1579
1150
|
continue;
|
|
1580
1151
|
try {
|
|
1581
|
-
await this.plugins.configure(plugin);
|
|
1152
|
+
await this.plugins.configure(plugin);
|
|
1582
1153
|
}
|
|
1583
1154
|
catch (error) {
|
|
1584
1155
|
plugin.error = true;
|
|
1585
1156
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1586
1157
|
}
|
|
1587
1158
|
}
|
|
1588
|
-
this.wssSendRefreshRequired();
|
|
1159
|
+
this.frontend.wssSendRefreshRequired();
|
|
1589
1160
|
}, 30 * 1000);
|
|
1590
|
-
// Setting reachability to true
|
|
1591
1161
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1592
1162
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1593
|
-
if (this.
|
|
1594
|
-
this.
|
|
1595
|
-
if (this.
|
|
1596
|
-
this.setAggregatorReachability(this.
|
|
1163
|
+
if (this.serverNode)
|
|
1164
|
+
this.setServerNodeReachability(this.serverNode, true);
|
|
1165
|
+
if (this.aggregatorNode)
|
|
1166
|
+
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1167
|
+
this.frontend.wssSendRefreshRequired();
|
|
1597
1168
|
}, 60 * 1000);
|
|
1598
1169
|
}, 1000);
|
|
1599
1170
|
}
|
|
1600
|
-
/**
|
|
1601
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1602
|
-
* @private
|
|
1603
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1604
|
-
*/
|
|
1605
1171
|
async startChildbridge() {
|
|
1606
|
-
|
|
1607
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1608
|
-
if (!this.storageManager)
|
|
1172
|
+
if (!this.matterStorageManager)
|
|
1609
1173
|
throw new Error('No storage manager initialized');
|
|
1610
|
-
|
|
1174
|
+
for (const plugin of this.plugins) {
|
|
1175
|
+
if (!plugin.enabled)
|
|
1176
|
+
continue;
|
|
1177
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
1178
|
+
plugin.locked = true;
|
|
1179
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1180
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1181
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1182
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1611
1185
|
await this.startPlugins();
|
|
1612
1186
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1613
1187
|
let failCount = 0;
|
|
1614
1188
|
this.startMatterInterval = setInterval(async () => {
|
|
1615
1189
|
let allStarted = true;
|
|
1616
1190
|
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
1191
|
if (!plugin.enabled)
|
|
1619
1192
|
continue;
|
|
1620
1193
|
if (plugin.error) {
|
|
@@ -1642,23 +1215,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
1215
|
clearInterval(this.startMatterInterval);
|
|
1643
1216
|
this.startMatterInterval = undefined;
|
|
1644
1217
|
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
|
-
// Configure the plugins
|
|
1649
1218
|
this.configureTimeout = setTimeout(async () => {
|
|
1650
1219
|
for (const plugin of this.plugins) {
|
|
1651
1220
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1652
1221
|
continue;
|
|
1653
1222
|
try {
|
|
1654
|
-
await this.plugins.configure(plugin);
|
|
1223
|
+
await this.plugins.configure(plugin);
|
|
1655
1224
|
}
|
|
1656
1225
|
catch (error) {
|
|
1657
1226
|
plugin.error = true;
|
|
1658
1227
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1659
1228
|
}
|
|
1660
1229
|
}
|
|
1661
|
-
this.wssSendRefreshRequired();
|
|
1230
|
+
this.frontend.wssSendRefreshRequired();
|
|
1662
1231
|
}, 30 * 1000);
|
|
1663
1232
|
for (const plugin of this.plugins) {
|
|
1664
1233
|
if (!plugin.enabled || plugin.error)
|
|
@@ -1667,8 +1236,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1667
1236
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1668
1237
|
continue;
|
|
1669
1238
|
}
|
|
1670
|
-
if (!plugin.
|
|
1671
|
-
this.log.error(`
|
|
1239
|
+
if (!plugin.serverNode) {
|
|
1240
|
+
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1672
1241
|
continue;
|
|
1673
1242
|
}
|
|
1674
1243
|
if (!plugin.storageContext) {
|
|
@@ -1679,919 +1248,333 @@ export class Matterbridge extends EventEmitter {
|
|
|
1679
1248
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1680
1249
|
continue;
|
|
1681
1250
|
}
|
|
1682
|
-
|
|
1683
|
-
// Setting reachability to true
|
|
1251
|
+
this.startServerNode(plugin.serverNode);
|
|
1684
1252
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1685
1253
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1686
|
-
if (plugin.
|
|
1687
|
-
this.
|
|
1254
|
+
if (plugin.serverNode)
|
|
1255
|
+
this.setServerNodeReachability(plugin.serverNode, true);
|
|
1688
1256
|
if (plugin.type === 'AccessoryPlatform' && plugin.device)
|
|
1689
1257
|
this.setDeviceReachability(plugin.device, true);
|
|
1690
|
-
if (plugin.type === 'DynamicPlatform' && plugin.
|
|
1691
|
-
this.setAggregatorReachability(plugin.
|
|
1258
|
+
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1259
|
+
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1260
|
+
this.frontend.wssSendRefreshRequired();
|
|
1692
1261
|
}, 60 * 1000);
|
|
1693
1262
|
}
|
|
1694
1263
|
}, 1000);
|
|
1695
1264
|
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Starts the Matterbridge controller.
|
|
1698
|
-
* @private
|
|
1699
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
|
-
*/
|
|
1701
1265
|
async startController() {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
1707
|
-
this.
|
|
1708
|
-
this.
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
this.log.
|
|
1715
|
-
|
|
1716
|
-
this.log.info('
|
|
1717
|
-
|
|
1718
|
-
|
|
1266
|
+
}
|
|
1267
|
+
async startMatterStorage() {
|
|
1268
|
+
this.log.info(`Starting matter node storage...`);
|
|
1269
|
+
this.matterStorageService = this.environment.get(StorageService);
|
|
1270
|
+
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
1271
|
+
this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
|
|
1272
|
+
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1273
|
+
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1274
|
+
this.log.info('Matter node storage started');
|
|
1275
|
+
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1276
|
+
}
|
|
1277
|
+
async backupMatterStorage(storageName, backupName) {
|
|
1278
|
+
this.log.info('Creating matter node storage backup...');
|
|
1279
|
+
await copyDirectory(storageName, backupName);
|
|
1280
|
+
this.log.info('Created matter node storage backup');
|
|
1281
|
+
}
|
|
1282
|
+
async stopMatterStorage() {
|
|
1283
|
+
this.log.info('Closing matter node storage...');
|
|
1284
|
+
this.matterStorageManager?.close();
|
|
1285
|
+
this.matterStorageService = undefined;
|
|
1286
|
+
this.matterStorageManager = undefined;
|
|
1287
|
+
this.matterbridgeContext = undefined;
|
|
1288
|
+
this.log.info('Matter node storage closed');
|
|
1289
|
+
}
|
|
1290
|
+
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1291
|
+
if (!this.matterStorageService)
|
|
1292
|
+
throw new Error('No storage service initialized');
|
|
1293
|
+
this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
|
|
1294
|
+
const storageManager = await this.matterStorageService.open(pluginName);
|
|
1295
|
+
const storageContext = storageManager.createContext('persist');
|
|
1296
|
+
const random = randomBytes(8).toString('hex');
|
|
1297
|
+
await storageContext.set('storeId', pluginName);
|
|
1298
|
+
await storageContext.set('deviceName', deviceName);
|
|
1299
|
+
await storageContext.set('deviceType', deviceType);
|
|
1300
|
+
await storageContext.set('vendorId', vendorId);
|
|
1301
|
+
await storageContext.set('vendorName', vendorName.slice(0, 32));
|
|
1302
|
+
await storageContext.set('productId', productId);
|
|
1303
|
+
await storageContext.set('productName', productName.slice(0, 32));
|
|
1304
|
+
await storageContext.set('nodeLabel', productName.slice(0, 32));
|
|
1305
|
+
await storageContext.set('productLabel', productName.slice(0, 32));
|
|
1306
|
+
await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
|
|
1307
|
+
await storageContext.set('uniqueId', await storageContext.get('uniqueId', 'UI' + random));
|
|
1308
|
+
await storageContext.set('softwareVersion', this.matterbridgeVersion !== '' && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
|
|
1309
|
+
await storageContext.set('softwareVersionString', this.matterbridgeVersion !== '' ? this.matterbridgeVersion : '1.0.0');
|
|
1310
|
+
await storageContext.set('hardwareVersion', this.systemInformation.osRelease !== '' && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
|
|
1311
|
+
await storageContext.set('hardwareVersionString', this.systemInformation.osRelease !== '' ? this.systemInformation.osRelease : '1.0.0');
|
|
1312
|
+
this.log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`);
|
|
1313
|
+
this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
|
|
1314
|
+
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
|
|
1315
|
+
this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
1316
|
+
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
|
|
1317
|
+
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1318
|
+
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1319
|
+
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1320
|
+
return storageContext;
|
|
1321
|
+
}
|
|
1322
|
+
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1323
|
+
const storeId = await storageContext.get('storeId');
|
|
1324
|
+
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
1325
|
+
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
|
|
1326
|
+
this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
1327
|
+
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
|
|
1328
|
+
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1329
|
+
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1330
|
+
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1331
|
+
const serverNode = await ServerNode.create({
|
|
1332
|
+
id: storeId,
|
|
1333
|
+
network: {
|
|
1334
|
+
listeningAddressIpv4: this.ipv4address,
|
|
1335
|
+
listeningAddressIpv6: this.ipv6address,
|
|
1336
|
+
port,
|
|
1337
|
+
},
|
|
1338
|
+
commissioning: {
|
|
1339
|
+
passcode,
|
|
1340
|
+
discriminator,
|
|
1341
|
+
},
|
|
1342
|
+
productDescription: {
|
|
1343
|
+
name: await storageContext.get('deviceName'),
|
|
1344
|
+
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1345
|
+
},
|
|
1346
|
+
basicInformation: {
|
|
1347
|
+
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1348
|
+
vendorName: await storageContext.get('vendorName'),
|
|
1349
|
+
productId: await storageContext.get('productId'),
|
|
1350
|
+
productName: await storageContext.get('productName'),
|
|
1351
|
+
productLabel: await storageContext.get('productName'),
|
|
1352
|
+
nodeLabel: await storageContext.get('productName'),
|
|
1353
|
+
serialNumber: await storageContext.get('serialNumber'),
|
|
1354
|
+
uniqueId: await storageContext.get('uniqueId'),
|
|
1355
|
+
softwareVersion: await storageContext.get('softwareVersion'),
|
|
1356
|
+
softwareVersionString: await storageContext.get('softwareVersionString'),
|
|
1357
|
+
hardwareVersion: await storageContext.get('hardwareVersion'),
|
|
1358
|
+
hardwareVersionString: await storageContext.get('hardwareVersionString'),
|
|
1359
|
+
},
|
|
1719
1360
|
});
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1361
|
+
const sanitizeFabrics = (fabrics) => {
|
|
1362
|
+
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1363
|
+
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1364
|
+
if (this.bridgeMode === 'bridge') {
|
|
1365
|
+
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1366
|
+
this.matterbridgeSessionInformations = [];
|
|
1367
|
+
this.matterbridgePaired = true;
|
|
1368
|
+
}
|
|
1369
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1370
|
+
const plugin = this.plugins.get(storeId);
|
|
1371
|
+
if (plugin) {
|
|
1372
|
+
plugin.fabricInformations = sanitizedFabrics;
|
|
1373
|
+
plugin.sessionInformations = [];
|
|
1374
|
+
plugin.paired = true;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1379
|
+
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1380
|
+
serverNode.lifecycle.online.on(() => {
|
|
1381
|
+
this.log.notice(`Server node for ${storeId} is online`);
|
|
1382
|
+
if (!serverNode.lifecycle.isCommissioned) {
|
|
1383
|
+
this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
|
|
1384
|
+
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
1385
|
+
if (this.bridgeMode === 'bridge') {
|
|
1386
|
+
this.matterbridgeQrPairingCode = qrPairingCode;
|
|
1387
|
+
this.matterbridgeManualPairingCode = manualPairingCode;
|
|
1388
|
+
this.matterbridgeFabricInformations = [];
|
|
1389
|
+
this.matterbridgeSessionInformations = [];
|
|
1390
|
+
this.matterbridgePaired = false;
|
|
1391
|
+
this.matterbridgeConnected = false;
|
|
1392
|
+
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1393
|
+
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1394
|
+
}
|
|
1395
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1396
|
+
const plugin = this.plugins.get(storeId);
|
|
1397
|
+
if (plugin) {
|
|
1398
|
+
plugin.qrPairingCode = qrPairingCode;
|
|
1399
|
+
plugin.manualPairingCode = manualPairingCode;
|
|
1400
|
+
plugin.fabricInformations = [];
|
|
1401
|
+
plugin.sessionInformations = [];
|
|
1402
|
+
plugin.paired = false;
|
|
1403
|
+
plugin.connected = false;
|
|
1404
|
+
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1405
|
+
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1737
1408
|
}
|
|
1738
1409
|
else {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
throw new Error('Discriminator value must be less than 4096');
|
|
1742
|
-
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1410
|
+
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
1411
|
+
sanitizeFabrics(serverNode.state.commissioning.fabrics);
|
|
1743
1412
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1413
|
+
this.frontend.wssSendRefreshRequired();
|
|
1414
|
+
});
|
|
1415
|
+
serverNode.lifecycle.offline.on(() => {
|
|
1416
|
+
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1417
|
+
if (this.bridgeMode === 'bridge') {
|
|
1418
|
+
this.matterbridgeQrPairingCode = undefined;
|
|
1419
|
+
this.matterbridgeManualPairingCode = undefined;
|
|
1420
|
+
this.matterbridgeFabricInformations = [];
|
|
1421
|
+
this.matterbridgeSessionInformations = [];
|
|
1422
|
+
this.matterbridgePaired = false;
|
|
1423
|
+
this.matterbridgeConnected = false;
|
|
1746
1424
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
this.log.
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1425
|
+
this.frontend.wssSendRefreshRequired();
|
|
1426
|
+
});
|
|
1427
|
+
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1428
|
+
let action = '';
|
|
1429
|
+
switch (fabricAction) {
|
|
1430
|
+
case FabricAction.Added:
|
|
1431
|
+
action = 'added';
|
|
1432
|
+
break;
|
|
1433
|
+
case FabricAction.Removed:
|
|
1434
|
+
action = 'removed';
|
|
1435
|
+
break;
|
|
1436
|
+
case FabricAction.Updated:
|
|
1437
|
+
action = 'updated';
|
|
1438
|
+
break;
|
|
1439
|
+
}
|
|
1440
|
+
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1441
|
+
sanitizeFabrics(serverNode.state.commissioning.fabrics);
|
|
1442
|
+
this.frontend.wssSendRefreshRequired();
|
|
1443
|
+
});
|
|
1444
|
+
const sanitizeSessions = (sessions) => {
|
|
1445
|
+
const sanitizedSessions = this.sanitizeSessionInformation(sessions.map((session) => ({
|
|
1446
|
+
...session,
|
|
1447
|
+
secure: session.name.startsWith('secure'),
|
|
1448
|
+
})));
|
|
1449
|
+
this.log.debug(`Sessions: ${debugStringify(sanitizedSessions)}`);
|
|
1450
|
+
if (this.bridgeMode === 'bridge') {
|
|
1451
|
+
this.matterbridgeSessionInformations = sanitizedSessions;
|
|
1452
|
+
}
|
|
1453
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1454
|
+
const plugin = this.plugins.get(storeId);
|
|
1455
|
+
if (plugin) {
|
|
1456
|
+
plugin.sessionInformations = sanitizedSessions;
|
|
1457
|
+
}
|
|
1770
1458
|
}
|
|
1459
|
+
};
|
|
1460
|
+
serverNode.events.sessions.opened.on((session) => {
|
|
1461
|
+
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1462
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1463
|
+
this.frontend.wssSendRefreshRequired();
|
|
1464
|
+
});
|
|
1465
|
+
serverNode.events.sessions.closed.on((session) => {
|
|
1466
|
+
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1467
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1468
|
+
this.frontend.wssSendRefreshRequired();
|
|
1469
|
+
});
|
|
1470
|
+
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1471
|
+
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1472
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1473
|
+
this.frontend.wssSendRefreshRequired();
|
|
1474
|
+
});
|
|
1475
|
+
this.log.info(`Created server node for ${storeId}`);
|
|
1476
|
+
return serverNode;
|
|
1477
|
+
}
|
|
1478
|
+
async startServerNode(matterServerNode) {
|
|
1479
|
+
if (!matterServerNode)
|
|
1771
1480
|
return;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1778
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1481
|
+
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1482
|
+
await matterServerNode.start();
|
|
1483
|
+
}
|
|
1484
|
+
async stopServerNode(matterServerNode) {
|
|
1485
|
+
if (!matterServerNode)
|
|
1779
1486
|
return;
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1783
|
-
for (const nodeId of nodeIds) {
|
|
1784
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1785
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1786
|
-
autoSubscribe: false,
|
|
1787
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) => this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1788
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) => this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1789
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1790
|
-
switch (info) {
|
|
1791
|
-
case NodeStateInformation.Connected:
|
|
1792
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1793
|
-
break;
|
|
1794
|
-
case NodeStateInformation.Disconnected:
|
|
1795
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1796
|
-
break;
|
|
1797
|
-
case NodeStateInformation.Reconnecting:
|
|
1798
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1799
|
-
break;
|
|
1800
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1801
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1802
|
-
break;
|
|
1803
|
-
case NodeStateInformation.StructureChanged:
|
|
1804
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1805
|
-
break;
|
|
1806
|
-
case NodeStateInformation.Decommissioned:
|
|
1807
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1808
|
-
break;
|
|
1809
|
-
default:
|
|
1810
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1811
|
-
break;
|
|
1812
|
-
}
|
|
1813
|
-
},
|
|
1814
|
-
});
|
|
1815
|
-
node.logStructure();
|
|
1816
|
-
// Get the interaction client
|
|
1817
|
-
this.log.info('Getting the interaction client');
|
|
1818
|
-
const interactionClient = await node.getInteractionClient();
|
|
1819
|
-
let cluster;
|
|
1820
|
-
let attributes;
|
|
1821
|
-
// Log BasicInformationCluster
|
|
1822
|
-
cluster = BasicInformationCluster;
|
|
1823
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1824
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1825
|
-
});
|
|
1826
|
-
if (attributes.length > 0)
|
|
1827
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1828
|
-
attributes.forEach((attribute) => {
|
|
1829
|
-
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1830
|
-
});
|
|
1831
|
-
// Log PowerSourceCluster
|
|
1832
|
-
cluster = PowerSourceCluster;
|
|
1833
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1834
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1835
|
-
});
|
|
1836
|
-
if (attributes.length > 0)
|
|
1837
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1838
|
-
attributes.forEach((attribute) => {
|
|
1839
|
-
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1840
|
-
});
|
|
1841
|
-
// Log ThreadNetworkDiagnostics
|
|
1842
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1843
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1844
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1845
|
-
});
|
|
1846
|
-
if (attributes.length > 0)
|
|
1847
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1848
|
-
attributes.forEach((attribute) => {
|
|
1849
|
-
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1850
|
-
});
|
|
1851
|
-
// Log SwitchCluster
|
|
1852
|
-
cluster = SwitchCluster;
|
|
1853
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1854
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1855
|
-
});
|
|
1856
|
-
if (attributes.length > 0)
|
|
1857
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1858
|
-
attributes.forEach((attribute) => {
|
|
1859
|
-
this.log.info(`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`);
|
|
1860
|
-
});
|
|
1861
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1862
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1863
|
-
ignoreInitialTriggers: false,
|
|
1864
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) => this.log.info(`***${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}`),
|
|
1865
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1866
|
-
this.log.info(`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`);
|
|
1867
|
-
},
|
|
1868
|
-
});
|
|
1869
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1870
|
-
}
|
|
1487
|
+
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1488
|
+
await matterServerNode.close();
|
|
1871
1489
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
async startMatterStorage(storageType, storageName) {
|
|
1882
|
-
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1883
|
-
if (storageType === 'disk') {
|
|
1884
|
-
const storageDisk = new StorageBackendDisk(storageName);
|
|
1885
|
-
this.storageManager = new StorageManager(storageDisk);
|
|
1886
|
-
}
|
|
1887
|
-
else if (storageType === 'json') {
|
|
1888
|
-
if (!storageName.endsWith('.json'))
|
|
1889
|
-
storageName += '.json';
|
|
1890
|
-
const storageJson = new StorageBackendJsonFile(storageName);
|
|
1891
|
-
this.storageManager = new StorageManager(storageJson);
|
|
1892
|
-
}
|
|
1893
|
-
else {
|
|
1894
|
-
this.log.error(`Unsupported matter storage type ${storageType}`);
|
|
1895
|
-
await this.cleanup('Unsupported matter storage type');
|
|
1490
|
+
async createAggregatorNode(storageContext) {
|
|
1491
|
+
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1492
|
+
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1493
|
+
return aggregatorNode;
|
|
1494
|
+
}
|
|
1495
|
+
async addBridgedEndpoint(pluginName, device) {
|
|
1496
|
+
const plugin = this.plugins.get(pluginName);
|
|
1497
|
+
if (!plugin) {
|
|
1498
|
+
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1896
1499
|
return;
|
|
1897
1500
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
this.
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
}
|
|
1501
|
+
if (this.bridgeMode === 'bridge') {
|
|
1502
|
+
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1503
|
+
if (!this.aggregatorNode)
|
|
1504
|
+
this.log.error('Aggregator node not found for Matterbridge');
|
|
1505
|
+
await this.aggregatorNode?.add(device);
|
|
1904
1506
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
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');
|
|
1507
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
1508
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
1509
|
+
this.createAccessoryPlugin(plugin, device);
|
|
1916
1510
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1511
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
1512
|
+
plugin.locked = true;
|
|
1513
|
+
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} aggregator node`);
|
|
1514
|
+
if (!plugin.aggregatorNode)
|
|
1515
|
+
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${db}`);
|
|
1516
|
+
await plugin.aggregatorNode?.add(device);
|
|
1921
1517
|
}
|
|
1922
1518
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1519
|
+
if (plugin.registeredDevices !== undefined)
|
|
1520
|
+
plugin.registeredDevices++;
|
|
1521
|
+
if (plugin.addedDevices !== undefined)
|
|
1522
|
+
plugin.addedDevices++;
|
|
1523
|
+
this.devices.set(device);
|
|
1524
|
+
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}`);
|
|
1928
1525
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
*/
|
|
1935
|
-
async convertStorage(context, pluginName) {
|
|
1936
|
-
if (this.edge !== false)
|
|
1526
|
+
async removeBridgedEndpoint(pluginName, device) {
|
|
1527
|
+
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1528
|
+
const plugin = this.plugins.get(pluginName);
|
|
1529
|
+
if (!plugin) {
|
|
1530
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1937
1531
|
return;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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).`);
|
|
1532
|
+
}
|
|
1533
|
+
if (this.bridgeMode === 'bridge') {
|
|
1534
|
+
if (!this.aggregatorNode) {
|
|
1535
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1971
1536
|
return;
|
|
1972
1537
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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.
|
|
2146
|
-
*/
|
|
2147
|
-
async backupMatterStorage(storageName, backupName) {
|
|
2148
|
-
try {
|
|
2149
|
-
this.log.debug(`Making backup copy of ${storageName}`);
|
|
2150
|
-
await fs.copyFile(storageName, backupName);
|
|
2151
|
-
this.log.debug(`Successfully backed up ${storageName} to ${backupName}`);
|
|
1538
|
+
await device.delete();
|
|
1539
|
+
this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1540
|
+
if (plugin.registeredDevices !== undefined)
|
|
1541
|
+
plugin.registeredDevices--;
|
|
1542
|
+
if (plugin.addedDevices !== undefined)
|
|
1543
|
+
plugin.addedDevices--;
|
|
2152
1544
|
}
|
|
2153
|
-
|
|
2154
|
-
if (
|
|
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)}`);
|
|
1545
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
1546
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
2164
1547
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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}`);
|
|
1548
|
+
else if (plugin.type === 'DynamicPlatform') {
|
|
1549
|
+
if (!plugin.aggregatorNode) {
|
|
1550
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1551
|
+
return;
|
|
2186
1552
|
}
|
|
1553
|
+
await device.delete();
|
|
2187
1554
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
this.log.debug('Storage closed');
|
|
2201
|
-
this.storageManager = undefined;
|
|
2202
|
-
this.matterbridgeContext = undefined;
|
|
2203
|
-
this.mattercontrollerContext = undefined;
|
|
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;
|
|
2436
|
-
}
|
|
2437
|
-
/**
|
|
2438
|
-
* Creates a commissioning server storage context.
|
|
2439
|
-
*
|
|
2440
|
-
* @param pluginName - The name of the plugin.
|
|
2441
|
-
* @param deviceName - The name of the device.
|
|
2442
|
-
* @param deviceType - The type of the device.
|
|
2443
|
-
* @param vendorId - The vendor ID.
|
|
2444
|
-
* @param vendorName - The vendor name.
|
|
2445
|
-
* @param productId - The product ID.
|
|
2446
|
-
* @param productName - The product name.
|
|
2447
|
-
* @param serialNumber - The serial number of the device (optional).
|
|
2448
|
-
* @param uniqueId - The unique ID of the device (optional).
|
|
2449
|
-
* @param softwareVersion - The software version of the device (optional).
|
|
2450
|
-
* @param softwareVersionString - The software version string of the device (optional).
|
|
2451
|
-
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2452
|
-
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2453
|
-
* @returns The storage context for the commissioning server.
|
|
2454
|
-
*/
|
|
2455
|
-
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
2456
|
-
if (!this.storageManager)
|
|
2457
|
-
throw new Error('No storage manager initialized');
|
|
2458
|
-
this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2459
|
-
const random = randomBytes(8).toString('hex');
|
|
2460
|
-
const storageContext = this.storageManager.createContext(pluginName);
|
|
2461
|
-
await storageContext.set('deviceName', deviceName);
|
|
2462
|
-
await storageContext.set('deviceType', deviceType);
|
|
2463
|
-
await storageContext.set('vendorId', vendorId);
|
|
2464
|
-
await storageContext.set('vendorName', vendorName.slice(0, 32));
|
|
2465
|
-
await storageContext.set('productId', productId);
|
|
2466
|
-
await storageContext.set('productName', productName.slice(0, 32));
|
|
2467
|
-
await storageContext.set('nodeLabel', productName.slice(0, 32));
|
|
2468
|
-
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 ?? '1.0.0');
|
|
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 ?? '1.0.0');
|
|
2475
|
-
this.log.debug(`Created commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2476
|
-
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
2477
|
-
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2478
|
-
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2479
|
-
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2480
|
-
return storageContext;
|
|
2481
|
-
}
|
|
2482
|
-
/**
|
|
2483
|
-
* Imports the commissioning server context for a specific plugin and device.
|
|
2484
|
-
* @param pluginName - The name of the plugin.
|
|
2485
|
-
* @param device - The MatterbridgeDevice object representing the device.
|
|
2486
|
-
* @returns The commissioning server context.
|
|
2487
|
-
* @throws Error if the BasicInformationCluster is not found.
|
|
2488
|
-
*/
|
|
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')}`);
|
|
2519
|
-
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2520
|
-
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2521
|
-
return storageContext;
|
|
2522
|
-
}
|
|
2523
|
-
/**
|
|
2524
|
-
* Shows the commissioning server QR code for a given plugin.
|
|
2525
|
-
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2526
|
-
* @param {StorageContext} storageContext - The storage context instance.
|
|
2527
|
-
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2528
|
-
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2529
|
-
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2530
|
-
*/
|
|
2531
|
-
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
2532
|
-
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
2533
|
-
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
2534
|
-
await this.cleanup('No storage initialized in showCommissioningQRCode');
|
|
2535
|
-
return;
|
|
2536
|
-
}
|
|
2537
|
-
if (!commissioningServer.isCommissioned()) {
|
|
2538
|
-
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
2539
|
-
const QrCode = new QrCodeSchema();
|
|
2540
|
-
this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
|
|
2541
|
-
// eslint-disable-next-line no-console
|
|
2542
|
-
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
2543
|
-
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
2544
|
-
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
2545
|
-
if (pluginName === 'Matterbridge') {
|
|
2546
|
-
this.matterbridgeQrPairingCode = qrPairingCode;
|
|
2547
|
-
this.matterbridgeManualPairingCode = manualPairingCode;
|
|
2548
|
-
this.matterbridgeFabricInformations = [];
|
|
2549
|
-
this.matterbridgeSessionInformations = [];
|
|
2550
|
-
this.matterbridgePaired = false;
|
|
2551
|
-
this.matterbridgeConnected = false;
|
|
2552
|
-
}
|
|
2553
|
-
if (pluginName !== 'Matterbridge') {
|
|
2554
|
-
const plugin = this.plugins.get(pluginName);
|
|
2555
|
-
if (plugin) {
|
|
2556
|
-
plugin.qrPairingCode = qrPairingCode;
|
|
2557
|
-
plugin.manualPairingCode = manualPairingCode;
|
|
2558
|
-
plugin.fabricInformations = [];
|
|
2559
|
-
plugin.sessionInformations = [];
|
|
2560
|
-
plugin.paired = false;
|
|
2561
|
-
plugin.connected = false;
|
|
1555
|
+
this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1556
|
+
if (plugin.registeredDevices !== undefined)
|
|
1557
|
+
plugin.registeredDevices--;
|
|
1558
|
+
if (plugin.addedDevices !== undefined)
|
|
1559
|
+
plugin.addedDevices--;
|
|
1560
|
+
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1561
|
+
if (plugin.serverNode) {
|
|
1562
|
+
await this.stopServerNode(plugin.serverNode);
|
|
1563
|
+
plugin.locked = false;
|
|
1564
|
+
plugin.aggregatorNode = undefined;
|
|
1565
|
+
plugin.serverNode = undefined;
|
|
1566
|
+
this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
|
|
2562
1567
|
}
|
|
2563
1568
|
}
|
|
2564
1569
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
this.log.info(`- fabric index ${zb}${info.fabricIndex}${nf} id ${zb}${info.fabricId}${nf} vendor ${zb}${info.rootVendorId}${nf} ${this.getVendorIdName(info.rootVendorId)} ${info.label}`);
|
|
2572
|
-
});
|
|
2573
|
-
if (pluginName === 'Matterbridge') {
|
|
2574
|
-
this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2575
|
-
this.matterbridgeSessionInformations = [];
|
|
2576
|
-
this.matterbridgePaired = true;
|
|
2577
|
-
}
|
|
2578
|
-
if (pluginName !== 'Matterbridge') {
|
|
2579
|
-
const plugin = this.plugins.get(pluginName);
|
|
2580
|
-
if (plugin) {
|
|
2581
|
-
plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2582
|
-
plugin.sessionInformations = [];
|
|
2583
|
-
plugin.paired = true;
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
1570
|
+
this.devices.remove(device);
|
|
1571
|
+
}
|
|
1572
|
+
async removeAllBridgedEndpoints(pluginName) {
|
|
1573
|
+
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1574
|
+
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1575
|
+
await this.removeBridgedEndpoint(pluginName, device);
|
|
2586
1576
|
}
|
|
2587
|
-
this.wssSendRefreshRequired();
|
|
2588
1577
|
}
|
|
2589
|
-
/**
|
|
2590
|
-
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2591
|
-
*
|
|
2592
|
-
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2593
|
-
* @returns An array of sanitized exposed fabric information objects.
|
|
2594
|
-
*/
|
|
2595
1578
|
sanitizeFabricInformations(fabricInfo) {
|
|
2596
1579
|
return fabricInfo.map((info) => {
|
|
2597
1580
|
return {
|
|
@@ -2605,12 +1588,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2605
1588
|
};
|
|
2606
1589
|
});
|
|
2607
1590
|
}
|
|
2608
|
-
/**
|
|
2609
|
-
* Sanitizes the session information by converting bigint properties to string.
|
|
2610
|
-
*
|
|
2611
|
-
* @param sessionInfo - The array of session information objects.
|
|
2612
|
-
* @returns An array of sanitized session information objects.
|
|
2613
|
-
*/
|
|
2614
1591
|
sanitizeSessionInformation(sessionInfo) {
|
|
2615
1592
|
return sessionInfo
|
|
2616
1593
|
.filter((session) => session.isPeerActive)
|
|
@@ -2638,48 +1615,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2638
1615
|
};
|
|
2639
1616
|
});
|
|
2640
1617
|
}
|
|
2641
|
-
|
|
2642
|
-
* Sets the reachability of a commissioning server and trigger.
|
|
2643
|
-
*
|
|
2644
|
-
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2645
|
-
* @param {boolean} reachable - The new reachability status.
|
|
2646
|
-
*/
|
|
2647
|
-
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2648
|
-
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2649
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2650
|
-
basicInformationCluster.setReachableAttribute(reachable);
|
|
2651
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2652
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1618
|
+
setServerNodeReachability(serverNode, reachable) {
|
|
2653
1619
|
}
|
|
2654
|
-
|
|
2655
|
-
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2656
|
-
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2657
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2658
|
-
*/
|
|
2659
|
-
setAggregatorReachability(matterAggregator, reachable) {
|
|
2660
|
-
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2661
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2662
|
-
basicInformationCluster.setReachableAttribute(reachable);
|
|
2663
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2664
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2665
|
-
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2666
|
-
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2667
|
-
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2668
|
-
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2669
|
-
});
|
|
1620
|
+
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2670
1621
|
}
|
|
2671
|
-
/**
|
|
2672
|
-
* Sets the reachability of a device and trigger.
|
|
2673
|
-
*
|
|
2674
|
-
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2675
|
-
* @param {boolean} reachable - The new reachability status of the device.
|
|
2676
|
-
*/
|
|
2677
1622
|
setDeviceReachability(device, reachable) {
|
|
2678
|
-
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2679
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2680
|
-
basicInformationCluster.setReachableAttribute(reachable);
|
|
2681
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2682
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2683
1623
|
}
|
|
2684
1624
|
getVendorIdName = (vendorId) => {
|
|
2685
1625
|
if (!vendorId)
|
|
@@ -2722,71 +1662,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2722
1662
|
}
|
|
2723
1663
|
return vendorName;
|
|
2724
1664
|
};
|
|
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
|
-
/**
|
|
2761
|
-
* Spawns a child process with the given command and arguments.
|
|
2762
|
-
* @param {string} command - The command to execute.
|
|
2763
|
-
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2764
|
-
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2765
|
-
*/
|
|
2766
1665
|
async spawnCommand(command, args = []) {
|
|
2767
|
-
/*
|
|
2768
|
-
npm > npm.cmd on windows
|
|
2769
|
-
cmd.exe ['dir'] on windows
|
|
2770
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2771
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
2772
|
-
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2773
|
-
});
|
|
2774
|
-
|
|
2775
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2776
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2777
|
-
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2778
|
-
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2779
|
-
*/
|
|
2780
1666
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2781
1667
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2782
|
-
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2783
1668
|
const argstring = 'npm ' + args.join(' ');
|
|
2784
1669
|
args.splice(0, args.length, '/c', argstring);
|
|
2785
1670
|
command = 'cmd.exe';
|
|
2786
1671
|
}
|
|
2787
|
-
// Decide when using sudo on linux
|
|
2788
|
-
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2789
|
-
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2790
1672
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2791
1673
|
args.unshift(command);
|
|
2792
1674
|
command = 'sudo';
|
|
@@ -2801,7 +1683,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2801
1683
|
reject(err);
|
|
2802
1684
|
});
|
|
2803
1685
|
childProcess.on('close', (code, signal) => {
|
|
2804
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
|
|
1686
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
|
|
2805
1687
|
if (code === 0) {
|
|
2806
1688
|
if (cmdLine.startsWith('npm install -g'))
|
|
2807
1689
|
this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
|
|
@@ -2814,7 +1696,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2814
1696
|
}
|
|
2815
1697
|
});
|
|
2816
1698
|
childProcess.on('exit', (code, signal) => {
|
|
2817
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
1699
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
2818
1700
|
if (code === 0) {
|
|
2819
1701
|
this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
|
|
2820
1702
|
resolve(true);
|
|
@@ -2832,1060 +1714,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2832
1714
|
childProcess.stdout.on('data', (data) => {
|
|
2833
1715
|
const message = data.toString().trim();
|
|
2834
1716
|
this.log.debug(`Spawn output (stdout): ${message}`);
|
|
2835
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
1717
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
2836
1718
|
});
|
|
2837
1719
|
}
|
|
2838
1720
|
if (childProcess.stderr) {
|
|
2839
1721
|
childProcess.stderr.on('data', (data) => {
|
|
2840
1722
|
const message = data.toString().trim();
|
|
2841
1723
|
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,
|
|
1724
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
3170
1725
|
});
|
|
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
1726
|
}
|
|
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
1727
|
});
|
|
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
|
-
}
|
|
3788
|
-
});
|
|
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
1728
|
}
|
|
3890
1729
|
}
|
|
3891
|
-
//# sourceMappingURL=matterbridge.js.map
|