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