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