matterbridge 3.1.2 → 3.1.3-dev-20250712-616f7ed
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 +25 -0
- 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 +6 -89
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/waterHeater.js +2 -82
- package/dist/frontend.js +16 -417
- package/dist/globalMatterbridge.js +0 -47
- package/dist/helpers.js +0 -53
- package/dist/index.js +1 -30
- 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 +54 -803
- 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 +51 -1053
- 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 +5 -5
- 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 -110
- 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 -303
- 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 -33
- 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 -1196
- 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
|
-
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
101
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
134
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,73 +301,50 @@ 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
349
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
|
|
487
350
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -490,7 +353,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
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
|
-
// Call 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);
|
|
@@ -1341,7 +1057,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1341
1057
|
}
|
|
1342
1058
|
}
|
|
1343
1059
|
this.log.notice('Stopped matter server nodes');
|
|
1344
|
-
// Matter commisioning reset
|
|
1345
1060
|
if (message === 'shutting down with reset...') {
|
|
1346
1061
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1347
1062
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1351,36 +1066,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1351
1066
|
await this.matterbridgeContext?.clearAll();
|
|
1352
1067
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1353
1068
|
}
|
|
1354
|
-
// Stop matter storage
|
|
1355
1069
|
await this.stopMatterStorage();
|
|
1356
|
-
// Stop the frontend
|
|
1357
1070
|
await this.frontend.stop();
|
|
1358
|
-
// Remove the matterfilelogger
|
|
1359
1071
|
try {
|
|
1360
1072
|
Logger.removeLogger('matterfilelogger');
|
|
1361
1073
|
}
|
|
1362
1074
|
catch (error) {
|
|
1363
1075
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1364
1076
|
}
|
|
1365
|
-
// Close the matterbridge node storage and context
|
|
1366
1077
|
if (this.nodeStorage && this.nodeContext) {
|
|
1367
|
-
/*
|
|
1368
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1369
|
-
this.log.info('Saving registered devices...');
|
|
1370
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1371
|
-
this.devices.forEach(async (device) => {
|
|
1372
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1373
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1374
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1375
|
-
});
|
|
1376
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1377
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1378
|
-
*/
|
|
1379
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1380
1078
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1381
1079
|
await this.nodeContext.close();
|
|
1382
1080
|
this.nodeContext = undefined;
|
|
1383
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1384
1081
|
for (const plugin of this.plugins) {
|
|
1385
1082
|
if (plugin.nodeContext) {
|
|
1386
1083
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1397,10 +1094,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1397
1094
|
}
|
|
1398
1095
|
this.plugins.clear();
|
|
1399
1096
|
this.devices.clear();
|
|
1400
|
-
// Factory reset
|
|
1401
1097
|
if (message === 'shutting down with factory reset...') {
|
|
1402
1098
|
try {
|
|
1403
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1404
1099
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1405
1100
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1406
1101
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1414,7 +1109,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1414
1109
|
}
|
|
1415
1110
|
}
|
|
1416
1111
|
try {
|
|
1417
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1418
1112
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1419
1113
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1420
1114
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1429,13 +1123,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1429
1123
|
}
|
|
1430
1124
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1431
1125
|
}
|
|
1432
|
-
// Deregisters the process handlers
|
|
1433
1126
|
this.deregisterProcessHandlers();
|
|
1434
1127
|
if (restart) {
|
|
1435
1128
|
if (message === 'updating...') {
|
|
1436
1129
|
this.log.info('Cleanup completed. Updating...');
|
|
1437
1130
|
Matterbridge.instance = undefined;
|
|
1438
|
-
this.emit('update');
|
|
1131
|
+
this.emit('update');
|
|
1439
1132
|
}
|
|
1440
1133
|
else if (message === 'restarting...') {
|
|
1441
1134
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1456,13 +1149,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1456
1149
|
this.log.debug('Cleanup already started...');
|
|
1457
1150
|
}
|
|
1458
1151
|
}
|
|
1459
|
-
/**
|
|
1460
|
-
* Creates and configures the server node for a single not bridged device.
|
|
1461
|
-
*
|
|
1462
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1463
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1464
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1465
|
-
*/
|
|
1466
1152
|
async createDeviceServerNode(plugin, device) {
|
|
1467
1153
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1468
1154
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1473,14 +1159,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1473
1159
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1474
1160
|
}
|
|
1475
1161
|
}
|
|
1476
|
-
/**
|
|
1477
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1478
|
-
*
|
|
1479
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1480
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1481
|
-
* @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
|
|
1482
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1483
|
-
*/
|
|
1484
1162
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1485
1163
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1486
1164
|
plugin.locked = true;
|
|
@@ -1494,13 +1172,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1494
1172
|
await this.startServerNode(plugin.serverNode);
|
|
1495
1173
|
}
|
|
1496
1174
|
}
|
|
1497
|
-
/**
|
|
1498
|
-
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
1499
|
-
*
|
|
1500
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1501
|
-
* @param {boolean} [start] - Whether to start the server node after adding the aggregator node.
|
|
1502
|
-
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
1503
|
-
*/
|
|
1504
1175
|
async createDynamicPlugin(plugin, start = false) {
|
|
1505
1176
|
if (!plugin.locked) {
|
|
1506
1177
|
plugin.locked = true;
|
|
@@ -1513,14 +1184,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1513
1184
|
await this.startServerNode(plugin.serverNode);
|
|
1514
1185
|
}
|
|
1515
1186
|
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Starts the Matterbridge in bridge mode.
|
|
1518
|
-
*
|
|
1519
|
-
* @private
|
|
1520
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1521
|
-
*/
|
|
1522
1187
|
async startBridge() {
|
|
1523
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1524
1188
|
if (!this.matterStorageManager)
|
|
1525
1189
|
throw new Error('No storage manager initialized');
|
|
1526
1190
|
if (!this.matterbridgeContext)
|
|
@@ -1559,16 +1223,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1559
1223
|
clearInterval(this.startMatterInterval);
|
|
1560
1224
|
this.startMatterInterval = undefined;
|
|
1561
1225
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1562
|
-
|
|
1563
|
-
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1564
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1226
|
+
this.startServerNode(this.serverNode);
|
|
1565
1227
|
for (const device of this.devices.array()) {
|
|
1566
1228
|
if (device.mode === 'server' && device.serverNode) {
|
|
1567
1229
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1568
|
-
this.startServerNode(device.serverNode);
|
|
1230
|
+
this.startServerNode(device.serverNode);
|
|
1569
1231
|
}
|
|
1570
1232
|
}
|
|
1571
|
-
// Configure the plugins
|
|
1572
1233
|
this.configureTimeout = setTimeout(async () => {
|
|
1573
1234
|
for (const plugin of this.plugins) {
|
|
1574
1235
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1586,24 +1247,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1586
1247
|
}
|
|
1587
1248
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1588
1249
|
}, 30 * 1000).unref();
|
|
1589
|
-
// Setting reachability to true
|
|
1590
1250
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1591
1251
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1592
1252
|
if (this.aggregatorNode)
|
|
1593
1253
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1594
1254
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1595
1255
|
}, 60 * 1000).unref();
|
|
1596
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1597
1256
|
this.emit('bridge_started');
|
|
1598
1257
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1599
1258
|
}, 1000);
|
|
1600
1259
|
}
|
|
1601
|
-
/**
|
|
1602
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1603
|
-
*
|
|
1604
|
-
* @private
|
|
1605
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1606
|
-
*/
|
|
1607
1260
|
async startChildbridge() {
|
|
1608
1261
|
if (!this.matterStorageManager)
|
|
1609
1262
|
throw new Error('No storage manager initialized');
|
|
@@ -1641,7 +1294,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1641
1294
|
clearInterval(this.startMatterInterval);
|
|
1642
1295
|
this.startMatterInterval = undefined;
|
|
1643
1296
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1644
|
-
// Configure the plugins
|
|
1645
1297
|
this.configureTimeout = setTimeout(async () => {
|
|
1646
1298
|
for (const plugin of this.plugins) {
|
|
1647
1299
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1678,9 +1330,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1678
1330
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1679
1331
|
continue;
|
|
1680
1332
|
}
|
|
1681
|
-
|
|
1682
|
-
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1683
|
-
// Setting reachability to true
|
|
1333
|
+
this.startServerNode(plugin.serverNode);
|
|
1684
1334
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1685
1335
|
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}`);
|
|
1686
1336
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1688,241 +1338,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1688
1338
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1689
1339
|
}, 60 * 1000).unref();
|
|
1690
1340
|
}
|
|
1691
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1692
1341
|
for (const device of this.devices.array()) {
|
|
1693
1342
|
if (device.mode === 'server' && device.serverNode) {
|
|
1694
1343
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1695
|
-
this.startServerNode(device.serverNode);
|
|
1344
|
+
this.startServerNode(device.serverNode);
|
|
1696
1345
|
}
|
|
1697
1346
|
}
|
|
1698
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1699
1347
|
this.emit('childbridge_started');
|
|
1700
1348
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1701
1349
|
}, 1000);
|
|
1702
1350
|
}
|
|
1703
|
-
/**
|
|
1704
|
-
* Starts the Matterbridge controller.
|
|
1705
|
-
*
|
|
1706
|
-
* @private
|
|
1707
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1708
|
-
*/
|
|
1709
1351
|
async startController() {
|
|
1710
|
-
/*
|
|
1711
|
-
if (!this.matterStorageManager) {
|
|
1712
|
-
this.log.error('No storage manager initialized');
|
|
1713
|
-
await this.cleanup('No storage manager initialized');
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
this.log.info('Creating context: mattercontrollerContext');
|
|
1717
|
-
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1718
|
-
if (!this.controllerContext) {
|
|
1719
|
-
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1720
|
-
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1721
|
-
return;
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1725
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1726
|
-
this.log.info('Creating matter commissioning controller');
|
|
1727
|
-
this.commissioningController = new CommissioningController({
|
|
1728
|
-
autoConnect: false,
|
|
1729
|
-
});
|
|
1730
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1731
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1732
|
-
|
|
1733
|
-
this.log.info('Starting matter server');
|
|
1734
|
-
await this.matterServer.start();
|
|
1735
|
-
this.log.info('Matter server started');
|
|
1736
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1737
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1738
|
-
regulatoryCountryCode: 'XX',
|
|
1739
|
-
};
|
|
1740
|
-
const commissioningController = new CommissioningController({
|
|
1741
|
-
environment: {
|
|
1742
|
-
environment,
|
|
1743
|
-
id: uniqueId,
|
|
1744
|
-
},
|
|
1745
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1746
|
-
adminFabricLabel,
|
|
1747
|
-
});
|
|
1748
|
-
|
|
1749
|
-
if (hasParameter('pairingcode')) {
|
|
1750
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1751
|
-
const pairingCode = getParameter('pairingcode');
|
|
1752
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1753
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1754
|
-
|
|
1755
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1756
|
-
if (pairingCode !== undefined) {
|
|
1757
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1758
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1759
|
-
longDiscriminator = undefined;
|
|
1760
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1761
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1762
|
-
} else {
|
|
1763
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1764
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1765
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1766
|
-
}
|
|
1767
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1768
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
const options = {
|
|
1772
|
-
commissioning: commissioningOptions,
|
|
1773
|
-
discovery: {
|
|
1774
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1775
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1776
|
-
},
|
|
1777
|
-
passcode: setupPin,
|
|
1778
|
-
} as NodeCommissioningOptions;
|
|
1779
|
-
this.log.info('Commissioning with options:', options);
|
|
1780
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1781
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1782
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1783
|
-
} // (hasParameter('pairingcode'))
|
|
1784
|
-
|
|
1785
|
-
if (hasParameter('unpairall')) {
|
|
1786
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1787
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1788
|
-
for (const nodeId of nodeIds) {
|
|
1789
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1790
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1791
|
-
}
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
if (hasParameter('discover')) {
|
|
1796
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1797
|
-
// console.log(discover);
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1801
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1806
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1807
|
-
for (const nodeId of nodeIds) {
|
|
1808
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1809
|
-
|
|
1810
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1811
|
-
autoSubscribe: false,
|
|
1812
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1813
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1814
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1815
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1816
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1817
|
-
switch (info) {
|
|
1818
|
-
case NodeStateInformation.Connected:
|
|
1819
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1820
|
-
break;
|
|
1821
|
-
case NodeStateInformation.Disconnected:
|
|
1822
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1823
|
-
break;
|
|
1824
|
-
case NodeStateInformation.Reconnecting:
|
|
1825
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1826
|
-
break;
|
|
1827
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1828
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1829
|
-
break;
|
|
1830
|
-
case NodeStateInformation.StructureChanged:
|
|
1831
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1832
|
-
break;
|
|
1833
|
-
case NodeStateInformation.Decommissioned:
|
|
1834
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1835
|
-
break;
|
|
1836
|
-
default:
|
|
1837
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1838
|
-
break;
|
|
1839
|
-
}
|
|
1840
|
-
},
|
|
1841
|
-
});
|
|
1842
|
-
|
|
1843
|
-
node.logStructure();
|
|
1844
|
-
|
|
1845
|
-
// Get the interaction client
|
|
1846
|
-
this.log.info('Getting the interaction client');
|
|
1847
|
-
const interactionClient = await node.getInteractionClient();
|
|
1848
|
-
let cluster;
|
|
1849
|
-
let attributes;
|
|
1850
|
-
|
|
1851
|
-
// Log BasicInformationCluster
|
|
1852
|
-
cluster = BasicInformationCluster;
|
|
1853
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1854
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1855
|
-
});
|
|
1856
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1857
|
-
attributes.forEach((attribute) => {
|
|
1858
|
-
this.log.info(
|
|
1859
|
-
`- 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}`,
|
|
1860
|
-
);
|
|
1861
|
-
});
|
|
1862
|
-
|
|
1863
|
-
// Log PowerSourceCluster
|
|
1864
|
-
cluster = PowerSourceCluster;
|
|
1865
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1866
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1867
|
-
});
|
|
1868
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1869
|
-
attributes.forEach((attribute) => {
|
|
1870
|
-
this.log.info(
|
|
1871
|
-
`- 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}`,
|
|
1872
|
-
);
|
|
1873
|
-
});
|
|
1874
|
-
|
|
1875
|
-
// Log ThreadNetworkDiagnostics
|
|
1876
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1877
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1878
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1879
|
-
});
|
|
1880
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1881
|
-
attributes.forEach((attribute) => {
|
|
1882
|
-
this.log.info(
|
|
1883
|
-
`- 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}`,
|
|
1884
|
-
);
|
|
1885
|
-
});
|
|
1886
|
-
|
|
1887
|
-
// Log SwitchCluster
|
|
1888
|
-
cluster = SwitchCluster;
|
|
1889
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1890
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1891
|
-
});
|
|
1892
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1893
|
-
attributes.forEach((attribute) => {
|
|
1894
|
-
this.log.info(
|
|
1895
|
-
`- 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}`,
|
|
1896
|
-
);
|
|
1897
|
-
});
|
|
1898
|
-
|
|
1899
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1900
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1901
|
-
ignoreInitialTriggers: false,
|
|
1902
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1903
|
-
this.log.info(
|
|
1904
|
-
`***${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}`,
|
|
1905
|
-
),
|
|
1906
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1907
|
-
this.log.info(
|
|
1908
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1909
|
-
);
|
|
1910
|
-
},
|
|
1911
|
-
});
|
|
1912
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1913
|
-
}
|
|
1914
|
-
*/
|
|
1915
1352
|
}
|
|
1916
|
-
/** */
|
|
1917
|
-
/** Matter.js methods */
|
|
1918
|
-
/** */
|
|
1919
|
-
/**
|
|
1920
|
-
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1921
|
-
*
|
|
1922
|
-
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1923
|
-
*/
|
|
1924
1353
|
async startMatterStorage() {
|
|
1925
|
-
// Setup Matter storage
|
|
1926
1354
|
this.log.info(`Starting matter node storage...`);
|
|
1927
1355
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1928
1356
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1931,17 +1359,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1931
1359
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1932
1360
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1933
1361
|
this.log.info('Matter node storage started');
|
|
1934
|
-
// Backup matter storage since it is created/opened correctly
|
|
1935
1362
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1936
1363
|
}
|
|
1937
|
-
/**
|
|
1938
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1939
|
-
*
|
|
1940
|
-
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1941
|
-
* @param {string} backupName - The name of the backup directory to be created.
|
|
1942
|
-
* @private
|
|
1943
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1944
|
-
*/
|
|
1945
1364
|
async backupMatterStorage(storageName, backupName) {
|
|
1946
1365
|
this.log.info('Creating matter node storage backup...');
|
|
1947
1366
|
try {
|
|
@@ -1952,11 +1371,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1952
1371
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1953
1372
|
}
|
|
1954
1373
|
}
|
|
1955
|
-
/**
|
|
1956
|
-
* Stops the matter storage.
|
|
1957
|
-
*
|
|
1958
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1959
|
-
*/
|
|
1960
1374
|
async stopMatterStorage() {
|
|
1961
1375
|
this.log.info('Closing matter node storage...');
|
|
1962
1376
|
await this.matterStorageManager?.close();
|
|
@@ -1965,19 +1379,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1965
1379
|
this.matterbridgeContext = undefined;
|
|
1966
1380
|
this.log.info('Matter node storage closed');
|
|
1967
1381
|
}
|
|
1968
|
-
/**
|
|
1969
|
-
* Creates a server node storage context.
|
|
1970
|
-
*
|
|
1971
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1972
|
-
* @param {string} deviceName - The name of the device.
|
|
1973
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1974
|
-
* @param {number} vendorId - The vendor ID.
|
|
1975
|
-
* @param {string} vendorName - The vendor name.
|
|
1976
|
-
* @param {number} productId - The product ID.
|
|
1977
|
-
* @param {string} productName - The product name.
|
|
1978
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1979
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1980
|
-
*/
|
|
1981
1382
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1982
1383
|
const { randomBytes } = await import('node:crypto');
|
|
1983
1384
|
if (!this.matterStorageService)
|
|
@@ -2011,15 +1412,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2011
1412
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2012
1413
|
return storageContext;
|
|
2013
1414
|
}
|
|
2014
|
-
/**
|
|
2015
|
-
* Creates a server node.
|
|
2016
|
-
*
|
|
2017
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2018
|
-
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2019
|
-
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2020
|
-
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2021
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2022
|
-
*/
|
|
2023
1415
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
2024
1416
|
const storeId = await storageContext.get('storeId');
|
|
2025
1417
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -2029,37 +1421,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2029
1421
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2030
1422
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2031
1423
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2032
|
-
/**
|
|
2033
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2034
|
-
*/
|
|
2035
1424
|
const serverNode = await ServerNode.create({
|
|
2036
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
2037
1425
|
id: storeId,
|
|
2038
|
-
// Provide Network relevant configuration like the port
|
|
2039
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
2040
1426
|
network: {
|
|
2041
1427
|
listeningAddressIpv4: this.ipv4address,
|
|
2042
1428
|
listeningAddressIpv6: this.ipv6address,
|
|
2043
1429
|
port,
|
|
2044
1430
|
},
|
|
2045
|
-
// Provide the certificate for the device
|
|
2046
1431
|
operationalCredentials: {
|
|
2047
1432
|
certification: this.certification,
|
|
2048
1433
|
},
|
|
2049
|
-
// Provide Commissioning relevant settings
|
|
2050
|
-
// Optional for development/testing purposes
|
|
2051
1434
|
commissioning: {
|
|
2052
1435
|
passcode,
|
|
2053
1436
|
discriminator,
|
|
2054
1437
|
},
|
|
2055
|
-
// Provide Node announcement settings
|
|
2056
|
-
// Optional: If Ommitted some development defaults are used
|
|
2057
1438
|
productDescription: {
|
|
2058
1439
|
name: await storageContext.get('deviceName'),
|
|
2059
1440
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
2060
1441
|
},
|
|
2061
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2062
|
-
// Optional: If Omitted some development defaults are used
|
|
2063
1442
|
basicInformation: {
|
|
2064
1443
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2065
1444
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2077,13 +1456,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2077
1456
|
},
|
|
2078
1457
|
});
|
|
2079
1458
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2080
|
-
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
2081
1459
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
2082
1460
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
2083
1461
|
if (this.bridgeMode === 'bridge') {
|
|
2084
1462
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
2085
1463
|
if (resetSessions)
|
|
2086
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1464
|
+
this.matterbridgeSessionInformations = undefined;
|
|
2087
1465
|
this.matterbridgePaired = true;
|
|
2088
1466
|
}
|
|
2089
1467
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -2091,22 +1469,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2091
1469
|
if (plugin) {
|
|
2092
1470
|
plugin.fabricInformations = sanitizedFabrics;
|
|
2093
1471
|
if (resetSessions)
|
|
2094
|
-
plugin.sessionInformations = undefined;
|
|
1472
|
+
plugin.sessionInformations = undefined;
|
|
2095
1473
|
plugin.paired = true;
|
|
2096
1474
|
}
|
|
2097
1475
|
}
|
|
2098
1476
|
};
|
|
2099
|
-
/**
|
|
2100
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2101
|
-
* This means: It is added to the first fabric.
|
|
2102
|
-
*/
|
|
2103
1477
|
serverNode.lifecycle.commissioned.on(() => {
|
|
2104
1478
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
2105
1479
|
clearTimeout(this.endAdvertiseTimeout);
|
|
2106
1480
|
});
|
|
2107
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2108
1481
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2109
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2110
1482
|
serverNode.lifecycle.online.on(async () => {
|
|
2111
1483
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2112
1484
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2145,7 +1517,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2145
1517
|
}
|
|
2146
1518
|
}
|
|
2147
1519
|
}
|
|
2148
|
-
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
2149
1520
|
this.startEndAdvertiseTimer(serverNode);
|
|
2150
1521
|
}
|
|
2151
1522
|
else {
|
|
@@ -2157,7 +1528,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2157
1528
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2158
1529
|
this.emit('online', storeId);
|
|
2159
1530
|
});
|
|
2160
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2161
1531
|
serverNode.lifecycle.offline.on(() => {
|
|
2162
1532
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2163
1533
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -2182,10 +1552,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2182
1552
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2183
1553
|
this.emit('offline', storeId);
|
|
2184
1554
|
});
|
|
2185
|
-
/**
|
|
2186
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2187
|
-
* information is needed.
|
|
2188
|
-
*/
|
|
2189
1555
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2190
1556
|
let action = '';
|
|
2191
1557
|
switch (fabricAction) {
|
|
@@ -2216,24 +1582,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2216
1582
|
}
|
|
2217
1583
|
}
|
|
2218
1584
|
};
|
|
2219
|
-
/**
|
|
2220
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2221
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2222
|
-
*/
|
|
2223
1585
|
serverNode.events.sessions.opened.on((session) => {
|
|
2224
1586
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2225
1587
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2226
1588
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2227
1589
|
});
|
|
2228
|
-
/**
|
|
2229
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2230
|
-
*/
|
|
2231
1590
|
serverNode.events.sessions.closed.on((session) => {
|
|
2232
1591
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2233
1592
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2234
1593
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2235
1594
|
});
|
|
2236
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2237
1595
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2238
1596
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2239
1597
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -2242,11 +1600,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2242
1600
|
this.log.info(`Created server node for ${storeId}`);
|
|
2243
1601
|
return serverNode;
|
|
2244
1602
|
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2247
|
-
*
|
|
2248
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2249
|
-
*/
|
|
2250
1603
|
startEndAdvertiseTimer(matterServerNode) {
|
|
2251
1604
|
if (this.endAdvertiseTimeout) {
|
|
2252
1605
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -2275,25 +1628,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2275
1628
|
this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
|
|
2276
1629
|
}, 15 * 60 * 1000).unref();
|
|
2277
1630
|
}
|
|
2278
|
-
/**
|
|
2279
|
-
* Starts the specified server node.
|
|
2280
|
-
*
|
|
2281
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2282
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2283
|
-
*/
|
|
2284
1631
|
async startServerNode(matterServerNode) {
|
|
2285
1632
|
if (!matterServerNode)
|
|
2286
1633
|
return;
|
|
2287
1634
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2288
1635
|
await matterServerNode.start();
|
|
2289
1636
|
}
|
|
2290
|
-
/**
|
|
2291
|
-
* Stops the specified server node.
|
|
2292
|
-
*
|
|
2293
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2294
|
-
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2295
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2296
|
-
*/
|
|
2297
1637
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
2298
1638
|
if (!matterServerNode)
|
|
2299
1639
|
return;
|
|
@@ -2306,12 +1646,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2306
1646
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2307
1647
|
}
|
|
2308
1648
|
}
|
|
2309
|
-
/**
|
|
2310
|
-
* Advertises the specified server node.
|
|
2311
|
-
*
|
|
2312
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2313
|
-
* @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.
|
|
2314
|
-
*/
|
|
2315
1649
|
async advertiseServerNode(matterServerNode) {
|
|
2316
1650
|
if (matterServerNode) {
|
|
2317
1651
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2320,39 +1654,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
2320
1654
|
return { qrPairingCode, manualPairingCode };
|
|
2321
1655
|
}
|
|
2322
1656
|
}
|
|
2323
|
-
/**
|
|
2324
|
-
* Stop advertise the specified server node.
|
|
2325
|
-
*
|
|
2326
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2327
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2328
|
-
*/
|
|
2329
1657
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2330
1658
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2331
1659
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2332
1660
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2333
1661
|
}
|
|
2334
1662
|
}
|
|
2335
|
-
/**
|
|
2336
|
-
* Creates an aggregator node with the specified storage context.
|
|
2337
|
-
*
|
|
2338
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2339
|
-
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2340
|
-
*/
|
|
2341
1663
|
async createAggregatorNode(storageContext) {
|
|
2342
1664
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2343
1665
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2344
1666
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2345
1667
|
return aggregatorNode;
|
|
2346
1668
|
}
|
|
2347
|
-
/**
|
|
2348
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2349
|
-
*
|
|
2350
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2351
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2352
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2353
|
-
*/
|
|
2354
1669
|
async addBridgedEndpoint(pluginName, device) {
|
|
2355
|
-
// Check if the plugin is registered
|
|
2356
1670
|
const plugin = this.plugins.get(pluginName);
|
|
2357
1671
|
if (!plugin) {
|
|
2358
1672
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -2372,7 +1686,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2372
1686
|
}
|
|
2373
1687
|
else if (this.bridgeMode === 'bridge') {
|
|
2374
1688
|
if (device.mode === 'matter') {
|
|
2375
|
-
// Register and add the device to the matterbridge server node
|
|
2376
1689
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
2377
1690
|
if (!this.serverNode) {
|
|
2378
1691
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -2389,7 +1702,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2389
1702
|
}
|
|
2390
1703
|
}
|
|
2391
1704
|
else {
|
|
2392
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2393
1705
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2394
1706
|
if (!this.aggregatorNode) {
|
|
2395
1707
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2407,7 +1719,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2407
1719
|
}
|
|
2408
1720
|
}
|
|
2409
1721
|
else if (this.bridgeMode === 'childbridge') {
|
|
2410
|
-
// Register and add the device to the plugin server node
|
|
2411
1722
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2412
1723
|
try {
|
|
2413
1724
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2431,12 +1742,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2431
1742
|
return;
|
|
2432
1743
|
}
|
|
2433
1744
|
}
|
|
2434
|
-
// Register and add the device to the plugin aggregator node
|
|
2435
1745
|
if (plugin.type === 'DynamicPlatform') {
|
|
2436
1746
|
try {
|
|
2437
1747
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2438
1748
|
await this.createDynamicPlugin(plugin);
|
|
2439
|
-
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
2440
1749
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2441
1750
|
if (!plugin.aggregatorNode) {
|
|
2442
1751
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2459,28 +1768,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2459
1768
|
plugin.registeredDevices++;
|
|
2460
1769
|
if (plugin.addedDevices !== undefined)
|
|
2461
1770
|
plugin.addedDevices++;
|
|
2462
|
-
// Add the device to the DeviceManager
|
|
2463
1771
|
this.devices.set(device);
|
|
2464
|
-
// Subscribe to the reachable$Changed event
|
|
2465
1772
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2466
1773
|
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}`);
|
|
2467
1774
|
}
|
|
2468
|
-
/**
|
|
2469
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2470
|
-
*
|
|
2471
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2472
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2473
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2474
|
-
*/
|
|
2475
1775
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2476
1776
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2477
|
-
// Check if the plugin is registered
|
|
2478
1777
|
const plugin = this.plugins.get(pluginName);
|
|
2479
1778
|
if (!plugin) {
|
|
2480
1779
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2481
1780
|
return;
|
|
2482
1781
|
}
|
|
2483
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2484
1782
|
if (this.bridgeMode === 'bridge') {
|
|
2485
1783
|
if (!this.aggregatorNode) {
|
|
2486
1784
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2495,7 +1793,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2495
1793
|
}
|
|
2496
1794
|
else if (this.bridgeMode === 'childbridge') {
|
|
2497
1795
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2498
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2499
1796
|
}
|
|
2500
1797
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2501
1798
|
if (!plugin.aggregatorNode) {
|
|
@@ -2510,21 +1807,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2510
1807
|
if (plugin.addedDevices !== undefined)
|
|
2511
1808
|
plugin.addedDevices--;
|
|
2512
1809
|
}
|
|
2513
|
-
// Remove the device from the DeviceManager
|
|
2514
1810
|
this.devices.remove(device);
|
|
2515
1811
|
}
|
|
2516
|
-
/**
|
|
2517
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2518
|
-
*
|
|
2519
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2520
|
-
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2521
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2522
|
-
*
|
|
2523
|
-
* @remarks
|
|
2524
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2525
|
-
* It also applies a delay between each removal if specified.
|
|
2526
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2527
|
-
*/
|
|
2528
1812
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2529
1813
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2530
1814
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2535,15 +1819,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2535
1819
|
if (delay > 0)
|
|
2536
1820
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2537
1821
|
}
|
|
2538
|
-
/**
|
|
2539
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2540
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2541
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2542
|
-
*
|
|
2543
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2544
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2545
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2546
|
-
*/
|
|
2547
1822
|
async subscribeAttributeChanged(plugin, device) {
|
|
2548
1823
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2549
1824
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -2559,12 +1834,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2559
1834
|
});
|
|
2560
1835
|
}
|
|
2561
1836
|
}
|
|
2562
|
-
/**
|
|
2563
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2564
|
-
*
|
|
2565
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2566
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2567
|
-
*/
|
|
2568
1837
|
sanitizeFabricInformations(fabricInfo) {
|
|
2569
1838
|
return fabricInfo.map((info) => {
|
|
2570
1839
|
return {
|
|
@@ -2578,12 +1847,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2578
1847
|
};
|
|
2579
1848
|
});
|
|
2580
1849
|
}
|
|
2581
|
-
/**
|
|
2582
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2583
|
-
*
|
|
2584
|
-
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2585
|
-
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2586
|
-
*/
|
|
2587
1850
|
sanitizeSessionInformation(sessions) {
|
|
2588
1851
|
return sessions
|
|
2589
1852
|
.filter((session) => session.isPeerActive)
|
|
@@ -2610,21 +1873,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2610
1873
|
};
|
|
2611
1874
|
});
|
|
2612
1875
|
}
|
|
2613
|
-
/**
|
|
2614
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2615
|
-
*
|
|
2616
|
-
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2617
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2618
|
-
*/
|
|
2619
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2620
1876
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2621
|
-
/*
|
|
2622
|
-
for (const child of aggregatorNode.parts) {
|
|
2623
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2624
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2625
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2626
|
-
}
|
|
2627
|
-
*/
|
|
2628
1877
|
}
|
|
2629
1878
|
getVendorIdName = (vendorId) => {
|
|
2630
1879
|
if (!vendorId)
|
|
@@ -2661,11 +1910,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2661
1910
|
case 5264:
|
|
2662
1911
|
vendorName = '(Shelly)';
|
|
2663
1912
|
break;
|
|
1913
|
+
case 0x1488:
|
|
1914
|
+
vendorName = '(ShortcutLabsFlic)';
|
|
1915
|
+
break;
|
|
2664
1916
|
case 65521:
|
|
2665
|
-
vendorName = '(
|
|
1917
|
+
vendorName = '(MatterTest)';
|
|
2666
1918
|
break;
|
|
2667
1919
|
}
|
|
2668
1920
|
return vendorName;
|
|
2669
1921
|
};
|
|
2670
1922
|
}
|
|
2671
|
-
//# sourceMappingURL=matterbridge.js.map
|