matterbridge 3.0.7-dev-20250618-fb768ee → 3.0.7
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 +3 -2
- package/README-DEV.md +4 -4
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -2
- package/dist/cli.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/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/devices/export.d.ts +5 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/evse.d.ts +67 -0
- package/dist/evse.d.ts.map +1 -0
- package/dist/evse.js +65 -9
- package/dist/evse.js.map +1 -0
- package/dist/frontend.d.ts +256 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +374 -16
- package/dist/frontend.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +32 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +20 -0
- package/dist/globalMatterbridge.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 +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -0
- package/dist/laundryWasher.d.ts +243 -0
- package/dist/laundryWasher.d.ts.map +1 -0
- package/dist/laundryWasher.js +92 -7
- package/dist/laundryWasher.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 +748 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +34 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1333 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +54 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +644 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +578 -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 +1145 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +995 -40
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +3083 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +204 -10
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +290 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +221 -6
- 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 +269 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/roboticVacuumCleaner.d.ts +102 -0
- package/dist/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/roboticVacuumCleaner.js +81 -6
- package/dist/roboticVacuumCleaner.js.map +1 -0
- package/dist/shelly.d.ts +161 -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/createDirectory.d.ts +32 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +31 -0
- package/dist/utils/createDirectory.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 +12 -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/spawn.d.ts +12 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +16 -0
- package/dist/utils/spawn.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 +90 -0
- package/dist/waterHeater.d.ts.map +1 -0
- package/dist/waterHeater.js +62 -2
- 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.6.0
|
|
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, createDirectory } from './utils/export.js';
|
|
9
35
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
10
36
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -15,11 +41,15 @@ import { bridge } from './matterbridgeDeviceTypes.js';
|
|
|
15
41
|
import { Frontend } from './frontend.js';
|
|
16
42
|
import { addVirtualDevices } from './helpers.js';
|
|
17
43
|
import spawn from './utils/spawn.js';
|
|
44
|
+
// @matter
|
|
18
45
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
|
|
19
46
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
20
47
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
21
48
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
22
49
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
50
|
+
/**
|
|
51
|
+
* Represents the Matterbridge application.
|
|
52
|
+
*/
|
|
23
53
|
export class Matterbridge extends EventEmitter {
|
|
24
54
|
systemInformation = {
|
|
25
55
|
interfaceName: '',
|
|
@@ -67,7 +97,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
67
97
|
shellySysUpdate: false,
|
|
68
98
|
shellyMainUpdate: false,
|
|
69
99
|
profile: getParameter('profile'),
|
|
70
|
-
loggerLevel: "info"
|
|
100
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
71
101
|
fileLogger: false,
|
|
72
102
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
73
103
|
matterFileLogger: false,
|
|
@@ -106,9 +136,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
106
136
|
plugins;
|
|
107
137
|
devices;
|
|
108
138
|
frontend = new Frontend(this);
|
|
139
|
+
// Matterbridge storage
|
|
109
140
|
nodeStorage;
|
|
110
141
|
nodeContext;
|
|
111
142
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
143
|
+
// Cleanup
|
|
112
144
|
hasCleanupStarted = false;
|
|
113
145
|
initialized = false;
|
|
114
146
|
execRunningCount = 0;
|
|
@@ -122,19 +154,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
122
154
|
sigtermHandler;
|
|
123
155
|
exceptionHandler;
|
|
124
156
|
rejectionHandler;
|
|
157
|
+
// Matter environment
|
|
125
158
|
environment = Environment.default;
|
|
159
|
+
// Matter storage
|
|
126
160
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
127
161
|
matterStorageService;
|
|
128
162
|
matterStorageManager;
|
|
129
163
|
matterbridgeContext;
|
|
130
164
|
controllerContext;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
// Matter parameters
|
|
166
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
167
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
168
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
169
|
+
port; // first server node port
|
|
170
|
+
passcode; // first server node passcode
|
|
171
|
+
discriminator; // first server node discriminator
|
|
172
|
+
certification; // device certification
|
|
138
173
|
serverNode;
|
|
139
174
|
aggregatorNode;
|
|
140
175
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -142,21 +177,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
142
177
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
143
178
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
144
179
|
static instance;
|
|
180
|
+
// We load asyncronously so is private
|
|
145
181
|
constructor() {
|
|
146
182
|
super();
|
|
147
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Emits an event of the specified type with the provided arguments.
|
|
186
|
+
*
|
|
187
|
+
* @template K - The type of the event.
|
|
188
|
+
* @param {K} eventName - The name of the event to emit.
|
|
189
|
+
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
190
|
+
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
191
|
+
*/
|
|
148
192
|
emit(eventName, ...args) {
|
|
149
193
|
return super.emit(eventName, ...args);
|
|
150
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Registers an event listener for the specified event type.
|
|
197
|
+
*
|
|
198
|
+
* @template K - The type of the event.
|
|
199
|
+
* @param {K} eventName - The name of the event to listen for.
|
|
200
|
+
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
201
|
+
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
202
|
+
*/
|
|
151
203
|
on(eventName, listener) {
|
|
152
204
|
return super.on(eventName, listener);
|
|
153
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Retrieves the list of Matterbridge devices.
|
|
208
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
209
|
+
*/
|
|
154
210
|
getDevices() {
|
|
155
211
|
return this.devices.array();
|
|
156
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Retrieves the list of registered plugins.
|
|
215
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
216
|
+
*/
|
|
157
217
|
getPlugins() {
|
|
158
218
|
return this.plugins.array();
|
|
159
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
222
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
223
|
+
*/
|
|
160
224
|
async setLogLevel(logLevel) {
|
|
161
225
|
if (this.log)
|
|
162
226
|
this.log.logLevel = logLevel;
|
|
@@ -170,19 +234,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
170
234
|
for (const plugin of this.plugins) {
|
|
171
235
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
172
236
|
continue;
|
|
173
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
174
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
237
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
238
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
239
|
+
}
|
|
240
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
241
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
242
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
243
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
244
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
245
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
181
246
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
182
247
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
183
248
|
}
|
|
249
|
+
/** ***********************************************************************************************************************************/
|
|
250
|
+
/** loadInstance() and cleanup() methods */
|
|
251
|
+
/** ***********************************************************************************************************************************/
|
|
252
|
+
/**
|
|
253
|
+
* Loads an instance of the Matterbridge class.
|
|
254
|
+
* If an instance already exists, return that instance.
|
|
255
|
+
*
|
|
256
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
257
|
+
* @returns The loaded Matterbridge instance.
|
|
258
|
+
*/
|
|
184
259
|
static async loadInstance(initialize = false) {
|
|
185
260
|
if (!Matterbridge.instance) {
|
|
261
|
+
// eslint-disable-next-line no-console
|
|
186
262
|
if (hasParameter('debug'))
|
|
187
263
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
188
264
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -191,8 +267,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
191
267
|
}
|
|
192
268
|
return Matterbridge.instance;
|
|
193
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Call cleanup() and dispose MdnsService.
|
|
272
|
+
*
|
|
273
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
274
|
+
*/
|
|
194
275
|
async destroyInstance() {
|
|
195
276
|
this.log.info(`Destroy instance...`);
|
|
277
|
+
// Save server nodes to close
|
|
196
278
|
const servers = [];
|
|
197
279
|
if (this.bridgeMode === 'bridge') {
|
|
198
280
|
if (this.serverNode)
|
|
@@ -204,74 +286,107 @@ export class Matterbridge extends EventEmitter {
|
|
|
204
286
|
servers.push(plugin.serverNode);
|
|
205
287
|
}
|
|
206
288
|
}
|
|
289
|
+
// Let any already‐queued microtasks run first
|
|
207
290
|
await Promise.resolve();
|
|
291
|
+
// Cleanup
|
|
208
292
|
await this.cleanup('destroying instance...', false);
|
|
293
|
+
// Close servers mdns service
|
|
209
294
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
210
295
|
for (const server of servers) {
|
|
211
296
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
212
297
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
213
298
|
}
|
|
299
|
+
// Let any already‐queued microtasks run first
|
|
214
300
|
await Promise.resolve();
|
|
301
|
+
// Wait for the cleanup to finish
|
|
215
302
|
await new Promise((resolve) => {
|
|
216
303
|
setTimeout(resolve, 500);
|
|
217
304
|
});
|
|
218
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Initializes the Matterbridge application.
|
|
308
|
+
*
|
|
309
|
+
* @remarks
|
|
310
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
311
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
312
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
313
|
+
*
|
|
314
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
315
|
+
*/
|
|
219
316
|
async initialize() {
|
|
317
|
+
// Emit the initialize_started event
|
|
220
318
|
this.emit('initialize_started');
|
|
221
|
-
|
|
319
|
+
// Create the matterbridge logger
|
|
320
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
321
|
+
// Set the restart mode
|
|
222
322
|
if (hasParameter('service'))
|
|
223
323
|
this.restartMode = 'service';
|
|
224
324
|
if (hasParameter('docker'))
|
|
225
325
|
this.restartMode = 'docker';
|
|
326
|
+
// Set the matterbridge home directory
|
|
226
327
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
227
328
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
228
329
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
330
|
+
// Set the matterbridge directory
|
|
229
331
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
230
332
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
231
333
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
232
334
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
233
335
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
336
|
+
// Set the matterbridge plugin directory
|
|
234
337
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
235
338
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
236
339
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
340
|
+
// Set the matterbridge cert directory
|
|
237
341
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
238
342
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
239
343
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
344
|
+
// Set the matterbridge root directory
|
|
240
345
|
const { fileURLToPath } = await import('node:url');
|
|
241
346
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
242
347
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
243
348
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
349
|
+
// Setup the matter environment
|
|
244
350
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
245
351
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
246
352
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
247
353
|
this.environment.vars.set('runtime.signals', false);
|
|
248
354
|
this.environment.vars.set('runtime.exitcode', false);
|
|
355
|
+
// Register process handlers
|
|
249
356
|
this.registerProcessHandlers();
|
|
357
|
+
// Initialize nodeStorage and nodeContext
|
|
250
358
|
try {
|
|
251
359
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
252
360
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
253
361
|
this.log.debug('Creating node storage context for matterbridge');
|
|
254
362
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
363
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
364
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
255
365
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
256
366
|
for (const key of keys) {
|
|
257
367
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
368
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
369
|
await this.nodeStorage?.storage.get(key);
|
|
259
370
|
}
|
|
260
371
|
const storages = await this.nodeStorage.getStorageNames();
|
|
261
372
|
for (const storage of storages) {
|
|
262
373
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
263
374
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
375
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
376
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
264
377
|
const keys = (await nodeContext?.storage.keys());
|
|
265
378
|
keys.forEach(async (key) => {
|
|
266
379
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
267
380
|
await nodeContext?.get(key);
|
|
268
381
|
});
|
|
269
382
|
}
|
|
383
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
270
384
|
this.log.debug('Creating node storage backup...');
|
|
271
385
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
272
386
|
this.log.debug('Created node storage backup');
|
|
273
387
|
}
|
|
274
388
|
catch (error) {
|
|
389
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
275
390
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
276
391
|
if (hasParameter('norestore')) {
|
|
277
392
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -286,14 +401,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
286
401
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
287
402
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
288
403
|
}
|
|
404
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
289
405
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
406
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
290
407
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
408
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
291
409
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
410
|
+
// Certificate management
|
|
292
411
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
293
412
|
try {
|
|
294
413
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
295
414
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
296
415
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
416
|
+
// Set the vendorId, vendorName, productId and productName if they are present in the pairing file
|
|
297
417
|
if (isValidNumber(pairingFileJson.vendorId))
|
|
298
418
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
299
419
|
if (isValidString(pairingFileJson.vendorName, 3))
|
|
@@ -302,16 +422,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
302
422
|
this.aggregatorProductId = VendorId(pairingFileJson.productId);
|
|
303
423
|
if (isValidString(pairingFileJson.productName, 3))
|
|
304
424
|
this.aggregatorProductName = pairingFileJson.productName;
|
|
425
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
305
426
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
306
427
|
this.passcode = pairingFileJson.passcode;
|
|
307
428
|
this.discriminator = pairingFileJson.discriminator;
|
|
308
429
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
309
430
|
}
|
|
431
|
+
// Set the certification if it is present in the pairing file
|
|
310
432
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
311
433
|
const hexStringToUint8Array = (hexString) => {
|
|
312
434
|
const matches = hexString.match(/.{1,2}/g);
|
|
313
435
|
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
314
436
|
};
|
|
437
|
+
// const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
|
|
438
|
+
// console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
|
|
315
439
|
this.certification = {
|
|
316
440
|
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
317
441
|
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
@@ -324,41 +448,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
324
448
|
catch (error) {
|
|
325
449
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
326
450
|
}
|
|
451
|
+
// Store the passcode, discriminator and port in the node context
|
|
327
452
|
await this.nodeContext.set('matterport', this.port);
|
|
328
453
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
329
454
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
330
455
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
456
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
331
457
|
if (hasParameter('logger')) {
|
|
332
458
|
const level = getParameter('logger');
|
|
333
459
|
if (level === 'debug') {
|
|
334
|
-
this.log.logLevel = "debug"
|
|
460
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
335
461
|
}
|
|
336
462
|
else if (level === 'info') {
|
|
337
|
-
this.log.logLevel = "info"
|
|
463
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
338
464
|
}
|
|
339
465
|
else if (level === 'notice') {
|
|
340
|
-
this.log.logLevel = "notice"
|
|
466
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
341
467
|
}
|
|
342
468
|
else if (level === 'warn') {
|
|
343
|
-
this.log.logLevel = "warn"
|
|
469
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
344
470
|
}
|
|
345
471
|
else if (level === 'error') {
|
|
346
|
-
this.log.logLevel = "error"
|
|
472
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
347
473
|
}
|
|
348
474
|
else if (level === 'fatal') {
|
|
349
|
-
this.log.logLevel = "fatal"
|
|
475
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
350
476
|
}
|
|
351
477
|
else {
|
|
352
478
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
353
|
-
this.log.logLevel = "info"
|
|
479
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
354
480
|
}
|
|
355
481
|
}
|
|
356
482
|
else {
|
|
357
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
483
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
358
484
|
}
|
|
359
485
|
this.frontend.logLevel = this.log.logLevel;
|
|
360
486
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
361
487
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
488
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
362
489
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
363
490
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
364
491
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -367,6 +494,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
367
494
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
368
495
|
if (this.profile !== undefined)
|
|
369
496
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
497
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
370
498
|
if (hasParameter('matterlogger')) {
|
|
371
499
|
const level = getParameter('matterlogger');
|
|
372
500
|
if (level === 'debug') {
|
|
@@ -398,6 +526,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
398
526
|
Logger.format = MatterLogFormat.ANSI;
|
|
399
527
|
Logger.setLogger('default', this.createMatterLogger());
|
|
400
528
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
529
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
401
530
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
402
531
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
403
532
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -406,6 +535,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
406
535
|
});
|
|
407
536
|
}
|
|
408
537
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
538
|
+
// Log network interfaces
|
|
409
539
|
const networkInterfaces = os.networkInterfaces();
|
|
410
540
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
411
541
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -417,6 +547,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
417
547
|
});
|
|
418
548
|
}
|
|
419
549
|
}
|
|
550
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
420
551
|
if (hasParameter('mdnsinterface')) {
|
|
421
552
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
422
553
|
}
|
|
@@ -425,6 +556,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
425
556
|
if (this.mdnsInterface === '')
|
|
426
557
|
this.mdnsInterface = undefined;
|
|
427
558
|
}
|
|
559
|
+
// Validate mdnsInterface
|
|
428
560
|
if (this.mdnsInterface) {
|
|
429
561
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
430
562
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -437,6 +569,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
437
569
|
}
|
|
438
570
|
if (this.mdnsInterface)
|
|
439
571
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
572
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
440
573
|
if (hasParameter('ipv4address')) {
|
|
441
574
|
this.ipv4address = getParameter('ipv4address');
|
|
442
575
|
}
|
|
@@ -445,6 +578,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
445
578
|
if (this.ipv4address === '')
|
|
446
579
|
this.ipv4address = undefined;
|
|
447
580
|
}
|
|
581
|
+
// Validate ipv4address
|
|
448
582
|
if (this.ipv4address) {
|
|
449
583
|
let isValid = false;
|
|
450
584
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -460,6 +594,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
460
594
|
await this.nodeContext.remove('matteripv4address');
|
|
461
595
|
}
|
|
462
596
|
}
|
|
597
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
463
598
|
if (hasParameter('ipv6address')) {
|
|
464
599
|
this.ipv6address = getParameter('ipv6address');
|
|
465
600
|
}
|
|
@@ -468,6 +603,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
468
603
|
if (this.ipv6address === '')
|
|
469
604
|
this.ipv6address = undefined;
|
|
470
605
|
}
|
|
606
|
+
// Validate ipv6address
|
|
471
607
|
if (this.ipv6address) {
|
|
472
608
|
let isValid = false;
|
|
473
609
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -488,6 +624,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
488
624
|
await this.nodeContext.remove('matteripv6address');
|
|
489
625
|
}
|
|
490
626
|
}
|
|
627
|
+
// Initialize the virtual mode
|
|
491
628
|
if (hasParameter('novirtual')) {
|
|
492
629
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
493
630
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -496,14 +633,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
496
633
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
497
634
|
}
|
|
498
635
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
636
|
+
// Initialize PluginManager
|
|
499
637
|
this.plugins = new PluginManager(this);
|
|
500
638
|
await this.plugins.loadFromStorage();
|
|
501
639
|
this.plugins.logLevel = this.log.logLevel;
|
|
640
|
+
// Initialize DeviceManager
|
|
502
641
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
503
642
|
this.devices.logLevel = this.log.logLevel;
|
|
643
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
504
644
|
for (const plugin of this.plugins) {
|
|
505
645
|
const packageJson = await this.plugins.parse(plugin);
|
|
506
646
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
647
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
648
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
507
649
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
508
650
|
try {
|
|
509
651
|
await spawn.spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -525,6 +667,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
525
667
|
await plugin.nodeContext.set('description', plugin.description);
|
|
526
668
|
await plugin.nodeContext.set('author', plugin.author);
|
|
527
669
|
}
|
|
670
|
+
// Log system info and create .matterbridge directory
|
|
528
671
|
await this.logNodeAndSystemInfo();
|
|
529
672
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
530
673
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -532,6 +675,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
532
675
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
533
676
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
534
677
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
678
|
+
// Check node version and throw error
|
|
535
679
|
const minNodeVersion = 18;
|
|
536
680
|
const nodeVersion = process.versions.node;
|
|
537
681
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -539,10 +683,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
539
683
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
540
684
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
541
685
|
}
|
|
686
|
+
// Parse command line
|
|
542
687
|
await this.parseCommandLine();
|
|
688
|
+
// Emit the initialize_completed event
|
|
543
689
|
this.emit('initialize_completed');
|
|
544
690
|
this.initialized = true;
|
|
545
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
694
|
+
* @private
|
|
695
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
696
|
+
*/
|
|
546
697
|
async parseCommandLine() {
|
|
547
698
|
if (hasParameter('help')) {
|
|
548
699
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -664,6 +815,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
664
815
|
this.shutdown = true;
|
|
665
816
|
return;
|
|
666
817
|
}
|
|
818
|
+
// Start the matter storage and create the matterbridge context
|
|
667
819
|
try {
|
|
668
820
|
await this.startMatterStorage();
|
|
669
821
|
}
|
|
@@ -671,12 +823,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
671
823
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
672
824
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
673
825
|
}
|
|
826
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
674
827
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
675
828
|
this.initialized = true;
|
|
676
829
|
await this.shutdownProcessAndReset();
|
|
677
830
|
this.shutdown = true;
|
|
678
831
|
return;
|
|
679
832
|
}
|
|
833
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
680
834
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
681
835
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
682
836
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -701,30 +855,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
701
855
|
this.shutdown = true;
|
|
702
856
|
return;
|
|
703
857
|
}
|
|
858
|
+
// Initialize frontend
|
|
704
859
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
705
860
|
await this.frontend.start(getIntParameter('frontend'));
|
|
861
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
706
862
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
707
863
|
const { checkUpdates } = await import('./update.js');
|
|
708
864
|
checkUpdates(this);
|
|
709
865
|
}, 30 * 1000).unref();
|
|
866
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
710
867
|
this.checkUpdateInterval = setInterval(async () => {
|
|
711
868
|
const { checkUpdates } = await import('./update.js');
|
|
712
869
|
checkUpdates(this);
|
|
713
870
|
}, 12 * 60 * 60 * 1000).unref();
|
|
871
|
+
// Start the matterbridge in mode test
|
|
714
872
|
if (hasParameter('test')) {
|
|
715
873
|
this.bridgeMode = 'bridge';
|
|
716
874
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
717
875
|
return;
|
|
718
876
|
}
|
|
877
|
+
// Start the matterbridge in mode controller
|
|
719
878
|
if (hasParameter('controller')) {
|
|
720
879
|
this.bridgeMode = 'controller';
|
|
721
880
|
await this.startController();
|
|
722
881
|
return;
|
|
723
882
|
}
|
|
883
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
724
884
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
725
885
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
726
886
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
727
887
|
}
|
|
888
|
+
// Start matterbridge in bridge mode
|
|
728
889
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
729
890
|
this.bridgeMode = 'bridge';
|
|
730
891
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -732,6 +893,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
732
893
|
await this.startBridge();
|
|
733
894
|
return;
|
|
734
895
|
}
|
|
896
|
+
// Start matterbridge in childbridge mode
|
|
735
897
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
736
898
|
this.bridgeMode = 'childbridge';
|
|
737
899
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -740,10 +902,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
740
902
|
return;
|
|
741
903
|
}
|
|
742
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* Asynchronously loads and starts the registered plugins.
|
|
907
|
+
*
|
|
908
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
909
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
910
|
+
*
|
|
911
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
912
|
+
*/
|
|
743
913
|
async startPlugins() {
|
|
914
|
+
// Check, load and start the plugins
|
|
744
915
|
for (const plugin of this.plugins) {
|
|
745
916
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
746
917
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
918
|
+
// Check if the plugin is available
|
|
747
919
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
748
920
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
749
921
|
plugin.enabled = false;
|
|
@@ -763,10 +935,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
763
935
|
plugin.addedDevices = undefined;
|
|
764
936
|
plugin.qrPairingCode = undefined;
|
|
765
937
|
plugin.manualPairingCode = undefined;
|
|
766
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
938
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
767
939
|
}
|
|
768
940
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
769
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
944
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
945
|
+
*/
|
|
770
946
|
registerProcessHandlers() {
|
|
771
947
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
772
948
|
process.removeAllListeners('uncaughtException');
|
|
@@ -793,6 +969,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
793
969
|
};
|
|
794
970
|
process.on('SIGTERM', this.sigtermHandler);
|
|
795
971
|
}
|
|
972
|
+
/**
|
|
973
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
974
|
+
*/
|
|
796
975
|
deregisterProcessHandlers() {
|
|
797
976
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
798
977
|
if (this.exceptionHandler)
|
|
@@ -809,12 +988,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
809
988
|
process.off('SIGTERM', this.sigtermHandler);
|
|
810
989
|
this.sigtermHandler = undefined;
|
|
811
990
|
}
|
|
991
|
+
/**
|
|
992
|
+
* Logs the node and system information.
|
|
993
|
+
*/
|
|
812
994
|
async logNodeAndSystemInfo() {
|
|
995
|
+
// IP address information
|
|
813
996
|
const networkInterfaces = os.networkInterfaces();
|
|
814
997
|
this.systemInformation.interfaceName = '';
|
|
815
998
|
this.systemInformation.ipv4Address = '';
|
|
816
999
|
this.systemInformation.ipv6Address = '';
|
|
817
1000
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
1001
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
818
1002
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
819
1003
|
continue;
|
|
820
1004
|
if (!interfaceDetails) {
|
|
@@ -840,19 +1024,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
840
1024
|
break;
|
|
841
1025
|
}
|
|
842
1026
|
}
|
|
1027
|
+
// Node information
|
|
843
1028
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
844
1029
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
845
1030
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
846
1031
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1032
|
+
// Host system information
|
|
847
1033
|
this.systemInformation.hostname = os.hostname();
|
|
848
1034
|
this.systemInformation.user = os.userInfo().username;
|
|
849
|
-
this.systemInformation.osType = os.type();
|
|
850
|
-
this.systemInformation.osRelease = os.release();
|
|
851
|
-
this.systemInformation.osPlatform = os.platform();
|
|
852
|
-
this.systemInformation.osArch = os.arch();
|
|
853
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
854
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
855
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1035
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1036
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1037
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1038
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1039
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1040
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1041
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1042
|
+
// Log the system information
|
|
856
1043
|
this.log.debug('Host System Information:');
|
|
857
1044
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
858
1045
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -868,14 +1055,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
868
1055
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
869
1056
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
870
1057
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1058
|
+
// Log directories
|
|
871
1059
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
872
1060
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
873
1061
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
874
1062
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
875
1063
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1064
|
+
// Global node_modules directory
|
|
876
1065
|
if (this.nodeContext)
|
|
877
1066
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
878
1067
|
if (this.globalModulesDirectory === '') {
|
|
1068
|
+
// First run of Matterbridge so the node storage is empty
|
|
879
1069
|
try {
|
|
880
1070
|
this.execRunningCount++;
|
|
881
1071
|
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
|
|
@@ -889,53 +1079,84 @@ export class Matterbridge extends EventEmitter {
|
|
|
889
1079
|
}
|
|
890
1080
|
else
|
|
891
1081
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1082
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
|
|
1083
|
+
else {
|
|
1084
|
+
this.getGlobalNodeModules()
|
|
1085
|
+
.then(async (globalModulesDirectory) => {
|
|
1086
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
1087
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1088
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1089
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1090
|
+
})
|
|
1091
|
+
.catch((error) => {
|
|
1092
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1093
|
+
});
|
|
1094
|
+
}*/
|
|
1095
|
+
// Matterbridge version
|
|
892
1096
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
893
1097
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
894
1098
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
895
1099
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1100
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
896
1101
|
if (this.nodeContext)
|
|
897
1102
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
898
1103
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1104
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
899
1105
|
if (this.nodeContext)
|
|
900
1106
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
901
1107
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1108
|
+
// Current working directory
|
|
902
1109
|
const currentDir = process.cwd();
|
|
903
1110
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1111
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
904
1112
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
905
1113
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
906
1114
|
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1117
|
+
*
|
|
1118
|
+
* @returns {Function} The MatterLogger function.
|
|
1119
|
+
*/
|
|
907
1120
|
createMatterLogger() {
|
|
908
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1121
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
909
1122
|
return (level, formattedLog) => {
|
|
910
1123
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
911
1124
|
const message = formattedLog.slice(65);
|
|
912
1125
|
matterLogger.logName = logger;
|
|
913
1126
|
switch (level) {
|
|
914
1127
|
case MatterLogLevel.DEBUG:
|
|
915
|
-
matterLogger.log("debug"
|
|
1128
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
916
1129
|
break;
|
|
917
1130
|
case MatterLogLevel.INFO:
|
|
918
|
-
matterLogger.log("info"
|
|
1131
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
919
1132
|
break;
|
|
920
1133
|
case MatterLogLevel.NOTICE:
|
|
921
|
-
matterLogger.log("notice"
|
|
1134
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
922
1135
|
break;
|
|
923
1136
|
case MatterLogLevel.WARN:
|
|
924
|
-
matterLogger.log("warn"
|
|
1137
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
925
1138
|
break;
|
|
926
1139
|
case MatterLogLevel.ERROR:
|
|
927
|
-
matterLogger.log("error"
|
|
1140
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
928
1141
|
break;
|
|
929
1142
|
case MatterLogLevel.FATAL:
|
|
930
|
-
matterLogger.log("fatal"
|
|
1143
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
931
1144
|
break;
|
|
932
1145
|
default:
|
|
933
|
-
matterLogger.log("debug"
|
|
1146
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
934
1147
|
break;
|
|
935
1148
|
}
|
|
936
1149
|
};
|
|
937
1150
|
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Creates a Matter File Logger.
|
|
1153
|
+
*
|
|
1154
|
+
* @param {string} filePath - The path to the log file.
|
|
1155
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1156
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1157
|
+
*/
|
|
938
1158
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1159
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
939
1160
|
let fileSize = 0;
|
|
940
1161
|
if (unlink) {
|
|
941
1162
|
try {
|
|
@@ -984,12 +1205,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
1205
|
}
|
|
985
1206
|
};
|
|
986
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1210
|
+
*/
|
|
987
1211
|
async restartProcess() {
|
|
988
1212
|
await this.cleanup('restarting...', true);
|
|
989
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Shut down the process by exiting the current process.
|
|
1216
|
+
*/
|
|
990
1217
|
async shutdownProcess() {
|
|
991
1218
|
await this.cleanup('shutting down...', false);
|
|
992
1219
|
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Update matterbridge and and shut down the process.
|
|
1222
|
+
*/
|
|
993
1223
|
async updateProcess() {
|
|
994
1224
|
this.log.info('Updating matterbridge...');
|
|
995
1225
|
try {
|
|
@@ -1002,52 +1232,73 @@ export class Matterbridge extends EventEmitter {
|
|
|
1002
1232
|
this.frontend.wssSendRestartRequired();
|
|
1003
1233
|
await this.cleanup('updating...', false);
|
|
1004
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Unregister all devices and shut down the process.
|
|
1237
|
+
*/
|
|
1005
1238
|
async unregisterAndShutdownProcess() {
|
|
1006
1239
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1007
1240
|
for (const plugin of this.plugins) {
|
|
1008
1241
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1009
1242
|
}
|
|
1010
1243
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1011
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1244
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1012
1245
|
this.log.debug('Cleaning up and shutting down...');
|
|
1013
1246
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1014
1247
|
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Reset commissioning and shut down the process.
|
|
1250
|
+
*/
|
|
1015
1251
|
async shutdownProcessAndReset() {
|
|
1016
1252
|
await this.cleanup('shutting down with reset...', false);
|
|
1017
1253
|
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Factory reset and shut down the process.
|
|
1256
|
+
*/
|
|
1018
1257
|
async shutdownProcessAndFactoryReset() {
|
|
1019
1258
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1020
1259
|
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Cleans up the Matterbridge instance.
|
|
1262
|
+
* @param {string} message - The cleanup message.
|
|
1263
|
+
* @param {boolean} [restart=false] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1264
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1265
|
+
*/
|
|
1021
1266
|
async cleanup(message, restart = false) {
|
|
1022
1267
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1023
1268
|
this.emit('cleanup_started');
|
|
1024
1269
|
this.hasCleanupStarted = true;
|
|
1025
1270
|
this.log.info(message);
|
|
1271
|
+
// Clear the start matter interval
|
|
1026
1272
|
if (this.startMatterInterval) {
|
|
1027
1273
|
clearInterval(this.startMatterInterval);
|
|
1028
1274
|
this.startMatterInterval = undefined;
|
|
1029
1275
|
this.log.debug('Start matter interval cleared');
|
|
1030
1276
|
}
|
|
1277
|
+
// Clear the check update timeout
|
|
1031
1278
|
if (this.checkUpdateTimeout) {
|
|
1032
1279
|
clearInterval(this.checkUpdateTimeout);
|
|
1033
1280
|
this.checkUpdateTimeout = undefined;
|
|
1034
1281
|
this.log.debug('Check update timeout cleared');
|
|
1035
1282
|
}
|
|
1283
|
+
// Clear the check update interval
|
|
1036
1284
|
if (this.checkUpdateInterval) {
|
|
1037
1285
|
clearInterval(this.checkUpdateInterval);
|
|
1038
1286
|
this.checkUpdateInterval = undefined;
|
|
1039
1287
|
this.log.debug('Check update interval cleared');
|
|
1040
1288
|
}
|
|
1289
|
+
// Clear the configure timeout
|
|
1041
1290
|
if (this.configureTimeout) {
|
|
1042
1291
|
clearTimeout(this.configureTimeout);
|
|
1043
1292
|
this.configureTimeout = undefined;
|
|
1044
1293
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1045
1294
|
}
|
|
1295
|
+
// Clear the reachability timeout
|
|
1046
1296
|
if (this.reachabilityTimeout) {
|
|
1047
1297
|
clearTimeout(this.reachabilityTimeout);
|
|
1048
1298
|
this.reachabilityTimeout = undefined;
|
|
1049
1299
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1050
1300
|
}
|
|
1301
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1051
1302
|
for (const plugin of this.plugins) {
|
|
1052
1303
|
if (!plugin.enabled || plugin.error)
|
|
1053
1304
|
continue;
|
|
@@ -1058,9 +1309,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1058
1309
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1059
1310
|
}
|
|
1060
1311
|
}
|
|
1312
|
+
// Stop matter server nodes
|
|
1061
1313
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1062
1314
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1063
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1315
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1064
1316
|
if (this.bridgeMode === 'bridge') {
|
|
1065
1317
|
if (this.serverNode) {
|
|
1066
1318
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1076,6 +1328,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1076
1328
|
}
|
|
1077
1329
|
}
|
|
1078
1330
|
this.log.notice('Stopped matter server nodes');
|
|
1331
|
+
// Matter commisioning reset
|
|
1079
1332
|
if (message === 'shutting down with reset...') {
|
|
1080
1333
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1081
1334
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1085,18 +1338,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1085
1338
|
await this.matterbridgeContext?.clearAll();
|
|
1086
1339
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1087
1340
|
}
|
|
1341
|
+
// Stop matter storage
|
|
1088
1342
|
await this.stopMatterStorage();
|
|
1343
|
+
// Stop the frontend
|
|
1089
1344
|
await this.frontend.stop();
|
|
1345
|
+
// Remove the matterfilelogger
|
|
1090
1346
|
try {
|
|
1091
1347
|
Logger.removeLogger('matterfilelogger');
|
|
1092
1348
|
}
|
|
1093
1349
|
catch (error) {
|
|
1094
1350
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1095
1351
|
}
|
|
1352
|
+
// Close the matterbridge node storage and context
|
|
1096
1353
|
if (this.nodeStorage && this.nodeContext) {
|
|
1354
|
+
/*
|
|
1355
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1356
|
+
this.log.info('Saving registered devices...');
|
|
1357
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1358
|
+
this.devices.forEach(async (device) => {
|
|
1359
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1360
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1361
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1362
|
+
});
|
|
1363
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1364
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1365
|
+
*/
|
|
1366
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1097
1367
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1098
1368
|
await this.nodeContext.close();
|
|
1099
1369
|
this.nodeContext = undefined;
|
|
1370
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1100
1371
|
for (const plugin of this.plugins) {
|
|
1101
1372
|
if (plugin.nodeContext) {
|
|
1102
1373
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1113,8 +1384,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1113
1384
|
}
|
|
1114
1385
|
this.plugins.clear();
|
|
1115
1386
|
this.devices.clear();
|
|
1387
|
+
// Factory reset
|
|
1116
1388
|
if (message === 'shutting down with factory reset...') {
|
|
1117
1389
|
try {
|
|
1390
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1118
1391
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1119
1392
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1120
1393
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1128,6 +1401,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1128
1401
|
}
|
|
1129
1402
|
}
|
|
1130
1403
|
try {
|
|
1404
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1131
1405
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1132
1406
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1133
1407
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1142,12 +1416,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1142
1416
|
}
|
|
1143
1417
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1144
1418
|
}
|
|
1419
|
+
// Deregisters the process handlers
|
|
1145
1420
|
this.deregisterProcessHandlers();
|
|
1146
1421
|
if (restart) {
|
|
1147
1422
|
if (message === 'updating...') {
|
|
1148
1423
|
this.log.info('Cleanup completed. Updating...');
|
|
1149
1424
|
Matterbridge.instance = undefined;
|
|
1150
|
-
this.emit('update');
|
|
1425
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1151
1426
|
}
|
|
1152
1427
|
else if (message === 'restarting...') {
|
|
1153
1428
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1168,6 +1443,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1168
1443
|
this.log.debug('Cleanup already started...');
|
|
1169
1444
|
}
|
|
1170
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1448
|
+
*
|
|
1449
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1450
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1451
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1452
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1453
|
+
*/
|
|
1171
1454
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1172
1455
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1173
1456
|
plugin.locked = true;
|
|
@@ -1181,6 +1464,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1181
1464
|
await this.startServerNode(plugin.serverNode);
|
|
1182
1465
|
}
|
|
1183
1466
|
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1469
|
+
*
|
|
1470
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1471
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1472
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1473
|
+
*/
|
|
1184
1474
|
async createDynamicPlugin(plugin, start = false) {
|
|
1185
1475
|
if (!plugin.locked) {
|
|
1186
1476
|
plugin.locked = true;
|
|
@@ -1193,7 +1483,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1193
1483
|
await this.startServerNode(plugin.serverNode);
|
|
1194
1484
|
}
|
|
1195
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Starts the Matterbridge in bridge mode.
|
|
1488
|
+
* @private
|
|
1489
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1490
|
+
*/
|
|
1196
1491
|
async startBridge() {
|
|
1492
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1197
1493
|
if (!this.matterStorageManager)
|
|
1198
1494
|
throw new Error('No storage manager initialized');
|
|
1199
1495
|
if (!this.matterbridgeContext)
|
|
@@ -1232,7 +1528,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1232
1528
|
clearInterval(this.startMatterInterval);
|
|
1233
1529
|
this.startMatterInterval = undefined;
|
|
1234
1530
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1531
|
+
// Start the Matter server node
|
|
1235
1532
|
this.startServerNode(this.serverNode);
|
|
1533
|
+
// Configure the plugins
|
|
1236
1534
|
this.configureTimeout = setTimeout(async () => {
|
|
1237
1535
|
for (const plugin of this.plugins) {
|
|
1238
1536
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1250,6 +1548,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1250
1548
|
}
|
|
1251
1549
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1252
1550
|
}, 30 * 1000);
|
|
1551
|
+
// Setting reachability to true
|
|
1253
1552
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1254
1553
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1255
1554
|
if (this.aggregatorNode)
|
|
@@ -1258,6 +1557,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1258
1557
|
}, 60 * 1000);
|
|
1259
1558
|
}, 1000);
|
|
1260
1559
|
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1562
|
+
* @private
|
|
1563
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1564
|
+
*/
|
|
1261
1565
|
async startChildbridge() {
|
|
1262
1566
|
if (!this.matterStorageManager)
|
|
1263
1567
|
throw new Error('No storage manager initialized');
|
|
@@ -1295,6 +1599,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1295
1599
|
clearInterval(this.startMatterInterval);
|
|
1296
1600
|
this.startMatterInterval = undefined;
|
|
1297
1601
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1602
|
+
// Configure the plugins
|
|
1298
1603
|
this.configureTimeout = setTimeout(async () => {
|
|
1299
1604
|
for (const plugin of this.plugins) {
|
|
1300
1605
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1331,7 +1636,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1331
1636
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1332
1637
|
continue;
|
|
1333
1638
|
}
|
|
1639
|
+
// Start the Matter server node
|
|
1334
1640
|
this.startServerNode(plugin.serverNode);
|
|
1641
|
+
// Setting reachability to true
|
|
1335
1642
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1336
1643
|
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}`);
|
|
1337
1644
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1341,9 +1648,227 @@ export class Matterbridge extends EventEmitter {
|
|
|
1341
1648
|
}
|
|
1342
1649
|
}, 1000);
|
|
1343
1650
|
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Starts the Matterbridge controller.
|
|
1653
|
+
* @private
|
|
1654
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1655
|
+
*/
|
|
1344
1656
|
async startController() {
|
|
1657
|
+
/*
|
|
1658
|
+
if (!this.matterStorageManager) {
|
|
1659
|
+
this.log.error('No storage manager initialized');
|
|
1660
|
+
await this.cleanup('No storage manager initialized');
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1664
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1665
|
+
if (!this.controllerContext) {
|
|
1666
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1667
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1672
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1673
|
+
this.log.info('Creating matter commissioning controller');
|
|
1674
|
+
this.commissioningController = new CommissioningController({
|
|
1675
|
+
autoConnect: false,
|
|
1676
|
+
});
|
|
1677
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1678
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1679
|
+
|
|
1680
|
+
this.log.info('Starting matter server');
|
|
1681
|
+
await this.matterServer.start();
|
|
1682
|
+
this.log.info('Matter server started');
|
|
1683
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1684
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1685
|
+
regulatoryCountryCode: 'XX',
|
|
1686
|
+
};
|
|
1687
|
+
const commissioningController = new CommissioningController({
|
|
1688
|
+
environment: {
|
|
1689
|
+
environment,
|
|
1690
|
+
id: uniqueId,
|
|
1691
|
+
},
|
|
1692
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1693
|
+
adminFabricLabel,
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
if (hasParameter('pairingcode')) {
|
|
1697
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1698
|
+
const pairingCode = getParameter('pairingcode');
|
|
1699
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1700
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1701
|
+
|
|
1702
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1703
|
+
if (pairingCode !== undefined) {
|
|
1704
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1705
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1706
|
+
longDiscriminator = undefined;
|
|
1707
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1708
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1709
|
+
} else {
|
|
1710
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1711
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1712
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1713
|
+
}
|
|
1714
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1715
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
const options = {
|
|
1719
|
+
commissioning: commissioningOptions,
|
|
1720
|
+
discovery: {
|
|
1721
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1722
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1723
|
+
},
|
|
1724
|
+
passcode: setupPin,
|
|
1725
|
+
} as NodeCommissioningOptions;
|
|
1726
|
+
this.log.info('Commissioning with options:', options);
|
|
1727
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1728
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1729
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1730
|
+
} // (hasParameter('pairingcode'))
|
|
1731
|
+
|
|
1732
|
+
if (hasParameter('unpairall')) {
|
|
1733
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1734
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1735
|
+
for (const nodeId of nodeIds) {
|
|
1736
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1737
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1738
|
+
}
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (hasParameter('discover')) {
|
|
1743
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1744
|
+
// console.log(discover);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1748
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1753
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1754
|
+
for (const nodeId of nodeIds) {
|
|
1755
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1756
|
+
|
|
1757
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1758
|
+
autoSubscribe: false,
|
|
1759
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1760
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1761
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1762
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1763
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1764
|
+
switch (info) {
|
|
1765
|
+
case NodeStateInformation.Connected:
|
|
1766
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1767
|
+
break;
|
|
1768
|
+
case NodeStateInformation.Disconnected:
|
|
1769
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1770
|
+
break;
|
|
1771
|
+
case NodeStateInformation.Reconnecting:
|
|
1772
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1773
|
+
break;
|
|
1774
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1775
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1776
|
+
break;
|
|
1777
|
+
case NodeStateInformation.StructureChanged:
|
|
1778
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1779
|
+
break;
|
|
1780
|
+
case NodeStateInformation.Decommissioned:
|
|
1781
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1782
|
+
break;
|
|
1783
|
+
default:
|
|
1784
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1785
|
+
break;
|
|
1786
|
+
}
|
|
1787
|
+
},
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
node.logStructure();
|
|
1791
|
+
|
|
1792
|
+
// Get the interaction client
|
|
1793
|
+
this.log.info('Getting the interaction client');
|
|
1794
|
+
const interactionClient = await node.getInteractionClient();
|
|
1795
|
+
let cluster;
|
|
1796
|
+
let attributes;
|
|
1797
|
+
|
|
1798
|
+
// Log BasicInformationCluster
|
|
1799
|
+
cluster = BasicInformationCluster;
|
|
1800
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1801
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1802
|
+
});
|
|
1803
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1804
|
+
attributes.forEach((attribute) => {
|
|
1805
|
+
this.log.info(
|
|
1806
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1807
|
+
);
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
// Log PowerSourceCluster
|
|
1811
|
+
cluster = PowerSourceCluster;
|
|
1812
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1813
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1814
|
+
});
|
|
1815
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1816
|
+
attributes.forEach((attribute) => {
|
|
1817
|
+
this.log.info(
|
|
1818
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1819
|
+
);
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
// Log ThreadNetworkDiagnostics
|
|
1823
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1824
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1825
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1826
|
+
});
|
|
1827
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1828
|
+
attributes.forEach((attribute) => {
|
|
1829
|
+
this.log.info(
|
|
1830
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1831
|
+
);
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
// Log SwitchCluster
|
|
1835
|
+
cluster = SwitchCluster;
|
|
1836
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1837
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1838
|
+
});
|
|
1839
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1840
|
+
attributes.forEach((attribute) => {
|
|
1841
|
+
this.log.info(
|
|
1842
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1843
|
+
);
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1847
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1848
|
+
ignoreInitialTriggers: false,
|
|
1849
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1850
|
+
this.log.info(
|
|
1851
|
+
`***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
|
|
1852
|
+
),
|
|
1853
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1854
|
+
this.log.info(
|
|
1855
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1856
|
+
);
|
|
1857
|
+
},
|
|
1858
|
+
});
|
|
1859
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1860
|
+
}
|
|
1861
|
+
*/
|
|
1345
1862
|
}
|
|
1863
|
+
/** ***********************************************************************************************************************************/
|
|
1864
|
+
/** Matter.js methods */
|
|
1865
|
+
/** ***********************************************************************************************************************************/
|
|
1866
|
+
/**
|
|
1867
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1868
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1869
|
+
*/
|
|
1346
1870
|
async startMatterStorage() {
|
|
1871
|
+
// Setup Matter storage
|
|
1347
1872
|
this.log.info(`Starting matter node storage...`);
|
|
1348
1873
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1349
1874
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1352,8 +1877,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1352
1877
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1353
1878
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1354
1879
|
this.log.info('Matter node storage started');
|
|
1880
|
+
// Backup matter storage since it is created/opened correctly
|
|
1355
1881
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1356
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1885
|
+
*
|
|
1886
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1887
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1888
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1889
|
+
*/
|
|
1357
1890
|
async backupMatterStorage(storageName, backupName) {
|
|
1358
1891
|
this.log.info('Creating matter node storage backup...');
|
|
1359
1892
|
try {
|
|
@@ -1364,6 +1897,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1364
1897
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1365
1898
|
}
|
|
1366
1899
|
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Stops the matter storage.
|
|
1902
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1903
|
+
*/
|
|
1367
1904
|
async stopMatterStorage() {
|
|
1368
1905
|
this.log.info('Closing matter node storage...');
|
|
1369
1906
|
await this.matterStorageManager?.close();
|
|
@@ -1372,6 +1909,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1372
1909
|
this.matterbridgeContext = undefined;
|
|
1373
1910
|
this.log.info('Matter node storage closed');
|
|
1374
1911
|
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Creates a server node storage context.
|
|
1914
|
+
*
|
|
1915
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1916
|
+
* @param {string} deviceName - The name of the device.
|
|
1917
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1918
|
+
* @param {number} vendorId - The vendor ID.
|
|
1919
|
+
* @param {string} vendorName - The vendor name.
|
|
1920
|
+
* @param {number} productId - The product ID.
|
|
1921
|
+
* @param {string} productName - The product name.
|
|
1922
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1923
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1924
|
+
*/
|
|
1375
1925
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1376
1926
|
const { randomBytes } = await import('node:crypto');
|
|
1377
1927
|
if (!this.matterStorageService)
|
|
@@ -1405,6 +1955,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1405
1955
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1406
1956
|
return storageContext;
|
|
1407
1957
|
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Creates a server node.
|
|
1960
|
+
*
|
|
1961
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1962
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1963
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1964
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1965
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1966
|
+
*/
|
|
1408
1967
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1409
1968
|
const storeId = await storageContext.get('storeId');
|
|
1410
1969
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1414,24 +1973,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1414
1973
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1415
1974
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1416
1975
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1976
|
+
/**
|
|
1977
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1978
|
+
*/
|
|
1417
1979
|
const serverNode = await ServerNode.create({
|
|
1980
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1418
1981
|
id: storeId,
|
|
1982
|
+
// Provide Network relevant configuration like the port
|
|
1983
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1419
1984
|
network: {
|
|
1420
1985
|
listeningAddressIpv4: this.ipv4address,
|
|
1421
1986
|
listeningAddressIpv6: this.ipv6address,
|
|
1422
1987
|
port,
|
|
1423
1988
|
},
|
|
1989
|
+
// Provide the certificate for the device
|
|
1424
1990
|
operationalCredentials: {
|
|
1425
1991
|
certification: this.certification,
|
|
1426
1992
|
},
|
|
1993
|
+
// Provide Commissioning relevant settings
|
|
1994
|
+
// Optional for development/testing purposes
|
|
1427
1995
|
commissioning: {
|
|
1428
1996
|
passcode,
|
|
1429
1997
|
discriminator,
|
|
1430
1998
|
},
|
|
1999
|
+
// Provide Node announcement settings
|
|
2000
|
+
// Optional: If Ommitted some development defaults are used
|
|
1431
2001
|
productDescription: {
|
|
1432
2002
|
name: await storageContext.get('deviceName'),
|
|
1433
2003
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1434
2004
|
},
|
|
2005
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2006
|
+
// Optional: If Omitted some development defaults are used
|
|
1435
2007
|
basicInformation: {
|
|
1436
2008
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1437
2009
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1449,12 +2021,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1449
2021
|
},
|
|
1450
2022
|
});
|
|
1451
2023
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2024
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1452
2025
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1453
2026
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1454
2027
|
if (this.bridgeMode === 'bridge') {
|
|
1455
2028
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1456
2029
|
if (resetSessions)
|
|
1457
|
-
this.matterbridgeSessionInformations = undefined;
|
|
2030
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1458
2031
|
this.matterbridgePaired = true;
|
|
1459
2032
|
}
|
|
1460
2033
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1462,16 +2035,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1462
2035
|
if (plugin) {
|
|
1463
2036
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1464
2037
|
if (resetSessions)
|
|
1465
|
-
plugin.sessionInformations = undefined;
|
|
2038
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1466
2039
|
plugin.paired = true;
|
|
1467
2040
|
}
|
|
1468
2041
|
}
|
|
1469
2042
|
};
|
|
2043
|
+
/**
|
|
2044
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2045
|
+
* This means: It is added to the first fabric.
|
|
2046
|
+
*/
|
|
1470
2047
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1471
2048
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1472
2049
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1473
2050
|
});
|
|
2051
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1474
2052
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2053
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1475
2054
|
serverNode.lifecycle.online.on(async () => {
|
|
1476
2055
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1477
2056
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1510,6 +2089,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1510
2089
|
}
|
|
1511
2090
|
}
|
|
1512
2091
|
}
|
|
2092
|
+
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
1513
2093
|
this.startEndAdvertiseTimer(serverNode);
|
|
1514
2094
|
}
|
|
1515
2095
|
else {
|
|
@@ -1521,6 +2101,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1521
2101
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1522
2102
|
this.emit('online', storeId);
|
|
1523
2103
|
});
|
|
2104
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1524
2105
|
serverNode.lifecycle.offline.on(() => {
|
|
1525
2106
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1526
2107
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1545,6 +2126,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1545
2126
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1546
2127
|
this.emit('offline', storeId);
|
|
1547
2128
|
});
|
|
2129
|
+
/**
|
|
2130
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2131
|
+
* information is needed.
|
|
2132
|
+
*/
|
|
1548
2133
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1549
2134
|
let action = '';
|
|
1550
2135
|
switch (fabricAction) {
|
|
@@ -1578,16 +2163,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1578
2163
|
}
|
|
1579
2164
|
}
|
|
1580
2165
|
};
|
|
2166
|
+
/**
|
|
2167
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2168
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2169
|
+
*/
|
|
1581
2170
|
serverNode.events.sessions.opened.on((session) => {
|
|
1582
2171
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1583
2172
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1584
2173
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1585
2174
|
});
|
|
2175
|
+
/**
|
|
2176
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2177
|
+
*/
|
|
1586
2178
|
serverNode.events.sessions.closed.on((session) => {
|
|
1587
2179
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1588
2180
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1589
2181
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1590
2182
|
});
|
|
2183
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1591
2184
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1592
2185
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1593
2186
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1596,6 +2189,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1596
2189
|
this.log.info(`Created server node for ${storeId}`);
|
|
1597
2190
|
return serverNode;
|
|
1598
2191
|
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2194
|
+
*
|
|
2195
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2196
|
+
* @param {string} storeId - The store ID of the server node.
|
|
2197
|
+
*/
|
|
1599
2198
|
startEndAdvertiseTimer(matterServerNode) {
|
|
1600
2199
|
if (this.endAdvertiseTimeout) {
|
|
1601
2200
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -1624,12 +2223,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1624
2223
|
this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
|
|
1625
2224
|
}, 15 * 60 * 1000).unref();
|
|
1626
2225
|
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Starts the specified server node.
|
|
2228
|
+
*
|
|
2229
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2230
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2231
|
+
*/
|
|
1627
2232
|
async startServerNode(matterServerNode) {
|
|
1628
2233
|
if (!matterServerNode)
|
|
1629
2234
|
return;
|
|
1630
2235
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1631
2236
|
await matterServerNode.start();
|
|
1632
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Stops the specified server node.
|
|
2240
|
+
*
|
|
2241
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2242
|
+
* @param {number} [timeout=30000] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2243
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2244
|
+
*/
|
|
1633
2245
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1634
2246
|
if (!matterServerNode)
|
|
1635
2247
|
return;
|
|
@@ -1642,6 +2254,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
2254
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1643
2255
|
}
|
|
1644
2256
|
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Advertises the specified server node.
|
|
2259
|
+
*
|
|
2260
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2261
|
+
* @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.
|
|
2262
|
+
*/
|
|
1645
2263
|
async advertiseServerNode(matterServerNode) {
|
|
1646
2264
|
if (matterServerNode) {
|
|
1647
2265
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1650,25 +2268,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
1650
2268
|
return { qrPairingCode, manualPairingCode };
|
|
1651
2269
|
}
|
|
1652
2270
|
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Stop advertise the specified server node.
|
|
2273
|
+
*
|
|
2274
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2275
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2276
|
+
*/
|
|
1653
2277
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1654
2278
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1655
2279
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1656
2280
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1657
2281
|
}
|
|
1658
2282
|
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Creates an aggregator node with the specified storage context.
|
|
2285
|
+
*
|
|
2286
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2287
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2288
|
+
*/
|
|
1659
2289
|
async createAggregatorNode(storageContext) {
|
|
1660
2290
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1661
2291
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1662
2292
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1663
2293
|
return aggregatorNode;
|
|
1664
2294
|
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2297
|
+
*
|
|
2298
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2299
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2300
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2301
|
+
*/
|
|
1665
2302
|
async addBridgedEndpoint(pluginName, device) {
|
|
2303
|
+
// Check if the plugin is registered
|
|
1666
2304
|
const plugin = this.plugins.get(pluginName);
|
|
1667
2305
|
if (!plugin) {
|
|
1668
2306
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1669
2307
|
return;
|
|
1670
2308
|
}
|
|
1671
2309
|
if (this.bridgeMode === 'bridge') {
|
|
2310
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1672
2311
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1673
2312
|
if (!this.aggregatorNode) {
|
|
1674
2313
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1685,6 +2324,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1685
2324
|
}
|
|
1686
2325
|
}
|
|
1687
2326
|
else if (this.bridgeMode === 'childbridge') {
|
|
2327
|
+
// Register and add the device to the plugin server node
|
|
1688
2328
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1689
2329
|
try {
|
|
1690
2330
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1701,10 +2341,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1701
2341
|
return;
|
|
1702
2342
|
}
|
|
1703
2343
|
}
|
|
2344
|
+
// Register and add the device to the plugin aggregator node
|
|
1704
2345
|
if (plugin.type === 'DynamicPlatform') {
|
|
1705
2346
|
try {
|
|
1706
2347
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1707
2348
|
await this.createDynamicPlugin(plugin);
|
|
2349
|
+
// Fast plugins can add another device before the server node is created
|
|
1708
2350
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1709
2351
|
if (!plugin.aggregatorNode) {
|
|
1710
2352
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1724,17 +2366,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1724
2366
|
plugin.registeredDevices++;
|
|
1725
2367
|
if (plugin.addedDevices !== undefined)
|
|
1726
2368
|
plugin.addedDevices++;
|
|
2369
|
+
// Add the device to the DeviceManager
|
|
1727
2370
|
this.devices.set(device);
|
|
2371
|
+
// Subscribe to the reachable$Changed event
|
|
1728
2372
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1729
2373
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1730
2374
|
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2377
|
+
*
|
|
2378
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2379
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2380
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2381
|
+
*/
|
|
1731
2382
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1732
2383
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2384
|
+
// Check if the plugin is registered
|
|
1733
2385
|
const plugin = this.plugins.get(pluginName);
|
|
1734
2386
|
if (!plugin) {
|
|
1735
2387
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1736
2388
|
return;
|
|
1737
2389
|
}
|
|
2390
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1738
2391
|
if (this.bridgeMode === 'bridge') {
|
|
1739
2392
|
if (!this.aggregatorNode) {
|
|
1740
2393
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1749,6 +2402,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1749
2402
|
}
|
|
1750
2403
|
else if (this.bridgeMode === 'childbridge') {
|
|
1751
2404
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2405
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1752
2406
|
}
|
|
1753
2407
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1754
2408
|
if (!plugin.aggregatorNode) {
|
|
@@ -1763,8 +2417,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1763
2417
|
if (plugin.addedDevices !== undefined)
|
|
1764
2418
|
plugin.addedDevices--;
|
|
1765
2419
|
}
|
|
2420
|
+
// Remove the device from the DeviceManager
|
|
1766
2421
|
this.devices.remove(device);
|
|
1767
2422
|
}
|
|
2423
|
+
/**
|
|
2424
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2425
|
+
*
|
|
2426
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2427
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2428
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2429
|
+
*
|
|
2430
|
+
* @remarks
|
|
2431
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2432
|
+
* It also applies a delay between each removal if specified.
|
|
2433
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2434
|
+
*/
|
|
1768
2435
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1769
2436
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1770
2437
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1775,6 +2442,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1775
2442
|
if (delay > 0)
|
|
1776
2443
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1777
2444
|
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2447
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2448
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2449
|
+
*
|
|
2450
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2451
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2452
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2453
|
+
*/
|
|
1778
2454
|
async subscribeAttributeChanged(plugin, device) {
|
|
1779
2455
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1780
2456
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -1790,6 +2466,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1790
2466
|
});
|
|
1791
2467
|
}
|
|
1792
2468
|
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2471
|
+
*
|
|
2472
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2473
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2474
|
+
*/
|
|
1793
2475
|
sanitizeFabricInformations(fabricInfo) {
|
|
1794
2476
|
return fabricInfo.map((info) => {
|
|
1795
2477
|
return {
|
|
@@ -1803,6 +2485,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1803
2485
|
};
|
|
1804
2486
|
});
|
|
1805
2487
|
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2490
|
+
*
|
|
2491
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2492
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2493
|
+
*/
|
|
1806
2494
|
sanitizeSessionInformation(sessionInfo) {
|
|
1807
2495
|
return sessionInfo
|
|
1808
2496
|
.filter((session) => session.isPeerActive)
|
|
@@ -1830,7 +2518,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1830
2518
|
};
|
|
1831
2519
|
});
|
|
1832
2520
|
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2523
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2524
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2525
|
+
*/
|
|
2526
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1833
2527
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2528
|
+
/*
|
|
2529
|
+
for (const child of aggregatorNode.parts) {
|
|
2530
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2531
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2532
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2533
|
+
}
|
|
2534
|
+
*/
|
|
1834
2535
|
}
|
|
1835
2536
|
getVendorIdName = (vendorId) => {
|
|
1836
2537
|
if (!vendorId)
|
|
@@ -1874,3 +2575,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1874
2575
|
return vendorName;
|
|
1875
2576
|
};
|
|
1876
2577
|
}
|
|
2578
|
+
//# sourceMappingURL=matterbridge.js.map
|