matterbridge 3.1.3 → 3.1.4-dev-20250715-075e722
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 +16 -0
- package/README.md +18 -18
- 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 +18 -423
- 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 +55 -788
- package/dist/matterbridgeAccessoryPlatform.js +0 -36
- package/dist/matterbridgeBehaviors.js +1 -61
- package/dist/matterbridgeDeviceTypes.js +15 -579
- package/dist/matterbridgeDynamicPlatform.js +0 -36
- package/dist/matterbridgeEndpoint.js +42 -1106
- 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 +16 -96
- package/dist/utils/spawn.js +0 -18
- package/dist/utils/wait.js +9 -62
- package/npm-shrinkwrap.json +3 -3
- package/package.json +2 -3
- 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 -444
- 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 -1250
- 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 -195
- 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/bin/{matterbridge → matterbridge.js} +0 -0
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
|
-
|
|
31
|
-
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
|
|
32
|
-
// NodeStorage module
|
|
6
|
+
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
|
|
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: '',
|
|
@@ -97,7 +66,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
97
66
|
shellySysUpdate: false,
|
|
98
67
|
shellyMainUpdate: false,
|
|
99
68
|
profile: getParameter('profile'),
|
|
100
|
-
loggerLevel: "info"
|
|
69
|
+
loggerLevel: "info",
|
|
101
70
|
fileLogger: false,
|
|
102
71
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
103
72
|
matterFileLogger: false,
|
|
@@ -125,18 +94,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
125
94
|
shutdown = false;
|
|
126
95
|
edge = true;
|
|
127
96
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
128
|
-
|
|
129
|
-
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
97
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
130
98
|
matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
131
99
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
132
100
|
plugins;
|
|
133
101
|
devices;
|
|
134
102
|
frontend = new Frontend(this);
|
|
135
|
-
// Matterbridge storage
|
|
136
103
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
104
|
nodeStorage;
|
|
138
105
|
nodeContext;
|
|
139
|
-
// Cleanup
|
|
140
106
|
hasCleanupStarted = false;
|
|
141
107
|
initialized = false;
|
|
142
108
|
execRunningCount = 0;
|
|
@@ -150,23 +116,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
150
116
|
sigtermHandler;
|
|
151
117
|
exceptionHandler;
|
|
152
118
|
rejectionHandler;
|
|
153
|
-
// Matter environment
|
|
154
119
|
environment = Environment.default;
|
|
155
|
-
// Matter storage
|
|
156
120
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
157
121
|
matterStorageService;
|
|
158
122
|
matterStorageManager;
|
|
159
123
|
matterbridgeContext;
|
|
160
124
|
controllerContext;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
certification; // device certification
|
|
169
|
-
// Matter nodes
|
|
125
|
+
mdnsInterface;
|
|
126
|
+
ipv4address;
|
|
127
|
+
ipv6address;
|
|
128
|
+
port;
|
|
129
|
+
passcode;
|
|
130
|
+
discriminator;
|
|
131
|
+
certification;
|
|
170
132
|
serverNode;
|
|
171
133
|
aggregatorNode;
|
|
172
134
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -174,31 +136,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
174
136
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
175
137
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
176
138
|
static instance;
|
|
177
|
-
// We load asyncronously so is private
|
|
178
139
|
constructor() {
|
|
179
140
|
super();
|
|
180
141
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Retrieves the list of Matterbridge devices.
|
|
183
|
-
*
|
|
184
|
-
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
185
|
-
*/
|
|
186
142
|
getDevices() {
|
|
187
143
|
return this.devices.array();
|
|
188
144
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Retrieves the list of registered plugins.
|
|
191
|
-
*
|
|
192
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
193
|
-
*/
|
|
194
145
|
getPlugins() {
|
|
195
146
|
return this.plugins.array();
|
|
196
147
|
}
|
|
197
|
-
/**
|
|
198
|
-
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
199
|
-
*
|
|
200
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
201
|
-
*/
|
|
202
148
|
async setLogLevel(logLevel) {
|
|
203
149
|
if (this.log)
|
|
204
150
|
this.log.logLevel = logLevel;
|
|
@@ -212,31 +158,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
212
158
|
for (const plugin of this.plugins) {
|
|
213
159
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
214
160
|
continue;
|
|
215
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug"
|
|
216
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug"
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
161
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
162
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
163
|
+
}
|
|
164
|
+
let callbackLogLevel = "notice";
|
|
165
|
+
if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
166
|
+
callbackLogLevel = "info";
|
|
167
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
168
|
+
callbackLogLevel = "debug";
|
|
224
169
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
225
170
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
226
171
|
}
|
|
227
|
-
//* ************************************************************************************************************************************ */
|
|
228
|
-
// loadInstance() and cleanup() methods */
|
|
229
|
-
//* ************************************************************************************************************************************ */
|
|
230
|
-
/**
|
|
231
|
-
* Loads an instance of the Matterbridge class.
|
|
232
|
-
* If an instance already exists, return that instance.
|
|
233
|
-
*
|
|
234
|
-
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
235
|
-
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
236
|
-
*/
|
|
237
172
|
static async loadInstance(initialize = false) {
|
|
238
173
|
if (!Matterbridge.instance) {
|
|
239
|
-
// eslint-disable-next-line no-console
|
|
240
174
|
if (hasParameter('debug'))
|
|
241
175
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
242
176
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -245,17 +179,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
245
179
|
}
|
|
246
180
|
return Matterbridge.instance;
|
|
247
181
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Call cleanup() and dispose MdnsService.
|
|
250
|
-
*
|
|
251
|
-
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
252
|
-
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
253
|
-
*
|
|
254
|
-
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
255
|
-
*/
|
|
256
182
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
257
183
|
this.log.info(`Destroy instance...`);
|
|
258
|
-
// Save server nodes to close
|
|
259
184
|
const servers = [];
|
|
260
185
|
if (this.bridgeMode === 'bridge') {
|
|
261
186
|
if (this.serverNode)
|
|
@@ -273,109 +198,76 @@ export class Matterbridge extends EventEmitter {
|
|
|
273
198
|
servers.push(device.serverNode);
|
|
274
199
|
}
|
|
275
200
|
}
|
|
276
|
-
// Let any already‐queued microtasks run first
|
|
277
201
|
await Promise.resolve();
|
|
278
|
-
// Wait for the cleanup to finish
|
|
279
202
|
await new Promise((resolve) => {
|
|
280
203
|
setTimeout(resolve, pause);
|
|
281
204
|
});
|
|
282
|
-
// Cleanup
|
|
283
205
|
await this.cleanup('destroying instance...', false, timeout);
|
|
284
|
-
// Close servers mdns service
|
|
285
206
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
286
207
|
for (const server of servers) {
|
|
287
208
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
288
209
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
289
210
|
}
|
|
290
|
-
// Let any already‐queued microtasks run first
|
|
291
211
|
await Promise.resolve();
|
|
292
|
-
// Wait for the cleanup to finish
|
|
293
212
|
await new Promise((resolve) => {
|
|
294
213
|
setTimeout(resolve, pause);
|
|
295
214
|
});
|
|
296
215
|
}
|
|
297
|
-
/**
|
|
298
|
-
* Initializes the Matterbridge application.
|
|
299
|
-
*
|
|
300
|
-
* @remarks
|
|
301
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
302
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
303
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
304
|
-
*
|
|
305
|
-
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
306
|
-
*/
|
|
307
216
|
async initialize() {
|
|
308
|
-
// Emit the initialize_started event
|
|
309
217
|
this.emit('initialize_started');
|
|
310
|
-
// Set the restart mode
|
|
311
218
|
if (hasParameter('service'))
|
|
312
219
|
this.restartMode = 'service';
|
|
313
220
|
if (hasParameter('docker'))
|
|
314
221
|
this.restartMode = 'docker';
|
|
315
|
-
// Set the matterbridge home directory
|
|
316
222
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
317
223
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
318
224
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
319
|
-
// Set the matterbridge directory
|
|
320
225
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
321
226
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
322
227
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
323
228
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
324
229
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
325
|
-
// Set the matterbridge plugin directory
|
|
326
230
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
327
231
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
328
232
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
329
|
-
// Set the matterbridge cert directory
|
|
330
233
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
331
234
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
332
235
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
333
|
-
// Set the matterbridge root directory
|
|
334
236
|
const { fileURLToPath } = await import('node:url');
|
|
335
237
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
336
238
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
337
239
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
338
|
-
// Setup the matter environment
|
|
339
240
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
340
241
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
341
242
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
342
243
|
this.environment.vars.set('runtime.signals', false);
|
|
343
244
|
this.environment.vars.set('runtime.exitcode', false);
|
|
344
|
-
// Register process handlers
|
|
345
245
|
this.registerProcessHandlers();
|
|
346
|
-
// Initialize nodeStorage and nodeContext
|
|
347
246
|
try {
|
|
348
247
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
349
248
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
350
249
|
this.log.debug('Creating node storage context for matterbridge');
|
|
351
250
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
352
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
353
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
354
251
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
355
252
|
for (const key of keys) {
|
|
356
253
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
357
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
358
254
|
await this.nodeStorage?.storage.get(key);
|
|
359
255
|
}
|
|
360
256
|
const storages = await this.nodeStorage.getStorageNames();
|
|
361
257
|
for (const storage of storages) {
|
|
362
258
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
363
259
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
364
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
365
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
366
260
|
const keys = (await nodeContext?.storage.keys());
|
|
367
261
|
keys.forEach(async (key) => {
|
|
368
262
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
369
263
|
await nodeContext?.get(key);
|
|
370
264
|
});
|
|
371
265
|
}
|
|
372
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
373
266
|
this.log.debug('Creating node storage backup...');
|
|
374
267
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
375
268
|
this.log.debug('Created node storage backup');
|
|
376
269
|
}
|
|
377
270
|
catch (error) {
|
|
378
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
379
271
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
380
272
|
if (hasParameter('norestore')) {
|
|
381
273
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -389,20 +281,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
389
281
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
390
282
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
391
283
|
}
|
|
392
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
393
284
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
394
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
395
285
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
396
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
397
286
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
398
|
-
// Certificate management
|
|
399
287
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
400
288
|
try {
|
|
401
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
402
289
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
403
290
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
404
291
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
405
|
-
// Set the vendorId, vendorName, productId and productName if they are present in the pairing file
|
|
406
292
|
if (isValidNumber(pairingFileJson.vendorId))
|
|
407
293
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
408
294
|
if (isValidString(pairingFileJson.vendorName, 3))
|
|
@@ -411,14 +297,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
411
297
|
this.aggregatorProductId = pairingFileJson.productId;
|
|
412
298
|
if (isValidString(pairingFileJson.productName, 3))
|
|
413
299
|
this.aggregatorProductName = pairingFileJson.productName;
|
|
414
|
-
// Override the passcode and discriminator if they are present in the pairing file
|
|
415
300
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
416
301
|
this.passcode = pairingFileJson.passcode;
|
|
417
302
|
this.discriminator = pairingFileJson.discriminator;
|
|
418
303
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
419
304
|
}
|
|
420
|
-
// Set the certification if it is present in the pairing file
|
|
421
|
-
/* istanbul ignore next if */
|
|
422
305
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
423
306
|
const hexStringToUint8Array = (hexString) => {
|
|
424
307
|
const matches = hexString.match(/.{1,2}/g);
|
|
@@ -436,44 +319,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
436
319
|
catch (error) {
|
|
437
320
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
438
321
|
}
|
|
439
|
-
// Store the passcode, discriminator and port in the node context
|
|
440
322
|
await this.nodeContext.set('matterport', this.port);
|
|
441
323
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
442
324
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
443
325
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
444
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
445
326
|
if (hasParameter('logger')) {
|
|
446
327
|
const level = getParameter('logger');
|
|
447
328
|
if (level === 'debug') {
|
|
448
|
-
this.log.logLevel = "debug"
|
|
329
|
+
this.log.logLevel = "debug";
|
|
449
330
|
}
|
|
450
331
|
else if (level === 'info') {
|
|
451
|
-
this.log.logLevel = "info"
|
|
332
|
+
this.log.logLevel = "info";
|
|
452
333
|
}
|
|
453
334
|
else if (level === 'notice') {
|
|
454
|
-
this.log.logLevel = "notice"
|
|
335
|
+
this.log.logLevel = "notice";
|
|
455
336
|
}
|
|
456
337
|
else if (level === 'warn') {
|
|
457
|
-
this.log.logLevel = "warn"
|
|
338
|
+
this.log.logLevel = "warn";
|
|
458
339
|
}
|
|
459
340
|
else if (level === 'error') {
|
|
460
|
-
this.log.logLevel = "error"
|
|
341
|
+
this.log.logLevel = "error";
|
|
461
342
|
}
|
|
462
343
|
else if (level === 'fatal') {
|
|
463
|
-
this.log.logLevel = "fatal"
|
|
344
|
+
this.log.logLevel = "fatal";
|
|
464
345
|
}
|
|
465
346
|
else {
|
|
466
347
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
467
|
-
this.log.logLevel = "info"
|
|
348
|
+
this.log.logLevel = "info";
|
|
468
349
|
}
|
|
469
350
|
}
|
|
470
351
|
else {
|
|
471
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice"
|
|
352
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
472
353
|
}
|
|
473
354
|
this.frontend.logLevel = this.log.logLevel;
|
|
474
355
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
475
356
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
476
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
477
357
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
478
358
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
|
|
479
359
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -482,7 +362,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
482
362
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
483
363
|
if (this.profile !== undefined)
|
|
484
364
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
485
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
486
365
|
if (hasParameter('matterlogger')) {
|
|
487
366
|
const level = getParameter('matterlogger');
|
|
488
367
|
if (level === 'debug') {
|
|
@@ -513,9 +392,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
513
392
|
}
|
|
514
393
|
Logger.format = MatterLogFormat.ANSI;
|
|
515
394
|
Logger.setLogger('default', this.createMatterLogger());
|
|
516
|
-
// Logger.destinations.default.write = this.createMatterLogger();
|
|
517
395
|
this.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
518
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
519
396
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
520
397
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
521
398
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -524,20 +401,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
524
401
|
});
|
|
525
402
|
}
|
|
526
403
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
527
|
-
// Log network interfaces
|
|
528
404
|
const networkInterfaces = os.networkInterfaces();
|
|
529
|
-
// console.log(`Network interfaces:`, networkInterfaces);
|
|
530
405
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
531
406
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
532
407
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
533
408
|
if (ifaces && ifaces.length > 0) {
|
|
534
|
-
this.log.debug(`Network interface
|
|
409
|
+
this.log.debug(`Network interface ${BLUE}${ifaceName}${db}:`);
|
|
535
410
|
ifaces.forEach((iface) => {
|
|
536
|
-
this.log.debug(`- ${CYAN}${iface.family}${db} address ${CYAN}${iface.address}${db} netmask ${CYAN}${iface.netmask}${db} mac ${CYAN}${iface.mac}${db}
|
|
411
|
+
this.log.debug(`- ${CYAN}${iface.family}${db} address ${CYAN}${iface.address}${db} netmask ${CYAN}${iface.netmask}${db} mac ${CYAN}${iface.mac}${db}` +
|
|
412
|
+
`${iface.scopeid ? ` scopeid ${CYAN}${iface.scopeid}${db}` : ''}${iface.cidr ? ` cidr ${CYAN}${iface.cidr}${db}` : ''} ${CYAN}${iface.internal ? 'internal' : 'external'}${db}`);
|
|
537
413
|
});
|
|
538
414
|
}
|
|
539
415
|
}
|
|
540
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
541
416
|
if (hasParameter('mdnsinterface')) {
|
|
542
417
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
543
418
|
}
|
|
@@ -546,7 +421,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
546
421
|
if (this.mdnsInterface === '')
|
|
547
422
|
this.mdnsInterface = undefined;
|
|
548
423
|
}
|
|
549
|
-
// Validate mdnsInterface
|
|
550
424
|
if (this.mdnsInterface) {
|
|
551
425
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
552
426
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -559,7 +433,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
559
433
|
}
|
|
560
434
|
if (this.mdnsInterface)
|
|
561
435
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
562
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
563
436
|
if (hasParameter('ipv4address')) {
|
|
564
437
|
this.ipv4address = getParameter('ipv4address');
|
|
565
438
|
}
|
|
@@ -568,7 +441,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
568
441
|
if (this.ipv4address === '')
|
|
569
442
|
this.ipv4address = undefined;
|
|
570
443
|
}
|
|
571
|
-
// Validate ipv4address
|
|
572
444
|
if (this.ipv4address) {
|
|
573
445
|
let isValid = false;
|
|
574
446
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -584,7 +456,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
584
456
|
await this.nodeContext.remove('matteripv4address');
|
|
585
457
|
}
|
|
586
458
|
}
|
|
587
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
588
459
|
if (hasParameter('ipv6address')) {
|
|
589
460
|
this.ipv6address = getParameter('ipv6address');
|
|
590
461
|
}
|
|
@@ -593,7 +464,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
593
464
|
if (this.ipv6address === '')
|
|
594
465
|
this.ipv6address = undefined;
|
|
595
466
|
}
|
|
596
|
-
// Validate ipv6address
|
|
597
467
|
if (this.ipv6address) {
|
|
598
468
|
let isValid = false;
|
|
599
469
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -602,7 +472,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
602
472
|
isValid = true;
|
|
603
473
|
break;
|
|
604
474
|
}
|
|
605
|
-
/* istanbul ignore next */
|
|
606
475
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
607
476
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
608
477
|
isValid = true;
|
|
@@ -615,7 +484,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
615
484
|
await this.nodeContext.remove('matteripv6address');
|
|
616
485
|
}
|
|
617
486
|
}
|
|
618
|
-
// Initialize the virtual mode
|
|
619
487
|
if (hasParameter('novirtual')) {
|
|
620
488
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
621
489
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -624,19 +492,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
624
492
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
625
493
|
}
|
|
626
494
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
627
|
-
// Initialize PluginManager
|
|
628
495
|
this.plugins = new PluginManager(this);
|
|
629
496
|
await this.plugins.loadFromStorage();
|
|
630
497
|
this.plugins.logLevel = this.log.logLevel;
|
|
631
|
-
// Initialize DeviceManager
|
|
632
498
|
this.devices = new DeviceManager(this);
|
|
633
499
|
this.devices.logLevel = this.log.logLevel;
|
|
634
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
635
500
|
for (const plugin of this.plugins) {
|
|
636
501
|
const packageJson = await this.plugins.parse(plugin);
|
|
637
502
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
638
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
639
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
640
503
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
641
504
|
try {
|
|
642
505
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -659,7 +522,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
659
522
|
await plugin.nodeContext.set('description', plugin.description);
|
|
660
523
|
await plugin.nodeContext.set('author', plugin.author);
|
|
661
524
|
}
|
|
662
|
-
// Log system info and create .matterbridge directory
|
|
663
525
|
await this.logNodeAndSystemInfo();
|
|
664
526
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
665
527
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -667,7 +529,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
667
529
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
668
530
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
669
531
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
670
|
-
// Check node version and throw error
|
|
671
532
|
const minNodeVersion = 18;
|
|
672
533
|
const nodeVersion = process.versions.node;
|
|
673
534
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -675,18 +536,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
675
536
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
676
537
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
677
538
|
}
|
|
678
|
-
// Parse command line
|
|
679
539
|
await this.parseCommandLine();
|
|
680
|
-
// Emit the initialize_completed event
|
|
681
540
|
this.emit('initialize_completed');
|
|
682
541
|
this.initialized = true;
|
|
683
542
|
}
|
|
684
|
-
/**
|
|
685
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
686
|
-
*
|
|
687
|
-
* @private
|
|
688
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
689
|
-
*/
|
|
690
543
|
async parseCommandLine() {
|
|
691
544
|
if (hasParameter('help')) {
|
|
692
545
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -747,19 +600,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
747
600
|
}
|
|
748
601
|
index++;
|
|
749
602
|
}
|
|
750
|
-
/*
|
|
751
|
-
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
752
|
-
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
753
|
-
serializedRegisteredDevices?.forEach((device, index) => {
|
|
754
|
-
if (index !== serializedRegisteredDevices.length - 1) {
|
|
755
|
-
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
756
|
-
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
757
|
-
} else {
|
|
758
|
-
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
759
|
-
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
760
|
-
}
|
|
761
|
-
});
|
|
762
|
-
*/
|
|
763
603
|
this.shutdown = true;
|
|
764
604
|
return;
|
|
765
605
|
}
|
|
@@ -775,7 +615,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
775
615
|
}
|
|
776
616
|
if (hasParameter('loginterfaces')) {
|
|
777
617
|
const { logInterfaces } = await import('./utils/network.js');
|
|
778
|
-
this.log.info(`${plg}Matterbridge${nf} network interfaces log`);
|
|
779
618
|
logInterfaces();
|
|
780
619
|
this.shutdown = true;
|
|
781
620
|
return;
|
|
@@ -810,7 +649,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
810
649
|
this.shutdown = true;
|
|
811
650
|
return;
|
|
812
651
|
}
|
|
813
|
-
// Start the matter storage and create the matterbridge context
|
|
814
652
|
try {
|
|
815
653
|
await this.startMatterStorage();
|
|
816
654
|
}
|
|
@@ -818,21 +656,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
818
656
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
819
657
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
820
658
|
}
|
|
821
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
822
659
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
823
660
|
this.initialized = true;
|
|
824
661
|
await this.shutdownProcessAndReset();
|
|
825
662
|
this.shutdown = true;
|
|
826
663
|
return;
|
|
827
664
|
}
|
|
828
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
829
665
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
830
666
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
831
667
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
832
668
|
if (plugin) {
|
|
833
669
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
834
670
|
if (!matterStorageManager) {
|
|
835
|
-
/* istanbul ignore next */
|
|
836
671
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
837
672
|
}
|
|
838
673
|
else {
|
|
@@ -851,39 +686,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
851
686
|
this.shutdown = true;
|
|
852
687
|
return;
|
|
853
688
|
}
|
|
854
|
-
// Initialize frontend
|
|
855
689
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
856
690
|
await this.frontend.start(getIntParameter('frontend'));
|
|
857
|
-
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
858
691
|
clearTimeout(this.checkUpdateTimeout);
|
|
859
692
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
860
693
|
const { checkUpdates } = await import('./update.js');
|
|
861
694
|
checkUpdates(this);
|
|
862
695
|
}, 30 * 1000).unref();
|
|
863
|
-
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
864
696
|
clearInterval(this.checkUpdateInterval);
|
|
865
697
|
this.checkUpdateInterval = setInterval(async () => {
|
|
866
698
|
const { checkUpdates } = await import('./update.js');
|
|
867
699
|
checkUpdates(this);
|
|
868
700
|
}, 12 * 60 * 60 * 1000).unref();
|
|
869
|
-
// Start the matterbridge in mode test
|
|
870
701
|
if (hasParameter('test')) {
|
|
871
702
|
this.bridgeMode = 'bridge';
|
|
872
703
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
873
704
|
return;
|
|
874
705
|
}
|
|
875
|
-
// Start the matterbridge in mode controller
|
|
876
706
|
if (hasParameter('controller')) {
|
|
877
707
|
this.bridgeMode = 'controller';
|
|
878
708
|
await this.startController();
|
|
879
709
|
return;
|
|
880
710
|
}
|
|
881
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
882
711
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
883
712
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
884
713
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
885
714
|
}
|
|
886
|
-
// Start matterbridge in bridge mode
|
|
887
715
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
888
716
|
this.bridgeMode = 'bridge';
|
|
889
717
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -891,7 +719,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
891
719
|
await this.startBridge();
|
|
892
720
|
return;
|
|
893
721
|
}
|
|
894
|
-
// Start matterbridge in childbridge mode
|
|
895
722
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
896
723
|
this.bridgeMode = 'childbridge';
|
|
897
724
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -900,20 +727,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
900
727
|
return;
|
|
901
728
|
}
|
|
902
729
|
}
|
|
903
|
-
/**
|
|
904
|
-
* Asynchronously loads and starts the registered plugins.
|
|
905
|
-
*
|
|
906
|
-
* This method is responsible for initializing and starting all enabled plugins.
|
|
907
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
908
|
-
*
|
|
909
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
910
|
-
*/
|
|
911
730
|
async startPlugins() {
|
|
912
|
-
// Check, load and start the plugins
|
|
913
731
|
for (const plugin of this.plugins) {
|
|
914
732
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
915
733
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
916
|
-
// Check if the plugin is available
|
|
917
734
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
918
735
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
919
736
|
plugin.enabled = false;
|
|
@@ -931,14 +748,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
931
748
|
plugin.configured = false;
|
|
932
749
|
plugin.registeredDevices = undefined;
|
|
933
750
|
plugin.addedDevices = undefined;
|
|
934
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
751
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
935
752
|
}
|
|
936
753
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
937
754
|
}
|
|
938
|
-
/**
|
|
939
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
940
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
941
|
-
*/
|
|
942
755
|
registerProcessHandlers() {
|
|
943
756
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
944
757
|
process.removeAllListeners('uncaughtException');
|
|
@@ -965,9 +778,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
965
778
|
};
|
|
966
779
|
process.on('SIGTERM', this.sigtermHandler);
|
|
967
780
|
}
|
|
968
|
-
/**
|
|
969
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
970
|
-
*/
|
|
971
781
|
deregisterProcessHandlers() {
|
|
972
782
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
973
783
|
if (this.exceptionHandler)
|
|
@@ -984,17 +794,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
794
|
process.off('SIGTERM', this.sigtermHandler);
|
|
985
795
|
this.sigtermHandler = undefined;
|
|
986
796
|
}
|
|
987
|
-
/**
|
|
988
|
-
* Logs the node and system information.
|
|
989
|
-
*/
|
|
990
797
|
async logNodeAndSystemInfo() {
|
|
991
|
-
// IP address information
|
|
992
798
|
const networkInterfaces = os.networkInterfaces();
|
|
993
799
|
this.systemInformation.interfaceName = '';
|
|
994
800
|
this.systemInformation.ipv4Address = '';
|
|
995
801
|
this.systemInformation.ipv6Address = '';
|
|
996
802
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
997
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
998
803
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
999
804
|
continue;
|
|
1000
805
|
if (!interfaceDetails) {
|
|
@@ -1020,22 +825,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1020
825
|
break;
|
|
1021
826
|
}
|
|
1022
827
|
}
|
|
1023
|
-
// Node information
|
|
1024
828
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
1025
829
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
1026
830
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
1027
831
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1028
|
-
// Host system information
|
|
1029
832
|
this.systemInformation.hostname = os.hostname();
|
|
1030
833
|
this.systemInformation.user = os.userInfo().username;
|
|
1031
|
-
this.systemInformation.osType = os.type();
|
|
1032
|
-
this.systemInformation.osRelease = os.release();
|
|
1033
|
-
this.systemInformation.osPlatform = os.platform();
|
|
1034
|
-
this.systemInformation.osArch = os.arch();
|
|
1035
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
1036
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
1037
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1038
|
-
// Log the system information
|
|
834
|
+
this.systemInformation.osType = os.type();
|
|
835
|
+
this.systemInformation.osRelease = os.release();
|
|
836
|
+
this.systemInformation.osPlatform = os.platform();
|
|
837
|
+
this.systemInformation.osArch = os.arch();
|
|
838
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
839
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
840
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1039
841
|
this.log.debug('Host System Information:');
|
|
1040
842
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
1041
843
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -1051,17 +853,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1051
853
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
1052
854
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
1053
855
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1054
|
-
// Log directories
|
|
1055
856
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1056
857
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1057
858
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1058
859
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1059
860
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1060
|
-
// Global node_modules directory
|
|
1061
861
|
if (this.nodeContext)
|
|
1062
862
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1063
863
|
if (this.globalModulesDirectory === '') {
|
|
1064
|
-
// First run of Matterbridge so the node storage is empty
|
|
1065
864
|
try {
|
|
1066
865
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
1067
866
|
this.execRunningCount++;
|
|
@@ -1076,81 +875,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
1076
875
|
}
|
|
1077
876
|
else
|
|
1078
877
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1079
|
-
/* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
|
|
1080
|
-
else {
|
|
1081
|
-
this.getGlobalNodeModules()
|
|
1082
|
-
.then(async (globalModulesDirectory) => {
|
|
1083
|
-
this.globalModulesDirectory = globalModulesDirectory;
|
|
1084
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1085
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1086
|
-
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1087
|
-
})
|
|
1088
|
-
.catch((error) => {
|
|
1089
|
-
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1090
|
-
});
|
|
1091
|
-
}*/
|
|
1092
|
-
// Matterbridge version
|
|
1093
878
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1094
879
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
1095
880
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
1096
881
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1097
|
-
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
1098
882
|
if (this.nodeContext)
|
|
1099
883
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1100
884
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1101
|
-
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
1102
885
|
if (this.nodeContext)
|
|
1103
886
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
1104
887
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1105
|
-
// Current working directory
|
|
1106
888
|
const currentDir = process.cwd();
|
|
1107
889
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1108
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1109
890
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1110
891
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1111
892
|
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1114
|
-
*
|
|
1115
|
-
* @returns {Function} The MatterLogger function.
|
|
1116
|
-
*/
|
|
1117
893
|
createMatterLogger() {
|
|
1118
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
894
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1119
895
|
return (level, formattedLog) => {
|
|
1120
896
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1121
897
|
const message = formattedLog.slice(65);
|
|
1122
898
|
matterLogger.logName = logger;
|
|
1123
899
|
switch (level) {
|
|
1124
900
|
case MatterLogLevel.DEBUG:
|
|
1125
|
-
matterLogger.log("debug"
|
|
901
|
+
matterLogger.log("debug", message);
|
|
1126
902
|
break;
|
|
1127
903
|
case MatterLogLevel.INFO:
|
|
1128
|
-
matterLogger.log("info"
|
|
904
|
+
matterLogger.log("info", message);
|
|
1129
905
|
break;
|
|
1130
906
|
case MatterLogLevel.NOTICE:
|
|
1131
|
-
matterLogger.log("notice"
|
|
907
|
+
matterLogger.log("notice", message);
|
|
1132
908
|
break;
|
|
1133
909
|
case MatterLogLevel.WARN:
|
|
1134
|
-
matterLogger.log("warn"
|
|
910
|
+
matterLogger.log("warn", message);
|
|
1135
911
|
break;
|
|
1136
912
|
case MatterLogLevel.ERROR:
|
|
1137
|
-
matterLogger.log("error"
|
|
913
|
+
matterLogger.log("error", message);
|
|
1138
914
|
break;
|
|
1139
915
|
case MatterLogLevel.FATAL:
|
|
1140
|
-
matterLogger.log("fatal"
|
|
916
|
+
matterLogger.log("fatal", message);
|
|
1141
917
|
break;
|
|
1142
918
|
}
|
|
1143
919
|
};
|
|
1144
920
|
}
|
|
1145
|
-
/**
|
|
1146
|
-
* Creates a Matter File Logger.
|
|
1147
|
-
*
|
|
1148
|
-
* @param {string} filePath - The path to the log file.
|
|
1149
|
-
* @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
|
|
1150
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1151
|
-
*/
|
|
1152
921
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1153
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1154
922
|
let fileSize = 0;
|
|
1155
923
|
if (unlink) {
|
|
1156
924
|
try {
|
|
@@ -1161,12 +929,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1161
929
|
}
|
|
1162
930
|
}
|
|
1163
931
|
return async (level, formattedLog) => {
|
|
1164
|
-
/* istanbul ignore if */
|
|
1165
932
|
if (fileSize > 100000000) {
|
|
1166
|
-
return;
|
|
933
|
+
return;
|
|
1167
934
|
}
|
|
1168
935
|
fileSize += formattedLog.length;
|
|
1169
|
-
/* istanbul ignore if */
|
|
1170
936
|
if (fileSize > 100000000) {
|
|
1171
937
|
await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
|
|
1172
938
|
return;
|
|
@@ -1199,21 +965,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1199
965
|
}
|
|
1200
966
|
};
|
|
1201
967
|
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1204
|
-
*/
|
|
1205
968
|
async restartProcess() {
|
|
1206
969
|
await this.cleanup('restarting...', true);
|
|
1207
970
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Shut down the process.
|
|
1210
|
-
*/
|
|
1211
971
|
async shutdownProcess() {
|
|
1212
972
|
await this.cleanup('shutting down...', false);
|
|
1213
973
|
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Update matterbridge and shut down the process.
|
|
1216
|
-
*/
|
|
1217
974
|
async updateProcess() {
|
|
1218
975
|
this.log.info('Updating matterbridge...');
|
|
1219
976
|
try {
|
|
@@ -1227,75 +984,52 @@ export class Matterbridge extends EventEmitter {
|
|
|
1227
984
|
this.frontend.wssSendRestartRequired();
|
|
1228
985
|
await this.cleanup('updating...', false);
|
|
1229
986
|
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Unregister all devices and shut down the process.
|
|
1232
|
-
*/
|
|
1233
987
|
async unregisterAndShutdownProcess() {
|
|
1234
988
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1235
989
|
for (const plugin of this.plugins) {
|
|
1236
990
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1237
991
|
}
|
|
1238
992
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1239
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
993
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1240
994
|
this.log.debug('Cleaning up and shutting down...');
|
|
1241
995
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1242
996
|
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Reset commissioning and shut down the process.
|
|
1245
|
-
*/
|
|
1246
997
|
async shutdownProcessAndReset() {
|
|
1247
998
|
await this.cleanup('shutting down with reset...', false);
|
|
1248
999
|
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Factory reset and shut down the process.
|
|
1251
|
-
*/
|
|
1252
1000
|
async shutdownProcessAndFactoryReset() {
|
|
1253
1001
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1254
1002
|
}
|
|
1255
|
-
/**
|
|
1256
|
-
* Cleans up the Matterbridge instance.
|
|
1257
|
-
*
|
|
1258
|
-
* @param {string} message - The cleanup message.
|
|
1259
|
-
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1260
|
-
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1261
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1262
|
-
*/
|
|
1263
1003
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
1264
1004
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1265
1005
|
this.emit('cleanup_started');
|
|
1266
1006
|
this.hasCleanupStarted = true;
|
|
1267
1007
|
this.log.info(message);
|
|
1268
|
-
// Clear the start matter interval
|
|
1269
1008
|
if (this.startMatterInterval) {
|
|
1270
1009
|
clearInterval(this.startMatterInterval);
|
|
1271
1010
|
this.startMatterInterval = undefined;
|
|
1272
1011
|
this.log.debug('Start matter interval cleared');
|
|
1273
1012
|
}
|
|
1274
|
-
// Clear the check update timeout
|
|
1275
1013
|
if (this.checkUpdateTimeout) {
|
|
1276
1014
|
clearTimeout(this.checkUpdateTimeout);
|
|
1277
1015
|
this.checkUpdateTimeout = undefined;
|
|
1278
1016
|
this.log.debug('Check update timeout cleared');
|
|
1279
1017
|
}
|
|
1280
|
-
// Clear the check update interval
|
|
1281
1018
|
if (this.checkUpdateInterval) {
|
|
1282
1019
|
clearInterval(this.checkUpdateInterval);
|
|
1283
1020
|
this.checkUpdateInterval = undefined;
|
|
1284
1021
|
this.log.debug('Check update interval cleared');
|
|
1285
1022
|
}
|
|
1286
|
-
// Clear the configure timeout
|
|
1287
1023
|
if (this.configureTimeout) {
|
|
1288
1024
|
clearTimeout(this.configureTimeout);
|
|
1289
1025
|
this.configureTimeout = undefined;
|
|
1290
1026
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1291
1027
|
}
|
|
1292
|
-
// Clear the reachability timeout
|
|
1293
1028
|
if (this.reachabilityTimeout) {
|
|
1294
1029
|
clearTimeout(this.reachabilityTimeout);
|
|
1295
1030
|
this.reachabilityTimeout = undefined;
|
|
1296
1031
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1297
1032
|
}
|
|
1298
|
-
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1299
1033
|
for (const plugin of this.plugins) {
|
|
1300
1034
|
if (!plugin.enabled || plugin.error)
|
|
1301
1035
|
continue;
|
|
@@ -1306,10 +1040,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1306
1040
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1307
1041
|
}
|
|
1308
1042
|
}
|
|
1309
|
-
// Stop matter server nodes
|
|
1310
1043
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1311
1044
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1312
|
-
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
1045
|
+
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
1313
1046
|
if (this.bridgeMode === 'bridge') {
|
|
1314
1047
|
if (this.serverNode) {
|
|
1315
1048
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1331,7 +1064,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1331
1064
|
}
|
|
1332
1065
|
}
|
|
1333
1066
|
this.log.notice('Stopped matter server nodes');
|
|
1334
|
-
// Matter commisioning reset
|
|
1335
1067
|
if (message === 'shutting down with reset...') {
|
|
1336
1068
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1337
1069
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1341,36 +1073,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1341
1073
|
await this.matterbridgeContext?.clearAll();
|
|
1342
1074
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1343
1075
|
}
|
|
1344
|
-
// Stop matter storage
|
|
1345
1076
|
await this.stopMatterStorage();
|
|
1346
|
-
// Stop the frontend
|
|
1347
1077
|
await this.frontend.stop();
|
|
1348
|
-
// Remove the matterfilelogger
|
|
1349
1078
|
try {
|
|
1350
1079
|
Logger.removeLogger('matterfilelogger');
|
|
1351
1080
|
}
|
|
1352
1081
|
catch (error) {
|
|
1353
1082
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1354
1083
|
}
|
|
1355
|
-
// Close the matterbridge node storage and context
|
|
1356
1084
|
if (this.nodeStorage && this.nodeContext) {
|
|
1357
|
-
/*
|
|
1358
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1359
|
-
this.log.info('Saving registered devices...');
|
|
1360
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1361
|
-
this.devices.forEach(async (device) => {
|
|
1362
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1363
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1364
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1365
|
-
});
|
|
1366
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1367
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1368
|
-
*/
|
|
1369
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1370
1085
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1371
1086
|
await this.nodeContext.close();
|
|
1372
1087
|
this.nodeContext = undefined;
|
|
1373
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1374
1088
|
for (const plugin of this.plugins) {
|
|
1375
1089
|
if (plugin.nodeContext) {
|
|
1376
1090
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1387,10 +1101,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1387
1101
|
}
|
|
1388
1102
|
this.plugins.clear();
|
|
1389
1103
|
this.devices.clear();
|
|
1390
|
-
// Factory reset
|
|
1391
1104
|
if (message === 'shutting down with factory reset...') {
|
|
1392
1105
|
try {
|
|
1393
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1394
1106
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1395
1107
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1396
1108
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1404,7 +1116,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1404
1116
|
}
|
|
1405
1117
|
}
|
|
1406
1118
|
try {
|
|
1407
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1408
1119
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1409
1120
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1410
1121
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1419,13 +1130,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1419
1130
|
}
|
|
1420
1131
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1421
1132
|
}
|
|
1422
|
-
// Deregisters the process handlers
|
|
1423
1133
|
this.deregisterProcessHandlers();
|
|
1424
1134
|
if (restart) {
|
|
1425
1135
|
if (message === 'updating...') {
|
|
1426
1136
|
this.log.info('Cleanup completed. Updating...');
|
|
1427
1137
|
Matterbridge.instance = undefined;
|
|
1428
|
-
this.emit('update');
|
|
1138
|
+
this.emit('update');
|
|
1429
1139
|
}
|
|
1430
1140
|
else if (message === 'restarting...') {
|
|
1431
1141
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1446,13 +1156,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1446
1156
|
this.log.debug('Cleanup already started...');
|
|
1447
1157
|
}
|
|
1448
1158
|
}
|
|
1449
|
-
/**
|
|
1450
|
-
* Creates and configures the server node for a single not bridged device.
|
|
1451
|
-
*
|
|
1452
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1453
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1454
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1455
|
-
*/
|
|
1456
1159
|
async createDeviceServerNode(plugin, device) {
|
|
1457
1160
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1458
1161
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1463,14 +1166,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1463
1166
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1464
1167
|
}
|
|
1465
1168
|
}
|
|
1466
|
-
/**
|
|
1467
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1468
|
-
*
|
|
1469
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1470
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1471
|
-
* @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
|
|
1472
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1473
|
-
*/
|
|
1474
1169
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1475
1170
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1476
1171
|
plugin.locked = true;
|
|
@@ -1484,12 +1179,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1484
1179
|
await this.startServerNode(plugin.serverNode);
|
|
1485
1180
|
}
|
|
1486
1181
|
}
|
|
1487
|
-
/**
|
|
1488
|
-
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
1489
|
-
*
|
|
1490
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1491
|
-
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
1492
|
-
*/
|
|
1493
1182
|
async createDynamicPlugin(plugin) {
|
|
1494
1183
|
if (!plugin.locked) {
|
|
1495
1184
|
plugin.locked = true;
|
|
@@ -1500,14 +1189,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1500
1189
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1501
1190
|
}
|
|
1502
1191
|
}
|
|
1503
|
-
/**
|
|
1504
|
-
* Starts the Matterbridge in bridge mode.
|
|
1505
|
-
*
|
|
1506
|
-
* @private
|
|
1507
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1508
|
-
*/
|
|
1509
1192
|
async startBridge() {
|
|
1510
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1511
1193
|
if (!this.matterStorageManager)
|
|
1512
1194
|
throw new Error('No storage manager initialized');
|
|
1513
1195
|
if (!this.matterbridgeContext)
|
|
@@ -1546,16 +1228,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1546
1228
|
clearInterval(this.startMatterInterval);
|
|
1547
1229
|
this.startMatterInterval = undefined;
|
|
1548
1230
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1549
|
-
|
|
1550
|
-
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1551
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1231
|
+
this.startServerNode(this.serverNode);
|
|
1552
1232
|
for (const device of this.devices.array()) {
|
|
1553
1233
|
if (device.mode === 'server' && device.serverNode) {
|
|
1554
1234
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1555
|
-
this.startServerNode(device.serverNode);
|
|
1235
|
+
this.startServerNode(device.serverNode);
|
|
1556
1236
|
}
|
|
1557
1237
|
}
|
|
1558
|
-
// Configure the plugins
|
|
1559
1238
|
this.configureTimeout = setTimeout(async () => {
|
|
1560
1239
|
for (const plugin of this.plugins) {
|
|
1561
1240
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1573,24 +1252,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1573
1252
|
}
|
|
1574
1253
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1575
1254
|
}, 30 * 1000).unref();
|
|
1576
|
-
// Setting reachability to true
|
|
1577
1255
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1578
1256
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1579
1257
|
if (this.aggregatorNode)
|
|
1580
1258
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1581
1259
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1582
1260
|
}, 60 * 1000).unref();
|
|
1583
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1584
1261
|
this.emit('bridge_started');
|
|
1585
1262
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1586
1263
|
}, 1000);
|
|
1587
1264
|
}
|
|
1588
|
-
/**
|
|
1589
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1590
|
-
*
|
|
1591
|
-
* @private
|
|
1592
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1593
|
-
*/
|
|
1594
1265
|
async startChildbridge() {
|
|
1595
1266
|
if (!this.matterStorageManager)
|
|
1596
1267
|
throw new Error('No storage manager initialized');
|
|
@@ -1627,9 +1298,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1627
1298
|
return;
|
|
1628
1299
|
clearInterval(this.startMatterInterval);
|
|
1629
1300
|
this.startMatterInterval = undefined;
|
|
1630
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1301
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1631
1302
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1632
|
-
// Configure the plugins
|
|
1633
1303
|
this.configureTimeout = setTimeout(async () => {
|
|
1634
1304
|
for (const plugin of this.plugins) {
|
|
1635
1305
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1666,9 +1336,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1666
1336
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1667
1337
|
continue;
|
|
1668
1338
|
}
|
|
1669
|
-
|
|
1670
|
-
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1671
|
-
// Setting reachability to true
|
|
1339
|
+
this.startServerNode(plugin.serverNode);
|
|
1672
1340
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1673
1341
|
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}`);
|
|
1674
1342
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1676,241 +1344,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1676
1344
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1677
1345
|
}, 60 * 1000).unref();
|
|
1678
1346
|
}
|
|
1679
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1680
1347
|
for (const device of this.devices.array()) {
|
|
1681
1348
|
if (device.mode === 'server' && device.serverNode) {
|
|
1682
1349
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1683
|
-
this.startServerNode(device.serverNode);
|
|
1350
|
+
this.startServerNode(device.serverNode);
|
|
1684
1351
|
}
|
|
1685
1352
|
}
|
|
1686
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1687
1353
|
this.emit('childbridge_started');
|
|
1688
1354
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1689
1355
|
}, 1000);
|
|
1690
1356
|
}
|
|
1691
|
-
/**
|
|
1692
|
-
* Starts the Matterbridge controller.
|
|
1693
|
-
*
|
|
1694
|
-
* @private
|
|
1695
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1696
|
-
*/
|
|
1697
1357
|
async startController() {
|
|
1698
|
-
/*
|
|
1699
|
-
if (!this.matterStorageManager) {
|
|
1700
|
-
this.log.error('No storage manager initialized');
|
|
1701
|
-
await this.cleanup('No storage manager initialized');
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
this.log.info('Creating context: mattercontrollerContext');
|
|
1705
|
-
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1706
|
-
if (!this.controllerContext) {
|
|
1707
|
-
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1708
|
-
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1709
|
-
return;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1713
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1714
|
-
this.log.info('Creating matter commissioning controller');
|
|
1715
|
-
this.commissioningController = new CommissioningController({
|
|
1716
|
-
autoConnect: false,
|
|
1717
|
-
});
|
|
1718
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1719
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1720
|
-
|
|
1721
|
-
this.log.info('Starting matter server');
|
|
1722
|
-
await this.matterServer.start();
|
|
1723
|
-
this.log.info('Matter server started');
|
|
1724
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1725
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1726
|
-
regulatoryCountryCode: 'XX',
|
|
1727
|
-
};
|
|
1728
|
-
const commissioningController = new CommissioningController({
|
|
1729
|
-
environment: {
|
|
1730
|
-
environment,
|
|
1731
|
-
id: uniqueId,
|
|
1732
|
-
},
|
|
1733
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1734
|
-
adminFabricLabel,
|
|
1735
|
-
});
|
|
1736
|
-
|
|
1737
|
-
if (hasParameter('pairingcode')) {
|
|
1738
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1739
|
-
const pairingCode = getParameter('pairingcode');
|
|
1740
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1741
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1742
|
-
|
|
1743
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1744
|
-
if (pairingCode !== undefined) {
|
|
1745
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1746
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1747
|
-
longDiscriminator = undefined;
|
|
1748
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1749
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1750
|
-
} else {
|
|
1751
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1752
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1753
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1754
|
-
}
|
|
1755
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1756
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
const options = {
|
|
1760
|
-
commissioning: commissioningOptions,
|
|
1761
|
-
discovery: {
|
|
1762
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1763
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1764
|
-
},
|
|
1765
|
-
passcode: setupPin,
|
|
1766
|
-
} as NodeCommissioningOptions;
|
|
1767
|
-
this.log.info('Commissioning with options:', options);
|
|
1768
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1769
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1770
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1771
|
-
} // (hasParameter('pairingcode'))
|
|
1772
|
-
|
|
1773
|
-
if (hasParameter('unpairall')) {
|
|
1774
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1775
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1776
|
-
for (const nodeId of nodeIds) {
|
|
1777
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1778
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1779
|
-
}
|
|
1780
|
-
return;
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
if (hasParameter('discover')) {
|
|
1784
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1785
|
-
// console.log(discover);
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1789
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1794
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1795
|
-
for (const nodeId of nodeIds) {
|
|
1796
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1797
|
-
|
|
1798
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1799
|
-
autoSubscribe: false,
|
|
1800
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1801
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1802
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1803
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1804
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1805
|
-
switch (info) {
|
|
1806
|
-
case NodeStateInformation.Connected:
|
|
1807
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1808
|
-
break;
|
|
1809
|
-
case NodeStateInformation.Disconnected:
|
|
1810
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1811
|
-
break;
|
|
1812
|
-
case NodeStateInformation.Reconnecting:
|
|
1813
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1814
|
-
break;
|
|
1815
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1816
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1817
|
-
break;
|
|
1818
|
-
case NodeStateInformation.StructureChanged:
|
|
1819
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1820
|
-
break;
|
|
1821
|
-
case NodeStateInformation.Decommissioned:
|
|
1822
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1823
|
-
break;
|
|
1824
|
-
default:
|
|
1825
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1826
|
-
break;
|
|
1827
|
-
}
|
|
1828
|
-
},
|
|
1829
|
-
});
|
|
1830
|
-
|
|
1831
|
-
node.logStructure();
|
|
1832
|
-
|
|
1833
|
-
// Get the interaction client
|
|
1834
|
-
this.log.info('Getting the interaction client');
|
|
1835
|
-
const interactionClient = await node.getInteractionClient();
|
|
1836
|
-
let cluster;
|
|
1837
|
-
let attributes;
|
|
1838
|
-
|
|
1839
|
-
// Log BasicInformationCluster
|
|
1840
|
-
cluster = BasicInformationCluster;
|
|
1841
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1842
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1843
|
-
});
|
|
1844
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1845
|
-
attributes.forEach((attribute) => {
|
|
1846
|
-
this.log.info(
|
|
1847
|
-
`- 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}`,
|
|
1848
|
-
);
|
|
1849
|
-
});
|
|
1850
|
-
|
|
1851
|
-
// Log PowerSourceCluster
|
|
1852
|
-
cluster = PowerSourceCluster;
|
|
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 ThreadNetworkDiagnostics
|
|
1864
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
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 SwitchCluster
|
|
1876
|
-
cluster = SwitchCluster;
|
|
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
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1888
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1889
|
-
ignoreInitialTriggers: false,
|
|
1890
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1891
|
-
this.log.info(
|
|
1892
|
-
`***${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}`,
|
|
1893
|
-
),
|
|
1894
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1895
|
-
this.log.info(
|
|
1896
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1897
|
-
);
|
|
1898
|
-
},
|
|
1899
|
-
});
|
|
1900
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1901
|
-
}
|
|
1902
|
-
*/
|
|
1903
1358
|
}
|
|
1904
|
-
/** */
|
|
1905
|
-
/** Matter.js methods */
|
|
1906
|
-
/** */
|
|
1907
|
-
/**
|
|
1908
|
-
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1909
|
-
*
|
|
1910
|
-
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1911
|
-
*/
|
|
1912
1359
|
async startMatterStorage() {
|
|
1913
|
-
// Setup Matter storage
|
|
1914
1360
|
this.log.info(`Starting matter node storage...`);
|
|
1915
1361
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1916
1362
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1919,17 +1365,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1919
1365
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1920
1366
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1921
1367
|
this.log.info('Matter node storage started');
|
|
1922
|
-
// Backup matter storage since it is created/opened correctly
|
|
1923
1368
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1924
1369
|
}
|
|
1925
|
-
/**
|
|
1926
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1927
|
-
*
|
|
1928
|
-
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1929
|
-
* @param {string} backupName - The name of the backup directory to be created.
|
|
1930
|
-
* @private
|
|
1931
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1932
|
-
*/
|
|
1933
1370
|
async backupMatterStorage(storageName, backupName) {
|
|
1934
1371
|
this.log.info('Creating matter node storage backup...');
|
|
1935
1372
|
try {
|
|
@@ -1940,11 +1377,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1940
1377
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1941
1378
|
}
|
|
1942
1379
|
}
|
|
1943
|
-
/**
|
|
1944
|
-
* Stops the matter storage.
|
|
1945
|
-
*
|
|
1946
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1947
|
-
*/
|
|
1948
1380
|
async stopMatterStorage() {
|
|
1949
1381
|
this.log.info('Closing matter node storage...');
|
|
1950
1382
|
await this.matterStorageManager?.close();
|
|
@@ -1953,19 +1385,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1953
1385
|
this.matterbridgeContext = undefined;
|
|
1954
1386
|
this.log.info('Matter node storage closed');
|
|
1955
1387
|
}
|
|
1956
|
-
/**
|
|
1957
|
-
* Creates a server node storage context.
|
|
1958
|
-
*
|
|
1959
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1960
|
-
* @param {string} deviceName - The name of the device.
|
|
1961
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1962
|
-
* @param {number} vendorId - The vendor ID.
|
|
1963
|
-
* @param {string} vendorName - The vendor name.
|
|
1964
|
-
* @param {number} productId - The product ID.
|
|
1965
|
-
* @param {string} productName - The product name.
|
|
1966
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1967
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1968
|
-
*/
|
|
1969
1388
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1970
1389
|
const { randomBytes } = await import('node:crypto');
|
|
1971
1390
|
if (!this.matterStorageService)
|
|
@@ -1999,15 +1418,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1999
1418
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2000
1419
|
return storageContext;
|
|
2001
1420
|
}
|
|
2002
|
-
/**
|
|
2003
|
-
* Creates a server node.
|
|
2004
|
-
*
|
|
2005
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2006
|
-
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2007
|
-
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2008
|
-
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2009
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2010
|
-
*/
|
|
2011
1421
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
2012
1422
|
const storeId = await storageContext.get('storeId');
|
|
2013
1423
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -2017,37 +1427,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2017
1427
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2018
1428
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2019
1429
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2020
|
-
/**
|
|
2021
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2022
|
-
*/
|
|
2023
1430
|
const serverNode = await ServerNode.create({
|
|
2024
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
2025
1431
|
id: storeId,
|
|
2026
|
-
// Provide Network relevant configuration like the port
|
|
2027
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
2028
1432
|
network: {
|
|
2029
1433
|
listeningAddressIpv4: this.ipv4address,
|
|
2030
1434
|
listeningAddressIpv6: this.ipv6address,
|
|
2031
1435
|
port,
|
|
2032
1436
|
},
|
|
2033
|
-
// Provide the certificate for the device
|
|
2034
1437
|
operationalCredentials: {
|
|
2035
1438
|
certification: this.certification,
|
|
2036
1439
|
},
|
|
2037
|
-
// Provide Commissioning relevant settings
|
|
2038
|
-
// Optional for development/testing purposes
|
|
2039
1440
|
commissioning: {
|
|
2040
1441
|
passcode,
|
|
2041
1442
|
discriminator,
|
|
2042
1443
|
},
|
|
2043
|
-
// Provide Node announcement settings
|
|
2044
|
-
// Optional: If Ommitted some development defaults are used
|
|
2045
1444
|
productDescription: {
|
|
2046
1445
|
name: await storageContext.get('deviceName'),
|
|
2047
1446
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
2048
1447
|
},
|
|
2049
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2050
|
-
// Optional: If Omitted some development defaults are used
|
|
2051
1448
|
basicInformation: {
|
|
2052
1449
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2053
1450
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2064,20 +1461,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2064
1461
|
reachable: true,
|
|
2065
1462
|
},
|
|
2066
1463
|
});
|
|
2067
|
-
/**
|
|
2068
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2069
|
-
* This means: It is added to the first fabric.
|
|
2070
|
-
*/
|
|
2071
1464
|
serverNode.lifecycle.commissioned.on(() => {
|
|
2072
1465
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
2073
1466
|
clearTimeout(this.endAdvertiseTimeout);
|
|
2074
1467
|
});
|
|
2075
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2076
1468
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
2077
1469
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
2078
1470
|
clearTimeout(this.endAdvertiseTimeout);
|
|
2079
1471
|
});
|
|
2080
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2081
1472
|
serverNode.lifecycle.online.on(async () => {
|
|
2082
1473
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2083
1474
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2085,7 +1476,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2085
1476
|
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
2086
1477
|
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
2087
1478
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2088
|
-
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
2089
1479
|
this.startEndAdvertiseTimer(serverNode);
|
|
2090
1480
|
}
|
|
2091
1481
|
else {
|
|
@@ -2096,19 +1486,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2096
1486
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2097
1487
|
this.emit('online', storeId);
|
|
2098
1488
|
});
|
|
2099
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2100
1489
|
serverNode.lifecycle.offline.on(() => {
|
|
2101
1490
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2102
|
-
this.matterbridgeInformation.matterbridgeEndAdvertise = true;
|
|
1491
|
+
this.matterbridgeInformation.matterbridgeEndAdvertise = true;
|
|
2103
1492
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
2104
1493
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2105
1494
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2106
1495
|
this.emit('offline', storeId);
|
|
2107
1496
|
});
|
|
2108
|
-
/**
|
|
2109
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2110
|
-
* information is needed.
|
|
2111
|
-
*/
|
|
2112
1497
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2113
1498
|
let action = '';
|
|
2114
1499
|
switch (fabricAction) {
|
|
@@ -2125,22 +1510,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2125
1510
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
2126
1511
|
this.frontend.wssSendRefreshRequired('fabrics');
|
|
2127
1512
|
});
|
|
2128
|
-
/**
|
|
2129
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2130
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2131
|
-
*/
|
|
2132
1513
|
serverNode.events.sessions.opened.on((session) => {
|
|
2133
1514
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2134
1515
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2135
1516
|
});
|
|
2136
|
-
/**
|
|
2137
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2138
|
-
*/
|
|
2139
1517
|
serverNode.events.sessions.closed.on((session) => {
|
|
2140
1518
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2141
1519
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2142
1520
|
});
|
|
2143
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2144
1521
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2145
1522
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2146
1523
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
@@ -2148,11 +1525,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2148
1525
|
this.log.info(`Created server node for ${storeId}`);
|
|
2149
1526
|
return serverNode;
|
|
2150
1527
|
}
|
|
2151
|
-
/**
|
|
2152
|
-
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2153
|
-
*
|
|
2154
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2155
|
-
*/
|
|
2156
1528
|
startEndAdvertiseTimer(matterServerNode) {
|
|
2157
1529
|
if (this.endAdvertiseTimeout) {
|
|
2158
1530
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -2171,25 +1543,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2171
1543
|
this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
|
|
2172
1544
|
}, 15 * 60 * 1000).unref();
|
|
2173
1545
|
}
|
|
2174
|
-
/**
|
|
2175
|
-
* Starts the specified server node.
|
|
2176
|
-
*
|
|
2177
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2178
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2179
|
-
*/
|
|
2180
1546
|
async startServerNode(matterServerNode) {
|
|
2181
1547
|
if (!matterServerNode)
|
|
2182
1548
|
return;
|
|
2183
1549
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2184
1550
|
await matterServerNode.start();
|
|
2185
1551
|
}
|
|
2186
|
-
/**
|
|
2187
|
-
* Stops the specified server node.
|
|
2188
|
-
*
|
|
2189
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2190
|
-
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2191
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2192
|
-
*/
|
|
2193
1552
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
2194
1553
|
if (!matterServerNode)
|
|
2195
1554
|
return;
|
|
@@ -2202,12 +1561,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2202
1561
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2203
1562
|
}
|
|
2204
1563
|
}
|
|
2205
|
-
/**
|
|
2206
|
-
* Advertises the specified server node.
|
|
2207
|
-
*
|
|
2208
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2209
|
-
* @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.
|
|
2210
|
-
*/
|
|
2211
1564
|
async advertiseServerNode(matterServerNode) {
|
|
2212
1565
|
if (matterServerNode) {
|
|
2213
1566
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2216,39 +1569,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
2216
1569
|
return { qrPairingCode, manualPairingCode };
|
|
2217
1570
|
}
|
|
2218
1571
|
}
|
|
2219
|
-
/**
|
|
2220
|
-
* Stop advertise the specified server node.
|
|
2221
|
-
*
|
|
2222
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2223
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2224
|
-
*/
|
|
2225
1572
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2226
1573
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2227
1574
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2228
1575
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2229
1576
|
}
|
|
2230
1577
|
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Creates an aggregator node with the specified storage context.
|
|
2233
|
-
*
|
|
2234
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2235
|
-
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2236
|
-
*/
|
|
2237
1578
|
async createAggregatorNode(storageContext) {
|
|
2238
1579
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2239
1580
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2240
1581
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2241
1582
|
return aggregatorNode;
|
|
2242
1583
|
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2245
|
-
*
|
|
2246
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2247
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2248
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2249
|
-
*/
|
|
2250
1584
|
async addBridgedEndpoint(pluginName, device) {
|
|
2251
|
-
// Check if the plugin is registered
|
|
2252
1585
|
const plugin = this.plugins.get(pluginName);
|
|
2253
1586
|
if (!plugin) {
|
|
2254
1587
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -2268,7 +1601,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2268
1601
|
}
|
|
2269
1602
|
else if (this.bridgeMode === 'bridge') {
|
|
2270
1603
|
if (device.mode === 'matter') {
|
|
2271
|
-
// Register and add the device to the matterbridge server node
|
|
2272
1604
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
2273
1605
|
if (!this.serverNode) {
|
|
2274
1606
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -2285,7 +1617,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2285
1617
|
}
|
|
2286
1618
|
}
|
|
2287
1619
|
else {
|
|
2288
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2289
1620
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2290
1621
|
if (!this.aggregatorNode) {
|
|
2291
1622
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2303,7 +1634,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2303
1634
|
}
|
|
2304
1635
|
}
|
|
2305
1636
|
else if (this.bridgeMode === 'childbridge') {
|
|
2306
|
-
// Register and add the device to the plugin server node
|
|
2307
1637
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2308
1638
|
try {
|
|
2309
1639
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2327,12 +1657,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2327
1657
|
return;
|
|
2328
1658
|
}
|
|
2329
1659
|
}
|
|
2330
|
-
// Register and add the device to the plugin aggregator node
|
|
2331
1660
|
if (plugin.type === 'DynamicPlatform') {
|
|
2332
1661
|
try {
|
|
2333
1662
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2334
1663
|
await this.createDynamicPlugin(plugin);
|
|
2335
|
-
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
2336
1664
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2337
1665
|
if (!plugin.aggregatorNode) {
|
|
2338
1666
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2355,28 +1683,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2355
1683
|
plugin.registeredDevices++;
|
|
2356
1684
|
if (plugin.addedDevices !== undefined)
|
|
2357
1685
|
plugin.addedDevices++;
|
|
2358
|
-
// Add the device to the DeviceManager
|
|
2359
1686
|
this.devices.set(device);
|
|
2360
|
-
// Subscribe to the reachable$Changed event
|
|
2361
1687
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2362
1688
|
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}`);
|
|
2363
1689
|
}
|
|
2364
|
-
/**
|
|
2365
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2366
|
-
*
|
|
2367
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2368
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2369
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2370
|
-
*/
|
|
2371
1690
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2372
1691
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2373
|
-
// Check if the plugin is registered
|
|
2374
1692
|
const plugin = this.plugins.get(pluginName);
|
|
2375
1693
|
if (!plugin) {
|
|
2376
1694
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2377
1695
|
return;
|
|
2378
1696
|
}
|
|
2379
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2380
1697
|
if (this.bridgeMode === 'bridge') {
|
|
2381
1698
|
if (!this.aggregatorNode) {
|
|
2382
1699
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2391,7 +1708,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2391
1708
|
}
|
|
2392
1709
|
else if (this.bridgeMode === 'childbridge') {
|
|
2393
1710
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2394
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2395
1711
|
}
|
|
2396
1712
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2397
1713
|
if (!plugin.aggregatorNode) {
|
|
@@ -2406,21 +1722,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2406
1722
|
if (plugin.addedDevices !== undefined)
|
|
2407
1723
|
plugin.addedDevices--;
|
|
2408
1724
|
}
|
|
2409
|
-
// Remove the device from the DeviceManager
|
|
2410
1725
|
this.devices.remove(device);
|
|
2411
1726
|
}
|
|
2412
|
-
/**
|
|
2413
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2414
|
-
*
|
|
2415
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2416
|
-
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2417
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2418
|
-
*
|
|
2419
|
-
* @remarks
|
|
2420
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2421
|
-
* It also applies a delay between each removal if specified.
|
|
2422
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2423
|
-
*/
|
|
2424
1727
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2425
1728
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2426
1729
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2431,15 +1734,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2431
1734
|
if (delay > 0)
|
|
2432
1735
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2433
1736
|
}
|
|
2434
|
-
/**
|
|
2435
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2436
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2437
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2438
|
-
*
|
|
2439
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2440
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2441
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2442
|
-
*/
|
|
2443
1737
|
async subscribeAttributeChanged(plugin, device) {
|
|
2444
1738
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2445
1739
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -2455,12 +1749,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2455
1749
|
});
|
|
2456
1750
|
}
|
|
2457
1751
|
}
|
|
2458
|
-
/**
|
|
2459
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2460
|
-
*
|
|
2461
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2462
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2463
|
-
*/
|
|
2464
1752
|
sanitizeFabricInformations(fabricInfo) {
|
|
2465
1753
|
return fabricInfo.map((info) => {
|
|
2466
1754
|
return {
|
|
@@ -2474,12 +1762,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2474
1762
|
};
|
|
2475
1763
|
});
|
|
2476
1764
|
}
|
|
2477
|
-
/**
|
|
2478
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2479
|
-
*
|
|
2480
|
-
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2481
|
-
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2482
|
-
*/
|
|
2483
1765
|
sanitizeSessionInformation(sessions) {
|
|
2484
1766
|
return sessions
|
|
2485
1767
|
.filter((session) => session.isPeerActive)
|
|
@@ -2506,21 +1788,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2506
1788
|
};
|
|
2507
1789
|
});
|
|
2508
1790
|
}
|
|
2509
|
-
/**
|
|
2510
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2511
|
-
*
|
|
2512
|
-
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2513
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2514
|
-
*/
|
|
2515
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2516
1791
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2517
|
-
/*
|
|
2518
|
-
for (const child of aggregatorNode.parts) {
|
|
2519
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2520
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2521
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2522
|
-
}
|
|
2523
|
-
*/
|
|
2524
1792
|
}
|
|
2525
1793
|
getVendorIdName = (vendorId) => {
|
|
2526
1794
|
if (!vendorId)
|
|
@@ -2560,11 +1828,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2560
1828
|
case 0x1488:
|
|
2561
1829
|
vendorName = '(ShortcutLabsFlic)';
|
|
2562
1830
|
break;
|
|
2563
|
-
case 65521:
|
|
1831
|
+
case 65521:
|
|
2564
1832
|
vendorName = '(MatterTest)';
|
|
2565
1833
|
break;
|
|
2566
1834
|
}
|
|
2567
1835
|
return vendorName;
|
|
2568
1836
|
};
|
|
2569
1837
|
}
|
|
2570
|
-
//# sourceMappingURL=matterbridge.js.map
|