matterbridge 3.0.1-dev-20250506-c77ed36 → 3.0.1
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 +4 -4
- 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 +240 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +328 -15
- 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 +433 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +747 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +34 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1166 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +48 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +494 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +431 -12
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +34 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +956 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +801 -11
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2706 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +142 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +294 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +225 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +187 -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 +273 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +264 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +92 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +146 -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 +52 -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 +95 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +93 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +69 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +76 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/parameter.d.ts +58 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +53 -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.f221e2c1.js → main.15906009.js} +3 -3
- package/frontend/build/static/js/{main.f221e2c1.js.map → main.15906009.js.map} +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- /package/frontend/build/static/js/{main.f221e2c1.js.LICENSE.txt → main.15906009.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
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';
|
|
5
28
|
import { inspect } from 'node:util';
|
|
29
|
+
// AnsiLogger module
|
|
6
30
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
31
|
+
// NodeStorage module
|
|
7
32
|
import { NodeStorageManager } from './storage/export.js';
|
|
33
|
+
// Matterbridge
|
|
8
34
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber } from './utils/export.js';
|
|
9
35
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
10
36
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -12,14 +38,19 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
12
38
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
13
39
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
14
40
|
import { Frontend } from './frontend.js';
|
|
41
|
+
// @matter
|
|
15
42
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
|
|
16
43
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
17
44
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
18
45
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
19
46
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
47
|
+
// Default colors
|
|
20
48
|
const plg = '\u001B[38;5;33m';
|
|
21
49
|
const dev = '\u001B[38;5;79m';
|
|
22
50
|
const typ = '\u001B[38;5;207m';
|
|
51
|
+
/**
|
|
52
|
+
* Represents the Matterbridge application.
|
|
53
|
+
*/
|
|
23
54
|
export class Matterbridge extends EventEmitter {
|
|
24
55
|
systemInformation = {
|
|
25
56
|
interfaceName: '',
|
|
@@ -66,7 +97,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
66
97
|
shellySysUpdate: false,
|
|
67
98
|
shellyMainUpdate: false,
|
|
68
99
|
profile: getParameter('profile'),
|
|
69
|
-
loggerLevel: "info"
|
|
100
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
70
101
|
fileLogger: false,
|
|
71
102
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
72
103
|
matterFileLogger: false,
|
|
@@ -105,9 +136,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
105
136
|
plugins;
|
|
106
137
|
devices;
|
|
107
138
|
frontend = new Frontend(this);
|
|
139
|
+
// Matterbridge storage
|
|
108
140
|
nodeStorage;
|
|
109
141
|
nodeContext;
|
|
110
142
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
143
|
+
// Cleanup
|
|
111
144
|
hasCleanupStarted = false;
|
|
112
145
|
initialized = false;
|
|
113
146
|
execRunningCount = 0;
|
|
@@ -120,18 +153,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
120
153
|
sigtermHandler;
|
|
121
154
|
exceptionHandler;
|
|
122
155
|
rejectionHandler;
|
|
156
|
+
// Matter environment
|
|
123
157
|
environment = Environment.default;
|
|
158
|
+
// Matter storage
|
|
124
159
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
125
160
|
matterStorageService;
|
|
126
161
|
matterStorageManager;
|
|
127
162
|
matterbridgeContext;
|
|
128
163
|
controllerContext;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
164
|
+
// Matter parameters
|
|
165
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
166
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
167
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
168
|
+
port; // first server node port
|
|
169
|
+
passcode; // first server node passcode
|
|
170
|
+
discriminator; // first server node discriminator
|
|
135
171
|
serverNode;
|
|
136
172
|
aggregatorNode;
|
|
137
173
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -139,21 +175,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
139
175
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
140
176
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
141
177
|
static instance;
|
|
178
|
+
// We load asyncronously so is private
|
|
142
179
|
constructor() {
|
|
143
180
|
super();
|
|
144
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Emits an event of the specified type with the provided arguments.
|
|
184
|
+
*
|
|
185
|
+
* @template K - The type of the event.
|
|
186
|
+
* @param {K} eventName - The name of the event to emit.
|
|
187
|
+
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
188
|
+
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
189
|
+
*/
|
|
145
190
|
emit(eventName, ...args) {
|
|
146
191
|
return super.emit(eventName, ...args);
|
|
147
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Registers an event listener for the specified event type.
|
|
195
|
+
*
|
|
196
|
+
* @template K - The type of the event.
|
|
197
|
+
* @param {K} eventName - The name of the event to listen for.
|
|
198
|
+
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
199
|
+
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
200
|
+
*/
|
|
148
201
|
on(eventName, listener) {
|
|
149
202
|
return super.on(eventName, listener);
|
|
150
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Retrieves the list of Matterbridge devices.
|
|
206
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
207
|
+
*/
|
|
151
208
|
getDevices() {
|
|
152
209
|
return this.devices.array();
|
|
153
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Retrieves the list of registered plugins.
|
|
213
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
214
|
+
*/
|
|
154
215
|
getPlugins() {
|
|
155
216
|
return this.plugins.array();
|
|
156
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Set the logger logLevel for the Matterbridge classes.
|
|
220
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
221
|
+
*/
|
|
157
222
|
async setLogLevel(logLevel) {
|
|
158
223
|
if (this.log)
|
|
159
224
|
this.log.logLevel = logLevel;
|
|
@@ -167,19 +232,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
167
232
|
for (const plugin of this.plugins) {
|
|
168
233
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
169
234
|
continue;
|
|
170
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
171
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
235
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
236
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
237
|
+
}
|
|
238
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
239
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
240
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
241
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
242
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
243
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
178
244
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
179
245
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
180
246
|
}
|
|
247
|
+
/** ***********************************************************************************************************************************/
|
|
248
|
+
/** loadInstance() and cleanup() methods */
|
|
249
|
+
/** ***********************************************************************************************************************************/
|
|
250
|
+
/**
|
|
251
|
+
* Loads an instance of the Matterbridge class.
|
|
252
|
+
* If an instance already exists, return that instance.
|
|
253
|
+
*
|
|
254
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
255
|
+
* @returns The loaded Matterbridge instance.
|
|
256
|
+
*/
|
|
181
257
|
static async loadInstance(initialize = false) {
|
|
182
258
|
if (!Matterbridge.instance) {
|
|
259
|
+
// eslint-disable-next-line no-console
|
|
183
260
|
if (hasParameter('debug'))
|
|
184
261
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
185
262
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -188,8 +265,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
188
265
|
}
|
|
189
266
|
return Matterbridge.instance;
|
|
190
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Call cleanup().
|
|
270
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
271
|
+
*
|
|
272
|
+
*/
|
|
191
273
|
async destroyInstance() {
|
|
192
274
|
this.log.info(`Destroy instance...`);
|
|
275
|
+
// Save server nodes to close
|
|
193
276
|
const servers = [];
|
|
194
277
|
if (this.bridgeMode === 'bridge') {
|
|
195
278
|
if (this.serverNode)
|
|
@@ -201,55 +284,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
201
284
|
servers.push(plugin.serverNode);
|
|
202
285
|
}
|
|
203
286
|
}
|
|
287
|
+
// Cleanup
|
|
204
288
|
await this.cleanup('destroying instance...', false);
|
|
289
|
+
// Close servers mdns service
|
|
205
290
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
206
291
|
for (const server of servers) {
|
|
207
292
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
208
293
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
209
294
|
}
|
|
295
|
+
// Wait for the cleanup to finish
|
|
210
296
|
await new Promise((resolve) => {
|
|
211
297
|
setTimeout(resolve, 1000);
|
|
212
298
|
});
|
|
213
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Initializes the Matterbridge application.
|
|
302
|
+
*
|
|
303
|
+
* @remarks
|
|
304
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
305
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
306
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
307
|
+
*
|
|
308
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
309
|
+
*/
|
|
214
310
|
async initialize() {
|
|
311
|
+
// Set the restart mode
|
|
215
312
|
if (hasParameter('service'))
|
|
216
313
|
this.restartMode = 'service';
|
|
217
314
|
if (hasParameter('docker'))
|
|
218
315
|
this.restartMode = 'docker';
|
|
316
|
+
// Set the matterbridge directory
|
|
219
317
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
220
318
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
319
|
+
// Setup the matter environment
|
|
221
320
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
222
321
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
223
322
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
224
323
|
this.environment.vars.set('runtime.signals', false);
|
|
225
324
|
this.environment.vars.set('runtime.exitcode', false);
|
|
226
|
-
|
|
325
|
+
// Create the matterbridge logger
|
|
326
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
327
|
+
// Register process handlers
|
|
227
328
|
this.registerProcessHandlers();
|
|
329
|
+
// Initialize nodeStorage and nodeContext
|
|
228
330
|
try {
|
|
229
331
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
230
332
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
231
333
|
this.log.debug('Creating node storage context for matterbridge');
|
|
232
334
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
335
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
336
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
337
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
234
338
|
for (const key of keys) {
|
|
235
339
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
340
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
236
341
|
await this.nodeStorage?.storage.get(key);
|
|
237
342
|
}
|
|
238
343
|
const storages = await this.nodeStorage.getStorageNames();
|
|
239
344
|
for (const storage of storages) {
|
|
240
345
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
241
346
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
347
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
348
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
349
|
const keys = (await nodeContext?.storage.keys());
|
|
243
350
|
keys.forEach(async (key) => {
|
|
244
351
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
245
352
|
await nodeContext?.get(key);
|
|
246
353
|
});
|
|
247
354
|
}
|
|
355
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
248
356
|
this.log.debug('Creating node storage backup...');
|
|
249
357
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
250
358
|
this.log.debug('Created node storage backup');
|
|
251
359
|
}
|
|
252
360
|
catch (error) {
|
|
361
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
253
362
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
254
363
|
if (hasParameter('norestore')) {
|
|
255
364
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -264,41 +373,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
264
373
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
265
374
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
266
375
|
}
|
|
376
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
267
377
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
378
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
268
379
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
380
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
269
381
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
270
382
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
383
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
271
384
|
if (hasParameter('logger')) {
|
|
272
385
|
const level = getParameter('logger');
|
|
273
386
|
if (level === 'debug') {
|
|
274
|
-
this.log.logLevel = "debug"
|
|
387
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
275
388
|
}
|
|
276
389
|
else if (level === 'info') {
|
|
277
|
-
this.log.logLevel = "info"
|
|
390
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
278
391
|
}
|
|
279
392
|
else if (level === 'notice') {
|
|
280
|
-
this.log.logLevel = "notice"
|
|
393
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
281
394
|
}
|
|
282
395
|
else if (level === 'warn') {
|
|
283
|
-
this.log.logLevel = "warn"
|
|
396
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
284
397
|
}
|
|
285
398
|
else if (level === 'error') {
|
|
286
|
-
this.log.logLevel = "error"
|
|
399
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
287
400
|
}
|
|
288
401
|
else if (level === 'fatal') {
|
|
289
|
-
this.log.logLevel = "fatal"
|
|
402
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
290
403
|
}
|
|
291
404
|
else {
|
|
292
405
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
293
|
-
this.log.logLevel = "info"
|
|
406
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
294
407
|
}
|
|
295
408
|
}
|
|
296
409
|
else {
|
|
297
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
410
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
298
411
|
}
|
|
299
412
|
this.frontend.logLevel = this.log.logLevel;
|
|
300
413
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
301
414
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
415
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
302
416
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
303
417
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
304
418
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -307,6 +421,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
307
421
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
308
422
|
if (this.profile !== undefined)
|
|
309
423
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
424
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
310
425
|
if (hasParameter('matterlogger')) {
|
|
311
426
|
const level = getParameter('matterlogger');
|
|
312
427
|
if (level === 'debug') {
|
|
@@ -338,6 +453,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
338
453
|
Logger.format = MatterLogFormat.ANSI;
|
|
339
454
|
Logger.setLogger('default', this.createMatterLogger());
|
|
340
455
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
456
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
341
457
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
342
458
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
343
459
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -346,6 +462,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
346
462
|
});
|
|
347
463
|
}
|
|
348
464
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
465
|
+
// Log network interfaces
|
|
349
466
|
const networkInterfaces = os.networkInterfaces();
|
|
350
467
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
351
468
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -357,6 +474,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
357
474
|
});
|
|
358
475
|
}
|
|
359
476
|
}
|
|
477
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
360
478
|
if (hasParameter('mdnsinterface')) {
|
|
361
479
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
362
480
|
}
|
|
@@ -365,6 +483,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
365
483
|
if (this.mdnsInterface === '')
|
|
366
484
|
this.mdnsInterface = undefined;
|
|
367
485
|
}
|
|
486
|
+
// Validate mdnsInterface
|
|
368
487
|
if (this.mdnsInterface) {
|
|
369
488
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
370
489
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -377,6 +496,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
377
496
|
}
|
|
378
497
|
if (this.mdnsInterface)
|
|
379
498
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
499
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
380
500
|
if (hasParameter('ipv4address')) {
|
|
381
501
|
this.ipv4address = getParameter('ipv4address');
|
|
382
502
|
}
|
|
@@ -385,6 +505,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
385
505
|
if (this.ipv4address === '')
|
|
386
506
|
this.ipv4address = undefined;
|
|
387
507
|
}
|
|
508
|
+
// Validate ipv4address
|
|
388
509
|
if (this.ipv4address) {
|
|
389
510
|
let isValid = false;
|
|
390
511
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -400,6 +521,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
400
521
|
await this.nodeContext.remove('matteripv4address');
|
|
401
522
|
}
|
|
402
523
|
}
|
|
524
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
403
525
|
if (hasParameter('ipv6address')) {
|
|
404
526
|
this.ipv6address = getParameter('ipv6address');
|
|
405
527
|
}
|
|
@@ -408,6 +530,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
408
530
|
if (this.ipv6address === '')
|
|
409
531
|
this.ipv6address = undefined;
|
|
410
532
|
}
|
|
533
|
+
// Validate ipv6address
|
|
411
534
|
if (this.ipv6address) {
|
|
412
535
|
let isValid = false;
|
|
413
536
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -428,14 +551,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
428
551
|
await this.nodeContext.remove('matteripv6address');
|
|
429
552
|
}
|
|
430
553
|
}
|
|
554
|
+
// Initialize PluginManager
|
|
431
555
|
this.plugins = new PluginManager(this);
|
|
432
556
|
await this.plugins.loadFromStorage();
|
|
433
557
|
this.plugins.logLevel = this.log.logLevel;
|
|
558
|
+
// Initialize DeviceManager
|
|
434
559
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
435
560
|
this.devices.logLevel = this.log.logLevel;
|
|
561
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
436
562
|
for (const plugin of this.plugins) {
|
|
437
563
|
const packageJson = await this.plugins.parse(plugin);
|
|
438
564
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
565
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
566
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
439
567
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
440
568
|
try {
|
|
441
569
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -457,6 +585,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
457
585
|
await plugin.nodeContext.set('description', plugin.description);
|
|
458
586
|
await plugin.nodeContext.set('author', plugin.author);
|
|
459
587
|
}
|
|
588
|
+
// Log system info and create .matterbridge directory
|
|
460
589
|
await this.logNodeAndSystemInfo();
|
|
461
590
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
462
591
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -464,6 +593,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
464
593
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
465
594
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
466
595
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
596
|
+
// Check node version and throw error
|
|
467
597
|
const minNodeVersion = 18;
|
|
468
598
|
const nodeVersion = process.versions.node;
|
|
469
599
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -471,9 +601,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
601
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
472
602
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
473
603
|
}
|
|
604
|
+
// Parse command line
|
|
474
605
|
await this.parseCommandLine();
|
|
475
606
|
this.initialized = true;
|
|
476
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
610
|
+
* @private
|
|
611
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
612
|
+
*/
|
|
477
613
|
async parseCommandLine() {
|
|
478
614
|
if (hasParameter('help')) {
|
|
479
615
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -592,6 +728,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
592
728
|
this.shutdown = true;
|
|
593
729
|
return;
|
|
594
730
|
}
|
|
731
|
+
// Start the matter storage and create the matterbridge context
|
|
595
732
|
try {
|
|
596
733
|
await this.startMatterStorage();
|
|
597
734
|
}
|
|
@@ -599,12 +736,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
599
736
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
600
737
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
601
738
|
}
|
|
739
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
602
740
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
603
741
|
this.initialized = true;
|
|
604
742
|
await this.shutdownProcessAndReset();
|
|
605
743
|
this.shutdown = true;
|
|
606
744
|
return;
|
|
607
745
|
}
|
|
746
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
608
747
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
609
748
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
610
749
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -629,30 +768,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
629
768
|
this.shutdown = true;
|
|
630
769
|
return;
|
|
631
770
|
}
|
|
771
|
+
// Initialize frontend
|
|
632
772
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
633
773
|
await this.frontend.start(getIntParameter('frontend'));
|
|
774
|
+
// Check in 30 seconds the latest versions
|
|
634
775
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
635
776
|
const { checkUpdates } = await import('./update.js');
|
|
636
777
|
checkUpdates(this);
|
|
637
778
|
}, 30 * 1000).unref();
|
|
779
|
+
// Check each 24 hours the latest versions
|
|
638
780
|
this.checkUpdateInterval = setInterval(async () => {
|
|
639
781
|
const { checkUpdates } = await import('./update.js');
|
|
640
782
|
checkUpdates(this);
|
|
641
783
|
}, 12 * 60 * 60 * 1000).unref();
|
|
784
|
+
// Start the matterbridge in mode test
|
|
642
785
|
if (hasParameter('test')) {
|
|
643
786
|
this.bridgeMode = 'bridge';
|
|
644
787
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
645
788
|
return;
|
|
646
789
|
}
|
|
790
|
+
// Start the matterbridge in mode controller
|
|
647
791
|
if (hasParameter('controller')) {
|
|
648
792
|
this.bridgeMode = 'controller';
|
|
649
793
|
await this.startController();
|
|
650
794
|
return;
|
|
651
795
|
}
|
|
796
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
652
797
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
653
798
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
654
799
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
655
800
|
}
|
|
801
|
+
// Start matterbridge in bridge mode
|
|
656
802
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
657
803
|
this.bridgeMode = 'bridge';
|
|
658
804
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -660,6 +806,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
660
806
|
await this.startBridge();
|
|
661
807
|
return;
|
|
662
808
|
}
|
|
809
|
+
// Start matterbridge in childbridge mode
|
|
663
810
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
664
811
|
this.bridgeMode = 'childbridge';
|
|
665
812
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -668,10 +815,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
668
815
|
return;
|
|
669
816
|
}
|
|
670
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Asynchronously loads and starts the registered plugins.
|
|
820
|
+
*
|
|
821
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
822
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
823
|
+
*
|
|
824
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
825
|
+
*/
|
|
671
826
|
async startPlugins() {
|
|
827
|
+
// Check, load and start the plugins
|
|
672
828
|
for (const plugin of this.plugins) {
|
|
673
829
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
674
830
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
831
|
+
// Check if the plugin is available
|
|
675
832
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
676
833
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
677
834
|
plugin.enabled = false;
|
|
@@ -691,10 +848,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
691
848
|
plugin.addedDevices = undefined;
|
|
692
849
|
plugin.qrPairingCode = undefined;
|
|
693
850
|
plugin.manualPairingCode = undefined;
|
|
694
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
851
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
695
852
|
}
|
|
696
853
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
697
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
857
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
858
|
+
*/
|
|
698
859
|
registerProcessHandlers() {
|
|
699
860
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
700
861
|
process.removeAllListeners('uncaughtException');
|
|
@@ -721,6 +882,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
721
882
|
};
|
|
722
883
|
process.on('SIGTERM', this.sigtermHandler);
|
|
723
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
887
|
+
*/
|
|
724
888
|
deregisterProcesslHandlers() {
|
|
725
889
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
726
890
|
if (this.exceptionHandler)
|
|
@@ -737,12 +901,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
737
901
|
process.off('SIGTERM', this.sigtermHandler);
|
|
738
902
|
this.sigtermHandler = undefined;
|
|
739
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Logs the node and system information.
|
|
906
|
+
*/
|
|
740
907
|
async logNodeAndSystemInfo() {
|
|
908
|
+
// IP address information
|
|
741
909
|
const networkInterfaces = os.networkInterfaces();
|
|
742
910
|
this.systemInformation.interfaceName = '';
|
|
743
911
|
this.systemInformation.ipv4Address = '';
|
|
744
912
|
this.systemInformation.ipv6Address = '';
|
|
745
913
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
914
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
746
915
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
747
916
|
continue;
|
|
748
917
|
if (!interfaceDetails) {
|
|
@@ -768,19 +937,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
768
937
|
break;
|
|
769
938
|
}
|
|
770
939
|
}
|
|
940
|
+
// Node information
|
|
771
941
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
772
942
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
773
943
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
774
944
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
945
|
+
// Host system information
|
|
775
946
|
this.systemInformation.hostname = os.hostname();
|
|
776
947
|
this.systemInformation.user = os.userInfo().username;
|
|
777
|
-
this.systemInformation.osType = os.type();
|
|
778
|
-
this.systemInformation.osRelease = os.release();
|
|
779
|
-
this.systemInformation.osPlatform = os.platform();
|
|
780
|
-
this.systemInformation.osArch = os.arch();
|
|
781
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
782
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
783
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
948
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
949
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
950
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
951
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
952
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
953
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
954
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
955
|
+
// Log the system information
|
|
784
956
|
this.log.debug('Host System Information:');
|
|
785
957
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
786
958
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -796,16 +968,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
796
968
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
797
969
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
798
970
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
971
|
+
// Home directory
|
|
799
972
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
800
973
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
801
974
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
975
|
+
// Package root directory
|
|
802
976
|
const { fileURLToPath } = await import('node:url');
|
|
803
977
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
804
978
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
805
979
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
806
980
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
981
|
+
// Global node_modules directory
|
|
807
982
|
if (this.nodeContext)
|
|
808
983
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
984
|
+
// First run of Matterbridge so the node storage is empty
|
|
809
985
|
if (this.globalModulesDirectory === '') {
|
|
810
986
|
try {
|
|
811
987
|
this.execRunningCount++;
|
|
@@ -821,6 +997,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
821
997
|
}
|
|
822
998
|
else
|
|
823
999
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1000
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
1001
|
+
else {
|
|
1002
|
+
this.getGlobalNodeModules()
|
|
1003
|
+
.then(async (globalModulesDirectory) => {
|
|
1004
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
1005
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1006
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1007
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1008
|
+
})
|
|
1009
|
+
.catch((error) => {
|
|
1010
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1011
|
+
});
|
|
1012
|
+
}*/
|
|
1013
|
+
// Create the data directory .matterbridge in the home directory
|
|
824
1014
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
825
1015
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
826
1016
|
try {
|
|
@@ -844,6 +1034,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
844
1034
|
}
|
|
845
1035
|
}
|
|
846
1036
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1037
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
847
1038
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
848
1039
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
849
1040
|
try {
|
|
@@ -867,6 +1058,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
867
1058
|
}
|
|
868
1059
|
}
|
|
869
1060
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1061
|
+
// Create the matter cert directory in the home directory
|
|
870
1062
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
871
1063
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
872
1064
|
try {
|
|
@@ -890,50 +1082,68 @@ export class Matterbridge extends EventEmitter {
|
|
|
890
1082
|
}
|
|
891
1083
|
}
|
|
892
1084
|
this.log.debug(`Matterbridge Matter Cert Directory: ${this.matterbridgeCertDirectory}`);
|
|
1085
|
+
// Matterbridge version
|
|
893
1086
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
894
1087
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
895
1088
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
896
1089
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
+
// Matterbridge latest version
|
|
897
1091
|
if (this.nodeContext)
|
|
898
1092
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
899
1093
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
+
// this.getMatterbridgeLatestVersion();
|
|
1095
|
+
// Current working directory
|
|
900
1096
|
const currentDir = process.cwd();
|
|
901
1097
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1098
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
902
1099
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
903
1100
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
904
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1104
|
+
*
|
|
1105
|
+
* @returns {Function} The MatterLogger function.
|
|
1106
|
+
*/
|
|
905
1107
|
createMatterLogger() {
|
|
906
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1108
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
907
1109
|
return (_level, formattedLog) => {
|
|
908
1110
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
909
1111
|
const message = formattedLog.slice(65);
|
|
910
1112
|
matterLogger.logName = logger;
|
|
911
1113
|
switch (_level) {
|
|
912
1114
|
case MatterLogLevel.DEBUG:
|
|
913
|
-
matterLogger.log("debug"
|
|
1115
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
914
1116
|
break;
|
|
915
1117
|
case MatterLogLevel.INFO:
|
|
916
|
-
matterLogger.log("info"
|
|
1118
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
917
1119
|
break;
|
|
918
1120
|
case MatterLogLevel.NOTICE:
|
|
919
|
-
matterLogger.log("notice"
|
|
1121
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
920
1122
|
break;
|
|
921
1123
|
case MatterLogLevel.WARN:
|
|
922
|
-
matterLogger.log("warn"
|
|
1124
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
923
1125
|
break;
|
|
924
1126
|
case MatterLogLevel.ERROR:
|
|
925
|
-
matterLogger.log("error"
|
|
1127
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
926
1128
|
break;
|
|
927
1129
|
case MatterLogLevel.FATAL:
|
|
928
|
-
matterLogger.log("fatal"
|
|
1130
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
929
1131
|
break;
|
|
930
1132
|
default:
|
|
931
|
-
matterLogger.log("debug"
|
|
1133
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
932
1134
|
break;
|
|
933
1135
|
}
|
|
934
1136
|
};
|
|
935
1137
|
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Creates a Matter File Logger.
|
|
1140
|
+
*
|
|
1141
|
+
* @param {string} filePath - The path to the log file.
|
|
1142
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1143
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1144
|
+
*/
|
|
936
1145
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1146
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
937
1147
|
let fileSize = 0;
|
|
938
1148
|
if (unlink) {
|
|
939
1149
|
try {
|
|
@@ -982,12 +1192,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
982
1192
|
}
|
|
983
1193
|
};
|
|
984
1194
|
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1197
|
+
*/
|
|
985
1198
|
async restartProcess() {
|
|
986
1199
|
await this.cleanup('restarting...', true);
|
|
987
1200
|
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Shut down the process by exiting the current process.
|
|
1203
|
+
*/
|
|
988
1204
|
async shutdownProcess() {
|
|
989
1205
|
await this.cleanup('shutting down...', false);
|
|
990
1206
|
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Update matterbridge and and shut down the process.
|
|
1209
|
+
*/
|
|
991
1210
|
async updateProcess() {
|
|
992
1211
|
this.log.info('Updating matterbridge...');
|
|
993
1212
|
try {
|
|
@@ -1000,52 +1219,73 @@ export class Matterbridge extends EventEmitter {
|
|
|
1000
1219
|
this.frontend.wssSendRestartRequired();
|
|
1001
1220
|
await this.cleanup('updating...', false);
|
|
1002
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Unregister all devices and shut down the process.
|
|
1224
|
+
*/
|
|
1003
1225
|
async unregisterAndShutdownProcess() {
|
|
1004
1226
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1005
1227
|
for (const plugin of this.plugins) {
|
|
1006
1228
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1007
1229
|
}
|
|
1008
1230
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1009
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1231
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1010
1232
|
this.log.debug('Cleaning up and shutting down...');
|
|
1011
1233
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1012
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Reset commissioning and shut down the process.
|
|
1237
|
+
*/
|
|
1013
1238
|
async shutdownProcessAndReset() {
|
|
1014
1239
|
await this.cleanup('shutting down with reset...', false);
|
|
1015
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Factory reset and shut down the process.
|
|
1243
|
+
*/
|
|
1016
1244
|
async shutdownProcessAndFactoryReset() {
|
|
1017
1245
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1018
1246
|
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Cleans up the Matterbridge instance.
|
|
1249
|
+
* @param message - The cleanup message.
|
|
1250
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1251
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1252
|
+
*/
|
|
1019
1253
|
async cleanup(message, restart = false) {
|
|
1020
1254
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1021
1255
|
this.emit('cleanup_started');
|
|
1022
1256
|
this.hasCleanupStarted = true;
|
|
1023
1257
|
this.log.info(message);
|
|
1258
|
+
// Clear the start matter interval
|
|
1024
1259
|
if (this.startMatterInterval) {
|
|
1025
1260
|
clearInterval(this.startMatterInterval);
|
|
1026
1261
|
this.startMatterInterval = undefined;
|
|
1027
1262
|
this.log.debug('Start matter interval cleared');
|
|
1028
1263
|
}
|
|
1264
|
+
// Clear the check update timeout
|
|
1029
1265
|
if (this.checkUpdateTimeout) {
|
|
1030
1266
|
clearInterval(this.checkUpdateTimeout);
|
|
1031
1267
|
this.checkUpdateTimeout = undefined;
|
|
1032
1268
|
this.log.debug('Check update timeout cleared');
|
|
1033
1269
|
}
|
|
1270
|
+
// Clear the check update interval
|
|
1034
1271
|
if (this.checkUpdateInterval) {
|
|
1035
1272
|
clearInterval(this.checkUpdateInterval);
|
|
1036
1273
|
this.checkUpdateInterval = undefined;
|
|
1037
1274
|
this.log.debug('Check update interval cleared');
|
|
1038
1275
|
}
|
|
1276
|
+
// Clear the configure timeout
|
|
1039
1277
|
if (this.configureTimeout) {
|
|
1040
1278
|
clearTimeout(this.configureTimeout);
|
|
1041
1279
|
this.configureTimeout = undefined;
|
|
1042
1280
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1043
1281
|
}
|
|
1282
|
+
// Clear the reachability timeout
|
|
1044
1283
|
if (this.reachabilityTimeout) {
|
|
1045
1284
|
clearTimeout(this.reachabilityTimeout);
|
|
1046
1285
|
this.reachabilityTimeout = undefined;
|
|
1047
1286
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1048
1287
|
}
|
|
1288
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1049
1289
|
for (const plugin of this.plugins) {
|
|
1050
1290
|
if (!plugin.enabled || plugin.error)
|
|
1051
1291
|
continue;
|
|
@@ -1056,9 +1296,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1056
1296
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1057
1297
|
}
|
|
1058
1298
|
}
|
|
1299
|
+
// Stop matter server nodes
|
|
1059
1300
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1060
1301
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1061
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1302
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1062
1303
|
if (this.bridgeMode === 'bridge') {
|
|
1063
1304
|
if (this.serverNode) {
|
|
1064
1305
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1074,6 +1315,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1074
1315
|
}
|
|
1075
1316
|
}
|
|
1076
1317
|
this.log.notice('Stopped matter server nodes');
|
|
1318
|
+
// Matter commisioning reset
|
|
1077
1319
|
if (message === 'shutting down with reset...') {
|
|
1078
1320
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1079
1321
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1083,17 +1325,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1083
1325
|
await this.matterbridgeContext?.clearAll();
|
|
1084
1326
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1085
1327
|
}
|
|
1328
|
+
// Stop matter storage
|
|
1086
1329
|
await this.stopMatterStorage();
|
|
1330
|
+
// Stop the frontend
|
|
1087
1331
|
await this.frontend.stop();
|
|
1332
|
+
// Remove the matterfilelogger
|
|
1088
1333
|
try {
|
|
1089
1334
|
Logger.removeLogger('matterfilelogger');
|
|
1335
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1090
1336
|
}
|
|
1091
1337
|
catch (error) {
|
|
1338
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1092
1339
|
}
|
|
1340
|
+
// Serialize registeredDevices
|
|
1093
1341
|
if (this.nodeStorage && this.nodeContext) {
|
|
1342
|
+
/*
|
|
1343
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1344
|
+
this.log.info('Saving registered devices...');
|
|
1345
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1346
|
+
this.devices.forEach(async (device) => {
|
|
1347
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1348
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1349
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1350
|
+
});
|
|
1351
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1352
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1353
|
+
*/
|
|
1354
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1094
1355
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1095
1356
|
await this.nodeContext.close();
|
|
1096
1357
|
this.nodeContext = undefined;
|
|
1358
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1097
1359
|
for (const plugin of this.plugins) {
|
|
1098
1360
|
if (plugin.nodeContext) {
|
|
1099
1361
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1110,8 +1372,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1110
1372
|
}
|
|
1111
1373
|
this.plugins.clear();
|
|
1112
1374
|
this.devices.clear();
|
|
1375
|
+
// Factory reset
|
|
1113
1376
|
if (message === 'shutting down with factory reset...') {
|
|
1114
1377
|
try {
|
|
1378
|
+
// Delete old matter storage file and backup
|
|
1115
1379
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1116
1380
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1117
1381
|
await fs.unlink(file);
|
|
@@ -1125,6 +1389,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1125
1389
|
}
|
|
1126
1390
|
}
|
|
1127
1391
|
try {
|
|
1392
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1128
1393
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1129
1394
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1130
1395
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1138,6 +1403,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1138
1403
|
}
|
|
1139
1404
|
}
|
|
1140
1405
|
try {
|
|
1406
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1141
1407
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1142
1408
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1143
1409
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1152,12 +1418,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1152
1418
|
}
|
|
1153
1419
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1154
1420
|
}
|
|
1421
|
+
// Deregisters the process handlers
|
|
1155
1422
|
this.deregisterProcesslHandlers();
|
|
1156
1423
|
if (restart) {
|
|
1157
1424
|
if (message === 'updating...') {
|
|
1158
1425
|
this.log.info('Cleanup completed. Updating...');
|
|
1159
1426
|
Matterbridge.instance = undefined;
|
|
1160
|
-
this.emit('update');
|
|
1427
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1161
1428
|
}
|
|
1162
1429
|
else if (message === 'restarting...') {
|
|
1163
1430
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1178,6 +1445,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1178
1445
|
this.log.debug('Cleanup already started...');
|
|
1179
1446
|
}
|
|
1180
1447
|
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1450
|
+
*
|
|
1451
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1452
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1453
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1454
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1455
|
+
*/
|
|
1181
1456
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1182
1457
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1183
1458
|
plugin.locked = true;
|
|
@@ -1191,6 +1466,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1191
1466
|
await this.startServerNode(plugin.serverNode);
|
|
1192
1467
|
}
|
|
1193
1468
|
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1471
|
+
*
|
|
1472
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1473
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1474
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1475
|
+
*/
|
|
1194
1476
|
async createDynamicPlugin(plugin, start = false) {
|
|
1195
1477
|
if (!plugin.locked) {
|
|
1196
1478
|
plugin.locked = true;
|
|
@@ -1203,7 +1485,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1203
1485
|
await this.startServerNode(plugin.serverNode);
|
|
1204
1486
|
}
|
|
1205
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Starts the Matterbridge in bridge mode.
|
|
1490
|
+
* @private
|
|
1491
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1492
|
+
*/
|
|
1206
1493
|
async startBridge() {
|
|
1494
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1207
1495
|
if (!this.matterStorageManager)
|
|
1208
1496
|
throw new Error('No storage manager initialized');
|
|
1209
1497
|
if (!this.matterbridgeContext)
|
|
@@ -1241,7 +1529,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1241
1529
|
clearInterval(this.startMatterInterval);
|
|
1242
1530
|
this.startMatterInterval = undefined;
|
|
1243
1531
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1532
|
+
// Start the Matter server node
|
|
1244
1533
|
this.startServerNode(this.serverNode);
|
|
1534
|
+
// Configure the plugins
|
|
1245
1535
|
this.configureTimeout = setTimeout(async () => {
|
|
1246
1536
|
for (const plugin of this.plugins) {
|
|
1247
1537
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1259,6 +1549,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1259
1549
|
}
|
|
1260
1550
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1261
1551
|
}, 30 * 1000);
|
|
1552
|
+
// Setting reachability to true
|
|
1262
1553
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1263
1554
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1264
1555
|
if (this.aggregatorNode)
|
|
@@ -1267,6 +1558,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1267
1558
|
}, 60 * 1000);
|
|
1268
1559
|
}, 1000);
|
|
1269
1560
|
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1563
|
+
* @private
|
|
1564
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1565
|
+
*/
|
|
1270
1566
|
async startChildbridge() {
|
|
1271
1567
|
if (!this.matterStorageManager)
|
|
1272
1568
|
throw new Error('No storage manager initialized');
|
|
@@ -1304,6 +1600,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1304
1600
|
clearInterval(this.startMatterInterval);
|
|
1305
1601
|
this.startMatterInterval = undefined;
|
|
1306
1602
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1603
|
+
// Configure the plugins
|
|
1307
1604
|
this.configureTimeout = setTimeout(async () => {
|
|
1308
1605
|
for (const plugin of this.plugins) {
|
|
1309
1606
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1340,7 +1637,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1340
1637
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1341
1638
|
continue;
|
|
1342
1639
|
}
|
|
1640
|
+
// Start the Matter server node
|
|
1343
1641
|
this.startServerNode(plugin.serverNode);
|
|
1642
|
+
// Setting reachability to true
|
|
1344
1643
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1345
1644
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1346
1645
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1350,6 +1649,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1350
1649
|
}
|
|
1351
1650
|
}, 1000);
|
|
1352
1651
|
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Starts the Matterbridge controller.
|
|
1654
|
+
* @private
|
|
1655
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1656
|
+
*/
|
|
1353
1657
|
async startController() {
|
|
1354
1658
|
if (!this.matterStorageManager) {
|
|
1355
1659
|
this.log.error('No storage manager initialized');
|
|
@@ -1364,8 +1668,207 @@ export class Matterbridge extends EventEmitter {
|
|
|
1364
1668
|
return;
|
|
1365
1669
|
}
|
|
1366
1670
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1671
|
+
/*
|
|
1672
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1673
|
+
this.log.info('Creating matter commissioning controller');
|
|
1674
|
+
this.commissioningController = new CommissioningController({
|
|
1675
|
+
autoConnect: false,
|
|
1676
|
+
});
|
|
1677
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1678
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1679
|
+
|
|
1680
|
+
this.log.info('Starting matter server');
|
|
1681
|
+
await this.matterServer.start();
|
|
1682
|
+
this.log.info('Matter server started');
|
|
1683
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1684
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1685
|
+
regulatoryCountryCode: 'XX',
|
|
1686
|
+
};
|
|
1687
|
+
const commissioningController = new CommissioningController({
|
|
1688
|
+
environment: {
|
|
1689
|
+
environment,
|
|
1690
|
+
id: uniqueId,
|
|
1691
|
+
},
|
|
1692
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1693
|
+
adminFabricLabel,
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
if (hasParameter('pairingcode')) {
|
|
1697
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1698
|
+
const pairingCode = getParameter('pairingcode');
|
|
1699
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1700
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1701
|
+
|
|
1702
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1703
|
+
if (pairingCode !== undefined) {
|
|
1704
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1705
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1706
|
+
longDiscriminator = undefined;
|
|
1707
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1708
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1709
|
+
} else {
|
|
1710
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1711
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1712
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1713
|
+
}
|
|
1714
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1715
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
const options = {
|
|
1719
|
+
commissioning: commissioningOptions,
|
|
1720
|
+
discovery: {
|
|
1721
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1722
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1723
|
+
},
|
|
1724
|
+
passcode: setupPin,
|
|
1725
|
+
} as NodeCommissioningOptions;
|
|
1726
|
+
this.log.info('Commissioning with options:', options);
|
|
1727
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1728
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1729
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1730
|
+
} // (hasParameter('pairingcode'))
|
|
1731
|
+
|
|
1732
|
+
if (hasParameter('unpairall')) {
|
|
1733
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1734
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1735
|
+
for (const nodeId of nodeIds) {
|
|
1736
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1737
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1738
|
+
}
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (hasParameter('discover')) {
|
|
1743
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1744
|
+
// console.log(discover);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1748
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1753
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1754
|
+
for (const nodeId of nodeIds) {
|
|
1755
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1756
|
+
|
|
1757
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1758
|
+
autoSubscribe: false,
|
|
1759
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1760
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1761
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1762
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1763
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1764
|
+
switch (info) {
|
|
1765
|
+
case NodeStateInformation.Connected:
|
|
1766
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1767
|
+
break;
|
|
1768
|
+
case NodeStateInformation.Disconnected:
|
|
1769
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1770
|
+
break;
|
|
1771
|
+
case NodeStateInformation.Reconnecting:
|
|
1772
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1773
|
+
break;
|
|
1774
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1775
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1776
|
+
break;
|
|
1777
|
+
case NodeStateInformation.StructureChanged:
|
|
1778
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1779
|
+
break;
|
|
1780
|
+
case NodeStateInformation.Decommissioned:
|
|
1781
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1782
|
+
break;
|
|
1783
|
+
default:
|
|
1784
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
},
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
node.logStructure();
|
|
1791
|
+
|
|
1792
|
+
// Get the interaction client
|
|
1793
|
+
this.log.info('Getting the interaction client');
|
|
1794
|
+
const interactionClient = await node.getInteractionClient();
|
|
1795
|
+
let cluster;
|
|
1796
|
+
let attributes;
|
|
1797
|
+
|
|
1798
|
+
// Log BasicInformationCluster
|
|
1799
|
+
cluster = BasicInformationCluster;
|
|
1800
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1801
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1802
|
+
});
|
|
1803
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1804
|
+
attributes.forEach((attribute) => {
|
|
1805
|
+
this.log.info(
|
|
1806
|
+
`- 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}`,
|
|
1807
|
+
);
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
// Log PowerSourceCluster
|
|
1811
|
+
cluster = PowerSourceCluster;
|
|
1812
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1813
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1814
|
+
});
|
|
1815
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1816
|
+
attributes.forEach((attribute) => {
|
|
1817
|
+
this.log.info(
|
|
1818
|
+
`- 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}`,
|
|
1819
|
+
);
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
// Log ThreadNetworkDiagnostics
|
|
1823
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1824
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1825
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1826
|
+
});
|
|
1827
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1828
|
+
attributes.forEach((attribute) => {
|
|
1829
|
+
this.log.info(
|
|
1830
|
+
`- 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}`,
|
|
1831
|
+
);
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
// Log SwitchCluster
|
|
1835
|
+
cluster = SwitchCluster;
|
|
1836
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1837
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1838
|
+
});
|
|
1839
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1840
|
+
attributes.forEach((attribute) => {
|
|
1841
|
+
this.log.info(
|
|
1842
|
+
`- 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}`,
|
|
1843
|
+
);
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1847
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1848
|
+
ignoreInitialTriggers: false,
|
|
1849
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1850
|
+
this.log.info(
|
|
1851
|
+
`***${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}`,
|
|
1852
|
+
),
|
|
1853
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1854
|
+
this.log.info(
|
|
1855
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1856
|
+
);
|
|
1857
|
+
},
|
|
1858
|
+
});
|
|
1859
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1860
|
+
}
|
|
1861
|
+
*/
|
|
1367
1862
|
}
|
|
1863
|
+
/** ***********************************************************************************************************************************/
|
|
1864
|
+
/** Matter.js methods */
|
|
1865
|
+
/** ***********************************************************************************************************************************/
|
|
1866
|
+
/**
|
|
1867
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1868
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1869
|
+
*/
|
|
1368
1870
|
async startMatterStorage() {
|
|
1871
|
+
// Setup Matter storage
|
|
1369
1872
|
this.log.info(`Starting matter node storage...`);
|
|
1370
1873
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1371
1874
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1374,13 +1877,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1374
1877
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1375
1878
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1376
1879
|
this.log.info('Matter node storage started');
|
|
1880
|
+
// Backup matter storage since it is created/opened correctly
|
|
1377
1881
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1378
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1885
|
+
*
|
|
1886
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1887
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1888
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1889
|
+
*/
|
|
1379
1890
|
async backupMatterStorage(storageName, backupName) {
|
|
1380
1891
|
this.log.info('Creating matter node storage backup...');
|
|
1381
1892
|
await copyDirectory(storageName, backupName);
|
|
1382
1893
|
this.log.info('Created matter node storage backup');
|
|
1383
1894
|
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Stops the matter storage.
|
|
1897
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1898
|
+
*/
|
|
1384
1899
|
async stopMatterStorage() {
|
|
1385
1900
|
this.log.info('Closing matter node storage...');
|
|
1386
1901
|
await this.matterStorageManager?.close();
|
|
@@ -1389,6 +1904,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1904
|
this.matterbridgeContext = undefined;
|
|
1390
1905
|
this.log.info('Matter node storage closed');
|
|
1391
1906
|
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Creates a server node storage context.
|
|
1909
|
+
*
|
|
1910
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1911
|
+
* @param {string} deviceName - The name of the device.
|
|
1912
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1913
|
+
* @param {number} vendorId - The vendor ID.
|
|
1914
|
+
* @param {string} vendorName - The vendor name.
|
|
1915
|
+
* @param {number} productId - The product ID.
|
|
1916
|
+
* @param {string} productName - The product name.
|
|
1917
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1918
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1919
|
+
*/
|
|
1392
1920
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1393
1921
|
const { randomBytes } = await import('node:crypto');
|
|
1394
1922
|
if (!this.matterStorageService)
|
|
@@ -1422,6 +1950,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1422
1950
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1423
1951
|
return storageContext;
|
|
1424
1952
|
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Creates a server node.
|
|
1955
|
+
*
|
|
1956
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1957
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1958
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1959
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1960
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1961
|
+
*/
|
|
1425
1962
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1426
1963
|
const storeId = await storageContext.get('storeId');
|
|
1427
1964
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1431,21 +1968,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1431
1968
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1432
1969
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1433
1970
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1971
|
+
/**
|
|
1972
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1973
|
+
*/
|
|
1434
1974
|
const serverNode = await ServerNode.create({
|
|
1975
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1435
1976
|
id: storeId,
|
|
1977
|
+
// Provide Network relevant configuration like the port
|
|
1978
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1436
1979
|
network: {
|
|
1437
1980
|
listeningAddressIpv4: this.ipv4address,
|
|
1438
1981
|
listeningAddressIpv6: this.ipv6address,
|
|
1439
1982
|
port,
|
|
1440
1983
|
},
|
|
1984
|
+
// Provide Commissioning relevant settings
|
|
1985
|
+
// Optional for development/testing purposes
|
|
1441
1986
|
commissioning: {
|
|
1442
1987
|
passcode,
|
|
1443
1988
|
discriminator,
|
|
1444
1989
|
},
|
|
1990
|
+
// Provide Node announcement settings
|
|
1991
|
+
// Optional: If Ommitted some development defaults are used
|
|
1445
1992
|
productDescription: {
|
|
1446
1993
|
name: await storageContext.get('deviceName'),
|
|
1447
1994
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1448
1995
|
},
|
|
1996
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
+
// Optional: If Omitted some development defaults are used
|
|
1449
1998
|
basicInformation: {
|
|
1450
1999
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1451
2000
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1463,12 +2012,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1463
2012
|
},
|
|
1464
2013
|
});
|
|
1465
2014
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2015
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1466
2016
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1467
2017
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1468
2018
|
if (this.bridgeMode === 'bridge') {
|
|
1469
2019
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1470
2020
|
if (resetSessions)
|
|
1471
|
-
this.matterbridgeSessionInformations = undefined;
|
|
2021
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1472
2022
|
this.matterbridgePaired = true;
|
|
1473
2023
|
}
|
|
1474
2024
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1476,13 +2026,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
2026
|
if (plugin) {
|
|
1477
2027
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1478
2028
|
if (resetSessions)
|
|
1479
|
-
plugin.sessionInformations = undefined;
|
|
2029
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1480
2030
|
plugin.paired = true;
|
|
1481
2031
|
}
|
|
1482
2032
|
}
|
|
1483
2033
|
};
|
|
2034
|
+
/**
|
|
2035
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2036
|
+
* This means: It is added to the first fabric.
|
|
2037
|
+
*/
|
|
1484
2038
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
2039
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1485
2040
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2041
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1486
2042
|
serverNode.lifecycle.online.on(async () => {
|
|
1487
2043
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1488
2044
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1551,6 +2107,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1551
2107
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1552
2108
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1553
2109
|
});
|
|
2110
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1554
2111
|
serverNode.lifecycle.offline.on(() => {
|
|
1555
2112
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1556
2113
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1574,6 +2131,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1574
2131
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1575
2132
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1576
2133
|
});
|
|
2134
|
+
/**
|
|
2135
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2136
|
+
* information is needed.
|
|
2137
|
+
*/
|
|
1577
2138
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1578
2139
|
let action = '';
|
|
1579
2140
|
switch (fabricAction) {
|
|
@@ -1607,16 +2168,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1607
2168
|
}
|
|
1608
2169
|
}
|
|
1609
2170
|
};
|
|
2171
|
+
/**
|
|
2172
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2173
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2174
|
+
*/
|
|
1610
2175
|
serverNode.events.sessions.opened.on((session) => {
|
|
1611
2176
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1612
2177
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1613
2178
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1614
2179
|
});
|
|
2180
|
+
/**
|
|
2181
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2182
|
+
*/
|
|
1615
2183
|
serverNode.events.sessions.closed.on((session) => {
|
|
1616
2184
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1617
2185
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1618
2186
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1619
2187
|
});
|
|
2188
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1620
2189
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1621
2190
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1622
2191
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1625,24 +2194,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1625
2194
|
this.log.info(`Created server node for ${storeId}`);
|
|
1626
2195
|
return serverNode;
|
|
1627
2196
|
}
|
|
2197
|
+
/**
|
|
2198
|
+
* Starts the specified server node.
|
|
2199
|
+
*
|
|
2200
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2201
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2202
|
+
*/
|
|
1628
2203
|
async startServerNode(matterServerNode) {
|
|
1629
2204
|
if (!matterServerNode)
|
|
1630
2205
|
return;
|
|
1631
2206
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1632
2207
|
await matterServerNode.start();
|
|
1633
2208
|
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Stops the specified server node.
|
|
2211
|
+
*
|
|
2212
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2213
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2214
|
+
*/
|
|
1634
2215
|
async stopServerNode(matterServerNode) {
|
|
1635
2216
|
if (!matterServerNode)
|
|
1636
2217
|
return;
|
|
1637
2218
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1638
2219
|
try {
|
|
1639
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2220
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1640
2221
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1641
2222
|
}
|
|
1642
2223
|
catch (error) {
|
|
1643
2224
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1644
2225
|
}
|
|
1645
2226
|
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Advertises the specified server node.
|
|
2229
|
+
*
|
|
2230
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2231
|
+
* @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.
|
|
2232
|
+
*/
|
|
1646
2233
|
async advertiseServerNode(matterServerNode) {
|
|
1647
2234
|
if (matterServerNode) {
|
|
1648
2235
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1651,24 +2238,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
1651
2238
|
return { qrPairingCode, manualPairingCode };
|
|
1652
2239
|
}
|
|
1653
2240
|
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Stop advertise the specified server node.
|
|
2243
|
+
*
|
|
2244
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2245
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2246
|
+
*/
|
|
1654
2247
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1655
2248
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1656
2249
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1657
2250
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1658
2251
|
}
|
|
1659
2252
|
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Creates an aggregator node with the specified storage context.
|
|
2255
|
+
*
|
|
2256
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2257
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2258
|
+
*/
|
|
1660
2259
|
async createAggregatorNode(storageContext) {
|
|
1661
2260
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1662
2261
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1663
2262
|
return aggregatorNode;
|
|
1664
2263
|
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2266
|
+
*
|
|
2267
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2268
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2269
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2270
|
+
*/
|
|
1665
2271
|
async addBridgedEndpoint(pluginName, device) {
|
|
2272
|
+
// Check if the plugin is registered
|
|
1666
2273
|
const plugin = this.plugins.get(pluginName);
|
|
1667
2274
|
if (!plugin) {
|
|
1668
2275
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1669
2276
|
return;
|
|
1670
2277
|
}
|
|
1671
2278
|
if (this.bridgeMode === 'bridge') {
|
|
2279
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1672
2280
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1673
2281
|
if (!this.aggregatorNode) {
|
|
1674
2282
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1685,6 +2293,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1685
2293
|
}
|
|
1686
2294
|
}
|
|
1687
2295
|
else if (this.bridgeMode === 'childbridge') {
|
|
2296
|
+
// Register and add the device to the plugin server node
|
|
1688
2297
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1689
2298
|
try {
|
|
1690
2299
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1701,10 +2310,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1701
2310
|
return;
|
|
1702
2311
|
}
|
|
1703
2312
|
}
|
|
2313
|
+
// Register and add the device to the plugin aggregator node
|
|
1704
2314
|
if (plugin.type === 'DynamicPlatform') {
|
|
1705
2315
|
try {
|
|
1706
2316
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1707
2317
|
await this.createDynamicPlugin(plugin);
|
|
2318
|
+
// Fast plugins can add another device before the server node is created
|
|
1708
2319
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1709
2320
|
if (!plugin.aggregatorNode) {
|
|
1710
2321
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1724,17 +2335,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1724
2335
|
plugin.registeredDevices++;
|
|
1725
2336
|
if (plugin.addedDevices !== undefined)
|
|
1726
2337
|
plugin.addedDevices++;
|
|
2338
|
+
// Add the device to the DeviceManager
|
|
1727
2339
|
this.devices.set(device);
|
|
2340
|
+
// Subscribe to the reachable$Changed event
|
|
1728
2341
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1729
2342
|
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}`);
|
|
1730
2343
|
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2346
|
+
*
|
|
2347
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2348
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2349
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2350
|
+
*/
|
|
1731
2351
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1732
2352
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2353
|
+
// Check if the plugin is registered
|
|
1733
2354
|
const plugin = this.plugins.get(pluginName);
|
|
1734
2355
|
if (!plugin) {
|
|
1735
2356
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1736
2357
|
return;
|
|
1737
2358
|
}
|
|
2359
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1738
2360
|
if (this.bridgeMode === 'bridge') {
|
|
1739
2361
|
if (!this.aggregatorNode) {
|
|
1740
2362
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1749,6 +2371,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1749
2371
|
}
|
|
1750
2372
|
else if (this.bridgeMode === 'childbridge') {
|
|
1751
2373
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2374
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1752
2375
|
}
|
|
1753
2376
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1754
2377
|
if (!plugin.aggregatorNode) {
|
|
@@ -1763,8 +2386,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1763
2386
|
if (plugin.addedDevices !== undefined)
|
|
1764
2387
|
plugin.addedDevices--;
|
|
1765
2388
|
}
|
|
2389
|
+
// Remove the device from the DeviceManager
|
|
1766
2390
|
this.devices.remove(device);
|
|
1767
2391
|
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2394
|
+
*
|
|
2395
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2396
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2397
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2398
|
+
*
|
|
2399
|
+
* @remarks
|
|
2400
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2401
|
+
* It also applies a delay between each removal if specified.
|
|
2402
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2403
|
+
*/
|
|
1768
2404
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1769
2405
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1770
2406
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1775,9 +2411,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1775
2411
|
if (delay > 0)
|
|
1776
2412
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1777
2413
|
}
|
|
2414
|
+
/**
|
|
2415
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2416
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2417
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2418
|
+
*
|
|
2419
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2420
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2421
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2422
|
+
*/
|
|
1778
2423
|
async subscribeAttributeChanged(plugin, device) {
|
|
1779
2424
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1780
2425
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2426
|
+
/*
|
|
2427
|
+
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
|
|
2428
|
+
setTimeout(async () => {
|
|
2429
|
+
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
|
|
2430
|
+
await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
|
|
2431
|
+
}, 60000).unref();
|
|
2432
|
+
*/
|
|
1781
2433
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1782
2434
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
1783
2435
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
@@ -1790,6 +2442,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1790
2442
|
});
|
|
1791
2443
|
}
|
|
1792
2444
|
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2447
|
+
*
|
|
2448
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2449
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2450
|
+
*/
|
|
1793
2451
|
sanitizeFabricInformations(fabricInfo) {
|
|
1794
2452
|
return fabricInfo.map((info) => {
|
|
1795
2453
|
return {
|
|
@@ -1803,6 +2461,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1803
2461
|
};
|
|
1804
2462
|
});
|
|
1805
2463
|
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2466
|
+
*
|
|
2467
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2468
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2469
|
+
*/
|
|
1806
2470
|
sanitizeSessionInformation(sessionInfo) {
|
|
1807
2471
|
return sessionInfo
|
|
1808
2472
|
.filter((session) => session.isPeerActive)
|
|
@@ -1830,7 +2494,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1830
2494
|
};
|
|
1831
2495
|
});
|
|
1832
2496
|
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2499
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2500
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2501
|
+
*/
|
|
2502
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1833
2503
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2504
|
+
/*
|
|
2505
|
+
for (const child of aggregatorNode.parts) {
|
|
2506
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2507
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2508
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2509
|
+
}
|
|
2510
|
+
*/
|
|
1834
2511
|
}
|
|
1835
2512
|
getVendorIdName = (vendorId) => {
|
|
1836
2513
|
if (!vendorId)
|
|
@@ -1873,14 +2550,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1873
2550
|
}
|
|
1874
2551
|
return vendorName;
|
|
1875
2552
|
};
|
|
2553
|
+
/**
|
|
2554
|
+
* Spawns a child process with the given command and arguments.
|
|
2555
|
+
* @param {string} command - The command to execute.
|
|
2556
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2557
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2558
|
+
*/
|
|
1876
2559
|
async spawnCommand(command, args = []) {
|
|
1877
2560
|
const { spawn } = await import('node:child_process');
|
|
2561
|
+
/*
|
|
2562
|
+
npm > npm.cmd on windows
|
|
2563
|
+
cmd.exe ['dir'] on windows
|
|
2564
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2565
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2566
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2567
|
+
});
|
|
2568
|
+
|
|
2569
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2570
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2571
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2572
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2573
|
+
*/
|
|
1878
2574
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1879
2575
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2576
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1880
2577
|
const argstring = 'npm ' + args.join(' ');
|
|
1881
2578
|
args.splice(0, args.length, '/c', argstring);
|
|
1882
2579
|
command = 'cmd.exe';
|
|
1883
2580
|
}
|
|
2581
|
+
// Decide when using sudo on linux
|
|
2582
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2583
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1884
2584
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1885
2585
|
args.unshift(command);
|
|
1886
2586
|
command = 'sudo';
|
|
@@ -1939,3 +2639,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1939
2639
|
});
|
|
1940
2640
|
}
|
|
1941
2641
|
}
|
|
2642
|
+
//# sourceMappingURL=matterbridge.js.map
|