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