matterbridge 3.1.0 → 3.1.1-dev-20250629-f14b886
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 +17 -4
- package/README-DEV.md +14 -2
- package/dist/cli.js +2 -91
- package/dist/clusters/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -24
- package/dist/deviceManager.js +1 -94
- package/dist/devices/export.js +0 -2
- package/dist/evse.js +9 -70
- package/dist/frontend.js +16 -413
- package/dist/globalMatterbridge.js +0 -47
- package/dist/helpers.js +0 -53
- package/dist/index.js +1 -32
- package/dist/laundryWasher.js +7 -92
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -3
- package/dist/matter/types.js +0 -3
- package/dist/matterbridge.js +50 -797
- package/dist/matterbridgeAccessoryPlatform.js +0 -36
- package/dist/matterbridgeBehaviors.js +1 -55
- package/dist/matterbridgeDeviceTypes.js +15 -579
- package/dist/matterbridgeDynamicPlatform.js +0 -36
- package/dist/matterbridgeEndpoint.js +40 -1022
- package/dist/matterbridgeEndpointHelpers.js +12 -322
- package/dist/matterbridgePlatform.js +0 -233
- package/dist/matterbridgeTypes.js +0 -25
- package/dist/pluginManager.js +3 -269
- package/dist/roboticVacuumCleaner.js +6 -83
- package/dist/shelly.js +7 -168
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -54
- package/dist/utils/colorUtils.js +2 -263
- package/dist/utils/commandLine.js +0 -54
- package/dist/utils/copyDirectory.js +1 -38
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/export.js +0 -1
- package/dist/utils/hex.js +0 -58
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/network.js +5 -83
- package/dist/utils/spawn.js +0 -18
- package/dist/utils/wait.js +9 -62
- package/dist/waterHeater.js +2 -77
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -2
- package/dist/cli.d.ts +0 -29
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/clusters/export.d.ts +0 -2
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -28
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -112
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/export.d.ts +0 -5
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/evse.d.ts +0 -72
- package/dist/evse.d.ts.map +0 -1
- package/dist/evse.js.map +0 -1
- package/dist/frontend.d.ts +0 -285
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/globalMatterbridge.d.ts +0 -59
- package/dist/globalMatterbridge.d.ts.map +0 -1
- package/dist/globalMatterbridge.js.map +0 -1
- package/dist/helpers.d.ts +0 -48
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -38
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/laundryWasher.d.ts +0 -243
- package/dist/laundryWasher.d.ts.map +0 -1
- package/dist/laundryWasher.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -450
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -1334
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -709
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1173
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -3198
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -310
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -184
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -291
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/roboticVacuumCleaner.d.ts +0 -104
- package/dist/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/roboticVacuumCleaner.js.map +0 -1
- package/dist/shelly.d.ts +0 -174
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -59
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -117
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -59
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -33
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createDirectory.d.ts +0 -34
- package/dist/utils/createDirectory.d.ts.map +0 -1
- package/dist/utils/createDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -39
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -32
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -54
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/export.d.ts +0 -12
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -49
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -103
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/network.d.ts +0 -76
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -14
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -56
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
- package/dist/waterHeater.d.ts +0 -106
- package/dist/waterHeater.d.ts.map +0 -1
- package/dist/waterHeater.js.map +0 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,43 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file contains the class Matterbridge.
|
|
3
|
-
*
|
|
4
|
-
* @file matterbridge.ts
|
|
5
|
-
* @author Luca Liguori
|
|
6
|
-
* @created 2023-12-29
|
|
7
|
-
* @version 1.6.0
|
|
8
|
-
* @license Apache-2.0
|
|
9
|
-
*
|
|
10
|
-
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
11
|
-
*
|
|
12
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
-
* you may not use this file except in compliance with the License.
|
|
14
|
-
* You may obtain a copy of the License at
|
|
15
|
-
*
|
|
16
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
-
*
|
|
18
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
-
* See the License for the specific language governing permissions and
|
|
22
|
-
* limitations under the License.
|
|
23
|
-
*/
|
|
24
|
-
// Node.js modules
|
|
25
1
|
import os from 'node:os';
|
|
26
2
|
import path from 'node:path';
|
|
27
3
|
import { promises as fs } from 'node:fs';
|
|
28
4
|
import EventEmitter from 'node:events';
|
|
29
5
|
import { inspect } from 'node:util';
|
|
30
|
-
// AnsiLogger module
|
|
31
6
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from 'node-ansi-logger';
|
|
32
|
-
// NodeStorage module
|
|
33
7
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
-
// @matter
|
|
35
8
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
36
9
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
37
10
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
38
11
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
39
12
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
40
|
-
// Matterbridge
|
|
41
13
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
|
|
42
14
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
43
15
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -48,9 +20,6 @@ import { bridge } from './matterbridgeDeviceTypes.js';
|
|
|
48
20
|
import { Frontend } from './frontend.js';
|
|
49
21
|
import { addVirtualDevices } from './helpers.js';
|
|
50
22
|
import spawn from './utils/spawn.js';
|
|
51
|
-
/**
|
|
52
|
-
* Represents the Matterbridge application.
|
|
53
|
-
*/
|
|
54
23
|
export class Matterbridge extends EventEmitter {
|
|
55
24
|
systemInformation = {
|
|
56
25
|
interfaceName: '',
|
|
@@ -98,7 +67,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
98
67
|
shellySysUpdate: false,
|
|
99
68
|
shellyMainUpdate: false,
|
|
100
69
|
profile: getParameter('profile'),
|
|
101
|
-
loggerLevel: "info"
|
|
70
|
+
loggerLevel: "info",
|
|
102
71
|
fileLogger: false,
|
|
103
72
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
104
73
|
matterFileLogger: false,
|
|
@@ -131,18 +100,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
131
100
|
shutdown = false;
|
|
132
101
|
edge = true;
|
|
133
102
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
134
|
-
|
|
135
|
-
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
103
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
136
104
|
matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
137
105
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
138
106
|
plugins;
|
|
139
107
|
devices;
|
|
140
108
|
frontend = new Frontend(this);
|
|
141
|
-
// Matterbridge storage
|
|
142
109
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
143
110
|
nodeStorage;
|
|
144
111
|
nodeContext;
|
|
145
|
-
// Cleanup
|
|
146
112
|
hasCleanupStarted = false;
|
|
147
113
|
initialized = false;
|
|
148
114
|
execRunningCount = 0;
|
|
@@ -156,23 +122,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
156
122
|
sigtermHandler;
|
|
157
123
|
exceptionHandler;
|
|
158
124
|
rejectionHandler;
|
|
159
|
-
// Matter environment
|
|
160
125
|
environment = Environment.default;
|
|
161
|
-
// Matter storage
|
|
162
126
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
163
127
|
matterStorageService;
|
|
164
128
|
matterStorageManager;
|
|
165
129
|
matterbridgeContext;
|
|
166
130
|
controllerContext;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
certification; // device certification
|
|
175
|
-
// Matter nodes
|
|
131
|
+
mdnsInterface;
|
|
132
|
+
ipv4address;
|
|
133
|
+
ipv6address;
|
|
134
|
+
port;
|
|
135
|
+
passcode;
|
|
136
|
+
discriminator;
|
|
137
|
+
certification;
|
|
176
138
|
serverNode;
|
|
177
139
|
aggregatorNode;
|
|
178
140
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -180,31 +142,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
180
142
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
181
143
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
182
144
|
static instance;
|
|
183
|
-
// We load asyncronously so is private
|
|
184
145
|
constructor() {
|
|
185
146
|
super();
|
|
186
147
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Retrieves the list of Matterbridge devices.
|
|
189
|
-
*
|
|
190
|
-
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
191
|
-
*/
|
|
192
148
|
getDevices() {
|
|
193
149
|
return this.devices.array();
|
|
194
150
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Retrieves the list of registered plugins.
|
|
197
|
-
*
|
|
198
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
199
|
-
*/
|
|
200
151
|
getPlugins() {
|
|
201
152
|
return this.plugins.array();
|
|
202
153
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
205
|
-
*
|
|
206
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
207
|
-
*/
|
|
208
154
|
async setLogLevel(logLevel) {
|
|
209
155
|
if (this.log)
|
|
210
156
|
this.log.logLevel = logLevel;
|
|
@@ -218,31 +164,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
218
164
|
for (const plugin of this.plugins) {
|
|
219
165
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
220
166
|
continue;
|
|
221
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug"
|
|
222
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug"
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
167
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
168
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
169
|
+
}
|
|
170
|
+
let callbackLogLevel = "notice";
|
|
171
|
+
if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
172
|
+
callbackLogLevel = "info";
|
|
173
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
174
|
+
callbackLogLevel = "debug";
|
|
230
175
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
231
176
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
232
177
|
}
|
|
233
|
-
//* ************************************************************************************************************************************ */
|
|
234
|
-
// loadInstance() and cleanup() methods */
|
|
235
|
-
//* ************************************************************************************************************************************ */
|
|
236
|
-
/**
|
|
237
|
-
* Loads an instance of the Matterbridge class.
|
|
238
|
-
* If an instance already exists, return that instance.
|
|
239
|
-
*
|
|
240
|
-
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
241
|
-
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
242
|
-
*/
|
|
243
178
|
static async loadInstance(initialize = false) {
|
|
244
179
|
if (!Matterbridge.instance) {
|
|
245
|
-
// eslint-disable-next-line no-console
|
|
246
180
|
if (hasParameter('debug'))
|
|
247
181
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
248
182
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -251,17 +185,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
251
185
|
}
|
|
252
186
|
return Matterbridge.instance;
|
|
253
187
|
}
|
|
254
|
-
/**
|
|
255
|
-
* Call cleanup() and dispose MdnsService.
|
|
256
|
-
*
|
|
257
|
-
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
258
|
-
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 500.
|
|
259
|
-
*
|
|
260
|
-
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
261
|
-
*/
|
|
262
188
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
263
189
|
this.log.info(`Destroy instance...`);
|
|
264
|
-
// Save server nodes to close
|
|
265
190
|
const servers = [];
|
|
266
191
|
if (this.bridgeMode === 'bridge') {
|
|
267
192
|
if (this.serverNode)
|
|
@@ -279,109 +204,76 @@ export class Matterbridge extends EventEmitter {
|
|
|
279
204
|
servers.push(device.serverNode);
|
|
280
205
|
}
|
|
281
206
|
}
|
|
282
|
-
// Let any already‐queued microtasks run first
|
|
283
207
|
await Promise.resolve();
|
|
284
|
-
// Wait for the cleanup to finish
|
|
285
208
|
await new Promise((resolve) => {
|
|
286
209
|
setTimeout(resolve, pause);
|
|
287
210
|
});
|
|
288
|
-
// Cleanup
|
|
289
211
|
await this.cleanup('destroying instance...', false, timeout);
|
|
290
|
-
// Close servers mdns service
|
|
291
212
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
292
213
|
for (const server of servers) {
|
|
293
214
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
294
215
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
295
216
|
}
|
|
296
|
-
// Let any already‐queued microtasks run first
|
|
297
217
|
await Promise.resolve();
|
|
298
|
-
// Wait for the cleanup to finish
|
|
299
218
|
await new Promise((resolve) => {
|
|
300
219
|
setTimeout(resolve, pause);
|
|
301
220
|
});
|
|
302
221
|
}
|
|
303
|
-
/**
|
|
304
|
-
* Initializes the Matterbridge application.
|
|
305
|
-
*
|
|
306
|
-
* @remarks
|
|
307
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
308
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
309
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
310
|
-
*
|
|
311
|
-
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
312
|
-
*/
|
|
313
222
|
async initialize() {
|
|
314
|
-
// Emit the initialize_started event
|
|
315
223
|
this.emit('initialize_started');
|
|
316
|
-
// Set the restart mode
|
|
317
224
|
if (hasParameter('service'))
|
|
318
225
|
this.restartMode = 'service';
|
|
319
226
|
if (hasParameter('docker'))
|
|
320
227
|
this.restartMode = 'docker';
|
|
321
|
-
// Set the matterbridge home directory
|
|
322
228
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
323
229
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
324
230
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
325
|
-
// Set the matterbridge directory
|
|
326
231
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
327
232
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
328
233
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
329
234
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
330
235
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
331
|
-
// Set the matterbridge plugin directory
|
|
332
236
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
333
237
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
334
238
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
335
|
-
// Set the matterbridge cert directory
|
|
336
239
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
337
240
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
338
241
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
339
|
-
// Set the matterbridge root directory
|
|
340
242
|
const { fileURLToPath } = await import('node:url');
|
|
341
243
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
342
244
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
343
245
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
344
|
-
// Setup the matter environment
|
|
345
246
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
346
247
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
347
248
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
348
249
|
this.environment.vars.set('runtime.signals', false);
|
|
349
250
|
this.environment.vars.set('runtime.exitcode', false);
|
|
350
|
-
// Register process handlers
|
|
351
251
|
this.registerProcessHandlers();
|
|
352
|
-
// Initialize nodeStorage and nodeContext
|
|
353
252
|
try {
|
|
354
253
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
355
254
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
356
255
|
this.log.debug('Creating node storage context for matterbridge');
|
|
357
256
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
358
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
359
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
360
257
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
361
258
|
for (const key of keys) {
|
|
362
259
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
363
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
364
260
|
await this.nodeStorage?.storage.get(key);
|
|
365
261
|
}
|
|
366
262
|
const storages = await this.nodeStorage.getStorageNames();
|
|
367
263
|
for (const storage of storages) {
|
|
368
264
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
369
265
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
370
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
371
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
372
266
|
const keys = (await nodeContext?.storage.keys());
|
|
373
267
|
keys.forEach(async (key) => {
|
|
374
268
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
375
269
|
await nodeContext?.get(key);
|
|
376
270
|
});
|
|
377
271
|
}
|
|
378
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
379
272
|
this.log.debug('Creating node storage backup...');
|
|
380
273
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
381
274
|
this.log.debug('Created node storage backup');
|
|
382
275
|
}
|
|
383
276
|
catch (error) {
|
|
384
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
385
277
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
386
278
|
if (hasParameter('norestore')) {
|
|
387
279
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -395,20 +287,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
395
287
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
396
288
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
397
289
|
}
|
|
398
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
399
290
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
400
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
401
291
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
402
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
403
292
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
404
|
-
// Certificate management
|
|
405
293
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
406
294
|
try {
|
|
407
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
408
295
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
409
296
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
410
297
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
411
|
-
// Set the vendorId, vendorName, productId and productName if they are present in the pairing file
|
|
412
298
|
if (isValidNumber(pairingFileJson.vendorId))
|
|
413
299
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
414
300
|
if (isValidString(pairingFileJson.vendorName, 3))
|
|
@@ -417,73 +303,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
417
303
|
this.aggregatorProductId = pairingFileJson.productId;
|
|
418
304
|
if (isValidString(pairingFileJson.productName, 3))
|
|
419
305
|
this.aggregatorProductName = pairingFileJson.productName;
|
|
420
|
-
// Override the passcode and discriminator if they are present in the pairing file
|
|
421
306
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
422
307
|
this.passcode = pairingFileJson.passcode;
|
|
423
308
|
this.discriminator = pairingFileJson.discriminator;
|
|
424
309
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
425
310
|
}
|
|
426
|
-
// Set the certification if it is present in the pairing file
|
|
427
|
-
/*
|
|
428
|
-
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
429
|
-
const hexStringToUint8Array = (hexString: string) => {
|
|
430
|
-
const matches = hexString.match(/.{1,2}/g);
|
|
431
|
-
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
432
|
-
};
|
|
433
|
-
// const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
|
|
434
|
-
// console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
|
|
435
|
-
|
|
436
|
-
this.certification = {
|
|
437
|
-
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
438
|
-
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
439
|
-
intermediateCertificate: hexStringToUint8Array(pairingFileJson.intermediateCertificate),
|
|
440
|
-
declaration: hexStringToUint8Array(pairingFileJson.declaration),
|
|
441
|
-
};
|
|
442
|
-
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using privateKey, certificate, intermediateCertificate and declaration from pairing file.`);
|
|
443
|
-
}
|
|
444
|
-
*/
|
|
445
311
|
}
|
|
446
312
|
catch (error) {
|
|
447
313
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
448
314
|
}
|
|
449
|
-
// Store the passcode, discriminator and port in the node context
|
|
450
315
|
await this.nodeContext.set('matterport', this.port);
|
|
451
316
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
452
317
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
453
318
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
454
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
455
319
|
if (hasParameter('logger')) {
|
|
456
320
|
const level = getParameter('logger');
|
|
457
321
|
if (level === 'debug') {
|
|
458
|
-
this.log.logLevel = "debug"
|
|
322
|
+
this.log.logLevel = "debug";
|
|
459
323
|
}
|
|
460
324
|
else if (level === 'info') {
|
|
461
|
-
this.log.logLevel = "info"
|
|
325
|
+
this.log.logLevel = "info";
|
|
462
326
|
}
|
|
463
327
|
else if (level === 'notice') {
|
|
464
|
-
this.log.logLevel = "notice"
|
|
328
|
+
this.log.logLevel = "notice";
|
|
465
329
|
}
|
|
466
330
|
else if (level === 'warn') {
|
|
467
|
-
this.log.logLevel = "warn"
|
|
331
|
+
this.log.logLevel = "warn";
|
|
468
332
|
}
|
|
469
333
|
else if (level === 'error') {
|
|
470
|
-
this.log.logLevel = "error"
|
|
334
|
+
this.log.logLevel = "error";
|
|
471
335
|
}
|
|
472
336
|
else if (level === 'fatal') {
|
|
473
|
-
this.log.logLevel = "fatal"
|
|
337
|
+
this.log.logLevel = "fatal";
|
|
474
338
|
}
|
|
475
339
|
else {
|
|
476
340
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
477
|
-
this.log.logLevel = "info"
|
|
341
|
+
this.log.logLevel = "info";
|
|
478
342
|
}
|
|
479
343
|
}
|
|
480
344
|
else {
|
|
481
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice"
|
|
345
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
482
346
|
}
|
|
483
347
|
this.frontend.logLevel = this.log.logLevel;
|
|
484
348
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
485
349
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
486
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
487
350
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
488
351
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
489
352
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -492,7 +355,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
492
355
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
493
356
|
if (this.profile !== undefined)
|
|
494
357
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
495
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
496
358
|
if (hasParameter('matterlogger')) {
|
|
497
359
|
const level = getParameter('matterlogger');
|
|
498
360
|
if (level === 'debug') {
|
|
@@ -524,7 +386,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
524
386
|
Logger.format = MatterLogFormat.ANSI;
|
|
525
387
|
Logger.setLogger('default', this.createMatterLogger());
|
|
526
388
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
527
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
528
389
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
529
390
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
530
391
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -533,7 +394,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
533
394
|
});
|
|
534
395
|
}
|
|
535
396
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
536
|
-
// Log network interfaces
|
|
537
397
|
const networkInterfaces = os.networkInterfaces();
|
|
538
398
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
539
399
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -545,7 +405,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
545
405
|
});
|
|
546
406
|
}
|
|
547
407
|
}
|
|
548
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
549
408
|
if (hasParameter('mdnsinterface')) {
|
|
550
409
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
551
410
|
}
|
|
@@ -554,7 +413,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
554
413
|
if (this.mdnsInterface === '')
|
|
555
414
|
this.mdnsInterface = undefined;
|
|
556
415
|
}
|
|
557
|
-
// Validate mdnsInterface
|
|
558
416
|
if (this.mdnsInterface) {
|
|
559
417
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
560
418
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -567,7 +425,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
567
425
|
}
|
|
568
426
|
if (this.mdnsInterface)
|
|
569
427
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
570
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
571
428
|
if (hasParameter('ipv4address')) {
|
|
572
429
|
this.ipv4address = getParameter('ipv4address');
|
|
573
430
|
}
|
|
@@ -576,7 +433,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
576
433
|
if (this.ipv4address === '')
|
|
577
434
|
this.ipv4address = undefined;
|
|
578
435
|
}
|
|
579
|
-
// Validate ipv4address
|
|
580
436
|
if (this.ipv4address) {
|
|
581
437
|
let isValid = false;
|
|
582
438
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -592,7 +448,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
592
448
|
await this.nodeContext.remove('matteripv4address');
|
|
593
449
|
}
|
|
594
450
|
}
|
|
595
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
596
451
|
if (hasParameter('ipv6address')) {
|
|
597
452
|
this.ipv6address = getParameter('ipv6address');
|
|
598
453
|
}
|
|
@@ -601,7 +456,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
601
456
|
if (this.ipv6address === '')
|
|
602
457
|
this.ipv6address = undefined;
|
|
603
458
|
}
|
|
604
|
-
// Validate ipv6address
|
|
605
459
|
if (this.ipv6address) {
|
|
606
460
|
let isValid = false;
|
|
607
461
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -610,7 +464,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
610
464
|
isValid = true;
|
|
611
465
|
break;
|
|
612
466
|
}
|
|
613
|
-
/* istanbul ignore next */
|
|
614
467
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
615
468
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
616
469
|
isValid = true;
|
|
@@ -623,7 +476,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
623
476
|
await this.nodeContext.remove('matteripv6address');
|
|
624
477
|
}
|
|
625
478
|
}
|
|
626
|
-
// Initialize the virtual mode
|
|
627
479
|
if (hasParameter('novirtual')) {
|
|
628
480
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
629
481
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -632,19 +484,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
632
484
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
633
485
|
}
|
|
634
486
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
635
|
-
// Initialize PluginManager
|
|
636
487
|
this.plugins = new PluginManager(this);
|
|
637
488
|
await this.plugins.loadFromStorage();
|
|
638
489
|
this.plugins.logLevel = this.log.logLevel;
|
|
639
|
-
// Initialize DeviceManager
|
|
640
490
|
this.devices = new DeviceManager(this);
|
|
641
491
|
this.devices.logLevel = this.log.logLevel;
|
|
642
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
643
492
|
for (const plugin of this.plugins) {
|
|
644
493
|
const packageJson = await this.plugins.parse(plugin);
|
|
645
494
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
646
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
647
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
648
495
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
649
496
|
try {
|
|
650
497
|
await spawn.spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -666,7 +513,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
666
513
|
await plugin.nodeContext.set('description', plugin.description);
|
|
667
514
|
await plugin.nodeContext.set('author', plugin.author);
|
|
668
515
|
}
|
|
669
|
-
// Log system info and create .matterbridge directory
|
|
670
516
|
await this.logNodeAndSystemInfo();
|
|
671
517
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
672
518
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -674,7 +520,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
674
520
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
675
521
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
676
522
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
677
|
-
// Check node version and throw error
|
|
678
523
|
const minNodeVersion = 18;
|
|
679
524
|
const nodeVersion = process.versions.node;
|
|
680
525
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -682,18 +527,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
682
527
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
683
528
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
684
529
|
}
|
|
685
|
-
// Parse command line
|
|
686
530
|
await this.parseCommandLine();
|
|
687
|
-
// Emit the initialize_completed event
|
|
688
531
|
this.emit('initialize_completed');
|
|
689
532
|
this.initialized = true;
|
|
690
533
|
}
|
|
691
|
-
/**
|
|
692
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
693
|
-
*
|
|
694
|
-
* @private
|
|
695
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
696
|
-
*/
|
|
697
534
|
async parseCommandLine() {
|
|
698
535
|
if (hasParameter('help')) {
|
|
699
536
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -754,19 +591,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
754
591
|
}
|
|
755
592
|
index++;
|
|
756
593
|
}
|
|
757
|
-
/*
|
|
758
|
-
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
759
|
-
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
760
|
-
serializedRegisteredDevices?.forEach((device, index) => {
|
|
761
|
-
if (index !== serializedRegisteredDevices.length - 1) {
|
|
762
|
-
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
763
|
-
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
764
|
-
} else {
|
|
765
|
-
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
766
|
-
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
*/
|
|
770
594
|
this.shutdown = true;
|
|
771
595
|
return;
|
|
772
596
|
}
|
|
@@ -816,7 +640,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
816
640
|
this.shutdown = true;
|
|
817
641
|
return;
|
|
818
642
|
}
|
|
819
|
-
// Start the matter storage and create the matterbridge context
|
|
820
643
|
try {
|
|
821
644
|
await this.startMatterStorage();
|
|
822
645
|
}
|
|
@@ -824,14 +647,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
824
647
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
825
648
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
826
649
|
}
|
|
827
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
828
650
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
829
651
|
this.initialized = true;
|
|
830
652
|
await this.shutdownProcessAndReset();
|
|
831
653
|
this.shutdown = true;
|
|
832
654
|
return;
|
|
833
655
|
}
|
|
834
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
835
656
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
836
657
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
837
658
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -856,37 +677,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
856
677
|
this.shutdown = true;
|
|
857
678
|
return;
|
|
858
679
|
}
|
|
859
|
-
// Initialize frontend
|
|
860
680
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
861
681
|
await this.frontend.start(getIntParameter('frontend'));
|
|
862
|
-
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
863
682
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
864
683
|
const { checkUpdates } = await import('./update.js');
|
|
865
684
|
checkUpdates(this);
|
|
866
685
|
}, 30 * 1000).unref();
|
|
867
|
-
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
868
686
|
this.checkUpdateInterval = setInterval(async () => {
|
|
869
687
|
const { checkUpdates } = await import('./update.js');
|
|
870
688
|
checkUpdates(this);
|
|
871
689
|
}, 12 * 60 * 60 * 1000).unref();
|
|
872
|
-
// Start the matterbridge in mode test
|
|
873
690
|
if (hasParameter('test')) {
|
|
874
691
|
this.bridgeMode = 'bridge';
|
|
875
692
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
876
693
|
return;
|
|
877
694
|
}
|
|
878
|
-
// Start the matterbridge in mode controller
|
|
879
695
|
if (hasParameter('controller')) {
|
|
880
696
|
this.bridgeMode = 'controller';
|
|
881
697
|
await this.startController();
|
|
882
698
|
return;
|
|
883
699
|
}
|
|
884
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
885
700
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
886
701
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
887
702
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
888
703
|
}
|
|
889
|
-
// Start matterbridge in bridge mode
|
|
890
704
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
891
705
|
this.bridgeMode = 'bridge';
|
|
892
706
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -894,7 +708,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
894
708
|
await this.startBridge();
|
|
895
709
|
return;
|
|
896
710
|
}
|
|
897
|
-
// Start matterbridge in childbridge mode
|
|
898
711
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
899
712
|
this.bridgeMode = 'childbridge';
|
|
900
713
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -903,20 +716,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
903
716
|
return;
|
|
904
717
|
}
|
|
905
718
|
}
|
|
906
|
-
/**
|
|
907
|
-
* Asynchronously loads and starts the registered plugins.
|
|
908
|
-
*
|
|
909
|
-
* This method is responsible for initializing and starting all enabled plugins.
|
|
910
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
911
|
-
*
|
|
912
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
913
|
-
*/
|
|
914
719
|
async startPlugins() {
|
|
915
|
-
// Check, load and start the plugins
|
|
916
720
|
for (const plugin of this.plugins) {
|
|
917
721
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
918
722
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
919
|
-
// Check if the plugin is available
|
|
920
723
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
921
724
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
922
725
|
plugin.enabled = false;
|
|
@@ -936,14 +739,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
936
739
|
plugin.addedDevices = undefined;
|
|
937
740
|
plugin.qrPairingCode = undefined;
|
|
938
741
|
plugin.manualPairingCode = undefined;
|
|
939
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
742
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
940
743
|
}
|
|
941
744
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
942
745
|
}
|
|
943
|
-
/**
|
|
944
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
945
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
946
|
-
*/
|
|
947
746
|
registerProcessHandlers() {
|
|
948
747
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
949
748
|
process.removeAllListeners('uncaughtException');
|
|
@@ -970,9 +769,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
970
769
|
};
|
|
971
770
|
process.on('SIGTERM', this.sigtermHandler);
|
|
972
771
|
}
|
|
973
|
-
/**
|
|
974
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
975
|
-
*/
|
|
976
772
|
deregisterProcessHandlers() {
|
|
977
773
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
978
774
|
if (this.exceptionHandler)
|
|
@@ -989,17 +785,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
989
785
|
process.off('SIGTERM', this.sigtermHandler);
|
|
990
786
|
this.sigtermHandler = undefined;
|
|
991
787
|
}
|
|
992
|
-
/**
|
|
993
|
-
* Logs the node and system information.
|
|
994
|
-
*/
|
|
995
788
|
async logNodeAndSystemInfo() {
|
|
996
|
-
// IP address information
|
|
997
789
|
const networkInterfaces = os.networkInterfaces();
|
|
998
790
|
this.systemInformation.interfaceName = '';
|
|
999
791
|
this.systemInformation.ipv4Address = '';
|
|
1000
792
|
this.systemInformation.ipv6Address = '';
|
|
1001
793
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
1002
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
1003
794
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
1004
795
|
continue;
|
|
1005
796
|
if (!interfaceDetails) {
|
|
@@ -1025,22 +816,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1025
816
|
break;
|
|
1026
817
|
}
|
|
1027
818
|
}
|
|
1028
|
-
// Node information
|
|
1029
819
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
1030
820
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
1031
821
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
1032
822
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1033
|
-
// Host system information
|
|
1034
823
|
this.systemInformation.hostname = os.hostname();
|
|
1035
824
|
this.systemInformation.user = os.userInfo().username;
|
|
1036
|
-
this.systemInformation.osType = os.type();
|
|
1037
|
-
this.systemInformation.osRelease = os.release();
|
|
1038
|
-
this.systemInformation.osPlatform = os.platform();
|
|
1039
|
-
this.systemInformation.osArch = os.arch();
|
|
1040
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
1041
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
1042
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1043
|
-
// Log the system information
|
|
825
|
+
this.systemInformation.osType = os.type();
|
|
826
|
+
this.systemInformation.osRelease = os.release();
|
|
827
|
+
this.systemInformation.osPlatform = os.platform();
|
|
828
|
+
this.systemInformation.osArch = os.arch();
|
|
829
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
830
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
831
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1044
832
|
this.log.debug('Host System Information:');
|
|
1045
833
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
1046
834
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -1056,17 +844,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1056
844
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
1057
845
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
1058
846
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1059
|
-
// Log directories
|
|
1060
847
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1061
848
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1062
849
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1063
850
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1064
851
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1065
|
-
// Global node_modules directory
|
|
1066
852
|
if (this.nodeContext)
|
|
1067
853
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1068
854
|
if (this.globalModulesDirectory === '') {
|
|
1069
|
-
// First run of Matterbridge so the node storage is empty
|
|
1070
855
|
try {
|
|
1071
856
|
this.execRunningCount++;
|
|
1072
857
|
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
|
|
@@ -1080,84 +865,53 @@ export class Matterbridge extends EventEmitter {
|
|
|
1080
865
|
}
|
|
1081
866
|
else
|
|
1082
867
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1083
|
-
/* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
|
|
1084
|
-
else {
|
|
1085
|
-
this.getGlobalNodeModules()
|
|
1086
|
-
.then(async (globalModulesDirectory) => {
|
|
1087
|
-
this.globalModulesDirectory = globalModulesDirectory;
|
|
1088
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1089
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1090
|
-
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1091
|
-
})
|
|
1092
|
-
.catch((error) => {
|
|
1093
|
-
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1094
|
-
});
|
|
1095
|
-
}*/
|
|
1096
|
-
// Matterbridge version
|
|
1097
868
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1098
869
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
1099
870
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
1100
871
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1101
|
-
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
1102
872
|
if (this.nodeContext)
|
|
1103
873
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1104
874
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1105
|
-
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
1106
875
|
if (this.nodeContext)
|
|
1107
876
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
1108
877
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1109
|
-
// Current working directory
|
|
1110
878
|
const currentDir = process.cwd();
|
|
1111
879
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1112
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1113
880
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1114
881
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1115
882
|
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1118
|
-
*
|
|
1119
|
-
* @returns {Function} The MatterLogger function.
|
|
1120
|
-
*/
|
|
1121
883
|
createMatterLogger() {
|
|
1122
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
884
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1123
885
|
return (level, formattedLog) => {
|
|
1124
886
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1125
887
|
const message = formattedLog.slice(65);
|
|
1126
888
|
matterLogger.logName = logger;
|
|
1127
889
|
switch (level) {
|
|
1128
890
|
case MatterLogLevel.DEBUG:
|
|
1129
|
-
matterLogger.log("debug"
|
|
891
|
+
matterLogger.log("debug", message);
|
|
1130
892
|
break;
|
|
1131
893
|
case MatterLogLevel.INFO:
|
|
1132
|
-
matterLogger.log("info"
|
|
894
|
+
matterLogger.log("info", message);
|
|
1133
895
|
break;
|
|
1134
896
|
case MatterLogLevel.NOTICE:
|
|
1135
|
-
matterLogger.log("notice"
|
|
897
|
+
matterLogger.log("notice", message);
|
|
1136
898
|
break;
|
|
1137
899
|
case MatterLogLevel.WARN:
|
|
1138
|
-
matterLogger.log("warn"
|
|
900
|
+
matterLogger.log("warn", message);
|
|
1139
901
|
break;
|
|
1140
902
|
case MatterLogLevel.ERROR:
|
|
1141
|
-
matterLogger.log("error"
|
|
903
|
+
matterLogger.log("error", message);
|
|
1142
904
|
break;
|
|
1143
905
|
case MatterLogLevel.FATAL:
|
|
1144
|
-
matterLogger.log("fatal"
|
|
906
|
+
matterLogger.log("fatal", message);
|
|
1145
907
|
break;
|
|
1146
908
|
default:
|
|
1147
|
-
matterLogger.log("debug"
|
|
909
|
+
matterLogger.log("debug", message);
|
|
1148
910
|
break;
|
|
1149
911
|
}
|
|
1150
912
|
};
|
|
1151
913
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Creates a Matter File Logger.
|
|
1154
|
-
*
|
|
1155
|
-
* @param {string} filePath - The path to the log file.
|
|
1156
|
-
* @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
|
|
1157
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1158
|
-
*/
|
|
1159
914
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1160
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1161
915
|
let fileSize = 0;
|
|
1162
916
|
if (unlink) {
|
|
1163
917
|
try {
|
|
@@ -1206,21 +960,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1206
960
|
}
|
|
1207
961
|
};
|
|
1208
962
|
}
|
|
1209
|
-
/**
|
|
1210
|
-
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1211
|
-
*/
|
|
1212
963
|
async restartProcess() {
|
|
1213
964
|
await this.cleanup('restarting...', true);
|
|
1214
965
|
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Shut down the process by exiting the current process.
|
|
1217
|
-
*/
|
|
1218
966
|
async shutdownProcess() {
|
|
1219
967
|
await this.cleanup('shutting down...', false);
|
|
1220
968
|
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Update matterbridge and and shut down the process.
|
|
1223
|
-
*/
|
|
1224
969
|
async updateProcess() {
|
|
1225
970
|
this.log.info('Updating matterbridge...');
|
|
1226
971
|
try {
|
|
@@ -1233,75 +978,52 @@ export class Matterbridge extends EventEmitter {
|
|
|
1233
978
|
this.frontend.wssSendRestartRequired();
|
|
1234
979
|
await this.cleanup('updating...', false);
|
|
1235
980
|
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Unregister all devices and shut down the process.
|
|
1238
|
-
*/
|
|
1239
981
|
async unregisterAndShutdownProcess() {
|
|
1240
982
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1241
983
|
for (const plugin of this.plugins) {
|
|
1242
984
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1243
985
|
}
|
|
1244
986
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1245
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
987
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1246
988
|
this.log.debug('Cleaning up and shutting down...');
|
|
1247
989
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1248
990
|
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Reset commissioning and shut down the process.
|
|
1251
|
-
*/
|
|
1252
991
|
async shutdownProcessAndReset() {
|
|
1253
992
|
await this.cleanup('shutting down with reset...', false);
|
|
1254
993
|
}
|
|
1255
|
-
/**
|
|
1256
|
-
* Factory reset and shut down the process.
|
|
1257
|
-
*/
|
|
1258
994
|
async shutdownProcessAndFactoryReset() {
|
|
1259
995
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1260
996
|
}
|
|
1261
|
-
/**
|
|
1262
|
-
* Cleans up the Matterbridge instance.
|
|
1263
|
-
*
|
|
1264
|
-
* @param {string} message - The cleanup message.
|
|
1265
|
-
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1266
|
-
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1267
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1268
|
-
*/
|
|
1269
997
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
1270
998
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1271
999
|
this.emit('cleanup_started');
|
|
1272
1000
|
this.hasCleanupStarted = true;
|
|
1273
1001
|
this.log.info(message);
|
|
1274
|
-
// Clear the start matter interval
|
|
1275
1002
|
if (this.startMatterInterval) {
|
|
1276
1003
|
clearInterval(this.startMatterInterval);
|
|
1277
1004
|
this.startMatterInterval = undefined;
|
|
1278
1005
|
this.log.debug('Start matter interval cleared');
|
|
1279
1006
|
}
|
|
1280
|
-
// Clear the check update timeout
|
|
1281
1007
|
if (this.checkUpdateTimeout) {
|
|
1282
1008
|
clearTimeout(this.checkUpdateTimeout);
|
|
1283
1009
|
this.checkUpdateTimeout = undefined;
|
|
1284
1010
|
this.log.debug('Check update timeout cleared');
|
|
1285
1011
|
}
|
|
1286
|
-
// Clear the check update interval
|
|
1287
1012
|
if (this.checkUpdateInterval) {
|
|
1288
1013
|
clearInterval(this.checkUpdateInterval);
|
|
1289
1014
|
this.checkUpdateInterval = undefined;
|
|
1290
1015
|
this.log.debug('Check update interval cleared');
|
|
1291
1016
|
}
|
|
1292
|
-
// Clear the configure timeout
|
|
1293
1017
|
if (this.configureTimeout) {
|
|
1294
1018
|
clearTimeout(this.configureTimeout);
|
|
1295
1019
|
this.configureTimeout = undefined;
|
|
1296
1020
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1297
1021
|
}
|
|
1298
|
-
// Clear the reachability timeout
|
|
1299
1022
|
if (this.reachabilityTimeout) {
|
|
1300
1023
|
clearTimeout(this.reachabilityTimeout);
|
|
1301
1024
|
this.reachabilityTimeout = undefined;
|
|
1302
1025
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1303
1026
|
}
|
|
1304
|
-
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1305
1027
|
for (const plugin of this.plugins) {
|
|
1306
1028
|
if (!plugin.enabled || plugin.error)
|
|
1307
1029
|
continue;
|
|
@@ -1312,10 +1034,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1312
1034
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1313
1035
|
}
|
|
1314
1036
|
}
|
|
1315
|
-
// Stop matter server nodes
|
|
1316
1037
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1317
1038
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1318
|
-
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
1039
|
+
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
1319
1040
|
if (this.bridgeMode === 'bridge') {
|
|
1320
1041
|
if (this.serverNode) {
|
|
1321
1042
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1338,7 +1059,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1338
1059
|
}
|
|
1339
1060
|
}
|
|
1340
1061
|
this.log.notice('Stopped matter server nodes');
|
|
1341
|
-
// Matter commisioning reset
|
|
1342
1062
|
if (message === 'shutting down with reset...') {
|
|
1343
1063
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1344
1064
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1348,36 +1068,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1348
1068
|
await this.matterbridgeContext?.clearAll();
|
|
1349
1069
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1350
1070
|
}
|
|
1351
|
-
// Stop matter storage
|
|
1352
1071
|
await this.stopMatterStorage();
|
|
1353
|
-
// Stop the frontend
|
|
1354
1072
|
await this.frontend.stop();
|
|
1355
|
-
// Remove the matterfilelogger
|
|
1356
1073
|
try {
|
|
1357
1074
|
Logger.removeLogger('matterfilelogger');
|
|
1358
1075
|
}
|
|
1359
1076
|
catch (error) {
|
|
1360
1077
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1361
1078
|
}
|
|
1362
|
-
// Close the matterbridge node storage and context
|
|
1363
1079
|
if (this.nodeStorage && this.nodeContext) {
|
|
1364
|
-
/*
|
|
1365
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1366
|
-
this.log.info('Saving registered devices...');
|
|
1367
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1368
|
-
this.devices.forEach(async (device) => {
|
|
1369
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1370
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1371
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1372
|
-
});
|
|
1373
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1374
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1375
|
-
*/
|
|
1376
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1377
1080
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1378
1081
|
await this.nodeContext.close();
|
|
1379
1082
|
this.nodeContext = undefined;
|
|
1380
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1381
1083
|
for (const plugin of this.plugins) {
|
|
1382
1084
|
if (plugin.nodeContext) {
|
|
1383
1085
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1394,10 +1096,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1394
1096
|
}
|
|
1395
1097
|
this.plugins.clear();
|
|
1396
1098
|
this.devices.clear();
|
|
1397
|
-
// Factory reset
|
|
1398
1099
|
if (message === 'shutting down with factory reset...') {
|
|
1399
1100
|
try {
|
|
1400
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1401
1101
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1402
1102
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1403
1103
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1411,7 +1111,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1411
1111
|
}
|
|
1412
1112
|
}
|
|
1413
1113
|
try {
|
|
1414
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1415
1114
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1416
1115
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1417
1116
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1426,13 +1125,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1426
1125
|
}
|
|
1427
1126
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1428
1127
|
}
|
|
1429
|
-
// Deregisters the process handlers
|
|
1430
1128
|
this.deregisterProcessHandlers();
|
|
1431
1129
|
if (restart) {
|
|
1432
1130
|
if (message === 'updating...') {
|
|
1433
1131
|
this.log.info('Cleanup completed. Updating...');
|
|
1434
1132
|
Matterbridge.instance = undefined;
|
|
1435
|
-
this.emit('update');
|
|
1133
|
+
this.emit('update');
|
|
1436
1134
|
}
|
|
1437
1135
|
else if (message === 'restarting...') {
|
|
1438
1136
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1453,13 +1151,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1453
1151
|
this.log.debug('Cleanup already started...');
|
|
1454
1152
|
}
|
|
1455
1153
|
}
|
|
1456
|
-
/**
|
|
1457
|
-
* Creates and configures the server node for a single not bridged device.
|
|
1458
|
-
*
|
|
1459
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1460
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1461
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1462
|
-
*/
|
|
1463
1154
|
async createDeviceServerNode(plugin, device) {
|
|
1464
1155
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1465
1156
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1470,14 +1161,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1470
1161
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1471
1162
|
}
|
|
1472
1163
|
}
|
|
1473
|
-
/**
|
|
1474
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1475
|
-
*
|
|
1476
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1477
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1478
|
-
* @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
|
|
1479
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1480
|
-
*/
|
|
1481
1164
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1482
1165
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1483
1166
|
plugin.locked = true;
|
|
@@ -1491,13 +1174,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1491
1174
|
await this.startServerNode(plugin.serverNode);
|
|
1492
1175
|
}
|
|
1493
1176
|
}
|
|
1494
|
-
/**
|
|
1495
|
-
* Creates and configures the server node for a dynamic plugin.
|
|
1496
|
-
*
|
|
1497
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1498
|
-
* @param {boolean} [start] - Whether to start the server node after adding the aggregator node.
|
|
1499
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1500
|
-
*/
|
|
1501
1177
|
async createDynamicPlugin(plugin, start = false) {
|
|
1502
1178
|
if (!plugin.locked) {
|
|
1503
1179
|
plugin.locked = true;
|
|
@@ -1510,14 +1186,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1510
1186
|
await this.startServerNode(plugin.serverNode);
|
|
1511
1187
|
}
|
|
1512
1188
|
}
|
|
1513
|
-
/**
|
|
1514
|
-
* Starts the Matterbridge in bridge mode.
|
|
1515
|
-
*
|
|
1516
|
-
* @private
|
|
1517
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1518
|
-
*/
|
|
1519
1189
|
async startBridge() {
|
|
1520
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1521
1190
|
if (!this.matterStorageManager)
|
|
1522
1191
|
throw new Error('No storage manager initialized');
|
|
1523
1192
|
if (!this.matterbridgeContext)
|
|
@@ -1556,16 +1225,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1556
1225
|
clearInterval(this.startMatterInterval);
|
|
1557
1226
|
this.startMatterInterval = undefined;
|
|
1558
1227
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1559
|
-
|
|
1560
|
-
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1561
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1228
|
+
this.startServerNode(this.serverNode);
|
|
1562
1229
|
for (const device of this.devices.array()) {
|
|
1563
1230
|
if (device.mode === 'server' && device.serverNode) {
|
|
1564
1231
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1565
|
-
this.startServerNode(device.serverNode);
|
|
1232
|
+
this.startServerNode(device.serverNode);
|
|
1566
1233
|
}
|
|
1567
1234
|
}
|
|
1568
|
-
// Configure the plugins
|
|
1569
1235
|
this.configureTimeout = setTimeout(async () => {
|
|
1570
1236
|
for (const plugin of this.plugins) {
|
|
1571
1237
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1583,24 +1249,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1583
1249
|
}
|
|
1584
1250
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1585
1251
|
}, 30 * 1000).unref();
|
|
1586
|
-
// Setting reachability to true
|
|
1587
1252
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1588
1253
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1589
1254
|
if (this.aggregatorNode)
|
|
1590
1255
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1591
1256
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1592
1257
|
}, 60 * 1000).unref();
|
|
1593
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1594
1258
|
this.emit('bridge_started');
|
|
1595
1259
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1596
1260
|
}, 1000);
|
|
1597
1261
|
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1600
|
-
*
|
|
1601
|
-
* @private
|
|
1602
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1603
|
-
*/
|
|
1604
1262
|
async startChildbridge() {
|
|
1605
1263
|
if (!this.matterStorageManager)
|
|
1606
1264
|
throw new Error('No storage manager initialized');
|
|
@@ -1638,7 +1296,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1638
1296
|
clearInterval(this.startMatterInterval);
|
|
1639
1297
|
this.startMatterInterval = undefined;
|
|
1640
1298
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1641
|
-
// Configure the plugins
|
|
1642
1299
|
this.configureTimeout = setTimeout(async () => {
|
|
1643
1300
|
for (const plugin of this.plugins) {
|
|
1644
1301
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1675,9 +1332,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1675
1332
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1676
1333
|
continue;
|
|
1677
1334
|
}
|
|
1678
|
-
|
|
1679
|
-
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1680
|
-
// Setting reachability to true
|
|
1335
|
+
this.startServerNode(plugin.serverNode);
|
|
1681
1336
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1682
1337
|
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}`);
|
|
1683
1338
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1685,241 +1340,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1685
1340
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1686
1341
|
}, 60 * 1000).unref();
|
|
1687
1342
|
}
|
|
1688
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1689
1343
|
for (const device of this.devices.array()) {
|
|
1690
1344
|
if (device.mode === 'server' && device.serverNode) {
|
|
1691
1345
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1692
|
-
this.startServerNode(device.serverNode);
|
|
1346
|
+
this.startServerNode(device.serverNode);
|
|
1693
1347
|
}
|
|
1694
1348
|
}
|
|
1695
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1696
1349
|
this.emit('childbridge_started');
|
|
1697
1350
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1698
1351
|
}, 1000);
|
|
1699
1352
|
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Starts the Matterbridge controller.
|
|
1702
|
-
*
|
|
1703
|
-
* @private
|
|
1704
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1705
|
-
*/
|
|
1706
1353
|
async startController() {
|
|
1707
|
-
/*
|
|
1708
|
-
if (!this.matterStorageManager) {
|
|
1709
|
-
this.log.error('No storage manager initialized');
|
|
1710
|
-
await this.cleanup('No storage manager initialized');
|
|
1711
|
-
return;
|
|
1712
|
-
}
|
|
1713
|
-
this.log.info('Creating context: mattercontrollerContext');
|
|
1714
|
-
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1715
|
-
if (!this.controllerContext) {
|
|
1716
|
-
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1717
|
-
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1718
|
-
return;
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1722
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1723
|
-
this.log.info('Creating matter commissioning controller');
|
|
1724
|
-
this.commissioningController = new CommissioningController({
|
|
1725
|
-
autoConnect: false,
|
|
1726
|
-
});
|
|
1727
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1728
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1729
|
-
|
|
1730
|
-
this.log.info('Starting matter server');
|
|
1731
|
-
await this.matterServer.start();
|
|
1732
|
-
this.log.info('Matter server started');
|
|
1733
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1734
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1735
|
-
regulatoryCountryCode: 'XX',
|
|
1736
|
-
};
|
|
1737
|
-
const commissioningController = new CommissioningController({
|
|
1738
|
-
environment: {
|
|
1739
|
-
environment,
|
|
1740
|
-
id: uniqueId,
|
|
1741
|
-
},
|
|
1742
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1743
|
-
adminFabricLabel,
|
|
1744
|
-
});
|
|
1745
|
-
|
|
1746
|
-
if (hasParameter('pairingcode')) {
|
|
1747
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1748
|
-
const pairingCode = getParameter('pairingcode');
|
|
1749
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1750
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1751
|
-
|
|
1752
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1753
|
-
if (pairingCode !== undefined) {
|
|
1754
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1755
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1756
|
-
longDiscriminator = undefined;
|
|
1757
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1758
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1759
|
-
} else {
|
|
1760
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1761
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1762
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1763
|
-
}
|
|
1764
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1765
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
const options = {
|
|
1769
|
-
commissioning: commissioningOptions,
|
|
1770
|
-
discovery: {
|
|
1771
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1772
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1773
|
-
},
|
|
1774
|
-
passcode: setupPin,
|
|
1775
|
-
} as NodeCommissioningOptions;
|
|
1776
|
-
this.log.info('Commissioning with options:', options);
|
|
1777
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1778
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1779
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1780
|
-
} // (hasParameter('pairingcode'))
|
|
1781
|
-
|
|
1782
|
-
if (hasParameter('unpairall')) {
|
|
1783
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1784
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1785
|
-
for (const nodeId of nodeIds) {
|
|
1786
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1787
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1788
|
-
}
|
|
1789
|
-
return;
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
if (hasParameter('discover')) {
|
|
1793
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1794
|
-
// console.log(discover);
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1798
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1799
|
-
return;
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1803
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1804
|
-
for (const nodeId of nodeIds) {
|
|
1805
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1806
|
-
|
|
1807
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1808
|
-
autoSubscribe: false,
|
|
1809
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1810
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1811
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1812
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1813
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1814
|
-
switch (info) {
|
|
1815
|
-
case NodeStateInformation.Connected:
|
|
1816
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1817
|
-
break;
|
|
1818
|
-
case NodeStateInformation.Disconnected:
|
|
1819
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1820
|
-
break;
|
|
1821
|
-
case NodeStateInformation.Reconnecting:
|
|
1822
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1823
|
-
break;
|
|
1824
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1825
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1826
|
-
break;
|
|
1827
|
-
case NodeStateInformation.StructureChanged:
|
|
1828
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1829
|
-
break;
|
|
1830
|
-
case NodeStateInformation.Decommissioned:
|
|
1831
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1832
|
-
break;
|
|
1833
|
-
default:
|
|
1834
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1835
|
-
break;
|
|
1836
|
-
}
|
|
1837
|
-
},
|
|
1838
|
-
});
|
|
1839
|
-
|
|
1840
|
-
node.logStructure();
|
|
1841
|
-
|
|
1842
|
-
// Get the interaction client
|
|
1843
|
-
this.log.info('Getting the interaction client');
|
|
1844
|
-
const interactionClient = await node.getInteractionClient();
|
|
1845
|
-
let cluster;
|
|
1846
|
-
let attributes;
|
|
1847
|
-
|
|
1848
|
-
// Log BasicInformationCluster
|
|
1849
|
-
cluster = BasicInformationCluster;
|
|
1850
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1851
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1852
|
-
});
|
|
1853
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1854
|
-
attributes.forEach((attribute) => {
|
|
1855
|
-
this.log.info(
|
|
1856
|
-
`- 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}`,
|
|
1857
|
-
);
|
|
1858
|
-
});
|
|
1859
|
-
|
|
1860
|
-
// Log PowerSourceCluster
|
|
1861
|
-
cluster = PowerSourceCluster;
|
|
1862
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1863
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1864
|
-
});
|
|
1865
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1866
|
-
attributes.forEach((attribute) => {
|
|
1867
|
-
this.log.info(
|
|
1868
|
-
`- 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}`,
|
|
1869
|
-
);
|
|
1870
|
-
});
|
|
1871
|
-
|
|
1872
|
-
// Log ThreadNetworkDiagnostics
|
|
1873
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1874
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1875
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1876
|
-
});
|
|
1877
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1878
|
-
attributes.forEach((attribute) => {
|
|
1879
|
-
this.log.info(
|
|
1880
|
-
`- 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}`,
|
|
1881
|
-
);
|
|
1882
|
-
});
|
|
1883
|
-
|
|
1884
|
-
// Log SwitchCluster
|
|
1885
|
-
cluster = SwitchCluster;
|
|
1886
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1887
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1888
|
-
});
|
|
1889
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1890
|
-
attributes.forEach((attribute) => {
|
|
1891
|
-
this.log.info(
|
|
1892
|
-
`- 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}`,
|
|
1893
|
-
);
|
|
1894
|
-
});
|
|
1895
|
-
|
|
1896
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1897
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1898
|
-
ignoreInitialTriggers: false,
|
|
1899
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1900
|
-
this.log.info(
|
|
1901
|
-
`***${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}`,
|
|
1902
|
-
),
|
|
1903
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1904
|
-
this.log.info(
|
|
1905
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1906
|
-
);
|
|
1907
|
-
},
|
|
1908
|
-
});
|
|
1909
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1910
|
-
}
|
|
1911
|
-
*/
|
|
1912
1354
|
}
|
|
1913
|
-
/** */
|
|
1914
|
-
/** Matter.js methods */
|
|
1915
|
-
/** */
|
|
1916
|
-
/**
|
|
1917
|
-
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1918
|
-
*
|
|
1919
|
-
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1920
|
-
*/
|
|
1921
1355
|
async startMatterStorage() {
|
|
1922
|
-
// Setup Matter storage
|
|
1923
1356
|
this.log.info(`Starting matter node storage...`);
|
|
1924
1357
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1925
1358
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1928,17 +1361,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1928
1361
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1929
1362
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1930
1363
|
this.log.info('Matter node storage started');
|
|
1931
|
-
// Backup matter storage since it is created/opened correctly
|
|
1932
1364
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1933
1365
|
}
|
|
1934
|
-
/**
|
|
1935
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1936
|
-
*
|
|
1937
|
-
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1938
|
-
* @param {string} backupName - The name of the backup directory to be created.
|
|
1939
|
-
* @private
|
|
1940
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1941
|
-
*/
|
|
1942
1366
|
async backupMatterStorage(storageName, backupName) {
|
|
1943
1367
|
this.log.info('Creating matter node storage backup...');
|
|
1944
1368
|
try {
|
|
@@ -1949,11 +1373,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1949
1373
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1950
1374
|
}
|
|
1951
1375
|
}
|
|
1952
|
-
/**
|
|
1953
|
-
* Stops the matter storage.
|
|
1954
|
-
*
|
|
1955
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1956
|
-
*/
|
|
1957
1376
|
async stopMatterStorage() {
|
|
1958
1377
|
this.log.info('Closing matter node storage...');
|
|
1959
1378
|
await this.matterStorageManager?.close();
|
|
@@ -1962,19 +1381,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1962
1381
|
this.matterbridgeContext = undefined;
|
|
1963
1382
|
this.log.info('Matter node storage closed');
|
|
1964
1383
|
}
|
|
1965
|
-
/**
|
|
1966
|
-
* Creates a server node storage context.
|
|
1967
|
-
*
|
|
1968
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1969
|
-
* @param {string} deviceName - The name of the device.
|
|
1970
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1971
|
-
* @param {number} vendorId - The vendor ID.
|
|
1972
|
-
* @param {string} vendorName - The vendor name.
|
|
1973
|
-
* @param {number} productId - The product ID.
|
|
1974
|
-
* @param {string} productName - The product name.
|
|
1975
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1976
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1977
|
-
*/
|
|
1978
1384
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1979
1385
|
const { randomBytes } = await import('node:crypto');
|
|
1980
1386
|
if (!this.matterStorageService)
|
|
@@ -2008,15 +1414,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2008
1414
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2009
1415
|
return storageContext;
|
|
2010
1416
|
}
|
|
2011
|
-
/**
|
|
2012
|
-
* Creates a server node.
|
|
2013
|
-
*
|
|
2014
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2015
|
-
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2016
|
-
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2017
|
-
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2018
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2019
|
-
*/
|
|
2020
1417
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
2021
1418
|
const storeId = await storageContext.get('storeId');
|
|
2022
1419
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -2026,37 +1423,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2026
1423
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2027
1424
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2028
1425
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2029
|
-
/**
|
|
2030
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2031
|
-
*/
|
|
2032
1426
|
const serverNode = await ServerNode.create({
|
|
2033
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
2034
1427
|
id: storeId,
|
|
2035
|
-
// Provide Network relevant configuration like the port
|
|
2036
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
2037
1428
|
network: {
|
|
2038
1429
|
listeningAddressIpv4: this.ipv4address,
|
|
2039
1430
|
listeningAddressIpv6: this.ipv6address,
|
|
2040
1431
|
port,
|
|
2041
1432
|
},
|
|
2042
|
-
// Provide the certificate for the device
|
|
2043
1433
|
operationalCredentials: {
|
|
2044
1434
|
certification: this.certification,
|
|
2045
1435
|
},
|
|
2046
|
-
// Provide Commissioning relevant settings
|
|
2047
|
-
// Optional for development/testing purposes
|
|
2048
1436
|
commissioning: {
|
|
2049
1437
|
passcode,
|
|
2050
1438
|
discriminator,
|
|
2051
1439
|
},
|
|
2052
|
-
// Provide Node announcement settings
|
|
2053
|
-
// Optional: If Ommitted some development defaults are used
|
|
2054
1440
|
productDescription: {
|
|
2055
1441
|
name: await storageContext.get('deviceName'),
|
|
2056
1442
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
2057
1443
|
},
|
|
2058
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2059
|
-
// Optional: If Omitted some development defaults are used
|
|
2060
1444
|
basicInformation: {
|
|
2061
1445
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2062
1446
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2074,13 +1458,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2074
1458
|
},
|
|
2075
1459
|
});
|
|
2076
1460
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2077
|
-
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
2078
1461
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
2079
1462
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
2080
1463
|
if (this.bridgeMode === 'bridge') {
|
|
2081
1464
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
2082
1465
|
if (resetSessions)
|
|
2083
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1466
|
+
this.matterbridgeSessionInformations = undefined;
|
|
2084
1467
|
this.matterbridgePaired = true;
|
|
2085
1468
|
}
|
|
2086
1469
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -2088,22 +1471,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2088
1471
|
if (plugin) {
|
|
2089
1472
|
plugin.fabricInformations = sanitizedFabrics;
|
|
2090
1473
|
if (resetSessions)
|
|
2091
|
-
plugin.sessionInformations = undefined;
|
|
1474
|
+
plugin.sessionInformations = undefined;
|
|
2092
1475
|
plugin.paired = true;
|
|
2093
1476
|
}
|
|
2094
1477
|
}
|
|
2095
1478
|
};
|
|
2096
|
-
/**
|
|
2097
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2098
|
-
* This means: It is added to the first fabric.
|
|
2099
|
-
*/
|
|
2100
1479
|
serverNode.lifecycle.commissioned.on(() => {
|
|
2101
1480
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
2102
1481
|
clearTimeout(this.endAdvertiseTimeout);
|
|
2103
1482
|
});
|
|
2104
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2105
1483
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2106
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2107
1484
|
serverNode.lifecycle.online.on(async () => {
|
|
2108
1485
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2109
1486
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2142,7 +1519,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2142
1519
|
}
|
|
2143
1520
|
}
|
|
2144
1521
|
}
|
|
2145
|
-
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
2146
1522
|
this.startEndAdvertiseTimer(serverNode);
|
|
2147
1523
|
}
|
|
2148
1524
|
else {
|
|
@@ -2154,7 +1530,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2154
1530
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2155
1531
|
this.emit('online', storeId);
|
|
2156
1532
|
});
|
|
2157
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2158
1533
|
serverNode.lifecycle.offline.on(() => {
|
|
2159
1534
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2160
1535
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -2179,10 +1554,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2179
1554
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2180
1555
|
this.emit('offline', storeId);
|
|
2181
1556
|
});
|
|
2182
|
-
/**
|
|
2183
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2184
|
-
* information is needed.
|
|
2185
|
-
*/
|
|
2186
1557
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2187
1558
|
let action = '';
|
|
2188
1559
|
switch (fabricAction) {
|
|
@@ -2213,24 +1584,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2213
1584
|
}
|
|
2214
1585
|
}
|
|
2215
1586
|
};
|
|
2216
|
-
/**
|
|
2217
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2218
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2219
|
-
*/
|
|
2220
1587
|
serverNode.events.sessions.opened.on((session) => {
|
|
2221
1588
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2222
1589
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2223
1590
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2224
1591
|
});
|
|
2225
|
-
/**
|
|
2226
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2227
|
-
*/
|
|
2228
1592
|
serverNode.events.sessions.closed.on((session) => {
|
|
2229
1593
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2230
1594
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2231
1595
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2232
1596
|
});
|
|
2233
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2234
1597
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2235
1598
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2236
1599
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -2239,11 +1602,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2239
1602
|
this.log.info(`Created server node for ${storeId}`);
|
|
2240
1603
|
return serverNode;
|
|
2241
1604
|
}
|
|
2242
|
-
/**
|
|
2243
|
-
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2244
|
-
*
|
|
2245
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2246
|
-
*/
|
|
2247
1605
|
startEndAdvertiseTimer(matterServerNode) {
|
|
2248
1606
|
if (this.endAdvertiseTimeout) {
|
|
2249
1607
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -2272,25 +1630,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2272
1630
|
this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
|
|
2273
1631
|
}, 15 * 60 * 1000).unref();
|
|
2274
1632
|
}
|
|
2275
|
-
/**
|
|
2276
|
-
* Starts the specified server node.
|
|
2277
|
-
*
|
|
2278
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2279
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2280
|
-
*/
|
|
2281
1633
|
async startServerNode(matterServerNode) {
|
|
2282
1634
|
if (!matterServerNode)
|
|
2283
1635
|
return;
|
|
2284
1636
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2285
1637
|
await matterServerNode.start();
|
|
2286
1638
|
}
|
|
2287
|
-
/**
|
|
2288
|
-
* Stops the specified server node.
|
|
2289
|
-
*
|
|
2290
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2291
|
-
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2292
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2293
|
-
*/
|
|
2294
1639
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
2295
1640
|
if (!matterServerNode)
|
|
2296
1641
|
return;
|
|
@@ -2303,12 +1648,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2303
1648
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2304
1649
|
}
|
|
2305
1650
|
}
|
|
2306
|
-
/**
|
|
2307
|
-
* Advertises the specified server node.
|
|
2308
|
-
*
|
|
2309
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2310
|
-
* @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.
|
|
2311
|
-
*/
|
|
2312
1651
|
async advertiseServerNode(matterServerNode) {
|
|
2313
1652
|
if (matterServerNode) {
|
|
2314
1653
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2317,39 +1656,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
2317
1656
|
return { qrPairingCode, manualPairingCode };
|
|
2318
1657
|
}
|
|
2319
1658
|
}
|
|
2320
|
-
/**
|
|
2321
|
-
* Stop advertise the specified server node.
|
|
2322
|
-
*
|
|
2323
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2324
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2325
|
-
*/
|
|
2326
1659
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2327
1660
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2328
1661
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2329
1662
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2330
1663
|
}
|
|
2331
1664
|
}
|
|
2332
|
-
/**
|
|
2333
|
-
* Creates an aggregator node with the specified storage context.
|
|
2334
|
-
*
|
|
2335
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2336
|
-
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2337
|
-
*/
|
|
2338
1665
|
async createAggregatorNode(storageContext) {
|
|
2339
1666
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2340
1667
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2341
1668
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2342
1669
|
return aggregatorNode;
|
|
2343
1670
|
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2346
|
-
*
|
|
2347
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2348
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2349
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2350
|
-
*/
|
|
2351
1671
|
async addBridgedEndpoint(pluginName, device) {
|
|
2352
|
-
// Check if the plugin is registered
|
|
2353
1672
|
const plugin = this.plugins.get(pluginName);
|
|
2354
1673
|
if (!plugin) {
|
|
2355
1674
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -2369,7 +1688,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2369
1688
|
}
|
|
2370
1689
|
else if (this.bridgeMode === 'bridge') {
|
|
2371
1690
|
if (device.mode === 'matter') {
|
|
2372
|
-
// Register and add the device to the matterbridge server node
|
|
2373
1691
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
2374
1692
|
if (!this.serverNode) {
|
|
2375
1693
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -2386,7 +1704,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2386
1704
|
}
|
|
2387
1705
|
}
|
|
2388
1706
|
else {
|
|
2389
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2390
1707
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2391
1708
|
if (!this.aggregatorNode) {
|
|
2392
1709
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2404,7 +1721,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2404
1721
|
}
|
|
2405
1722
|
}
|
|
2406
1723
|
else if (this.bridgeMode === 'childbridge') {
|
|
2407
|
-
// Register and add the device to the plugin server node
|
|
2408
1724
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2409
1725
|
try {
|
|
2410
1726
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2428,12 +1744,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2428
1744
|
return;
|
|
2429
1745
|
}
|
|
2430
1746
|
}
|
|
2431
|
-
// Register and add the device to the plugin aggregator node
|
|
2432
1747
|
if (plugin.type === 'DynamicPlatform') {
|
|
2433
1748
|
try {
|
|
2434
1749
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2435
1750
|
await this.createDynamicPlugin(plugin);
|
|
2436
|
-
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
2437
1751
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2438
1752
|
if (!plugin.aggregatorNode) {
|
|
2439
1753
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2456,28 +1770,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2456
1770
|
plugin.registeredDevices++;
|
|
2457
1771
|
if (plugin.addedDevices !== undefined)
|
|
2458
1772
|
plugin.addedDevices++;
|
|
2459
|
-
// Add the device to the DeviceManager
|
|
2460
1773
|
this.devices.set(device);
|
|
2461
|
-
// Subscribe to the reachable$Changed event
|
|
2462
1774
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2463
1775
|
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}`);
|
|
2464
1776
|
}
|
|
2465
|
-
/**
|
|
2466
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2467
|
-
*
|
|
2468
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2469
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2470
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2471
|
-
*/
|
|
2472
1777
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2473
1778
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2474
|
-
// Check if the plugin is registered
|
|
2475
1779
|
const plugin = this.plugins.get(pluginName);
|
|
2476
1780
|
if (!plugin) {
|
|
2477
1781
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2478
1782
|
return;
|
|
2479
1783
|
}
|
|
2480
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2481
1784
|
if (this.bridgeMode === 'bridge') {
|
|
2482
1785
|
if (!this.aggregatorNode) {
|
|
2483
1786
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2492,7 +1795,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2492
1795
|
}
|
|
2493
1796
|
else if (this.bridgeMode === 'childbridge') {
|
|
2494
1797
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2495
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2496
1798
|
}
|
|
2497
1799
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2498
1800
|
if (!plugin.aggregatorNode) {
|
|
@@ -2507,21 +1809,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2507
1809
|
if (plugin.addedDevices !== undefined)
|
|
2508
1810
|
plugin.addedDevices--;
|
|
2509
1811
|
}
|
|
2510
|
-
// Remove the device from the DeviceManager
|
|
2511
1812
|
this.devices.remove(device);
|
|
2512
1813
|
}
|
|
2513
|
-
/**
|
|
2514
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2515
|
-
*
|
|
2516
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2517
|
-
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2518
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2519
|
-
*
|
|
2520
|
-
* @remarks
|
|
2521
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2522
|
-
* It also applies a delay between each removal if specified.
|
|
2523
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2524
|
-
*/
|
|
2525
1814
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2526
1815
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2527
1816
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2532,15 +1821,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2532
1821
|
if (delay > 0)
|
|
2533
1822
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2534
1823
|
}
|
|
2535
|
-
/**
|
|
2536
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2537
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2538
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2539
|
-
*
|
|
2540
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2541
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2542
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2543
|
-
*/
|
|
2544
1824
|
async subscribeAttributeChanged(plugin, device) {
|
|
2545
1825
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2546
1826
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -2556,12 +1836,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2556
1836
|
});
|
|
2557
1837
|
}
|
|
2558
1838
|
}
|
|
2559
|
-
/**
|
|
2560
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2561
|
-
*
|
|
2562
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2563
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2564
|
-
*/
|
|
2565
1839
|
sanitizeFabricInformations(fabricInfo) {
|
|
2566
1840
|
return fabricInfo.map((info) => {
|
|
2567
1841
|
return {
|
|
@@ -2575,12 +1849,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2575
1849
|
};
|
|
2576
1850
|
});
|
|
2577
1851
|
}
|
|
2578
|
-
/**
|
|
2579
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2580
|
-
*
|
|
2581
|
-
* @param {SessionsBehavior.Session[]} session - The array of session information objects.
|
|
2582
|
-
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2583
|
-
*/
|
|
2584
1852
|
sanitizeSessionInformation(session) {
|
|
2585
1853
|
return session
|
|
2586
1854
|
.filter((session) => session.isPeerActive)
|
|
@@ -2607,21 +1875,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2607
1875
|
};
|
|
2608
1876
|
});
|
|
2609
1877
|
}
|
|
2610
|
-
/**
|
|
2611
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2612
|
-
*
|
|
2613
|
-
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2614
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2615
|
-
*/
|
|
2616
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2617
1878
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2618
|
-
/*
|
|
2619
|
-
for (const child of aggregatorNode.parts) {
|
|
2620
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2621
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2622
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2623
|
-
}
|
|
2624
|
-
*/
|
|
2625
1879
|
}
|
|
2626
1880
|
getVendorIdName = (vendorId) => {
|
|
2627
1881
|
if (!vendorId)
|
|
@@ -2665,4 +1919,3 @@ export class Matterbridge extends EventEmitter {
|
|
|
2665
1919
|
return vendorName;
|
|
2666
1920
|
};
|
|
2667
1921
|
}
|
|
2668
|
-
//# sourceMappingURL=matterbridge.js.map
|