matterbridge 2.2.2-dev.3 → 2.2.3
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 +15 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +37 -2
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +201 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +307 -24
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +412 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +723 -49
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +118 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +251 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +185 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +178 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +229 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +77 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +121 -6
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +32 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +45 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +32 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +42 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +40 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +65 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +10 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/isvalid.d.ts +87 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +86 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +70 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/parameter.d.ts +44 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +41 -0
- package/dist/utils/parameter.js.map +1 -0
- package/dist/utils/wait.d.ts +43 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +48 -5
- package/dist/utils/wait.js.map +1 -0
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.a02de6aa.js → main.6ffd2c31.js} +6 -6
- package/frontend/build/static/js/{main.a02de6aa.js.map → main.6ffd2c31.js.map} +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- /package/frontend/build/static/js/{main.a02de6aa.js.LICENSE.txt → main.6ffd2c31.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 1.5.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// Node.js modules
|
|
1
24
|
import os from 'node:os';
|
|
2
25
|
import path from 'node:path';
|
|
3
26
|
import { promises as fs } from 'node:fs';
|
|
4
27
|
import EventEmitter from 'node:events';
|
|
28
|
+
// AnsiLogger module
|
|
5
29
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
30
|
+
// NodeStorage module
|
|
6
31
|
import { NodeStorageManager } from './storage/export.js';
|
|
32
|
+
// Matterbridge
|
|
7
33
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
|
|
8
34
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
9
35
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -11,13 +37,18 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
11
37
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
12
38
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
13
39
|
import { Frontend } from './frontend.js';
|
|
40
|
+
// @matter
|
|
14
41
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
15
42
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
16
43
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
17
44
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
45
|
+
// Default colors
|
|
18
46
|
const plg = '\u001B[38;5;33m';
|
|
19
47
|
const dev = '\u001B[38;5;79m';
|
|
20
48
|
const typ = '\u001B[38;5;207m';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
21
52
|
export class Matterbridge extends EventEmitter {
|
|
22
53
|
systemInformation = {
|
|
23
54
|
interfaceName: '',
|
|
@@ -61,7 +92,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
61
92
|
shellySysUpdate: false,
|
|
62
93
|
shellyMainUpdate: false,
|
|
63
94
|
profile: getParameter('profile'),
|
|
64
|
-
loggerLevel: "info"
|
|
95
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
65
96
|
fileLogger: false,
|
|
66
97
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
67
98
|
matterFileLogger: false,
|
|
@@ -91,16 +122,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
91
122
|
profile = getParameter('profile');
|
|
92
123
|
shutdown = false;
|
|
93
124
|
edge = true;
|
|
94
|
-
failCountLimit = hasParameter('shelly') ?
|
|
125
|
+
failCountLimit = hasParameter('shelly') ? 600 : 60;
|
|
95
126
|
log;
|
|
96
127
|
matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
97
128
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
98
129
|
plugins;
|
|
99
130
|
devices;
|
|
100
131
|
frontend = new Frontend(this);
|
|
132
|
+
// Matterbridge storage
|
|
101
133
|
nodeStorage;
|
|
102
134
|
nodeContext;
|
|
103
135
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
136
|
+
// Cleanup
|
|
104
137
|
hasCleanupStarted = false;
|
|
105
138
|
initialized = false;
|
|
106
139
|
execRunningCount = 0;
|
|
@@ -113,38 +146,70 @@ export class Matterbridge extends EventEmitter {
|
|
|
113
146
|
sigtermHandler;
|
|
114
147
|
exceptionHandler;
|
|
115
148
|
rejectionHandler;
|
|
149
|
+
// Matter environment
|
|
116
150
|
environment = Environment.default;
|
|
151
|
+
// Matter storage
|
|
117
152
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
118
153
|
matterStorageService;
|
|
119
154
|
matterStorageManager;
|
|
120
155
|
matterbridgeContext;
|
|
121
156
|
mattercontrollerContext;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
157
|
+
// Matter parameters
|
|
158
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
159
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
160
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
161
|
+
port; // first server node port
|
|
162
|
+
passcode; // first server node passcode
|
|
163
|
+
discriminator; // first server node discriminator
|
|
128
164
|
serverNode;
|
|
129
165
|
aggregatorNode;
|
|
130
166
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
131
167
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
132
168
|
static instance;
|
|
169
|
+
// We load asyncronously so is private
|
|
133
170
|
constructor() {
|
|
134
171
|
super();
|
|
135
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Emits an event of the specified type with the provided arguments.
|
|
175
|
+
*
|
|
176
|
+
* @template K - The type of the event.
|
|
177
|
+
* @param {K} eventName - The name of the event to emit.
|
|
178
|
+
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
179
|
+
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
180
|
+
*/
|
|
136
181
|
emit(eventName, ...args) {
|
|
137
182
|
return super.emit(eventName, ...args);
|
|
138
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Registers an event listener for the specified event type.
|
|
186
|
+
*
|
|
187
|
+
* @template K - The type of the event.
|
|
188
|
+
* @param {K} eventName - The name of the event to listen for.
|
|
189
|
+
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
190
|
+
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
191
|
+
*/
|
|
139
192
|
on(eventName, listener) {
|
|
140
193
|
return super.on(eventName, listener);
|
|
141
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Retrieves the list of Matterbridge devices.
|
|
197
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
198
|
+
*/
|
|
142
199
|
getDevices() {
|
|
143
200
|
return this.devices.array();
|
|
144
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Retrieves the list of registered plugins.
|
|
204
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
205
|
+
*/
|
|
145
206
|
getPlugins() {
|
|
146
207
|
return this.plugins.array();
|
|
147
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Set the logger logLevel for the Matterbridge classes.
|
|
211
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
212
|
+
*/
|
|
148
213
|
async setLogLevel(logLevel) {
|
|
149
214
|
if (this.log)
|
|
150
215
|
this.log.logLevel = logLevel;
|
|
@@ -158,19 +223,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
158
223
|
for (const plugin of this.plugins) {
|
|
159
224
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
160
225
|
continue;
|
|
161
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
162
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
226
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
227
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
228
|
+
}
|
|
229
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
230
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
231
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
232
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
233
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
234
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
169
235
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
170
236
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
171
237
|
}
|
|
238
|
+
/** ***********************************************************************************************************************************/
|
|
239
|
+
/** loadInstance() and cleanup() methods */
|
|
240
|
+
/** ***********************************************************************************************************************************/
|
|
241
|
+
/**
|
|
242
|
+
* Loads an instance of the Matterbridge class.
|
|
243
|
+
* If an instance already exists, return that instance.
|
|
244
|
+
*
|
|
245
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
246
|
+
* @returns The loaded Matterbridge instance.
|
|
247
|
+
*/
|
|
172
248
|
static async loadInstance(initialize = false) {
|
|
173
249
|
if (!Matterbridge.instance) {
|
|
250
|
+
// eslint-disable-next-line no-console
|
|
174
251
|
if (hasParameter('debug'))
|
|
175
252
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
176
253
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -179,8 +256,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
179
256
|
}
|
|
180
257
|
return Matterbridge.instance;
|
|
181
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Call cleanup().
|
|
261
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
262
|
+
*
|
|
263
|
+
*/
|
|
182
264
|
async destroyInstance() {
|
|
183
265
|
this.log.info(`Destroy instance...`);
|
|
266
|
+
// Save server nodes to close
|
|
184
267
|
const servers = [];
|
|
185
268
|
if (this.bridgeMode === 'bridge') {
|
|
186
269
|
if (this.serverNode)
|
|
@@ -192,55 +275,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
192
275
|
servers.push(plugin.serverNode);
|
|
193
276
|
}
|
|
194
277
|
}
|
|
278
|
+
// Cleanup
|
|
195
279
|
await this.cleanup('destroying instance...', false);
|
|
280
|
+
// Close servers mdns service
|
|
196
281
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
197
282
|
for (const server of servers) {
|
|
198
283
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
199
284
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
200
285
|
}
|
|
286
|
+
// Wait for the cleanup to finish
|
|
201
287
|
await new Promise((resolve) => {
|
|
202
288
|
setTimeout(resolve, 1000);
|
|
203
289
|
});
|
|
204
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Initializes the Matterbridge application.
|
|
293
|
+
*
|
|
294
|
+
* @remarks
|
|
295
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
296
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
297
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
298
|
+
*
|
|
299
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
300
|
+
*/
|
|
205
301
|
async initialize() {
|
|
302
|
+
// Set the restart mode
|
|
206
303
|
if (hasParameter('service'))
|
|
207
304
|
this.restartMode = 'service';
|
|
208
305
|
if (hasParameter('docker'))
|
|
209
306
|
this.restartMode = 'docker';
|
|
307
|
+
// Set the matterbridge directory
|
|
210
308
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
211
309
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
310
|
+
// Setup the matter environment
|
|
212
311
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
213
312
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
214
313
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
215
314
|
this.environment.vars.set('runtime.signals', false);
|
|
216
315
|
this.environment.vars.set('runtime.exitcode', false);
|
|
217
|
-
|
|
316
|
+
// Create the matterbridge logger
|
|
317
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
318
|
+
// Register process handlers
|
|
218
319
|
this.registerProcessHandlers();
|
|
320
|
+
// Initialize nodeStorage and nodeContext
|
|
219
321
|
try {
|
|
220
322
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
221
323
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
222
324
|
this.log.debug('Creating node storage context for matterbridge');
|
|
223
325
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
326
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
327
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
224
328
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
225
329
|
for (const key of keys) {
|
|
226
330
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
331
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
227
332
|
await this.nodeStorage?.storage.get(key);
|
|
228
333
|
}
|
|
229
334
|
const storages = await this.nodeStorage.getStorageNames();
|
|
230
335
|
for (const storage of storages) {
|
|
231
336
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
232
337
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
338
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
340
|
const keys = (await nodeContext?.storage.keys());
|
|
234
341
|
keys.forEach(async (key) => {
|
|
235
342
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
236
343
|
await nodeContext?.get(key);
|
|
237
344
|
});
|
|
238
345
|
}
|
|
346
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
239
347
|
this.log.debug('Creating node storage backup...');
|
|
240
348
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
241
349
|
this.log.debug('Created node storage backup');
|
|
242
350
|
}
|
|
243
351
|
catch (error) {
|
|
352
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
244
353
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
245
354
|
if (hasParameter('norestore')) {
|
|
246
355
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -255,41 +364,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
255
364
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
256
365
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
257
366
|
}
|
|
367
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
258
368
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
369
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
259
370
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
371
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
260
372
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
261
373
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
374
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
262
375
|
if (hasParameter('logger')) {
|
|
263
376
|
const level = getParameter('logger');
|
|
264
377
|
if (level === 'debug') {
|
|
265
|
-
this.log.logLevel = "debug"
|
|
378
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
266
379
|
}
|
|
267
380
|
else if (level === 'info') {
|
|
268
|
-
this.log.logLevel = "info"
|
|
381
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
269
382
|
}
|
|
270
383
|
else if (level === 'notice') {
|
|
271
|
-
this.log.logLevel = "notice"
|
|
384
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
272
385
|
}
|
|
273
386
|
else if (level === 'warn') {
|
|
274
|
-
this.log.logLevel = "warn"
|
|
387
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
275
388
|
}
|
|
276
389
|
else if (level === 'error') {
|
|
277
|
-
this.log.logLevel = "error"
|
|
390
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
278
391
|
}
|
|
279
392
|
else if (level === 'fatal') {
|
|
280
|
-
this.log.logLevel = "fatal"
|
|
393
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
281
394
|
}
|
|
282
395
|
else {
|
|
283
396
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
284
|
-
this.log.logLevel = "info"
|
|
397
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
285
398
|
}
|
|
286
399
|
}
|
|
287
400
|
else {
|
|
288
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
401
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
289
402
|
}
|
|
290
403
|
this.frontend.logLevel = this.log.logLevel;
|
|
291
404
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
292
405
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
406
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
293
407
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
294
408
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
295
409
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -298,6 +412,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
298
412
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
299
413
|
if (this.profile !== undefined)
|
|
300
414
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
415
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
301
416
|
if (hasParameter('matterlogger')) {
|
|
302
417
|
const level = getParameter('matterlogger');
|
|
303
418
|
if (level === 'debug') {
|
|
@@ -329,6 +444,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
329
444
|
Logger.format = MatterLogFormat.ANSI;
|
|
330
445
|
Logger.setLogger('default', this.createMatterLogger());
|
|
331
446
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
447
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
332
448
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
333
449
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
334
450
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -337,6 +453,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
337
453
|
});
|
|
338
454
|
}
|
|
339
455
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
456
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
340
457
|
if (hasParameter('mdnsinterface')) {
|
|
341
458
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
342
459
|
}
|
|
@@ -345,6 +462,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
345
462
|
if (this.mdnsInterface === '')
|
|
346
463
|
this.mdnsInterface = undefined;
|
|
347
464
|
}
|
|
465
|
+
// Validate mdnsInterface
|
|
348
466
|
if (this.mdnsInterface) {
|
|
349
467
|
const networkInterfaces = os.networkInterfaces();
|
|
350
468
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -358,6 +476,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
358
476
|
}
|
|
359
477
|
if (this.mdnsInterface)
|
|
360
478
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
479
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
361
480
|
if (hasParameter('ipv4address')) {
|
|
362
481
|
this.ipv4address = getParameter('ipv4address');
|
|
363
482
|
}
|
|
@@ -366,6 +485,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
366
485
|
if (this.ipv4address === '')
|
|
367
486
|
this.ipv4address = undefined;
|
|
368
487
|
}
|
|
488
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
369
489
|
if (hasParameter('ipv6address')) {
|
|
370
490
|
this.ipv6address = getParameter('ipv6address');
|
|
371
491
|
}
|
|
@@ -374,14 +494,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
374
494
|
if (this.ipv6address === '')
|
|
375
495
|
this.ipv6address = undefined;
|
|
376
496
|
}
|
|
497
|
+
// Initialize PluginManager
|
|
377
498
|
this.plugins = new PluginManager(this);
|
|
378
499
|
await this.plugins.loadFromStorage();
|
|
379
500
|
this.plugins.logLevel = this.log.logLevel;
|
|
501
|
+
// Initialize DeviceManager
|
|
380
502
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
381
503
|
this.devices.logLevel = this.log.logLevel;
|
|
504
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
382
505
|
for (const plugin of this.plugins) {
|
|
383
506
|
const packageJson = await this.plugins.parse(plugin);
|
|
384
507
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
508
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
509
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
385
510
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
386
511
|
try {
|
|
387
512
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -403,6 +528,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
403
528
|
await plugin.nodeContext.set('description', plugin.description);
|
|
404
529
|
await plugin.nodeContext.set('author', plugin.author);
|
|
405
530
|
}
|
|
531
|
+
// Log system info and create .matterbridge directory
|
|
406
532
|
await this.logNodeAndSystemInfo();
|
|
407
533
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
408
534
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -410,6 +536,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
410
536
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
411
537
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
412
538
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
539
|
+
// Check node version and throw error
|
|
413
540
|
const minNodeVersion = 18;
|
|
414
541
|
const nodeVersion = process.versions.node;
|
|
415
542
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -417,9 +544,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
417
544
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
418
545
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
419
546
|
}
|
|
547
|
+
// Parse command line
|
|
420
548
|
await this.parseCommandLine();
|
|
421
549
|
this.initialized = true;
|
|
422
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
553
|
+
* @private
|
|
554
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
555
|
+
*/
|
|
423
556
|
async parseCommandLine() {
|
|
424
557
|
if (hasParameter('help')) {
|
|
425
558
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -531,6 +664,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
531
664
|
this.shutdown = true;
|
|
532
665
|
return;
|
|
533
666
|
}
|
|
667
|
+
// Start the matter storage and create the matterbridge context
|
|
534
668
|
try {
|
|
535
669
|
await this.startMatterStorage();
|
|
536
670
|
}
|
|
@@ -538,12 +672,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
538
672
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
539
673
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
540
674
|
}
|
|
675
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
541
676
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
542
677
|
this.initialized = true;
|
|
543
678
|
await this.shutdownProcessAndReset();
|
|
544
679
|
this.shutdown = true;
|
|
545
680
|
return;
|
|
546
681
|
}
|
|
682
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
547
683
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
548
684
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
549
685
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -568,30 +704,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
568
704
|
this.shutdown = true;
|
|
569
705
|
return;
|
|
570
706
|
}
|
|
707
|
+
// Initialize frontend
|
|
571
708
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
572
709
|
await this.frontend.start(getIntParameter('frontend'));
|
|
710
|
+
// Check in 30 seconds the latest versions
|
|
573
711
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
574
712
|
const { checkUpdates } = await import('./update.js');
|
|
575
713
|
checkUpdates(this);
|
|
576
714
|
}, 30 * 1000).unref();
|
|
715
|
+
// Check each 24 hours the latest versions
|
|
577
716
|
this.checkUpdateInterval = setInterval(async () => {
|
|
578
717
|
const { checkUpdates } = await import('./update.js');
|
|
579
718
|
checkUpdates(this);
|
|
580
719
|
}, 24 * 60 * 60 * 1000).unref();
|
|
720
|
+
// Start the matterbridge in mode test
|
|
581
721
|
if (hasParameter('test')) {
|
|
582
722
|
this.bridgeMode = 'bridge';
|
|
583
723
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
584
724
|
return;
|
|
585
725
|
}
|
|
726
|
+
// Start the matterbridge in mode controller
|
|
586
727
|
if (hasParameter('controller')) {
|
|
587
728
|
this.bridgeMode = 'controller';
|
|
588
729
|
await this.startController();
|
|
589
730
|
return;
|
|
590
731
|
}
|
|
732
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
591
733
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
592
734
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
593
735
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
594
736
|
}
|
|
737
|
+
// Start matterbridge in bridge mode
|
|
595
738
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
596
739
|
this.bridgeMode = 'bridge';
|
|
597
740
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -599,6 +742,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
599
742
|
await this.startBridge();
|
|
600
743
|
return;
|
|
601
744
|
}
|
|
745
|
+
// Start matterbridge in childbridge mode
|
|
602
746
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
603
747
|
this.bridgeMode = 'childbridge';
|
|
604
748
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -607,10 +751,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
607
751
|
return;
|
|
608
752
|
}
|
|
609
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Asynchronously loads and starts the registered plugins.
|
|
756
|
+
*
|
|
757
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
758
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
759
|
+
*
|
|
760
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
761
|
+
*/
|
|
610
762
|
async startPlugins() {
|
|
763
|
+
// Check, load and start the plugins
|
|
611
764
|
for (const plugin of this.plugins) {
|
|
612
765
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
613
766
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
767
|
+
// Check if the plugin is available
|
|
614
768
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
615
769
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
616
770
|
plugin.enabled = false;
|
|
@@ -630,20 +784,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
630
784
|
plugin.addedDevices = undefined;
|
|
631
785
|
plugin.qrPairingCode = undefined;
|
|
632
786
|
plugin.manualPairingCode = undefined;
|
|
633
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
787
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
634
788
|
}
|
|
635
789
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
636
790
|
}
|
|
791
|
+
/**
|
|
792
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
793
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
794
|
+
*/
|
|
637
795
|
registerProcessHandlers() {
|
|
638
796
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
639
797
|
process.removeAllListeners('uncaughtException');
|
|
640
798
|
process.removeAllListeners('unhandledRejection');
|
|
641
799
|
this.exceptionHandler = async (error) => {
|
|
642
800
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
801
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
643
802
|
};
|
|
644
803
|
process.on('uncaughtException', this.exceptionHandler);
|
|
645
804
|
this.rejectionHandler = async (reason, promise) => {
|
|
646
805
|
this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
806
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
647
807
|
};
|
|
648
808
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
649
809
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -656,6 +816,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
656
816
|
};
|
|
657
817
|
process.on('SIGTERM', this.sigtermHandler);
|
|
658
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
821
|
+
*/
|
|
659
822
|
deregisterProcesslHandlers() {
|
|
660
823
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
661
824
|
if (this.exceptionHandler)
|
|
@@ -672,12 +835,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
672
835
|
process.off('SIGTERM', this.sigtermHandler);
|
|
673
836
|
this.sigtermHandler = undefined;
|
|
674
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* Logs the node and system information.
|
|
840
|
+
*/
|
|
675
841
|
async logNodeAndSystemInfo() {
|
|
842
|
+
// IP address information
|
|
676
843
|
const networkInterfaces = os.networkInterfaces();
|
|
677
844
|
this.systemInformation.interfaceName = '';
|
|
678
845
|
this.systemInformation.ipv4Address = '';
|
|
679
846
|
this.systemInformation.ipv6Address = '';
|
|
680
847
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
848
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
681
849
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
682
850
|
continue;
|
|
683
851
|
if (!interfaceDetails) {
|
|
@@ -703,19 +871,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
703
871
|
break;
|
|
704
872
|
}
|
|
705
873
|
}
|
|
874
|
+
// Node information
|
|
706
875
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
707
876
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
708
877
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
709
878
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
879
|
+
// Host system information
|
|
710
880
|
this.systemInformation.hostname = os.hostname();
|
|
711
881
|
this.systemInformation.user = os.userInfo().username;
|
|
712
|
-
this.systemInformation.osType = os.type();
|
|
713
|
-
this.systemInformation.osRelease = os.release();
|
|
714
|
-
this.systemInformation.osPlatform = os.platform();
|
|
715
|
-
this.systemInformation.osArch = os.arch();
|
|
716
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
717
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
718
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
882
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
883
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
884
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
885
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
886
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
887
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
888
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
889
|
+
// Log the system information
|
|
719
890
|
this.log.debug('Host System Information:');
|
|
720
891
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
721
892
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -731,16 +902,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
731
902
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
732
903
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
733
904
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
905
|
+
// Home directory
|
|
734
906
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
735
907
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
736
908
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
909
|
+
// Package root directory
|
|
737
910
|
const { fileURLToPath } = await import('node:url');
|
|
738
911
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
739
912
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
740
913
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
741
914
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
915
|
+
// Global node_modules directory
|
|
742
916
|
if (this.nodeContext)
|
|
743
917
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
918
|
+
// First run of Matterbridge so the node storage is empty
|
|
744
919
|
if (this.globalModulesDirectory === '') {
|
|
745
920
|
try {
|
|
746
921
|
this.execRunningCount++;
|
|
@@ -756,6 +931,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
756
931
|
}
|
|
757
932
|
else
|
|
758
933
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
934
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
935
|
+
else {
|
|
936
|
+
this.getGlobalNodeModules()
|
|
937
|
+
.then(async (globalModulesDirectory) => {
|
|
938
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
939
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
940
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
941
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
942
|
+
})
|
|
943
|
+
.catch((error) => {
|
|
944
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
945
|
+
});
|
|
946
|
+
}*/
|
|
947
|
+
// Create the data directory .matterbridge in the home directory
|
|
759
948
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
760
949
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
761
950
|
try {
|
|
@@ -779,6 +968,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
779
968
|
}
|
|
780
969
|
}
|
|
781
970
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
971
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
782
972
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
783
973
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
784
974
|
try {
|
|
@@ -802,50 +992,68 @@ export class Matterbridge extends EventEmitter {
|
|
|
802
992
|
}
|
|
803
993
|
}
|
|
804
994
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
995
|
+
// Matterbridge version
|
|
805
996
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
806
997
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
807
998
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
808
999
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1000
|
+
// Matterbridge latest version
|
|
809
1001
|
if (this.nodeContext)
|
|
810
1002
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
811
1003
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1004
|
+
// this.getMatterbridgeLatestVersion();
|
|
1005
|
+
// Current working directory
|
|
812
1006
|
const currentDir = process.cwd();
|
|
813
1007
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1008
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
814
1009
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
815
1010
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
816
1011
|
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1014
|
+
*
|
|
1015
|
+
* @returns {Function} The MatterLogger function.
|
|
1016
|
+
*/
|
|
817
1017
|
createMatterLogger() {
|
|
818
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1018
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
819
1019
|
return (_level, formattedLog) => {
|
|
820
1020
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
821
1021
|
const message = formattedLog.slice(65);
|
|
822
1022
|
matterLogger.logName = logger;
|
|
823
1023
|
switch (_level) {
|
|
824
1024
|
case MatterLogLevel.DEBUG:
|
|
825
|
-
matterLogger.log("debug"
|
|
1025
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
826
1026
|
break;
|
|
827
1027
|
case MatterLogLevel.INFO:
|
|
828
|
-
matterLogger.log("info"
|
|
1028
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
829
1029
|
break;
|
|
830
1030
|
case MatterLogLevel.NOTICE:
|
|
831
|
-
matterLogger.log("notice"
|
|
1031
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
832
1032
|
break;
|
|
833
1033
|
case MatterLogLevel.WARN:
|
|
834
|
-
matterLogger.log("warn"
|
|
1034
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
835
1035
|
break;
|
|
836
1036
|
case MatterLogLevel.ERROR:
|
|
837
|
-
matterLogger.log("error"
|
|
1037
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
838
1038
|
break;
|
|
839
1039
|
case MatterLogLevel.FATAL:
|
|
840
|
-
matterLogger.log("fatal"
|
|
1040
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
841
1041
|
break;
|
|
842
1042
|
default:
|
|
843
|
-
matterLogger.log("debug"
|
|
1043
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
844
1044
|
break;
|
|
845
1045
|
}
|
|
846
1046
|
};
|
|
847
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Creates a Matter File Logger.
|
|
1050
|
+
*
|
|
1051
|
+
* @param {string} filePath - The path to the log file.
|
|
1052
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1053
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1054
|
+
*/
|
|
848
1055
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1056
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
849
1057
|
let fileSize = 0;
|
|
850
1058
|
if (unlink) {
|
|
851
1059
|
try {
|
|
@@ -894,12 +1102,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
894
1102
|
}
|
|
895
1103
|
};
|
|
896
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1107
|
+
*/
|
|
897
1108
|
async restartProcess() {
|
|
898
1109
|
await this.cleanup('restarting...', true);
|
|
899
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Shut down the process by exiting the current process.
|
|
1113
|
+
*/
|
|
900
1114
|
async shutdownProcess() {
|
|
901
1115
|
await this.cleanup('shutting down...', false);
|
|
902
1116
|
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Update matterbridge and and shut down the process.
|
|
1119
|
+
*/
|
|
903
1120
|
async updateProcess() {
|
|
904
1121
|
this.log.info('Updating matterbridge...');
|
|
905
1122
|
try {
|
|
@@ -912,51 +1129,72 @@ export class Matterbridge extends EventEmitter {
|
|
|
912
1129
|
this.frontend.wssSendRestartRequired();
|
|
913
1130
|
await this.cleanup('updating...', false);
|
|
914
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Unregister all devices and shut down the process.
|
|
1134
|
+
*/
|
|
915
1135
|
async unregisterAndShutdownProcess() {
|
|
916
1136
|
this.log.info('Unregistering all devices and shutting down...');
|
|
917
1137
|
for (const plugin of this.plugins) {
|
|
918
1138
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
919
1139
|
}
|
|
920
1140
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
921
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1141
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
922
1142
|
this.log.debug('Cleaning up and shutting down...');
|
|
923
1143
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
924
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Reset commissioning and shut down the process.
|
|
1147
|
+
*/
|
|
925
1148
|
async shutdownProcessAndReset() {
|
|
926
1149
|
await this.cleanup('shutting down with reset...', false);
|
|
927
1150
|
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Factory reset and shut down the process.
|
|
1153
|
+
*/
|
|
928
1154
|
async shutdownProcessAndFactoryReset() {
|
|
929
1155
|
await this.cleanup('shutting down with factory reset...', false);
|
|
930
1156
|
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Cleans up the Matterbridge instance.
|
|
1159
|
+
* @param message - The cleanup message.
|
|
1160
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1161
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1162
|
+
*/
|
|
931
1163
|
async cleanup(message, restart = false) {
|
|
932
1164
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
933
1165
|
this.hasCleanupStarted = true;
|
|
934
1166
|
this.log.info(message);
|
|
1167
|
+
// Clear the start matter interval
|
|
935
1168
|
if (this.startMatterInterval) {
|
|
936
1169
|
clearInterval(this.startMatterInterval);
|
|
937
1170
|
this.startMatterInterval = undefined;
|
|
938
1171
|
this.log.debug('Start matter interval cleared');
|
|
939
1172
|
}
|
|
1173
|
+
// Clear the check update timeout
|
|
940
1174
|
if (this.checkUpdateTimeout) {
|
|
941
1175
|
clearInterval(this.checkUpdateTimeout);
|
|
942
1176
|
this.checkUpdateTimeout = undefined;
|
|
943
1177
|
this.log.debug('Check update timeout cleared');
|
|
944
1178
|
}
|
|
1179
|
+
// Clear the check update interval
|
|
945
1180
|
if (this.checkUpdateInterval) {
|
|
946
1181
|
clearInterval(this.checkUpdateInterval);
|
|
947
1182
|
this.checkUpdateInterval = undefined;
|
|
948
1183
|
this.log.debug('Check update interval cleared');
|
|
949
1184
|
}
|
|
1185
|
+
// Clear the configure timeout
|
|
950
1186
|
if (this.configureTimeout) {
|
|
951
1187
|
clearTimeout(this.configureTimeout);
|
|
952
1188
|
this.configureTimeout = undefined;
|
|
953
1189
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
954
1190
|
}
|
|
1191
|
+
// Clear the reachability timeout
|
|
955
1192
|
if (this.reachabilityTimeout) {
|
|
956
1193
|
clearTimeout(this.reachabilityTimeout);
|
|
957
1194
|
this.reachabilityTimeout = undefined;
|
|
958
1195
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
959
1196
|
}
|
|
1197
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
960
1198
|
for (const plugin of this.plugins) {
|
|
961
1199
|
if (!plugin.enabled || plugin.error)
|
|
962
1200
|
continue;
|
|
@@ -967,9 +1205,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
967
1205
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
968
1206
|
}
|
|
969
1207
|
}
|
|
1208
|
+
// Stopping matter server nodes
|
|
970
1209
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
971
1210
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
972
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1211
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
973
1212
|
if (this.bridgeMode === 'bridge') {
|
|
974
1213
|
if (this.serverNode) {
|
|
975
1214
|
await this.stopServerNode(this.serverNode);
|
|
@@ -985,6 +1224,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
985
1224
|
}
|
|
986
1225
|
}
|
|
987
1226
|
this.log.notice('Stopped matter server nodes');
|
|
1227
|
+
// Matter commisioning reset
|
|
988
1228
|
if (message === 'shutting down with reset...') {
|
|
989
1229
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
990
1230
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -994,17 +1234,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
994
1234
|
await this.matterbridgeContext?.clearAll();
|
|
995
1235
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
996
1236
|
}
|
|
1237
|
+
// Stop matter storage
|
|
997
1238
|
await this.stopMatterStorage();
|
|
1239
|
+
// Stop the frontend
|
|
998
1240
|
await this.frontend.stop();
|
|
1241
|
+
// Remove the matterfilelogger
|
|
999
1242
|
try {
|
|
1000
1243
|
Logger.removeLogger('matterfilelogger');
|
|
1244
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1001
1245
|
}
|
|
1002
1246
|
catch (error) {
|
|
1247
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1003
1248
|
}
|
|
1249
|
+
// Serialize registeredDevices
|
|
1004
1250
|
if (this.nodeStorage && this.nodeContext) {
|
|
1251
|
+
/*
|
|
1252
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1253
|
+
this.log.info('Saving registered devices...');
|
|
1254
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1255
|
+
this.devices.forEach(async (device) => {
|
|
1256
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1257
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1258
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1259
|
+
});
|
|
1260
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1261
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1262
|
+
*/
|
|
1263
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1005
1264
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1006
1265
|
await this.nodeContext.close();
|
|
1007
1266
|
this.nodeContext = undefined;
|
|
1267
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1008
1268
|
for (const plugin of this.plugins) {
|
|
1009
1269
|
if (plugin.nodeContext) {
|
|
1010
1270
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1021,8 +1281,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1021
1281
|
}
|
|
1022
1282
|
this.plugins.clear();
|
|
1023
1283
|
this.devices.clear();
|
|
1284
|
+
// Factory reset
|
|
1024
1285
|
if (message === 'shutting down with factory reset...') {
|
|
1025
1286
|
try {
|
|
1287
|
+
// Delete old matter storage file and backup
|
|
1026
1288
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1027
1289
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1028
1290
|
await fs.unlink(file);
|
|
@@ -1036,6 +1298,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1036
1298
|
}
|
|
1037
1299
|
}
|
|
1038
1300
|
try {
|
|
1301
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1039
1302
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1040
1303
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1041
1304
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1049,6 +1312,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1049
1312
|
}
|
|
1050
1313
|
}
|
|
1051
1314
|
try {
|
|
1315
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1052
1316
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1053
1317
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1054
1318
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1063,12 +1327,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1063
1327
|
}
|
|
1064
1328
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1065
1329
|
}
|
|
1330
|
+
// Deregisters the process handlers
|
|
1066
1331
|
this.deregisterProcesslHandlers();
|
|
1067
1332
|
if (restart) {
|
|
1068
1333
|
if (message === 'updating...') {
|
|
1069
1334
|
this.log.info('Cleanup completed. Updating...');
|
|
1070
1335
|
Matterbridge.instance = undefined;
|
|
1071
|
-
this.emit('update');
|
|
1336
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1072
1337
|
}
|
|
1073
1338
|
else if (message === 'restarting...') {
|
|
1074
1339
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1088,6 +1353,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1088
1353
|
this.log.debug('Cleanup already started...');
|
|
1089
1354
|
}
|
|
1090
1355
|
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1358
|
+
*
|
|
1359
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1360
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1361
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1362
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1363
|
+
*/
|
|
1091
1364
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1092
1365
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1093
1366
|
plugin.locked = true;
|
|
@@ -1100,6 +1373,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1100
1373
|
await this.startServerNode(plugin.serverNode);
|
|
1101
1374
|
}
|
|
1102
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1378
|
+
*
|
|
1379
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1380
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1381
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1382
|
+
*/
|
|
1103
1383
|
async createDynamicPlugin(plugin, start = false) {
|
|
1104
1384
|
if (!plugin.locked) {
|
|
1105
1385
|
plugin.locked = true;
|
|
@@ -1111,7 +1391,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1111
1391
|
await this.startServerNode(plugin.serverNode);
|
|
1112
1392
|
}
|
|
1113
1393
|
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Starts the Matterbridge in bridge mode.
|
|
1396
|
+
* @private
|
|
1397
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1398
|
+
*/
|
|
1114
1399
|
async startBridge() {
|
|
1400
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1115
1401
|
if (!this.matterStorageManager)
|
|
1116
1402
|
throw new Error('No storage manager initialized');
|
|
1117
1403
|
if (!this.matterbridgeContext)
|
|
@@ -1149,14 +1435,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1149
1435
|
clearInterval(this.startMatterInterval);
|
|
1150
1436
|
this.startMatterInterval = undefined;
|
|
1151
1437
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1438
|
+
// Start the Matter server node
|
|
1152
1439
|
this.startServerNode(this.serverNode);
|
|
1440
|
+
// Configure the plugins
|
|
1153
1441
|
this.configureTimeout = setTimeout(async () => {
|
|
1154
1442
|
for (const plugin of this.plugins) {
|
|
1155
1443
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1156
1444
|
continue;
|
|
1157
1445
|
try {
|
|
1158
1446
|
if ((await this.plugins.configure(plugin)) === undefined) {
|
|
1159
|
-
|
|
1447
|
+
if (plugin.configured !== true)
|
|
1448
|
+
this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
|
|
1160
1449
|
}
|
|
1161
1450
|
}
|
|
1162
1451
|
catch (error) {
|
|
@@ -1166,6 +1455,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1166
1455
|
}
|
|
1167
1456
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1168
1457
|
}, 30 * 1000);
|
|
1458
|
+
// Setting reachability to true
|
|
1169
1459
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1170
1460
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1171
1461
|
if (this.aggregatorNode)
|
|
@@ -1174,6 +1464,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1174
1464
|
}, 60 * 1000);
|
|
1175
1465
|
}, 1000);
|
|
1176
1466
|
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1469
|
+
* @private
|
|
1470
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1471
|
+
*/
|
|
1177
1472
|
async startChildbridge() {
|
|
1178
1473
|
if (!this.matterStorageManager)
|
|
1179
1474
|
throw new Error('No storage manager initialized');
|
|
@@ -1218,13 +1513,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1218
1513
|
clearInterval(this.startMatterInterval);
|
|
1219
1514
|
this.startMatterInterval = undefined;
|
|
1220
1515
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1516
|
+
// Configure the plugins
|
|
1221
1517
|
this.configureTimeout = setTimeout(async () => {
|
|
1222
1518
|
for (const plugin of this.plugins) {
|
|
1223
1519
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1224
1520
|
continue;
|
|
1225
1521
|
try {
|
|
1226
1522
|
if ((await this.plugins.configure(plugin)) === undefined) {
|
|
1227
|
-
|
|
1523
|
+
if (plugin.configured !== true)
|
|
1524
|
+
this.frontend.wssSendSnackbarMessage(`The plugin ${plugin.name} failed to configure. Check the logs.`, 0, 'error');
|
|
1228
1525
|
}
|
|
1229
1526
|
}
|
|
1230
1527
|
catch (error) {
|
|
@@ -1253,7 +1550,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1253
1550
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1254
1551
|
continue;
|
|
1255
1552
|
}
|
|
1553
|
+
// Start the Matter server node
|
|
1256
1554
|
this.startServerNode(plugin.serverNode);
|
|
1555
|
+
// Setting reachability to true
|
|
1257
1556
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1258
1557
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggragator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1259
1558
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1263,9 +1562,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1263
1562
|
}
|
|
1264
1563
|
}, 1000);
|
|
1265
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Starts the Matterbridge controller.
|
|
1567
|
+
* @private
|
|
1568
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1569
|
+
*/
|
|
1266
1570
|
async startController() {
|
|
1571
|
+
/*
|
|
1572
|
+
if (!this.storageManager) {
|
|
1573
|
+
this.log.error('No storage manager initialized');
|
|
1574
|
+
await this.cleanup('No storage manager initialized');
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1578
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1579
|
+
if (!this.mattercontrollerContext) {
|
|
1580
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1581
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1586
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1587
|
+
this.log.info('Creating matter commissioning controller');
|
|
1588
|
+
this.commissioningController = new CommissioningController({
|
|
1589
|
+
autoConnect: false,
|
|
1590
|
+
});
|
|
1591
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1592
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1593
|
+
|
|
1594
|
+
this.log.info('Starting matter server');
|
|
1595
|
+
await this.matterServer.start();
|
|
1596
|
+
this.log.info('Matter server started');
|
|
1597
|
+
|
|
1598
|
+
if (hasParameter('pairingcode')) {
|
|
1599
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1600
|
+
const pairingCode = getParameter('pairingcode');
|
|
1601
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1602
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1603
|
+
|
|
1604
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1605
|
+
if (pairingCode !== undefined) {
|
|
1606
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1607
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1608
|
+
longDiscriminator = undefined;
|
|
1609
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1610
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1611
|
+
} else {
|
|
1612
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1613
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1614
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1615
|
+
}
|
|
1616
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1617
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1621
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1622
|
+
regulatoryCountryCode: 'XX',
|
|
1623
|
+
};
|
|
1624
|
+
const options = {
|
|
1625
|
+
commissioning: commissioningOptions,
|
|
1626
|
+
discovery: {
|
|
1627
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1628
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1629
|
+
},
|
|
1630
|
+
passcode: setupPin,
|
|
1631
|
+
} as NodeCommissioningOptions;
|
|
1632
|
+
this.log.info('Commissioning with options:', options);
|
|
1633
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1634
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1635
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1636
|
+
} // (hasParameter('pairingcode'))
|
|
1637
|
+
|
|
1638
|
+
if (hasParameter('unpairall')) {
|
|
1639
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1640
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1641
|
+
for (const nodeId of nodeIds) {
|
|
1642
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1643
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1644
|
+
}
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
if (hasParameter('discover')) {
|
|
1649
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1650
|
+
// console.log(discover);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1654
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1659
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1660
|
+
for (const nodeId of nodeIds) {
|
|
1661
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1662
|
+
|
|
1663
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1664
|
+
autoSubscribe: false,
|
|
1665
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1666
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1667
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1668
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1669
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1670
|
+
switch (info) {
|
|
1671
|
+
case NodeStateInformation.Connected:
|
|
1672
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1673
|
+
break;
|
|
1674
|
+
case NodeStateInformation.Disconnected:
|
|
1675
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1676
|
+
break;
|
|
1677
|
+
case NodeStateInformation.Reconnecting:
|
|
1678
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1679
|
+
break;
|
|
1680
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1681
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1682
|
+
break;
|
|
1683
|
+
case NodeStateInformation.StructureChanged:
|
|
1684
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1685
|
+
break;
|
|
1686
|
+
case NodeStateInformation.Decommissioned:
|
|
1687
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1688
|
+
break;
|
|
1689
|
+
default:
|
|
1690
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
node.logStructure();
|
|
1697
|
+
|
|
1698
|
+
// Get the interaction client
|
|
1699
|
+
this.log.info('Getting the interaction client');
|
|
1700
|
+
const interactionClient = await node.getInteractionClient();
|
|
1701
|
+
let cluster;
|
|
1702
|
+
let attributes;
|
|
1703
|
+
|
|
1704
|
+
// Log BasicInformationCluster
|
|
1705
|
+
cluster = BasicInformationCluster;
|
|
1706
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1707
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1708
|
+
});
|
|
1709
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1710
|
+
attributes.forEach((attribute) => {
|
|
1711
|
+
this.log.info(
|
|
1712
|
+
`- 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}`,
|
|
1713
|
+
);
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
// Log PowerSourceCluster
|
|
1717
|
+
cluster = PowerSourceCluster;
|
|
1718
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1719
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1720
|
+
});
|
|
1721
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1722
|
+
attributes.forEach((attribute) => {
|
|
1723
|
+
this.log.info(
|
|
1724
|
+
`- 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}`,
|
|
1725
|
+
);
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
// Log ThreadNetworkDiagnostics
|
|
1729
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1730
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1731
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1732
|
+
});
|
|
1733
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1734
|
+
attributes.forEach((attribute) => {
|
|
1735
|
+
this.log.info(
|
|
1736
|
+
`- 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}`,
|
|
1737
|
+
);
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
// Log SwitchCluster
|
|
1741
|
+
cluster = SwitchCluster;
|
|
1742
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1743
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1744
|
+
});
|
|
1745
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1746
|
+
attributes.forEach((attribute) => {
|
|
1747
|
+
this.log.info(
|
|
1748
|
+
`- 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}`,
|
|
1749
|
+
);
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1753
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1754
|
+
ignoreInitialTriggers: false,
|
|
1755
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1756
|
+
this.log.info(
|
|
1757
|
+
`***${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}`,
|
|
1758
|
+
),
|
|
1759
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1760
|
+
this.log.info(
|
|
1761
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1762
|
+
);
|
|
1763
|
+
},
|
|
1764
|
+
});
|
|
1765
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1766
|
+
}
|
|
1767
|
+
*/
|
|
1267
1768
|
}
|
|
1769
|
+
/** ***********************************************************************************************************************************/
|
|
1770
|
+
/** Matter.js methods */
|
|
1771
|
+
/** ***********************************************************************************************************************************/
|
|
1772
|
+
/**
|
|
1773
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1774
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1775
|
+
*/
|
|
1268
1776
|
async startMatterStorage() {
|
|
1777
|
+
// Setup Matter storage
|
|
1269
1778
|
this.log.info(`Starting matter node storage...`);
|
|
1270
1779
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1271
1780
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1273,13 +1782,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1273
1782
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1274
1783
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1275
1784
|
this.log.info('Matter node storage started');
|
|
1785
|
+
// Backup matter storage since it is created/opened correctly
|
|
1276
1786
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1277
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1790
|
+
*
|
|
1791
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1792
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1793
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1794
|
+
*/
|
|
1278
1795
|
async backupMatterStorage(storageName, backupName) {
|
|
1279
1796
|
this.log.info('Creating matter node storage backup...');
|
|
1280
1797
|
await copyDirectory(storageName, backupName);
|
|
1281
1798
|
this.log.info('Created matter node storage backup');
|
|
1282
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Stops the matter storage.
|
|
1802
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1803
|
+
*/
|
|
1283
1804
|
async stopMatterStorage() {
|
|
1284
1805
|
this.log.info('Closing matter node storage...');
|
|
1285
1806
|
this.matterStorageManager?.close();
|
|
@@ -1288,6 +1809,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1288
1809
|
this.matterbridgeContext = undefined;
|
|
1289
1810
|
this.log.info('Matter node storage closed');
|
|
1290
1811
|
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Creates a server node storage context.
|
|
1814
|
+
*
|
|
1815
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1816
|
+
* @param {string} deviceName - The name of the device.
|
|
1817
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1818
|
+
* @param {number} vendorId - The vendor ID.
|
|
1819
|
+
* @param {string} vendorName - The vendor name.
|
|
1820
|
+
* @param {number} productId - The product ID.
|
|
1821
|
+
* @param {string} productName - The product name.
|
|
1822
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1823
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1824
|
+
*/
|
|
1291
1825
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1292
1826
|
const { randomBytes } = await import('node:crypto');
|
|
1293
1827
|
if (!this.matterStorageService)
|
|
@@ -1321,6 +1855,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1321
1855
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1322
1856
|
return storageContext;
|
|
1323
1857
|
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Creates a server node.
|
|
1860
|
+
*
|
|
1861
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1862
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1863
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1864
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1865
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1866
|
+
*/
|
|
1324
1867
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1325
1868
|
const storeId = await storageContext.get('storeId');
|
|
1326
1869
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1330,21 +1873,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1330
1873
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1331
1874
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1332
1875
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1876
|
+
/**
|
|
1877
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1878
|
+
*/
|
|
1333
1879
|
const serverNode = await ServerNode.create({
|
|
1880
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1334
1881
|
id: storeId,
|
|
1882
|
+
// Provide Network relevant configuration like the port
|
|
1883
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1335
1884
|
network: {
|
|
1336
1885
|
listeningAddressIpv4: this.ipv4address,
|
|
1337
1886
|
listeningAddressIpv6: this.ipv6address,
|
|
1338
1887
|
port,
|
|
1339
1888
|
},
|
|
1889
|
+
// Provide Commissioning relevant settings
|
|
1890
|
+
// Optional for development/testing purposes
|
|
1340
1891
|
commissioning: {
|
|
1341
1892
|
passcode,
|
|
1342
1893
|
discriminator,
|
|
1343
1894
|
},
|
|
1895
|
+
// Provide Node announcement settings
|
|
1896
|
+
// Optional: If Ommitted some development defaults are used
|
|
1344
1897
|
productDescription: {
|
|
1345
1898
|
name: await storageContext.get('deviceName'),
|
|
1346
1899
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1347
1900
|
},
|
|
1901
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1902
|
+
// Optional: If Omitted some development defaults are used
|
|
1348
1903
|
basicInformation: {
|
|
1349
1904
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1350
1905
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1361,12 +1916,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1361
1916
|
},
|
|
1362
1917
|
});
|
|
1363
1918
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1919
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1364
1920
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1365
1921
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1366
1922
|
if (this.bridgeMode === 'bridge') {
|
|
1367
1923
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1368
1924
|
if (resetSessions)
|
|
1369
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1925
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1370
1926
|
this.matterbridgePaired = true;
|
|
1371
1927
|
}
|
|
1372
1928
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1374,13 +1930,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1374
1930
|
if (plugin) {
|
|
1375
1931
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1376
1932
|
if (resetSessions)
|
|
1377
|
-
plugin.sessionInformations = undefined;
|
|
1933
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1378
1934
|
plugin.paired = true;
|
|
1379
1935
|
}
|
|
1380
1936
|
}
|
|
1381
1937
|
};
|
|
1938
|
+
/**
|
|
1939
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1940
|
+
* This means: It is added to the first fabric.
|
|
1941
|
+
*/
|
|
1382
1942
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1943
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1383
1944
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1945
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1384
1946
|
serverNode.lifecycle.online.on(async () => {
|
|
1385
1947
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1386
1948
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1428,6 +1990,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1428
1990
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1429
1991
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1430
1992
|
});
|
|
1993
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1431
1994
|
serverNode.lifecycle.offline.on(() => {
|
|
1432
1995
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1433
1996
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1451,6 +2014,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1451
2014
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1452
2015
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1453
2016
|
});
|
|
2017
|
+
/**
|
|
2018
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2019
|
+
* information is needed.
|
|
2020
|
+
*/
|
|
1454
2021
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1455
2022
|
let action = '';
|
|
1456
2023
|
switch (fabricAction) {
|
|
@@ -1484,16 +2051,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1484
2051
|
}
|
|
1485
2052
|
}
|
|
1486
2053
|
};
|
|
2054
|
+
/**
|
|
2055
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2056
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2057
|
+
*/
|
|
1487
2058
|
serverNode.events.sessions.opened.on((session) => {
|
|
1488
2059
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1489
2060
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1490
2061
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1491
2062
|
});
|
|
2063
|
+
/**
|
|
2064
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2065
|
+
*/
|
|
1492
2066
|
serverNode.events.sessions.closed.on((session) => {
|
|
1493
2067
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1494
2068
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1495
2069
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1496
2070
|
});
|
|
2071
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1497
2072
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1498
2073
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1499
2074
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1502,24 +2077,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1502
2077
|
this.log.info(`Created server node for ${storeId}`);
|
|
1503
2078
|
return serverNode;
|
|
1504
2079
|
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Starts the specified server node.
|
|
2082
|
+
*
|
|
2083
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2084
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2085
|
+
*/
|
|
1505
2086
|
async startServerNode(matterServerNode) {
|
|
1506
2087
|
if (!matterServerNode)
|
|
1507
2088
|
return;
|
|
1508
2089
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1509
2090
|
await matterServerNode.start();
|
|
1510
2091
|
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Stops the specified server node.
|
|
2094
|
+
*
|
|
2095
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2096
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2097
|
+
*/
|
|
1511
2098
|
async stopServerNode(matterServerNode) {
|
|
1512
2099
|
if (!matterServerNode)
|
|
1513
2100
|
return;
|
|
1514
2101
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1515
2102
|
try {
|
|
1516
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2103
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1517
2104
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1518
2105
|
}
|
|
1519
2106
|
catch (error) {
|
|
1520
2107
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1521
2108
|
}
|
|
1522
2109
|
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Advertises the specified server node.
|
|
2112
|
+
*
|
|
2113
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2114
|
+
* @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
|
|
2115
|
+
*/
|
|
1523
2116
|
async advertiseServerNode(matterServerNode) {
|
|
1524
2117
|
if (matterServerNode) {
|
|
1525
2118
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1528,23 +2121,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
1528
2121
|
return { qrPairingCode, manualPairingCode };
|
|
1529
2122
|
}
|
|
1530
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Stop advertise the specified server node.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2128
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2129
|
+
*/
|
|
1531
2130
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1532
2131
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1533
2132
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1534
2133
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1535
2134
|
}
|
|
1536
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Creates an aggregator node with the specified storage context.
|
|
2138
|
+
*
|
|
2139
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2140
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2141
|
+
*/
|
|
1537
2142
|
async createAggregatorNode(storageContext) {
|
|
1538
2143
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1539
2144
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1540
2145
|
return aggregatorNode;
|
|
1541
2146
|
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2149
|
+
*
|
|
2150
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2151
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2152
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2153
|
+
*/
|
|
1542
2154
|
async addBridgedEndpoint(pluginName, device) {
|
|
2155
|
+
// Check if the plugin is registered
|
|
1543
2156
|
const plugin = this.plugins.get(pluginName);
|
|
1544
2157
|
if (!plugin) {
|
|
1545
2158
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1546
2159
|
return;
|
|
1547
2160
|
}
|
|
2161
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1548
2162
|
if (this.bridgeMode === 'bridge') {
|
|
1549
2163
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1550
2164
|
if (!this.aggregatorNode)
|
|
@@ -1567,16 +2181,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1567
2181
|
plugin.registeredDevices++;
|
|
1568
2182
|
if (plugin.addedDevices !== undefined)
|
|
1569
2183
|
plugin.addedDevices++;
|
|
2184
|
+
// Add the device to the DeviceManager
|
|
1570
2185
|
this.devices.set(device);
|
|
1571
2186
|
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}`);
|
|
1572
2187
|
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2190
|
+
*
|
|
2191
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2192
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2193
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2194
|
+
*/
|
|
1573
2195
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1574
2196
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2197
|
+
// Check if the plugin is registered
|
|
1575
2198
|
const plugin = this.plugins.get(pluginName);
|
|
1576
2199
|
if (!plugin) {
|
|
1577
2200
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1578
2201
|
return;
|
|
1579
2202
|
}
|
|
2203
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1580
2204
|
if (this.bridgeMode === 'bridge') {
|
|
1581
2205
|
if (!this.aggregatorNode) {
|
|
1582
2206
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1591,6 +2215,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1591
2215
|
}
|
|
1592
2216
|
else if (this.bridgeMode === 'childbridge') {
|
|
1593
2217
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2218
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1594
2219
|
}
|
|
1595
2220
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1596
2221
|
if (!plugin.aggregatorNode) {
|
|
@@ -1605,8 +2230,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1605
2230
|
if (plugin.addedDevices !== undefined)
|
|
1606
2231
|
plugin.addedDevices--;
|
|
1607
2232
|
}
|
|
2233
|
+
// Remove the device from the DeviceManager
|
|
1608
2234
|
this.devices.remove(device);
|
|
1609
2235
|
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2238
|
+
*
|
|
2239
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2240
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2241
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2242
|
+
*/
|
|
1610
2243
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1611
2244
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1612
2245
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1615,6 +2248,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
2248
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1616
2249
|
}
|
|
1617
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2253
|
+
*
|
|
2254
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2255
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2256
|
+
*/
|
|
1618
2257
|
sanitizeFabricInformations(fabricInfo) {
|
|
1619
2258
|
return fabricInfo.map((info) => {
|
|
1620
2259
|
return {
|
|
@@ -1628,6 +2267,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1628
2267
|
};
|
|
1629
2268
|
});
|
|
1630
2269
|
}
|
|
2270
|
+
/**
|
|
2271
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2272
|
+
*
|
|
2273
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2274
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2275
|
+
*/
|
|
1631
2276
|
sanitizeSessionInformation(sessionInfo) {
|
|
1632
2277
|
return sessionInfo
|
|
1633
2278
|
.filter((session) => session.isPeerActive)
|
|
@@ -1655,6 +2300,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1655
2300
|
};
|
|
1656
2301
|
});
|
|
1657
2302
|
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2305
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2306
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2307
|
+
*/
|
|
1658
2308
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
1659
2309
|
for (const child of aggregatorNode.parts) {
|
|
1660
2310
|
this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
|
|
@@ -1700,14 +2350,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1700
2350
|
}
|
|
1701
2351
|
return vendorName;
|
|
1702
2352
|
};
|
|
2353
|
+
/**
|
|
2354
|
+
* Spawns a child process with the given command and arguments.
|
|
2355
|
+
* @param {string} command - The command to execute.
|
|
2356
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2357
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2358
|
+
*/
|
|
1703
2359
|
async spawnCommand(command, args = []) {
|
|
1704
2360
|
const { spawn } = await import('node:child_process');
|
|
2361
|
+
/*
|
|
2362
|
+
npm > npm.cmd on windows
|
|
2363
|
+
cmd.exe ['dir'] on windows
|
|
2364
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2365
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2366
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2367
|
+
});
|
|
2368
|
+
|
|
2369
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2370
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2371
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2372
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2373
|
+
*/
|
|
1705
2374
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1706
2375
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2376
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1707
2377
|
const argstring = 'npm ' + args.join(' ');
|
|
1708
2378
|
args.splice(0, args.length, '/c', argstring);
|
|
1709
2379
|
command = 'cmd.exe';
|
|
1710
2380
|
}
|
|
2381
|
+
// Decide when using sudo on linux
|
|
2382
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2383
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1711
2384
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1712
2385
|
args.unshift(command);
|
|
1713
2386
|
command = 'sudo';
|
|
@@ -1766,3 +2439,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1766
2439
|
});
|
|
1767
2440
|
}
|
|
1768
2441
|
}
|
|
2442
|
+
//# sourceMappingURL=matterbridge.js.map
|