matterbridge 3.0.4-dev-20250525-b1cbfb7 → 3.0.4
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 +2 -2
- 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 +256 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +363 -16
- package/dist/frontend.js.map +1 -0
- package/dist/helpers.d.ts +47 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +51 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -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 +445 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +747 -47
- 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 +1398 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +61 -4
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +629 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +563 -15
- 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 +1053 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +903 -23
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2749 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +172 -10
- 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 +196 -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/roboticVacuumCleaner.d.ts +82 -0
- package/dist/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/roboticVacuumCleaner.js +78 -3
- package/dist/roboticVacuumCleaner.js.map +1 -0
- package/dist/shelly.d.ts +153 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +155 -7
- 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 +58 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +53 -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/commandLine.d.ts +58 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +53 -0
- package/dist/utils/commandLine.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 +38 -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 +71 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +11 -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/hex.d.ts +48 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +57 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +102 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +100 -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/wait.d.ts +52 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +58 -9
- package/dist/utils/wait.js.map +1 -0
- package/dist/waterHeater.d.ts +75 -0
- package/dist/waterHeater.d.ts.map +1 -0
- package/dist/waterHeater.js +52 -0
- package/dist/waterHeater.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
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.3
|
|
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 { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -14,11 +40,15 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
14
40
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
15
41
|
import { Frontend } from './frontend.js';
|
|
16
42
|
import { addVirtualDevices } from './helpers.js';
|
|
43
|
+
// @matter
|
|
17
44
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
|
|
18
45
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
19
46
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
20
47
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
21
48
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
22
52
|
export class Matterbridge extends EventEmitter {
|
|
23
53
|
systemInformation = {
|
|
24
54
|
interfaceName: '',
|
|
@@ -66,7 +96,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
66
96
|
shellySysUpdate: false,
|
|
67
97
|
shellyMainUpdate: false,
|
|
68
98
|
profile: getParameter('profile'),
|
|
69
|
-
loggerLevel: "info"
|
|
99
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
70
100
|
fileLogger: false,
|
|
71
101
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
72
102
|
matterFileLogger: false,
|
|
@@ -105,9 +135,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
105
135
|
plugins;
|
|
106
136
|
devices;
|
|
107
137
|
frontend = new Frontend(this);
|
|
138
|
+
// Matterbridge storage
|
|
108
139
|
nodeStorage;
|
|
109
140
|
nodeContext;
|
|
110
141
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
142
|
+
// Cleanup
|
|
111
143
|
hasCleanupStarted = false;
|
|
112
144
|
initialized = false;
|
|
113
145
|
execRunningCount = 0;
|
|
@@ -120,19 +152,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
120
152
|
sigtermHandler;
|
|
121
153
|
exceptionHandler;
|
|
122
154
|
rejectionHandler;
|
|
155
|
+
// Matter environment
|
|
123
156
|
environment = Environment.default;
|
|
157
|
+
// Matter storage
|
|
124
158
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
125
159
|
matterStorageService;
|
|
126
160
|
matterStorageManager;
|
|
127
161
|
matterbridgeContext;
|
|
128
162
|
controllerContext;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
163
|
+
// Matter parameters
|
|
164
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
165
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
166
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
167
|
+
port; // first server node port
|
|
168
|
+
passcode; // first server node passcode
|
|
169
|
+
discriminator; // first server node discriminator
|
|
170
|
+
certification; // device certification
|
|
136
171
|
serverNode;
|
|
137
172
|
aggregatorNode;
|
|
138
173
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -140,21 +175,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
140
175
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
141
176
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
142
177
|
static instance;
|
|
178
|
+
// We load asyncronously so is private
|
|
143
179
|
constructor() {
|
|
144
180
|
super();
|
|
145
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
|
+
*/
|
|
146
190
|
emit(eventName, ...args) {
|
|
147
191
|
return super.emit(eventName, ...args);
|
|
148
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
|
+
*/
|
|
149
201
|
on(eventName, listener) {
|
|
150
202
|
return super.on(eventName, listener);
|
|
151
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Retrieves the list of Matterbridge devices.
|
|
206
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
207
|
+
*/
|
|
152
208
|
getDevices() {
|
|
153
209
|
return this.devices.array();
|
|
154
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Retrieves the list of registered plugins.
|
|
213
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
214
|
+
*/
|
|
155
215
|
getPlugins() {
|
|
156
216
|
return this.plugins.array();
|
|
157
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
220
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
221
|
+
*/
|
|
158
222
|
async setLogLevel(logLevel) {
|
|
159
223
|
if (this.log)
|
|
160
224
|
this.log.logLevel = logLevel;
|
|
@@ -168,19 +232,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
168
232
|
for (const plugin of this.plugins) {
|
|
169
233
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
170
234
|
continue;
|
|
171
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
172
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 */;
|
|
179
244
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
180
245
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
181
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 {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
255
|
+
* @returns The loaded Matterbridge instance.
|
|
256
|
+
*/
|
|
182
257
|
static async loadInstance(initialize = false) {
|
|
183
258
|
if (!Matterbridge.instance) {
|
|
259
|
+
// eslint-disable-next-line no-console
|
|
184
260
|
if (hasParameter('debug'))
|
|
185
261
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
186
262
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -189,8 +265,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
189
265
|
}
|
|
190
266
|
return Matterbridge.instance;
|
|
191
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Call cleanup().
|
|
270
|
+
*
|
|
271
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
272
|
+
*/
|
|
192
273
|
async destroyInstance() {
|
|
193
274
|
this.log.info(`Destroy instance...`);
|
|
275
|
+
// Save server nodes to close
|
|
194
276
|
const servers = [];
|
|
195
277
|
if (this.bridgeMode === 'bridge') {
|
|
196
278
|
if (this.serverNode)
|
|
@@ -202,72 +284,103 @@ export class Matterbridge extends EventEmitter {
|
|
|
202
284
|
servers.push(plugin.serverNode);
|
|
203
285
|
}
|
|
204
286
|
}
|
|
287
|
+
// Cleanup
|
|
205
288
|
await this.cleanup('destroying instance...', false);
|
|
289
|
+
// Close servers mdns service
|
|
206
290
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
207
291
|
for (const server of servers) {
|
|
208
292
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
209
293
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
210
294
|
}
|
|
295
|
+
// Wait for the cleanup to finish
|
|
211
296
|
await new Promise((resolve) => {
|
|
212
297
|
setTimeout(resolve, 500);
|
|
213
298
|
});
|
|
214
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
|
+
*/
|
|
215
310
|
async initialize() {
|
|
311
|
+
// Emit the initialize_started event
|
|
216
312
|
this.emit('initialize_started');
|
|
217
|
-
|
|
313
|
+
// Create the matterbridge logger
|
|
314
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
315
|
+
// Set the restart mode
|
|
218
316
|
if (hasParameter('service'))
|
|
219
317
|
this.restartMode = 'service';
|
|
220
318
|
if (hasParameter('docker'))
|
|
221
319
|
this.restartMode = 'docker';
|
|
320
|
+
// Set the matterbridge home directory
|
|
222
321
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
223
322
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
224
323
|
await this.createDirectory(this.homeDirectory, 'Matterbridge Home Directory');
|
|
324
|
+
// Set the matterbridge directory
|
|
225
325
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
226
326
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
227
327
|
await this.createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory');
|
|
228
328
|
await this.createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory');
|
|
229
329
|
await this.createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory');
|
|
330
|
+
// Set the matterbridge plugin directory
|
|
230
331
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
231
332
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
232
333
|
await this.createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory');
|
|
334
|
+
// Set the matterbridge cert directory
|
|
233
335
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
234
336
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
235
337
|
await this.createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory');
|
|
338
|
+
// Set the matterbridge root directory
|
|
236
339
|
const { fileURLToPath } = await import('node:url');
|
|
237
340
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
238
341
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
239
342
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
343
|
+
// Setup the matter environment
|
|
240
344
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
241
345
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
242
346
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
243
347
|
this.environment.vars.set('runtime.signals', false);
|
|
244
348
|
this.environment.vars.set('runtime.exitcode', false);
|
|
349
|
+
// Register process handlers
|
|
245
350
|
this.registerProcessHandlers();
|
|
351
|
+
// Initialize nodeStorage and nodeContext
|
|
246
352
|
try {
|
|
247
353
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
248
354
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
249
355
|
this.log.debug('Creating node storage context for matterbridge');
|
|
250
356
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
357
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
359
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
252
360
|
for (const key of keys) {
|
|
253
361
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
363
|
await this.nodeStorage?.storage.get(key);
|
|
255
364
|
}
|
|
256
365
|
const storages = await this.nodeStorage.getStorageNames();
|
|
257
366
|
for (const storage of storages) {
|
|
258
367
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
259
368
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
369
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
370
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
260
371
|
const keys = (await nodeContext?.storage.keys());
|
|
261
372
|
keys.forEach(async (key) => {
|
|
262
373
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
263
374
|
await nodeContext?.get(key);
|
|
264
375
|
});
|
|
265
376
|
}
|
|
377
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
266
378
|
this.log.debug('Creating node storage backup...');
|
|
267
379
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
268
380
|
this.log.debug('Created node storage backup');
|
|
269
381
|
}
|
|
270
382
|
catch (error) {
|
|
383
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
271
384
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
272
385
|
if (hasParameter('norestore')) {
|
|
273
386
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -282,14 +395,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
282
395
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
283
396
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
284
397
|
}
|
|
398
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
285
399
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
400
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
286
401
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
402
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
287
403
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
404
|
+
// Certificate management
|
|
288
405
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
289
406
|
try {
|
|
290
407
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
291
408
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
292
409
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
410
|
+
// Set the vendorId, vendorName, productId and productName if they are present in the pairing file
|
|
293
411
|
if (isValidNumber(pairingFileJson.vendorId))
|
|
294
412
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
295
413
|
if (isValidString(pairingFileJson.vendorName, 3))
|
|
@@ -298,16 +416,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
298
416
|
this.aggregatorProductId = VendorId(pairingFileJson.productId);
|
|
299
417
|
if (isValidString(pairingFileJson.productName, 3))
|
|
300
418
|
this.aggregatorProductName = pairingFileJson.productName;
|
|
419
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
301
420
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
302
421
|
this.passcode = pairingFileJson.passcode;
|
|
303
422
|
this.discriminator = pairingFileJson.discriminator;
|
|
304
423
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
305
424
|
}
|
|
425
|
+
// Set the certification if it is present in the pairing file
|
|
306
426
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
307
427
|
const hexStringToUint8Array = (hexString) => {
|
|
308
428
|
const matches = hexString.match(/.{1,2}/g);
|
|
309
429
|
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
310
430
|
};
|
|
431
|
+
// const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
|
|
432
|
+
// console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
|
|
311
433
|
this.certification = {
|
|
312
434
|
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
313
435
|
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
@@ -320,41 +442,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
320
442
|
catch (error) {
|
|
321
443
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
322
444
|
}
|
|
445
|
+
// Store the passcode, discriminator and port in the node context
|
|
323
446
|
await this.nodeContext.set('matterport', this.port);
|
|
324
447
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
325
448
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
326
449
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
450
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
327
451
|
if (hasParameter('logger')) {
|
|
328
452
|
const level = getParameter('logger');
|
|
329
453
|
if (level === 'debug') {
|
|
330
|
-
this.log.logLevel = "debug"
|
|
454
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
331
455
|
}
|
|
332
456
|
else if (level === 'info') {
|
|
333
|
-
this.log.logLevel = "info"
|
|
457
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
334
458
|
}
|
|
335
459
|
else if (level === 'notice') {
|
|
336
|
-
this.log.logLevel = "notice"
|
|
460
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
337
461
|
}
|
|
338
462
|
else if (level === 'warn') {
|
|
339
|
-
this.log.logLevel = "warn"
|
|
463
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
340
464
|
}
|
|
341
465
|
else if (level === 'error') {
|
|
342
|
-
this.log.logLevel = "error"
|
|
466
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
343
467
|
}
|
|
344
468
|
else if (level === 'fatal') {
|
|
345
|
-
this.log.logLevel = "fatal"
|
|
469
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
346
470
|
}
|
|
347
471
|
else {
|
|
348
472
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
349
|
-
this.log.logLevel = "info"
|
|
473
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
350
474
|
}
|
|
351
475
|
}
|
|
352
476
|
else {
|
|
353
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
477
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
354
478
|
}
|
|
355
479
|
this.frontend.logLevel = this.log.logLevel;
|
|
356
480
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
357
481
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
482
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
358
483
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
359
484
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
360
485
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -363,6 +488,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
363
488
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
364
489
|
if (this.profile !== undefined)
|
|
365
490
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
491
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
366
492
|
if (hasParameter('matterlogger')) {
|
|
367
493
|
const level = getParameter('matterlogger');
|
|
368
494
|
if (level === 'debug') {
|
|
@@ -394,6 +520,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
394
520
|
Logger.format = MatterLogFormat.ANSI;
|
|
395
521
|
Logger.setLogger('default', this.createMatterLogger());
|
|
396
522
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
523
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
397
524
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
398
525
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
399
526
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -402,6 +529,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
402
529
|
});
|
|
403
530
|
}
|
|
404
531
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
532
|
+
// Log network interfaces
|
|
405
533
|
const networkInterfaces = os.networkInterfaces();
|
|
406
534
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
407
535
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -413,6 +541,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
413
541
|
});
|
|
414
542
|
}
|
|
415
543
|
}
|
|
544
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
416
545
|
if (hasParameter('mdnsinterface')) {
|
|
417
546
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
418
547
|
}
|
|
@@ -421,6 +550,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
421
550
|
if (this.mdnsInterface === '')
|
|
422
551
|
this.mdnsInterface = undefined;
|
|
423
552
|
}
|
|
553
|
+
// Validate mdnsInterface
|
|
424
554
|
if (this.mdnsInterface) {
|
|
425
555
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
426
556
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -433,6 +563,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
433
563
|
}
|
|
434
564
|
if (this.mdnsInterface)
|
|
435
565
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
566
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
436
567
|
if (hasParameter('ipv4address')) {
|
|
437
568
|
this.ipv4address = getParameter('ipv4address');
|
|
438
569
|
}
|
|
@@ -441,6 +572,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
441
572
|
if (this.ipv4address === '')
|
|
442
573
|
this.ipv4address = undefined;
|
|
443
574
|
}
|
|
575
|
+
// Validate ipv4address
|
|
444
576
|
if (this.ipv4address) {
|
|
445
577
|
let isValid = false;
|
|
446
578
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -456,6 +588,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
456
588
|
await this.nodeContext.remove('matteripv4address');
|
|
457
589
|
}
|
|
458
590
|
}
|
|
591
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
459
592
|
if (hasParameter('ipv6address')) {
|
|
460
593
|
this.ipv6address = getParameter('ipv6address');
|
|
461
594
|
}
|
|
@@ -464,6 +597,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
464
597
|
if (this.ipv6address === '')
|
|
465
598
|
this.ipv6address = undefined;
|
|
466
599
|
}
|
|
600
|
+
// Validate ipv6address
|
|
467
601
|
if (this.ipv6address) {
|
|
468
602
|
let isValid = false;
|
|
469
603
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -484,6 +618,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
484
618
|
await this.nodeContext.remove('matteripv6address');
|
|
485
619
|
}
|
|
486
620
|
}
|
|
621
|
+
// Initialize the virtual mode
|
|
487
622
|
if (hasParameter('novirtual')) {
|
|
488
623
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
489
624
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -492,14 +627,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
492
627
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
493
628
|
}
|
|
494
629
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
630
|
+
// Initialize PluginManager
|
|
495
631
|
this.plugins = new PluginManager(this);
|
|
496
632
|
await this.plugins.loadFromStorage();
|
|
497
633
|
this.plugins.logLevel = this.log.logLevel;
|
|
634
|
+
// Initialize DeviceManager
|
|
498
635
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
499
636
|
this.devices.logLevel = this.log.logLevel;
|
|
637
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
500
638
|
for (const plugin of this.plugins) {
|
|
501
639
|
const packageJson = await this.plugins.parse(plugin);
|
|
502
640
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
641
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
642
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
503
643
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
504
644
|
try {
|
|
505
645
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -521,6 +661,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
521
661
|
await plugin.nodeContext.set('description', plugin.description);
|
|
522
662
|
await plugin.nodeContext.set('author', plugin.author);
|
|
523
663
|
}
|
|
664
|
+
// Log system info and create .matterbridge directory
|
|
524
665
|
await this.logNodeAndSystemInfo();
|
|
525
666
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
526
667
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -528,6 +669,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
528
669
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
529
670
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
530
671
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
672
|
+
// Check node version and throw error
|
|
531
673
|
const minNodeVersion = 18;
|
|
532
674
|
const nodeVersion = process.versions.node;
|
|
533
675
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -535,10 +677,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
535
677
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
536
678
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
537
679
|
}
|
|
680
|
+
// Parse command line
|
|
538
681
|
await this.parseCommandLine();
|
|
682
|
+
// Emit the initialize_completed event
|
|
539
683
|
this.emit('initialize_completed');
|
|
540
684
|
this.initialized = true;
|
|
541
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
688
|
+
* @private
|
|
689
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
690
|
+
*/
|
|
542
691
|
async parseCommandLine() {
|
|
543
692
|
if (hasParameter('help')) {
|
|
544
693
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -660,6 +809,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
660
809
|
this.shutdown = true;
|
|
661
810
|
return;
|
|
662
811
|
}
|
|
812
|
+
// Start the matter storage and create the matterbridge context
|
|
663
813
|
try {
|
|
664
814
|
await this.startMatterStorage();
|
|
665
815
|
}
|
|
@@ -667,12 +817,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
667
817
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
668
818
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
669
819
|
}
|
|
820
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
670
821
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
671
822
|
this.initialized = true;
|
|
672
823
|
await this.shutdownProcessAndReset();
|
|
673
824
|
this.shutdown = true;
|
|
674
825
|
return;
|
|
675
826
|
}
|
|
827
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
676
828
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
677
829
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
678
830
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -697,30 +849,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
697
849
|
this.shutdown = true;
|
|
698
850
|
return;
|
|
699
851
|
}
|
|
852
|
+
// Initialize frontend
|
|
700
853
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
701
854
|
await this.frontend.start(getIntParameter('frontend'));
|
|
855
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
702
856
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
703
857
|
const { checkUpdates } = await import('./update.js');
|
|
704
858
|
checkUpdates(this);
|
|
705
859
|
}, 30 * 1000).unref();
|
|
860
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
706
861
|
this.checkUpdateInterval = setInterval(async () => {
|
|
707
862
|
const { checkUpdates } = await import('./update.js');
|
|
708
863
|
checkUpdates(this);
|
|
709
864
|
}, 12 * 60 * 60 * 1000).unref();
|
|
865
|
+
// Start the matterbridge in mode test
|
|
710
866
|
if (hasParameter('test')) {
|
|
711
867
|
this.bridgeMode = 'bridge';
|
|
712
868
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
713
869
|
return;
|
|
714
870
|
}
|
|
871
|
+
// Start the matterbridge in mode controller
|
|
715
872
|
if (hasParameter('controller')) {
|
|
716
873
|
this.bridgeMode = 'controller';
|
|
717
874
|
await this.startController();
|
|
718
875
|
return;
|
|
719
876
|
}
|
|
877
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
720
878
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
721
879
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
722
880
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
723
881
|
}
|
|
882
|
+
// Start matterbridge in bridge mode
|
|
724
883
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
725
884
|
this.bridgeMode = 'bridge';
|
|
726
885
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -728,6 +887,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
728
887
|
await this.startBridge();
|
|
729
888
|
return;
|
|
730
889
|
}
|
|
890
|
+
// Start matterbridge in childbridge mode
|
|
731
891
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
732
892
|
this.bridgeMode = 'childbridge';
|
|
733
893
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -736,10 +896,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
736
896
|
return;
|
|
737
897
|
}
|
|
738
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* Asynchronously loads and starts the registered plugins.
|
|
901
|
+
*
|
|
902
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
903
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
904
|
+
*
|
|
905
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
906
|
+
*/
|
|
739
907
|
async startPlugins() {
|
|
908
|
+
// Check, load and start the plugins
|
|
740
909
|
for (const plugin of this.plugins) {
|
|
741
910
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
742
911
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
912
|
+
// Check if the plugin is available
|
|
743
913
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
744
914
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
745
915
|
plugin.enabled = false;
|
|
@@ -759,10 +929,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
759
929
|
plugin.addedDevices = undefined;
|
|
760
930
|
plugin.qrPairingCode = undefined;
|
|
761
931
|
plugin.manualPairingCode = undefined;
|
|
762
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
932
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
763
933
|
}
|
|
764
934
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
765
935
|
}
|
|
936
|
+
/**
|
|
937
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
938
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
939
|
+
*/
|
|
766
940
|
registerProcessHandlers() {
|
|
767
941
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
768
942
|
process.removeAllListeners('uncaughtException');
|
|
@@ -789,6 +963,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
789
963
|
};
|
|
790
964
|
process.on('SIGTERM', this.sigtermHandler);
|
|
791
965
|
}
|
|
966
|
+
/**
|
|
967
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
968
|
+
*/
|
|
792
969
|
deregisterProcessHandlers() {
|
|
793
970
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
794
971
|
if (this.exceptionHandler)
|
|
@@ -805,12 +982,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
805
982
|
process.off('SIGTERM', this.sigtermHandler);
|
|
806
983
|
this.sigtermHandler = undefined;
|
|
807
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Logs the node and system information.
|
|
987
|
+
*/
|
|
808
988
|
async logNodeAndSystemInfo() {
|
|
989
|
+
// IP address information
|
|
809
990
|
const networkInterfaces = os.networkInterfaces();
|
|
810
991
|
this.systemInformation.interfaceName = '';
|
|
811
992
|
this.systemInformation.ipv4Address = '';
|
|
812
993
|
this.systemInformation.ipv6Address = '';
|
|
813
994
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
995
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
814
996
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
815
997
|
continue;
|
|
816
998
|
if (!interfaceDetails) {
|
|
@@ -836,19 +1018,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
836
1018
|
break;
|
|
837
1019
|
}
|
|
838
1020
|
}
|
|
1021
|
+
// Node information
|
|
839
1022
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
840
1023
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
841
1024
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
842
1025
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1026
|
+
// Host system information
|
|
843
1027
|
this.systemInformation.hostname = os.hostname();
|
|
844
1028
|
this.systemInformation.user = os.userInfo().username;
|
|
845
|
-
this.systemInformation.osType = os.type();
|
|
846
|
-
this.systemInformation.osRelease = os.release();
|
|
847
|
-
this.systemInformation.osPlatform = os.platform();
|
|
848
|
-
this.systemInformation.osArch = os.arch();
|
|
849
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
850
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
851
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1029
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1030
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1031
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1032
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1033
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1034
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1035
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1036
|
+
// Log the system information
|
|
852
1037
|
this.log.debug('Host System Information:');
|
|
853
1038
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
854
1039
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -864,14 +1049,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
864
1049
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
865
1050
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
866
1051
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1052
|
+
// Log directories
|
|
867
1053
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
868
1054
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
869
1055
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
870
1056
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
871
1057
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1058
|
+
// Global node_modules directory
|
|
872
1059
|
if (this.nodeContext)
|
|
873
1060
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
874
1061
|
if (this.globalModulesDirectory === '') {
|
|
1062
|
+
// First run of Matterbridge so the node storage is empty
|
|
875
1063
|
try {
|
|
876
1064
|
this.execRunningCount++;
|
|
877
1065
|
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
|
|
@@ -885,53 +1073,84 @@ export class Matterbridge extends EventEmitter {
|
|
|
885
1073
|
}
|
|
886
1074
|
else
|
|
887
1075
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1076
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
|
|
1077
|
+
else {
|
|
1078
|
+
this.getGlobalNodeModules()
|
|
1079
|
+
.then(async (globalModulesDirectory) => {
|
|
1080
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
1081
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1082
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1083
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1084
|
+
})
|
|
1085
|
+
.catch((error) => {
|
|
1086
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1087
|
+
});
|
|
1088
|
+
}*/
|
|
1089
|
+
// Matterbridge version
|
|
888
1090
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
889
1091
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
890
1092
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
891
1093
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1094
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
892
1095
|
if (this.nodeContext)
|
|
893
1096
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
894
1097
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1098
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
895
1099
|
if (this.nodeContext)
|
|
896
1100
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
897
1101
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1102
|
+
// Current working directory
|
|
898
1103
|
const currentDir = process.cwd();
|
|
899
1104
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1105
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
900
1106
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
901
1107
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
902
1108
|
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1111
|
+
*
|
|
1112
|
+
* @returns {Function} The MatterLogger function.
|
|
1113
|
+
*/
|
|
903
1114
|
createMatterLogger() {
|
|
904
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1115
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
905
1116
|
return (level, formattedLog) => {
|
|
906
1117
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
907
1118
|
const message = formattedLog.slice(65);
|
|
908
1119
|
matterLogger.logName = logger;
|
|
909
1120
|
switch (level) {
|
|
910
1121
|
case MatterLogLevel.DEBUG:
|
|
911
|
-
matterLogger.log("debug"
|
|
1122
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
912
1123
|
break;
|
|
913
1124
|
case MatterLogLevel.INFO:
|
|
914
|
-
matterLogger.log("info"
|
|
1125
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
915
1126
|
break;
|
|
916
1127
|
case MatterLogLevel.NOTICE:
|
|
917
|
-
matterLogger.log("notice"
|
|
1128
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
918
1129
|
break;
|
|
919
1130
|
case MatterLogLevel.WARN:
|
|
920
|
-
matterLogger.log("warn"
|
|
1131
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
921
1132
|
break;
|
|
922
1133
|
case MatterLogLevel.ERROR:
|
|
923
|
-
matterLogger.log("error"
|
|
1134
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
924
1135
|
break;
|
|
925
1136
|
case MatterLogLevel.FATAL:
|
|
926
|
-
matterLogger.log("fatal"
|
|
1137
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
927
1138
|
break;
|
|
928
1139
|
default:
|
|
929
|
-
matterLogger.log("debug"
|
|
1140
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
930
1141
|
break;
|
|
931
1142
|
}
|
|
932
1143
|
};
|
|
933
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Creates a Matter File Logger.
|
|
1147
|
+
*
|
|
1148
|
+
* @param {string} filePath - The path to the log file.
|
|
1149
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1150
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1151
|
+
*/
|
|
934
1152
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1153
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
935
1154
|
let fileSize = 0;
|
|
936
1155
|
if (unlink) {
|
|
937
1156
|
try {
|
|
@@ -980,12 +1199,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
980
1199
|
}
|
|
981
1200
|
};
|
|
982
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1204
|
+
*/
|
|
983
1205
|
async restartProcess() {
|
|
984
1206
|
await this.cleanup('restarting...', true);
|
|
985
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Shut down the process by exiting the current process.
|
|
1210
|
+
*/
|
|
986
1211
|
async shutdownProcess() {
|
|
987
1212
|
await this.cleanup('shutting down...', false);
|
|
988
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Update matterbridge and and shut down the process.
|
|
1216
|
+
*/
|
|
989
1217
|
async updateProcess() {
|
|
990
1218
|
this.log.info('Updating matterbridge...');
|
|
991
1219
|
try {
|
|
@@ -998,52 +1226,73 @@ export class Matterbridge extends EventEmitter {
|
|
|
998
1226
|
this.frontend.wssSendRestartRequired();
|
|
999
1227
|
await this.cleanup('updating...', false);
|
|
1000
1228
|
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Unregister all devices and shut down the process.
|
|
1231
|
+
*/
|
|
1001
1232
|
async unregisterAndShutdownProcess() {
|
|
1002
1233
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1003
1234
|
for (const plugin of this.plugins) {
|
|
1004
1235
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1005
1236
|
}
|
|
1006
1237
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1007
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1238
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1008
1239
|
this.log.debug('Cleaning up and shutting down...');
|
|
1009
1240
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1010
1241
|
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Reset commissioning and shut down the process.
|
|
1244
|
+
*/
|
|
1011
1245
|
async shutdownProcessAndReset() {
|
|
1012
1246
|
await this.cleanup('shutting down with reset...', false);
|
|
1013
1247
|
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Factory reset and shut down the process.
|
|
1250
|
+
*/
|
|
1014
1251
|
async shutdownProcessAndFactoryReset() {
|
|
1015
1252
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1016
1253
|
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Cleans up the Matterbridge instance.
|
|
1256
|
+
* @param {string} message - The cleanup message.
|
|
1257
|
+
* @param {boolean} [restart=false] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1258
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1259
|
+
*/
|
|
1017
1260
|
async cleanup(message, restart = false) {
|
|
1018
1261
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1019
1262
|
this.emit('cleanup_started');
|
|
1020
1263
|
this.hasCleanupStarted = true;
|
|
1021
1264
|
this.log.info(message);
|
|
1265
|
+
// Clear the start matter interval
|
|
1022
1266
|
if (this.startMatterInterval) {
|
|
1023
1267
|
clearInterval(this.startMatterInterval);
|
|
1024
1268
|
this.startMatterInterval = undefined;
|
|
1025
1269
|
this.log.debug('Start matter interval cleared');
|
|
1026
1270
|
}
|
|
1271
|
+
// Clear the check update timeout
|
|
1027
1272
|
if (this.checkUpdateTimeout) {
|
|
1028
1273
|
clearInterval(this.checkUpdateTimeout);
|
|
1029
1274
|
this.checkUpdateTimeout = undefined;
|
|
1030
1275
|
this.log.debug('Check update timeout cleared');
|
|
1031
1276
|
}
|
|
1277
|
+
// Clear the check update interval
|
|
1032
1278
|
if (this.checkUpdateInterval) {
|
|
1033
1279
|
clearInterval(this.checkUpdateInterval);
|
|
1034
1280
|
this.checkUpdateInterval = undefined;
|
|
1035
1281
|
this.log.debug('Check update interval cleared');
|
|
1036
1282
|
}
|
|
1283
|
+
// Clear the configure timeout
|
|
1037
1284
|
if (this.configureTimeout) {
|
|
1038
1285
|
clearTimeout(this.configureTimeout);
|
|
1039
1286
|
this.configureTimeout = undefined;
|
|
1040
1287
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1041
1288
|
}
|
|
1289
|
+
// Clear the reachability timeout
|
|
1042
1290
|
if (this.reachabilityTimeout) {
|
|
1043
1291
|
clearTimeout(this.reachabilityTimeout);
|
|
1044
1292
|
this.reachabilityTimeout = undefined;
|
|
1045
1293
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1046
1294
|
}
|
|
1295
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1047
1296
|
for (const plugin of this.plugins) {
|
|
1048
1297
|
if (!plugin.enabled || plugin.error)
|
|
1049
1298
|
continue;
|
|
@@ -1054,9 +1303,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1054
1303
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1055
1304
|
}
|
|
1056
1305
|
}
|
|
1306
|
+
// Stop matter server nodes
|
|
1057
1307
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1058
1308
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1059
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1309
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1060
1310
|
if (this.bridgeMode === 'bridge') {
|
|
1061
1311
|
if (this.serverNode) {
|
|
1062
1312
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1072,6 +1322,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1072
1322
|
}
|
|
1073
1323
|
}
|
|
1074
1324
|
this.log.notice('Stopped matter server nodes');
|
|
1325
|
+
// Matter commisioning reset
|
|
1075
1326
|
if (message === 'shutting down with reset...') {
|
|
1076
1327
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1077
1328
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1081,18 +1332,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1081
1332
|
await this.matterbridgeContext?.clearAll();
|
|
1082
1333
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1083
1334
|
}
|
|
1335
|
+
// Stop matter storage
|
|
1084
1336
|
await this.stopMatterStorage();
|
|
1337
|
+
// Stop the frontend
|
|
1085
1338
|
await this.frontend.stop();
|
|
1339
|
+
// Remove the matterfilelogger
|
|
1086
1340
|
try {
|
|
1087
1341
|
Logger.removeLogger('matterfilelogger');
|
|
1088
1342
|
}
|
|
1089
1343
|
catch (error) {
|
|
1090
1344
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1091
1345
|
}
|
|
1346
|
+
// Close the matterbridge node storage and context
|
|
1092
1347
|
if (this.nodeStorage && this.nodeContext) {
|
|
1348
|
+
/*
|
|
1349
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1350
|
+
this.log.info('Saving registered devices...');
|
|
1351
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1352
|
+
this.devices.forEach(async (device) => {
|
|
1353
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1354
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1355
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1356
|
+
});
|
|
1357
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1358
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1359
|
+
*/
|
|
1360
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1093
1361
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1094
1362
|
await this.nodeContext.close();
|
|
1095
1363
|
this.nodeContext = undefined;
|
|
1364
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1096
1365
|
for (const plugin of this.plugins) {
|
|
1097
1366
|
if (plugin.nodeContext) {
|
|
1098
1367
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1109,8 +1378,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1109
1378
|
}
|
|
1110
1379
|
this.plugins.clear();
|
|
1111
1380
|
this.devices.clear();
|
|
1381
|
+
// Factory reset
|
|
1112
1382
|
if (message === 'shutting down with factory reset...') {
|
|
1113
1383
|
try {
|
|
1384
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1114
1385
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1115
1386
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1116
1387
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1124,6 +1395,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1124
1395
|
}
|
|
1125
1396
|
}
|
|
1126
1397
|
try {
|
|
1398
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1127
1399
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1128
1400
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1129
1401
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1138,12 +1410,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1138
1410
|
}
|
|
1139
1411
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1140
1412
|
}
|
|
1413
|
+
// Deregisters the process handlers
|
|
1141
1414
|
this.deregisterProcessHandlers();
|
|
1142
1415
|
if (restart) {
|
|
1143
1416
|
if (message === 'updating...') {
|
|
1144
1417
|
this.log.info('Cleanup completed. Updating...');
|
|
1145
1418
|
Matterbridge.instance = undefined;
|
|
1146
|
-
this.emit('update');
|
|
1419
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1147
1420
|
}
|
|
1148
1421
|
else if (message === 'restarting...') {
|
|
1149
1422
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1164,6 +1437,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1164
1437
|
this.log.debug('Cleanup already started...');
|
|
1165
1438
|
}
|
|
1166
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1442
|
+
*
|
|
1443
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1444
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1445
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1446
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1447
|
+
*/
|
|
1167
1448
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1168
1449
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1169
1450
|
plugin.locked = true;
|
|
@@ -1177,6 +1458,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1177
1458
|
await this.startServerNode(plugin.serverNode);
|
|
1178
1459
|
}
|
|
1179
1460
|
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1463
|
+
*
|
|
1464
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1465
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1466
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1467
|
+
*/
|
|
1180
1468
|
async createDynamicPlugin(plugin, start = false) {
|
|
1181
1469
|
if (!plugin.locked) {
|
|
1182
1470
|
plugin.locked = true;
|
|
@@ -1189,7 +1477,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1189
1477
|
await this.startServerNode(plugin.serverNode);
|
|
1190
1478
|
}
|
|
1191
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Starts the Matterbridge in bridge mode.
|
|
1482
|
+
* @private
|
|
1483
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1484
|
+
*/
|
|
1192
1485
|
async startBridge() {
|
|
1486
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1193
1487
|
if (!this.matterStorageManager)
|
|
1194
1488
|
throw new Error('No storage manager initialized');
|
|
1195
1489
|
if (!this.matterbridgeContext)
|
|
@@ -1228,7 +1522,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1228
1522
|
clearInterval(this.startMatterInterval);
|
|
1229
1523
|
this.startMatterInterval = undefined;
|
|
1230
1524
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1525
|
+
// Start the Matter server node
|
|
1231
1526
|
this.startServerNode(this.serverNode);
|
|
1527
|
+
// Configure the plugins
|
|
1232
1528
|
this.configureTimeout = setTimeout(async () => {
|
|
1233
1529
|
for (const plugin of this.plugins) {
|
|
1234
1530
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1246,6 +1542,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1246
1542
|
}
|
|
1247
1543
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1248
1544
|
}, 30 * 1000);
|
|
1545
|
+
// Setting reachability to true
|
|
1249
1546
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1250
1547
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1251
1548
|
if (this.aggregatorNode)
|
|
@@ -1254,6 +1551,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1254
1551
|
}, 60 * 1000);
|
|
1255
1552
|
}, 1000);
|
|
1256
1553
|
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1556
|
+
* @private
|
|
1557
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1558
|
+
*/
|
|
1257
1559
|
async startChildbridge() {
|
|
1258
1560
|
if (!this.matterStorageManager)
|
|
1259
1561
|
throw new Error('No storage manager initialized');
|
|
@@ -1291,6 +1593,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1291
1593
|
clearInterval(this.startMatterInterval);
|
|
1292
1594
|
this.startMatterInterval = undefined;
|
|
1293
1595
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1596
|
+
// Configure the plugins
|
|
1294
1597
|
this.configureTimeout = setTimeout(async () => {
|
|
1295
1598
|
for (const plugin of this.plugins) {
|
|
1296
1599
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1327,7 +1630,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1327
1630
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1328
1631
|
continue;
|
|
1329
1632
|
}
|
|
1633
|
+
// Start the Matter server node
|
|
1330
1634
|
this.startServerNode(plugin.serverNode);
|
|
1635
|
+
// Setting reachability to true
|
|
1331
1636
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1332
1637
|
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}`);
|
|
1333
1638
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1337,6 +1642,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1337
1642
|
}
|
|
1338
1643
|
}, 1000);
|
|
1339
1644
|
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Starts the Matterbridge controller.
|
|
1647
|
+
* @private
|
|
1648
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1649
|
+
*/
|
|
1340
1650
|
async startController() {
|
|
1341
1651
|
if (!this.matterStorageManager) {
|
|
1342
1652
|
this.log.error('No storage manager initialized');
|
|
@@ -1351,8 +1661,207 @@ export class Matterbridge extends EventEmitter {
|
|
|
1351
1661
|
return;
|
|
1352
1662
|
}
|
|
1353
1663
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1664
|
+
/*
|
|
1665
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1666
|
+
this.log.info('Creating matter commissioning controller');
|
|
1667
|
+
this.commissioningController = new CommissioningController({
|
|
1668
|
+
autoConnect: false,
|
|
1669
|
+
});
|
|
1670
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1671
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1672
|
+
|
|
1673
|
+
this.log.info('Starting matter server');
|
|
1674
|
+
await this.matterServer.start();
|
|
1675
|
+
this.log.info('Matter server started');
|
|
1676
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1677
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1678
|
+
regulatoryCountryCode: 'XX',
|
|
1679
|
+
};
|
|
1680
|
+
const commissioningController = new CommissioningController({
|
|
1681
|
+
environment: {
|
|
1682
|
+
environment,
|
|
1683
|
+
id: uniqueId,
|
|
1684
|
+
},
|
|
1685
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1686
|
+
adminFabricLabel,
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
if (hasParameter('pairingcode')) {
|
|
1690
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1691
|
+
const pairingCode = getParameter('pairingcode');
|
|
1692
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1693
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1694
|
+
|
|
1695
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1696
|
+
if (pairingCode !== undefined) {
|
|
1697
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1698
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1699
|
+
longDiscriminator = undefined;
|
|
1700
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1701
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1702
|
+
} else {
|
|
1703
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1704
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1705
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1706
|
+
}
|
|
1707
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1708
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
const options = {
|
|
1712
|
+
commissioning: commissioningOptions,
|
|
1713
|
+
discovery: {
|
|
1714
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1715
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1716
|
+
},
|
|
1717
|
+
passcode: setupPin,
|
|
1718
|
+
} as NodeCommissioningOptions;
|
|
1719
|
+
this.log.info('Commissioning with options:', options);
|
|
1720
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1721
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1722
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1723
|
+
} // (hasParameter('pairingcode'))
|
|
1724
|
+
|
|
1725
|
+
if (hasParameter('unpairall')) {
|
|
1726
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1727
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1728
|
+
for (const nodeId of nodeIds) {
|
|
1729
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1730
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1731
|
+
}
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (hasParameter('discover')) {
|
|
1736
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1737
|
+
// console.log(discover);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1741
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1746
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1747
|
+
for (const nodeId of nodeIds) {
|
|
1748
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1749
|
+
|
|
1750
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1751
|
+
autoSubscribe: false,
|
|
1752
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1753
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1754
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1755
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1756
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1757
|
+
switch (info) {
|
|
1758
|
+
case NodeStateInformation.Connected:
|
|
1759
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1760
|
+
break;
|
|
1761
|
+
case NodeStateInformation.Disconnected:
|
|
1762
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1763
|
+
break;
|
|
1764
|
+
case NodeStateInformation.Reconnecting:
|
|
1765
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1766
|
+
break;
|
|
1767
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1768
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1769
|
+
break;
|
|
1770
|
+
case NodeStateInformation.StructureChanged:
|
|
1771
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1772
|
+
break;
|
|
1773
|
+
case NodeStateInformation.Decommissioned:
|
|
1774
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1775
|
+
break;
|
|
1776
|
+
default:
|
|
1777
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1778
|
+
break;
|
|
1779
|
+
}
|
|
1780
|
+
},
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
node.logStructure();
|
|
1784
|
+
|
|
1785
|
+
// Get the interaction client
|
|
1786
|
+
this.log.info('Getting the interaction client');
|
|
1787
|
+
const interactionClient = await node.getInteractionClient();
|
|
1788
|
+
let cluster;
|
|
1789
|
+
let attributes;
|
|
1790
|
+
|
|
1791
|
+
// Log BasicInformationCluster
|
|
1792
|
+
cluster = BasicInformationCluster;
|
|
1793
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1794
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1795
|
+
});
|
|
1796
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1797
|
+
attributes.forEach((attribute) => {
|
|
1798
|
+
this.log.info(
|
|
1799
|
+
`- 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}`,
|
|
1800
|
+
);
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
// Log PowerSourceCluster
|
|
1804
|
+
cluster = PowerSourceCluster;
|
|
1805
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1806
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1807
|
+
});
|
|
1808
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1809
|
+
attributes.forEach((attribute) => {
|
|
1810
|
+
this.log.info(
|
|
1811
|
+
`- 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}`,
|
|
1812
|
+
);
|
|
1813
|
+
});
|
|
1814
|
+
|
|
1815
|
+
// Log ThreadNetworkDiagnostics
|
|
1816
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1817
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1818
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1819
|
+
});
|
|
1820
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1821
|
+
attributes.forEach((attribute) => {
|
|
1822
|
+
this.log.info(
|
|
1823
|
+
`- 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}`,
|
|
1824
|
+
);
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
// Log SwitchCluster
|
|
1828
|
+
cluster = SwitchCluster;
|
|
1829
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1830
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1831
|
+
});
|
|
1832
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1833
|
+
attributes.forEach((attribute) => {
|
|
1834
|
+
this.log.info(
|
|
1835
|
+
`- 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}`,
|
|
1836
|
+
);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1840
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1841
|
+
ignoreInitialTriggers: false,
|
|
1842
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1843
|
+
this.log.info(
|
|
1844
|
+
`***${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}`,
|
|
1845
|
+
),
|
|
1846
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1847
|
+
this.log.info(
|
|
1848
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1849
|
+
);
|
|
1850
|
+
},
|
|
1851
|
+
});
|
|
1852
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1853
|
+
}
|
|
1854
|
+
*/
|
|
1354
1855
|
}
|
|
1856
|
+
/** ***********************************************************************************************************************************/
|
|
1857
|
+
/** Matter.js methods */
|
|
1858
|
+
/** ***********************************************************************************************************************************/
|
|
1859
|
+
/**
|
|
1860
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1861
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1862
|
+
*/
|
|
1355
1863
|
async startMatterStorage() {
|
|
1864
|
+
// Setup Matter storage
|
|
1356
1865
|
this.log.info(`Starting matter node storage...`);
|
|
1357
1866
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1358
1867
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1361,13 +1870,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1361
1870
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1362
1871
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1363
1872
|
this.log.info('Matter node storage started');
|
|
1873
|
+
// Backup matter storage since it is created/opened correctly
|
|
1364
1874
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1365
1875
|
}
|
|
1876
|
+
/**
|
|
1877
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1878
|
+
*
|
|
1879
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1880
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1881
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1882
|
+
*/
|
|
1366
1883
|
async backupMatterStorage(storageName, backupName) {
|
|
1367
1884
|
this.log.info('Creating matter node storage backup...');
|
|
1368
1885
|
await copyDirectory(storageName, backupName);
|
|
1369
1886
|
this.log.info('Created matter node storage backup');
|
|
1370
1887
|
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Stops the matter storage.
|
|
1890
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1891
|
+
*/
|
|
1371
1892
|
async stopMatterStorage() {
|
|
1372
1893
|
this.log.info('Closing matter node storage...');
|
|
1373
1894
|
await this.matterStorageManager?.close();
|
|
@@ -1376,6 +1897,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1376
1897
|
this.matterbridgeContext = undefined;
|
|
1377
1898
|
this.log.info('Matter node storage closed');
|
|
1378
1899
|
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Creates a server node storage context.
|
|
1902
|
+
*
|
|
1903
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1904
|
+
* @param {string} deviceName - The name of the device.
|
|
1905
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1906
|
+
* @param {number} vendorId - The vendor ID.
|
|
1907
|
+
* @param {string} vendorName - The vendor name.
|
|
1908
|
+
* @param {number} productId - The product ID.
|
|
1909
|
+
* @param {string} productName - The product name.
|
|
1910
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1911
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1912
|
+
*/
|
|
1379
1913
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1380
1914
|
const { randomBytes } = await import('node:crypto');
|
|
1381
1915
|
if (!this.matterStorageService)
|
|
@@ -1409,6 +1943,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1409
1943
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1410
1944
|
return storageContext;
|
|
1411
1945
|
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Creates a server node.
|
|
1948
|
+
*
|
|
1949
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1950
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1951
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1952
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1953
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1954
|
+
*/
|
|
1412
1955
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1413
1956
|
const storeId = await storageContext.get('storeId');
|
|
1414
1957
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1418,24 +1961,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1418
1961
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1419
1962
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1420
1963
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1964
|
+
/**
|
|
1965
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1966
|
+
*/
|
|
1421
1967
|
const serverNode = await ServerNode.create({
|
|
1968
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1422
1969
|
id: storeId,
|
|
1970
|
+
// Provide Network relevant configuration like the port
|
|
1971
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1423
1972
|
network: {
|
|
1424
1973
|
listeningAddressIpv4: this.ipv4address,
|
|
1425
1974
|
listeningAddressIpv6: this.ipv6address,
|
|
1426
1975
|
port,
|
|
1427
1976
|
},
|
|
1977
|
+
// Provide the certificate for the device
|
|
1428
1978
|
operationalCredentials: {
|
|
1429
1979
|
certification: this.certification,
|
|
1430
1980
|
},
|
|
1981
|
+
// Provide Commissioning relevant settings
|
|
1982
|
+
// Optional for development/testing purposes
|
|
1431
1983
|
commissioning: {
|
|
1432
1984
|
passcode,
|
|
1433
1985
|
discriminator,
|
|
1434
1986
|
},
|
|
1987
|
+
// Provide Node announcement settings
|
|
1988
|
+
// Optional: If Ommitted some development defaults are used
|
|
1435
1989
|
productDescription: {
|
|
1436
1990
|
name: await storageContext.get('deviceName'),
|
|
1437
1991
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1438
1992
|
},
|
|
1993
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1994
|
+
// Optional: If Omitted some development defaults are used
|
|
1439
1995
|
basicInformation: {
|
|
1440
1996
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1441
1997
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1453,12 +2009,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1453
2009
|
},
|
|
1454
2010
|
});
|
|
1455
2011
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2012
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1456
2013
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1457
2014
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1458
2015
|
if (this.bridgeMode === 'bridge') {
|
|
1459
2016
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1460
2017
|
if (resetSessions)
|
|
1461
|
-
this.matterbridgeSessionInformations = undefined;
|
|
2018
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1462
2019
|
this.matterbridgePaired = true;
|
|
1463
2020
|
}
|
|
1464
2021
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1466,13 +2023,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1466
2023
|
if (plugin) {
|
|
1467
2024
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1468
2025
|
if (resetSessions)
|
|
1469
|
-
plugin.sessionInformations = undefined;
|
|
2026
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1470
2027
|
plugin.paired = true;
|
|
1471
2028
|
}
|
|
1472
2029
|
}
|
|
1473
2030
|
};
|
|
2031
|
+
/**
|
|
2032
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2033
|
+
* This means: It is added to the first fabric.
|
|
2034
|
+
*/
|
|
1474
2035
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
2036
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1475
2037
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2038
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1476
2039
|
serverNode.lifecycle.online.on(async () => {
|
|
1477
2040
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1478
2041
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1541,6 +2104,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1541
2104
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1542
2105
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1543
2106
|
});
|
|
2107
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1544
2108
|
serverNode.lifecycle.offline.on(() => {
|
|
1545
2109
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1546
2110
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1564,6 +2128,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1564
2128
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1565
2129
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1566
2130
|
});
|
|
2131
|
+
/**
|
|
2132
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2133
|
+
* information is needed.
|
|
2134
|
+
*/
|
|
1567
2135
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1568
2136
|
let action = '';
|
|
1569
2137
|
switch (fabricAction) {
|
|
@@ -1597,16 +2165,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1597
2165
|
}
|
|
1598
2166
|
}
|
|
1599
2167
|
};
|
|
2168
|
+
/**
|
|
2169
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2170
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2171
|
+
*/
|
|
1600
2172
|
serverNode.events.sessions.opened.on((session) => {
|
|
1601
2173
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1602
2174
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1603
2175
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1604
2176
|
});
|
|
2177
|
+
/**
|
|
2178
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2179
|
+
*/
|
|
1605
2180
|
serverNode.events.sessions.closed.on((session) => {
|
|
1606
2181
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1607
2182
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1608
2183
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1609
2184
|
});
|
|
2185
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1610
2186
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1611
2187
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1612
2188
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1615,24 +2191,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
2191
|
this.log.info(`Created server node for ${storeId}`);
|
|
1616
2192
|
return serverNode;
|
|
1617
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Starts the specified server node.
|
|
2196
|
+
*
|
|
2197
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2198
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2199
|
+
*/
|
|
1618
2200
|
async startServerNode(matterServerNode) {
|
|
1619
2201
|
if (!matterServerNode)
|
|
1620
2202
|
return;
|
|
1621
2203
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1622
2204
|
await matterServerNode.start();
|
|
1623
2205
|
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Stops the specified server node.
|
|
2208
|
+
*
|
|
2209
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2210
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2211
|
+
*/
|
|
1624
2212
|
async stopServerNode(matterServerNode) {
|
|
1625
2213
|
if (!matterServerNode)
|
|
1626
2214
|
return;
|
|
1627
2215
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1628
2216
|
try {
|
|
1629
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2217
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1630
2218
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1631
2219
|
}
|
|
1632
2220
|
catch (error) {
|
|
1633
2221
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1634
2222
|
}
|
|
1635
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Advertises the specified server node.
|
|
2226
|
+
*
|
|
2227
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2228
|
+
* @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.
|
|
2229
|
+
*/
|
|
1636
2230
|
async advertiseServerNode(matterServerNode) {
|
|
1637
2231
|
if (matterServerNode) {
|
|
1638
2232
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1641,25 +2235,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
1641
2235
|
return { qrPairingCode, manualPairingCode };
|
|
1642
2236
|
}
|
|
1643
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Stop advertise the specified server node.
|
|
2240
|
+
*
|
|
2241
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2242
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2243
|
+
*/
|
|
1644
2244
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1645
2245
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1646
2246
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1647
2247
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1648
2248
|
}
|
|
1649
2249
|
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Creates an aggregator node with the specified storage context.
|
|
2252
|
+
*
|
|
2253
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2254
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2255
|
+
*/
|
|
1650
2256
|
async createAggregatorNode(storageContext) {
|
|
1651
2257
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1652
2258
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1653
2259
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1654
2260
|
return aggregatorNode;
|
|
1655
2261
|
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2264
|
+
*
|
|
2265
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2266
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2267
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2268
|
+
*/
|
|
1656
2269
|
async addBridgedEndpoint(pluginName, device) {
|
|
2270
|
+
// Check if the plugin is registered
|
|
1657
2271
|
const plugin = this.plugins.get(pluginName);
|
|
1658
2272
|
if (!plugin) {
|
|
1659
2273
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1660
2274
|
return;
|
|
1661
2275
|
}
|
|
1662
2276
|
if (this.bridgeMode === 'bridge') {
|
|
2277
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1663
2278
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1664
2279
|
if (!this.aggregatorNode) {
|
|
1665
2280
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1676,6 +2291,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1676
2291
|
}
|
|
1677
2292
|
}
|
|
1678
2293
|
else if (this.bridgeMode === 'childbridge') {
|
|
2294
|
+
// Register and add the device to the plugin server node
|
|
1679
2295
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1680
2296
|
try {
|
|
1681
2297
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1692,10 +2308,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1692
2308
|
return;
|
|
1693
2309
|
}
|
|
1694
2310
|
}
|
|
2311
|
+
// Register and add the device to the plugin aggregator node
|
|
1695
2312
|
if (plugin.type === 'DynamicPlatform') {
|
|
1696
2313
|
try {
|
|
1697
2314
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1698
2315
|
await this.createDynamicPlugin(plugin);
|
|
2316
|
+
// Fast plugins can add another device before the server node is created
|
|
1699
2317
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1700
2318
|
if (!plugin.aggregatorNode) {
|
|
1701
2319
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1715,17 +2333,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1715
2333
|
plugin.registeredDevices++;
|
|
1716
2334
|
if (plugin.addedDevices !== undefined)
|
|
1717
2335
|
plugin.addedDevices++;
|
|
2336
|
+
// Add the device to the DeviceManager
|
|
1718
2337
|
this.devices.set(device);
|
|
2338
|
+
// Subscribe to the reachable$Changed event
|
|
1719
2339
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1720
2340
|
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}`);
|
|
1721
2341
|
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2344
|
+
*
|
|
2345
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2346
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2347
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2348
|
+
*/
|
|
1722
2349
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1723
2350
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2351
|
+
// Check if the plugin is registered
|
|
1724
2352
|
const plugin = this.plugins.get(pluginName);
|
|
1725
2353
|
if (!plugin) {
|
|
1726
2354
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1727
2355
|
return;
|
|
1728
2356
|
}
|
|
2357
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1729
2358
|
if (this.bridgeMode === 'bridge') {
|
|
1730
2359
|
if (!this.aggregatorNode) {
|
|
1731
2360
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1740,6 +2369,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1740
2369
|
}
|
|
1741
2370
|
else if (this.bridgeMode === 'childbridge') {
|
|
1742
2371
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2372
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1743
2373
|
}
|
|
1744
2374
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1745
2375
|
if (!plugin.aggregatorNode) {
|
|
@@ -1754,8 +2384,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1754
2384
|
if (plugin.addedDevices !== undefined)
|
|
1755
2385
|
plugin.addedDevices--;
|
|
1756
2386
|
}
|
|
2387
|
+
// Remove the device from the DeviceManager
|
|
1757
2388
|
this.devices.remove(device);
|
|
1758
2389
|
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2392
|
+
*
|
|
2393
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2394
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2395
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2396
|
+
*
|
|
2397
|
+
* @remarks
|
|
2398
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2399
|
+
* It also applies a delay between each removal if specified.
|
|
2400
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2401
|
+
*/
|
|
1759
2402
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1760
2403
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1761
2404
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1766,6 +2409,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1766
2409
|
if (delay > 0)
|
|
1767
2410
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1768
2411
|
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2414
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2415
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2416
|
+
*
|
|
2417
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2418
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2419
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2420
|
+
*/
|
|
1769
2421
|
async subscribeAttributeChanged(plugin, device) {
|
|
1770
2422
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1771
2423
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -1781,6 +2433,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1781
2433
|
});
|
|
1782
2434
|
}
|
|
1783
2435
|
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2438
|
+
*
|
|
2439
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2440
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2441
|
+
*/
|
|
1784
2442
|
sanitizeFabricInformations(fabricInfo) {
|
|
1785
2443
|
return fabricInfo.map((info) => {
|
|
1786
2444
|
return {
|
|
@@ -1794,6 +2452,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1794
2452
|
};
|
|
1795
2453
|
});
|
|
1796
2454
|
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2457
|
+
*
|
|
2458
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2459
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2460
|
+
*/
|
|
1797
2461
|
sanitizeSessionInformation(sessionInfo) {
|
|
1798
2462
|
return sessionInfo
|
|
1799
2463
|
.filter((session) => session.isPeerActive)
|
|
@@ -1821,7 +2485,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1821
2485
|
};
|
|
1822
2486
|
});
|
|
1823
2487
|
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2490
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2491
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2492
|
+
*/
|
|
2493
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1824
2494
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2495
|
+
/*
|
|
2496
|
+
for (const child of aggregatorNode.parts) {
|
|
2497
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2498
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2499
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2500
|
+
}
|
|
2501
|
+
*/
|
|
1825
2502
|
}
|
|
1826
2503
|
getVendorIdName = (vendorId) => {
|
|
1827
2504
|
if (!vendorId)
|
|
@@ -1864,14 +2541,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
1864
2541
|
}
|
|
1865
2542
|
return vendorName;
|
|
1866
2543
|
};
|
|
2544
|
+
/**
|
|
2545
|
+
* Spawns a child process with the given command and arguments.
|
|
2546
|
+
* @param {string} command - The command to execute.
|
|
2547
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2548
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2549
|
+
*/
|
|
1867
2550
|
async spawnCommand(command, args = []) {
|
|
1868
2551
|
const { spawn } = await import('node:child_process');
|
|
2552
|
+
/*
|
|
2553
|
+
npm > npm.cmd on windows
|
|
2554
|
+
cmd.exe ['dir'] on windows
|
|
2555
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2556
|
+
*/
|
|
1869
2557
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1870
2558
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2559
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1871
2560
|
const argstring = 'npm ' + args.join(' ');
|
|
1872
2561
|
args.splice(0, args.length, '/c', argstring);
|
|
1873
2562
|
command = 'cmd.exe';
|
|
1874
2563
|
}
|
|
2564
|
+
// Decide when using sudo on linux
|
|
2565
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2566
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1875
2567
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1876
2568
|
args.unshift(command);
|
|
1877
2569
|
command = 'sudo';
|
|
@@ -1929,6 +2621,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1929
2621
|
}
|
|
1930
2622
|
});
|
|
1931
2623
|
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Creates a directory at the specified path if it doesn't already exist.
|
|
2626
|
+
*
|
|
2627
|
+
* @param {string} path - The path to the directory to create.
|
|
2628
|
+
* @param {string} name - The name of the directory.
|
|
2629
|
+
* @returns {Promise<void>} A promise that resolves when the directory has been created or already exists.
|
|
2630
|
+
*/
|
|
1932
2631
|
async createDirectory(path, name) {
|
|
1933
2632
|
try {
|
|
1934
2633
|
await fs.access(path);
|
|
@@ -1950,3 +2649,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1950
2649
|
}
|
|
1951
2650
|
}
|
|
1952
2651
|
}
|
|
2652
|
+
//# sourceMappingURL=matterbridge.js.map
|