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