matterbridge 3.1.3-dev-20250714-c9b85b3 → 3.1.3
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 +1 -11
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +9 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +87 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +83 -6
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +242 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +91 -7
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +110 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +89 -6
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/frontend.d.ts +303 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +423 -18
- 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 +444 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +784 -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 +291 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +269 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +59 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +54 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +117 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +263 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +12 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/hex.d.ts +49 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +58 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +76 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +83 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +11 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +18 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +56 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +62 -9
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/vitest/matterbridge.test.ts +5 -3
package/dist/matterbridge.js
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2023-12-29
|
|
7
|
+
* @version 1.6.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node.js modules
|
|
1
25
|
import os from 'node:os';
|
|
2
26
|
import path from 'node:path';
|
|
3
27
|
import { promises as fs } from 'node:fs';
|
|
4
28
|
import EventEmitter from 'node:events';
|
|
5
29
|
import { inspect } from 'node:util';
|
|
30
|
+
// AnsiLogger module
|
|
6
31
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
|
|
32
|
+
// NodeStorage module
|
|
7
33
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
+
// @matter
|
|
8
35
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
9
36
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
10
37
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
11
38
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
12
39
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
|
|
14
42
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
15
43
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -18,6 +46,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
18
46
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
19
47
|
import { Frontend } from './frontend.js';
|
|
20
48
|
import { addVirtualDevices } from './helpers.js';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
21
52
|
export class Matterbridge extends EventEmitter {
|
|
22
53
|
systemInformation = {
|
|
23
54
|
interfaceName: '',
|
|
@@ -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;
|
|
101
133
|
devices;
|
|
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) {
|
|
@@ -412,6 +537,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
412
537
|
});
|
|
413
538
|
}
|
|
414
539
|
}
|
|
540
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
415
541
|
if (hasParameter('mdnsinterface')) {
|
|
416
542
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
417
543
|
}
|
|
@@ -420,6 +546,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
420
546
|
if (this.mdnsInterface === '')
|
|
421
547
|
this.mdnsInterface = undefined;
|
|
422
548
|
}
|
|
549
|
+
// Validate mdnsInterface
|
|
423
550
|
if (this.mdnsInterface) {
|
|
424
551
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
425
552
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -432,6 +559,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
432
559
|
}
|
|
433
560
|
if (this.mdnsInterface)
|
|
434
561
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
562
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
435
563
|
if (hasParameter('ipv4address')) {
|
|
436
564
|
this.ipv4address = getParameter('ipv4address');
|
|
437
565
|
}
|
|
@@ -440,6 +568,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
440
568
|
if (this.ipv4address === '')
|
|
441
569
|
this.ipv4address = undefined;
|
|
442
570
|
}
|
|
571
|
+
// Validate ipv4address
|
|
443
572
|
if (this.ipv4address) {
|
|
444
573
|
let isValid = false;
|
|
445
574
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -455,6 +584,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
455
584
|
await this.nodeContext.remove('matteripv4address');
|
|
456
585
|
}
|
|
457
586
|
}
|
|
587
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
458
588
|
if (hasParameter('ipv6address')) {
|
|
459
589
|
this.ipv6address = getParameter('ipv6address');
|
|
460
590
|
}
|
|
@@ -463,6 +593,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
463
593
|
if (this.ipv6address === '')
|
|
464
594
|
this.ipv6address = undefined;
|
|
465
595
|
}
|
|
596
|
+
// Validate ipv6address
|
|
466
597
|
if (this.ipv6address) {
|
|
467
598
|
let isValid = false;
|
|
468
599
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -471,6 +602,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
602
|
isValid = true;
|
|
472
603
|
break;
|
|
473
604
|
}
|
|
605
|
+
/* istanbul ignore next */
|
|
474
606
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
475
607
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
476
608
|
isValid = true;
|
|
@@ -483,6 +615,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
483
615
|
await this.nodeContext.remove('matteripv6address');
|
|
484
616
|
}
|
|
485
617
|
}
|
|
618
|
+
// Initialize the virtual mode
|
|
486
619
|
if (hasParameter('novirtual')) {
|
|
487
620
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
488
621
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -491,14 +624,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
491
624
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
492
625
|
}
|
|
493
626
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
627
|
+
// Initialize PluginManager
|
|
494
628
|
this.plugins = new PluginManager(this);
|
|
495
629
|
await this.plugins.loadFromStorage();
|
|
496
630
|
this.plugins.logLevel = this.log.logLevel;
|
|
631
|
+
// Initialize DeviceManager
|
|
497
632
|
this.devices = new DeviceManager(this);
|
|
498
633
|
this.devices.logLevel = this.log.logLevel;
|
|
634
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
499
635
|
for (const plugin of this.plugins) {
|
|
500
636
|
const packageJson = await this.plugins.parse(plugin);
|
|
501
637
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
638
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
639
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
502
640
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
503
641
|
try {
|
|
504
642
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -521,6 +659,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
521
659
|
await plugin.nodeContext.set('description', plugin.description);
|
|
522
660
|
await plugin.nodeContext.set('author', plugin.author);
|
|
523
661
|
}
|
|
662
|
+
// Log system info and create .matterbridge directory
|
|
524
663
|
await this.logNodeAndSystemInfo();
|
|
525
664
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
526
665
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -528,6 +667,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
528
667
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
529
668
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
530
669
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
670
|
+
// Check node version and throw error
|
|
531
671
|
const minNodeVersion = 18;
|
|
532
672
|
const nodeVersion = process.versions.node;
|
|
533
673
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -535,10 +675,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
535
675
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
536
676
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
537
677
|
}
|
|
678
|
+
// Parse command line
|
|
538
679
|
await this.parseCommandLine();
|
|
680
|
+
// Emit the initialize_completed event
|
|
539
681
|
this.emit('initialize_completed');
|
|
540
682
|
this.initialized = true;
|
|
541
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
686
|
+
*
|
|
687
|
+
* @private
|
|
688
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
689
|
+
*/
|
|
542
690
|
async parseCommandLine() {
|
|
543
691
|
if (hasParameter('help')) {
|
|
544
692
|
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
|
}
|
|
@@ -649,6 +810,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
649
810
|
this.shutdown = true;
|
|
650
811
|
return;
|
|
651
812
|
}
|
|
813
|
+
// Start the matter storage and create the matterbridge context
|
|
652
814
|
try {
|
|
653
815
|
await this.startMatterStorage();
|
|
654
816
|
}
|
|
@@ -656,18 +818,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
656
818
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
657
819
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
658
820
|
}
|
|
821
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
659
822
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
660
823
|
this.initialized = true;
|
|
661
824
|
await this.shutdownProcessAndReset();
|
|
662
825
|
this.shutdown = true;
|
|
663
826
|
return;
|
|
664
827
|
}
|
|
828
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
665
829
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
666
830
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
667
831
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
668
832
|
if (plugin) {
|
|
669
833
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
670
834
|
if (!matterStorageManager) {
|
|
835
|
+
/* istanbul ignore next */
|
|
671
836
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
672
837
|
}
|
|
673
838
|
else {
|
|
@@ -686,32 +851,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
686
851
|
this.shutdown = true;
|
|
687
852
|
return;
|
|
688
853
|
}
|
|
854
|
+
// Initialize frontend
|
|
689
855
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
690
856
|
await this.frontend.start(getIntParameter('frontend'));
|
|
857
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
691
858
|
clearTimeout(this.checkUpdateTimeout);
|
|
692
859
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
693
860
|
const { checkUpdates } = await import('./update.js');
|
|
694
861
|
checkUpdates(this);
|
|
695
862
|
}, 30 * 1000).unref();
|
|
863
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
696
864
|
clearInterval(this.checkUpdateInterval);
|
|
697
865
|
this.checkUpdateInterval = setInterval(async () => {
|
|
698
866
|
const { checkUpdates } = await import('./update.js');
|
|
699
867
|
checkUpdates(this);
|
|
700
868
|
}, 12 * 60 * 60 * 1000).unref();
|
|
869
|
+
// Start the matterbridge in mode test
|
|
701
870
|
if (hasParameter('test')) {
|
|
702
871
|
this.bridgeMode = 'bridge';
|
|
703
872
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
704
873
|
return;
|
|
705
874
|
}
|
|
875
|
+
// Start the matterbridge in mode controller
|
|
706
876
|
if (hasParameter('controller')) {
|
|
707
877
|
this.bridgeMode = 'controller';
|
|
708
878
|
await this.startController();
|
|
709
879
|
return;
|
|
710
880
|
}
|
|
881
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
711
882
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
712
883
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
713
884
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
714
885
|
}
|
|
886
|
+
// Start matterbridge in bridge mode
|
|
715
887
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
716
888
|
this.bridgeMode = 'bridge';
|
|
717
889
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -719,6 +891,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
719
891
|
await this.startBridge();
|
|
720
892
|
return;
|
|
721
893
|
}
|
|
894
|
+
// Start matterbridge in childbridge mode
|
|
722
895
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
723
896
|
this.bridgeMode = 'childbridge';
|
|
724
897
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -727,10 +900,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
727
900
|
return;
|
|
728
901
|
}
|
|
729
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* Asynchronously loads and starts the registered plugins.
|
|
905
|
+
*
|
|
906
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
907
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
908
|
+
*
|
|
909
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
910
|
+
*/
|
|
730
911
|
async startPlugins() {
|
|
912
|
+
// Check, load and start the plugins
|
|
731
913
|
for (const plugin of this.plugins) {
|
|
732
914
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
733
915
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
916
|
+
// Check if the plugin is available
|
|
734
917
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
735
918
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
736
919
|
plugin.enabled = false;
|
|
@@ -748,10 +931,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
748
931
|
plugin.configured = false;
|
|
749
932
|
plugin.registeredDevices = undefined;
|
|
750
933
|
plugin.addedDevices = undefined;
|
|
751
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
934
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
752
935
|
}
|
|
753
936
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
754
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
940
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
941
|
+
*/
|
|
755
942
|
registerProcessHandlers() {
|
|
756
943
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
757
944
|
process.removeAllListeners('uncaughtException');
|
|
@@ -778,6 +965,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
778
965
|
};
|
|
779
966
|
process.on('SIGTERM', this.sigtermHandler);
|
|
780
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
970
|
+
*/
|
|
781
971
|
deregisterProcessHandlers() {
|
|
782
972
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
783
973
|
if (this.exceptionHandler)
|
|
@@ -794,12 +984,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
794
984
|
process.off('SIGTERM', this.sigtermHandler);
|
|
795
985
|
this.sigtermHandler = undefined;
|
|
796
986
|
}
|
|
987
|
+
/**
|
|
988
|
+
* Logs the node and system information.
|
|
989
|
+
*/
|
|
797
990
|
async logNodeAndSystemInfo() {
|
|
991
|
+
// IP address information
|
|
798
992
|
const networkInterfaces = os.networkInterfaces();
|
|
799
993
|
this.systemInformation.interfaceName = '';
|
|
800
994
|
this.systemInformation.ipv4Address = '';
|
|
801
995
|
this.systemInformation.ipv6Address = '';
|
|
802
996
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
997
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
803
998
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
804
999
|
continue;
|
|
805
1000
|
if (!interfaceDetails) {
|
|
@@ -825,19 +1020,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
825
1020
|
break;
|
|
826
1021
|
}
|
|
827
1022
|
}
|
|
1023
|
+
// Node information
|
|
828
1024
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
829
1025
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
830
1026
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
831
1027
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1028
|
+
// Host system information
|
|
832
1029
|
this.systemInformation.hostname = os.hostname();
|
|
833
1030
|
this.systemInformation.user = os.userInfo().username;
|
|
834
|
-
this.systemInformation.osType = os.type();
|
|
835
|
-
this.systemInformation.osRelease = os.release();
|
|
836
|
-
this.systemInformation.osPlatform = os.platform();
|
|
837
|
-
this.systemInformation.osArch = os.arch();
|
|
838
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
839
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
840
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1031
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1032
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1033
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1034
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1035
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1036
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1037
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1038
|
+
// Log the system information
|
|
841
1039
|
this.log.debug('Host System Information:');
|
|
842
1040
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
843
1041
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -853,14 +1051,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
853
1051
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
854
1052
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
855
1053
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1054
|
+
// Log directories
|
|
856
1055
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
857
1056
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
858
1057
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
859
1058
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
860
1059
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1060
|
+
// Global node_modules directory
|
|
861
1061
|
if (this.nodeContext)
|
|
862
1062
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
863
1063
|
if (this.globalModulesDirectory === '') {
|
|
1064
|
+
// First run of Matterbridge so the node storage is empty
|
|
864
1065
|
try {
|
|
865
1066
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
866
1067
|
this.execRunningCount++;
|
|
@@ -875,50 +1076,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
875
1076
|
}
|
|
876
1077
|
else
|
|
877
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
|
|
878
1093
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
879
1094
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
880
1095
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
881
1096
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1097
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
882
1098
|
if (this.nodeContext)
|
|
883
1099
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
884
1100
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1101
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
885
1102
|
if (this.nodeContext)
|
|
886
1103
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
887
1104
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1105
|
+
// Current working directory
|
|
888
1106
|
const currentDir = process.cwd();
|
|
889
1107
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1108
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
890
1109
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
891
1110
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
892
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
|
+
*/
|
|
893
1117
|
createMatterLogger() {
|
|
894
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1118
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
895
1119
|
return (level, formattedLog) => {
|
|
896
1120
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
897
1121
|
const message = formattedLog.slice(65);
|
|
898
1122
|
matterLogger.logName = logger;
|
|
899
1123
|
switch (level) {
|
|
900
1124
|
case MatterLogLevel.DEBUG:
|
|
901
|
-
matterLogger.log("debug"
|
|
1125
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
902
1126
|
break;
|
|
903
1127
|
case MatterLogLevel.INFO:
|
|
904
|
-
matterLogger.log("info"
|
|
1128
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
905
1129
|
break;
|
|
906
1130
|
case MatterLogLevel.NOTICE:
|
|
907
|
-
matterLogger.log("notice"
|
|
1131
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
908
1132
|
break;
|
|
909
1133
|
case MatterLogLevel.WARN:
|
|
910
|
-
matterLogger.log("warn"
|
|
1134
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
911
1135
|
break;
|
|
912
1136
|
case MatterLogLevel.ERROR:
|
|
913
|
-
matterLogger.log("error"
|
|
1137
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
914
1138
|
break;
|
|
915
1139
|
case MatterLogLevel.FATAL:
|
|
916
|
-
matterLogger.log("fatal"
|
|
1140
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
917
1141
|
break;
|
|
918
1142
|
}
|
|
919
1143
|
};
|
|
920
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
|
+
*/
|
|
921
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
|
|
922
1154
|
let fileSize = 0;
|
|
923
1155
|
if (unlink) {
|
|
924
1156
|
try {
|
|
@@ -929,10 +1161,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
929
1161
|
}
|
|
930
1162
|
}
|
|
931
1163
|
return async (level, formattedLog) => {
|
|
1164
|
+
/* istanbul ignore if */
|
|
932
1165
|
if (fileSize > 100000000) {
|
|
933
|
-
return;
|
|
1166
|
+
return; // Stop logging if the file size is greater than 100MB
|
|
934
1167
|
}
|
|
935
1168
|
fileSize += formattedLog.length;
|
|
1169
|
+
/* istanbul ignore if */
|
|
936
1170
|
if (fileSize > 100000000) {
|
|
937
1171
|
await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
|
|
938
1172
|
return;
|
|
@@ -965,12 +1199,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
965
1199
|
}
|
|
966
1200
|
};
|
|
967
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1204
|
+
*/
|
|
968
1205
|
async restartProcess() {
|
|
969
1206
|
await this.cleanup('restarting...', true);
|
|
970
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Shut down the process.
|
|
1210
|
+
*/
|
|
971
1211
|
async shutdownProcess() {
|
|
972
1212
|
await this.cleanup('shutting down...', false);
|
|
973
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Update matterbridge and shut down the process.
|
|
1216
|
+
*/
|
|
974
1217
|
async updateProcess() {
|
|
975
1218
|
this.log.info('Updating matterbridge...');
|
|
976
1219
|
try {
|
|
@@ -984,52 +1227,75 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
1227
|
this.frontend.wssSendRestartRequired();
|
|
985
1228
|
await this.cleanup('updating...', false);
|
|
986
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Unregister all devices and shut down the process.
|
|
1232
|
+
*/
|
|
987
1233
|
async unregisterAndShutdownProcess() {
|
|
988
1234
|
this.log.info('Unregistering all devices and shutting down...');
|
|
989
1235
|
for (const plugin of this.plugins) {
|
|
990
1236
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
991
1237
|
}
|
|
992
1238
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
993
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1239
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
994
1240
|
this.log.debug('Cleaning up and shutting down...');
|
|
995
1241
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
996
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Reset commissioning and shut down the process.
|
|
1245
|
+
*/
|
|
997
1246
|
async shutdownProcessAndReset() {
|
|
998
1247
|
await this.cleanup('shutting down with reset...', false);
|
|
999
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Factory reset and shut down the process.
|
|
1251
|
+
*/
|
|
1000
1252
|
async shutdownProcessAndFactoryReset() {
|
|
1001
1253
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1002
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Cleans up the Matterbridge instance.
|
|
1257
|
+
*
|
|
1258
|
+
* @param {string} message - The cleanup message.
|
|
1259
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1260
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1261
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1262
|
+
*/
|
|
1003
1263
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
1004
1264
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1005
1265
|
this.emit('cleanup_started');
|
|
1006
1266
|
this.hasCleanupStarted = true;
|
|
1007
1267
|
this.log.info(message);
|
|
1268
|
+
// Clear the start matter interval
|
|
1008
1269
|
if (this.startMatterInterval) {
|
|
1009
1270
|
clearInterval(this.startMatterInterval);
|
|
1010
1271
|
this.startMatterInterval = undefined;
|
|
1011
1272
|
this.log.debug('Start matter interval cleared');
|
|
1012
1273
|
}
|
|
1274
|
+
// Clear the check update timeout
|
|
1013
1275
|
if (this.checkUpdateTimeout) {
|
|
1014
1276
|
clearTimeout(this.checkUpdateTimeout);
|
|
1015
1277
|
this.checkUpdateTimeout = undefined;
|
|
1016
1278
|
this.log.debug('Check update timeout cleared');
|
|
1017
1279
|
}
|
|
1280
|
+
// Clear the check update interval
|
|
1018
1281
|
if (this.checkUpdateInterval) {
|
|
1019
1282
|
clearInterval(this.checkUpdateInterval);
|
|
1020
1283
|
this.checkUpdateInterval = undefined;
|
|
1021
1284
|
this.log.debug('Check update interval cleared');
|
|
1022
1285
|
}
|
|
1286
|
+
// Clear the configure timeout
|
|
1023
1287
|
if (this.configureTimeout) {
|
|
1024
1288
|
clearTimeout(this.configureTimeout);
|
|
1025
1289
|
this.configureTimeout = undefined;
|
|
1026
1290
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1027
1291
|
}
|
|
1292
|
+
// Clear the reachability timeout
|
|
1028
1293
|
if (this.reachabilityTimeout) {
|
|
1029
1294
|
clearTimeout(this.reachabilityTimeout);
|
|
1030
1295
|
this.reachabilityTimeout = undefined;
|
|
1031
1296
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1032
1297
|
}
|
|
1298
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1033
1299
|
for (const plugin of this.plugins) {
|
|
1034
1300
|
if (!plugin.enabled || plugin.error)
|
|
1035
1301
|
continue;
|
|
@@ -1040,9 +1306,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1040
1306
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1041
1307
|
}
|
|
1042
1308
|
}
|
|
1309
|
+
// Stop matter server nodes
|
|
1043
1310
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1044
1311
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1045
|
-
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
1312
|
+
await new Promise((resolve) => setTimeout(resolve, timeout)); // Wait for MessageExchange to finish
|
|
1046
1313
|
if (this.bridgeMode === 'bridge') {
|
|
1047
1314
|
if (this.serverNode) {
|
|
1048
1315
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1064,6 +1331,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1064
1331
|
}
|
|
1065
1332
|
}
|
|
1066
1333
|
this.log.notice('Stopped matter server nodes');
|
|
1334
|
+
// Matter commisioning reset
|
|
1067
1335
|
if (message === 'shutting down with reset...') {
|
|
1068
1336
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1069
1337
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1073,18 +1341,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1073
1341
|
await this.matterbridgeContext?.clearAll();
|
|
1074
1342
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1075
1343
|
}
|
|
1344
|
+
// Stop matter storage
|
|
1076
1345
|
await this.stopMatterStorage();
|
|
1346
|
+
// Stop the frontend
|
|
1077
1347
|
await this.frontend.stop();
|
|
1348
|
+
// Remove the matterfilelogger
|
|
1078
1349
|
try {
|
|
1079
1350
|
Logger.removeLogger('matterfilelogger');
|
|
1080
1351
|
}
|
|
1081
1352
|
catch (error) {
|
|
1082
1353
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1083
1354
|
}
|
|
1355
|
+
// Close the matterbridge node storage and context
|
|
1084
1356
|
if (this.nodeStorage && this.nodeContext) {
|
|
1357
|
+
/*
|
|
1358
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1359
|
+
this.log.info('Saving registered devices...');
|
|
1360
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1361
|
+
this.devices.forEach(async (device) => {
|
|
1362
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1363
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1364
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1365
|
+
});
|
|
1366
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1367
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1368
|
+
*/
|
|
1369
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1085
1370
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1086
1371
|
await this.nodeContext.close();
|
|
1087
1372
|
this.nodeContext = undefined;
|
|
1373
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1088
1374
|
for (const plugin of this.plugins) {
|
|
1089
1375
|
if (plugin.nodeContext) {
|
|
1090
1376
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1101,8 +1387,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1101
1387
|
}
|
|
1102
1388
|
this.plugins.clear();
|
|
1103
1389
|
this.devices.clear();
|
|
1390
|
+
// Factory reset
|
|
1104
1391
|
if (message === 'shutting down with factory reset...') {
|
|
1105
1392
|
try {
|
|
1393
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1106
1394
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1107
1395
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1108
1396
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1116,6 +1404,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1116
1404
|
}
|
|
1117
1405
|
}
|
|
1118
1406
|
try {
|
|
1407
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1119
1408
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1120
1409
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1121
1410
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1130,12 +1419,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1130
1419
|
}
|
|
1131
1420
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1132
1421
|
}
|
|
1422
|
+
// Deregisters the process handlers
|
|
1133
1423
|
this.deregisterProcessHandlers();
|
|
1134
1424
|
if (restart) {
|
|
1135
1425
|
if (message === 'updating...') {
|
|
1136
1426
|
this.log.info('Cleanup completed. Updating...');
|
|
1137
1427
|
Matterbridge.instance = undefined;
|
|
1138
|
-
this.emit('update');
|
|
1428
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1139
1429
|
}
|
|
1140
1430
|
else if (message === 'restarting...') {
|
|
1141
1431
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1156,6 +1446,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1156
1446
|
this.log.debug('Cleanup already started...');
|
|
1157
1447
|
}
|
|
1158
1448
|
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Creates and configures the server node for a single not bridged device.
|
|
1451
|
+
*
|
|
1452
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1453
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1454
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1455
|
+
*/
|
|
1159
1456
|
async createDeviceServerNode(plugin, device) {
|
|
1160
1457
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1161
1458
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1166,6 +1463,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1166
1463
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1167
1464
|
}
|
|
1168
1465
|
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1468
|
+
*
|
|
1469
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1470
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1471
|
+
* @param {boolean} [start] - Whether to start the server node after adding the device. Default is `false`.
|
|
1472
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1473
|
+
*/
|
|
1169
1474
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1170
1475
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1171
1476
|
plugin.locked = true;
|
|
@@ -1179,6 +1484,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1179
1484
|
await this.startServerNode(plugin.serverNode);
|
|
1180
1485
|
}
|
|
1181
1486
|
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
1489
|
+
*
|
|
1490
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1491
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
1492
|
+
*/
|
|
1182
1493
|
async createDynamicPlugin(plugin) {
|
|
1183
1494
|
if (!plugin.locked) {
|
|
1184
1495
|
plugin.locked = true;
|
|
@@ -1189,7 +1500,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1189
1500
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1190
1501
|
}
|
|
1191
1502
|
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Starts the Matterbridge in bridge mode.
|
|
1505
|
+
*
|
|
1506
|
+
* @private
|
|
1507
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1508
|
+
*/
|
|
1192
1509
|
async startBridge() {
|
|
1510
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1193
1511
|
if (!this.matterStorageManager)
|
|
1194
1512
|
throw new Error('No storage manager initialized');
|
|
1195
1513
|
if (!this.matterbridgeContext)
|
|
@@ -1228,13 +1546,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1228
1546
|
clearInterval(this.startMatterInterval);
|
|
1229
1547
|
this.startMatterInterval = undefined;
|
|
1230
1548
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1231
|
-
|
|
1549
|
+
// Start the Matter server node
|
|
1550
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1551
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1232
1552
|
for (const device of this.devices.array()) {
|
|
1233
1553
|
if (device.mode === 'server' && device.serverNode) {
|
|
1234
1554
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1235
|
-
this.startServerNode(device.serverNode);
|
|
1555
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1236
1556
|
}
|
|
1237
1557
|
}
|
|
1558
|
+
// Configure the plugins
|
|
1238
1559
|
this.configureTimeout = setTimeout(async () => {
|
|
1239
1560
|
for (const plugin of this.plugins) {
|
|
1240
1561
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1252,16 +1573,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1252
1573
|
}
|
|
1253
1574
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1254
1575
|
}, 30 * 1000).unref();
|
|
1576
|
+
// Setting reachability to true
|
|
1255
1577
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1256
1578
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1257
1579
|
if (this.aggregatorNode)
|
|
1258
1580
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1259
1581
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1260
1582
|
}, 60 * 1000).unref();
|
|
1583
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1261
1584
|
this.emit('bridge_started');
|
|
1262
1585
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1263
1586
|
}, 1000);
|
|
1264
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1590
|
+
*
|
|
1591
|
+
* @private
|
|
1592
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1593
|
+
*/
|
|
1265
1594
|
async startChildbridge() {
|
|
1266
1595
|
if (!this.matterStorageManager)
|
|
1267
1596
|
throw new Error('No storage manager initialized');
|
|
@@ -1298,8 +1627,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1298
1627
|
return;
|
|
1299
1628
|
clearInterval(this.startMatterInterval);
|
|
1300
1629
|
this.startMatterInterval = undefined;
|
|
1301
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1630
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second to ensure all plugins server nodes are ready
|
|
1302
1631
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1632
|
+
// Configure the plugins
|
|
1303
1633
|
this.configureTimeout = setTimeout(async () => {
|
|
1304
1634
|
for (const plugin of this.plugins) {
|
|
1305
1635
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1336,7 +1666,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1336
1666
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1337
1667
|
continue;
|
|
1338
1668
|
}
|
|
1339
|
-
|
|
1669
|
+
// Start the Matter server node
|
|
1670
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1671
|
+
// Setting reachability to true
|
|
1340
1672
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1341
1673
|
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}`);
|
|
1342
1674
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1344,19 +1676,241 @@ export class Matterbridge extends EventEmitter {
|
|
|
1344
1676
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1345
1677
|
}, 60 * 1000).unref();
|
|
1346
1678
|
}
|
|
1679
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1347
1680
|
for (const device of this.devices.array()) {
|
|
1348
1681
|
if (device.mode === 'server' && device.serverNode) {
|
|
1349
1682
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1350
|
-
this.startServerNode(device.serverNode);
|
|
1683
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1351
1684
|
}
|
|
1352
1685
|
}
|
|
1686
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1353
1687
|
this.emit('childbridge_started');
|
|
1354
1688
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1355
1689
|
}, 1000);
|
|
1356
1690
|
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Starts the Matterbridge controller.
|
|
1693
|
+
*
|
|
1694
|
+
* @private
|
|
1695
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1696
|
+
*/
|
|
1357
1697
|
async startController() {
|
|
1698
|
+
/*
|
|
1699
|
+
if (!this.matterStorageManager) {
|
|
1700
|
+
this.log.error('No storage manager initialized');
|
|
1701
|
+
await this.cleanup('No storage manager initialized');
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1705
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1706
|
+
if (!this.controllerContext) {
|
|
1707
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1708
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1713
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1714
|
+
this.log.info('Creating matter commissioning controller');
|
|
1715
|
+
this.commissioningController = new CommissioningController({
|
|
1716
|
+
autoConnect: false,
|
|
1717
|
+
});
|
|
1718
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1719
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1720
|
+
|
|
1721
|
+
this.log.info('Starting matter server');
|
|
1722
|
+
await this.matterServer.start();
|
|
1723
|
+
this.log.info('Matter server started');
|
|
1724
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1725
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1726
|
+
regulatoryCountryCode: 'XX',
|
|
1727
|
+
};
|
|
1728
|
+
const commissioningController = new CommissioningController({
|
|
1729
|
+
environment: {
|
|
1730
|
+
environment,
|
|
1731
|
+
id: uniqueId,
|
|
1732
|
+
},
|
|
1733
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1734
|
+
adminFabricLabel,
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
if (hasParameter('pairingcode')) {
|
|
1738
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1739
|
+
const pairingCode = getParameter('pairingcode');
|
|
1740
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1741
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1742
|
+
|
|
1743
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1744
|
+
if (pairingCode !== undefined) {
|
|
1745
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1746
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1747
|
+
longDiscriminator = undefined;
|
|
1748
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1749
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1750
|
+
} else {
|
|
1751
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1752
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1753
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1754
|
+
}
|
|
1755
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1756
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const options = {
|
|
1760
|
+
commissioning: commissioningOptions,
|
|
1761
|
+
discovery: {
|
|
1762
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1763
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1764
|
+
},
|
|
1765
|
+
passcode: setupPin,
|
|
1766
|
+
} as NodeCommissioningOptions;
|
|
1767
|
+
this.log.info('Commissioning with options:', options);
|
|
1768
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1769
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1770
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1771
|
+
} // (hasParameter('pairingcode'))
|
|
1772
|
+
|
|
1773
|
+
if (hasParameter('unpairall')) {
|
|
1774
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1775
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1776
|
+
for (const nodeId of nodeIds) {
|
|
1777
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1778
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1779
|
+
}
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
if (hasParameter('discover')) {
|
|
1784
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1785
|
+
// console.log(discover);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1789
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1794
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1795
|
+
for (const nodeId of nodeIds) {
|
|
1796
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1797
|
+
|
|
1798
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1799
|
+
autoSubscribe: false,
|
|
1800
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1801
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1802
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1803
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1804
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1805
|
+
switch (info) {
|
|
1806
|
+
case NodeStateInformation.Connected:
|
|
1807
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1808
|
+
break;
|
|
1809
|
+
case NodeStateInformation.Disconnected:
|
|
1810
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1811
|
+
break;
|
|
1812
|
+
case NodeStateInformation.Reconnecting:
|
|
1813
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1814
|
+
break;
|
|
1815
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1816
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1817
|
+
break;
|
|
1818
|
+
case NodeStateInformation.StructureChanged:
|
|
1819
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1820
|
+
break;
|
|
1821
|
+
case NodeStateInformation.Decommissioned:
|
|
1822
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1823
|
+
break;
|
|
1824
|
+
default:
|
|
1825
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1826
|
+
break;
|
|
1827
|
+
}
|
|
1828
|
+
},
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1831
|
+
node.logStructure();
|
|
1832
|
+
|
|
1833
|
+
// Get the interaction client
|
|
1834
|
+
this.log.info('Getting the interaction client');
|
|
1835
|
+
const interactionClient = await node.getInteractionClient();
|
|
1836
|
+
let cluster;
|
|
1837
|
+
let attributes;
|
|
1838
|
+
|
|
1839
|
+
// Log BasicInformationCluster
|
|
1840
|
+
cluster = BasicInformationCluster;
|
|
1841
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1842
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1843
|
+
});
|
|
1844
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1845
|
+
attributes.forEach((attribute) => {
|
|
1846
|
+
this.log.info(
|
|
1847
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1848
|
+
);
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
// Log PowerSourceCluster
|
|
1852
|
+
cluster = PowerSourceCluster;
|
|
1853
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1854
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1855
|
+
});
|
|
1856
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1857
|
+
attributes.forEach((attribute) => {
|
|
1858
|
+
this.log.info(
|
|
1859
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1860
|
+
);
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
// Log ThreadNetworkDiagnostics
|
|
1864
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1865
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1866
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1867
|
+
});
|
|
1868
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1869
|
+
attributes.forEach((attribute) => {
|
|
1870
|
+
this.log.info(
|
|
1871
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1872
|
+
);
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
// Log SwitchCluster
|
|
1876
|
+
cluster = SwitchCluster;
|
|
1877
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1878
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1879
|
+
});
|
|
1880
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1881
|
+
attributes.forEach((attribute) => {
|
|
1882
|
+
this.log.info(
|
|
1883
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1884
|
+
);
|
|
1885
|
+
});
|
|
1886
|
+
|
|
1887
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1888
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1889
|
+
ignoreInitialTriggers: false,
|
|
1890
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1891
|
+
this.log.info(
|
|
1892
|
+
`***${db}Commissioning controller attributeChangedCallback version ${version}: attribute ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${attributeName}${db} changed to ${typeof value === 'object' ? debugStringify(value ?? { none: true }) : value}`,
|
|
1893
|
+
),
|
|
1894
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1895
|
+
this.log.info(
|
|
1896
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1897
|
+
);
|
|
1898
|
+
},
|
|
1899
|
+
});
|
|
1900
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1901
|
+
}
|
|
1902
|
+
*/
|
|
1358
1903
|
}
|
|
1904
|
+
/** */
|
|
1905
|
+
/** Matter.js methods */
|
|
1906
|
+
/** */
|
|
1907
|
+
/**
|
|
1908
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1909
|
+
*
|
|
1910
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1911
|
+
*/
|
|
1359
1912
|
async startMatterStorage() {
|
|
1913
|
+
// Setup Matter storage
|
|
1360
1914
|
this.log.info(`Starting matter node storage...`);
|
|
1361
1915
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1362
1916
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1365,8 +1919,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1365
1919
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1366
1920
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1367
1921
|
this.log.info('Matter node storage started');
|
|
1922
|
+
// Backup matter storage since it is created/opened correctly
|
|
1368
1923
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1369
1924
|
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1927
|
+
*
|
|
1928
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1929
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1930
|
+
* @private
|
|
1931
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1932
|
+
*/
|
|
1370
1933
|
async backupMatterStorage(storageName, backupName) {
|
|
1371
1934
|
this.log.info('Creating matter node storage backup...');
|
|
1372
1935
|
try {
|
|
@@ -1377,6 +1940,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1377
1940
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1378
1941
|
}
|
|
1379
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Stops the matter storage.
|
|
1945
|
+
*
|
|
1946
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1947
|
+
*/
|
|
1380
1948
|
async stopMatterStorage() {
|
|
1381
1949
|
this.log.info('Closing matter node storage...');
|
|
1382
1950
|
await this.matterStorageManager?.close();
|
|
@@ -1385,6 +1953,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1385
1953
|
this.matterbridgeContext = undefined;
|
|
1386
1954
|
this.log.info('Matter node storage closed');
|
|
1387
1955
|
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Creates a server node storage context.
|
|
1958
|
+
*
|
|
1959
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1960
|
+
* @param {string} deviceName - The name of the device.
|
|
1961
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1962
|
+
* @param {number} vendorId - The vendor ID.
|
|
1963
|
+
* @param {string} vendorName - The vendor name.
|
|
1964
|
+
* @param {number} productId - The product ID.
|
|
1965
|
+
* @param {string} productName - The product name.
|
|
1966
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1967
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1968
|
+
*/
|
|
1388
1969
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1389
1970
|
const { randomBytes } = await import('node:crypto');
|
|
1390
1971
|
if (!this.matterStorageService)
|
|
@@ -1418,6 +1999,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1418
1999
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1419
2000
|
return storageContext;
|
|
1420
2001
|
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Creates a server node.
|
|
2004
|
+
*
|
|
2005
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2006
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2007
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2008
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2009
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2010
|
+
*/
|
|
1421
2011
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1422
2012
|
const storeId = await storageContext.get('storeId');
|
|
1423
2013
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1427,24 +2017,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1427
2017
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1428
2018
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1429
2019
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2020
|
+
/**
|
|
2021
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2022
|
+
*/
|
|
1430
2023
|
const serverNode = await ServerNode.create({
|
|
2024
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1431
2025
|
id: storeId,
|
|
2026
|
+
// Provide Network relevant configuration like the port
|
|
2027
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1432
2028
|
network: {
|
|
1433
2029
|
listeningAddressIpv4: this.ipv4address,
|
|
1434
2030
|
listeningAddressIpv6: this.ipv6address,
|
|
1435
2031
|
port,
|
|
1436
2032
|
},
|
|
2033
|
+
// Provide the certificate for the device
|
|
1437
2034
|
operationalCredentials: {
|
|
1438
2035
|
certification: this.certification,
|
|
1439
2036
|
},
|
|
2037
|
+
// Provide Commissioning relevant settings
|
|
2038
|
+
// Optional for development/testing purposes
|
|
1440
2039
|
commissioning: {
|
|
1441
2040
|
passcode,
|
|
1442
2041
|
discriminator,
|
|
1443
2042
|
},
|
|
2043
|
+
// Provide Node announcement settings
|
|
2044
|
+
// Optional: If Ommitted some development defaults are used
|
|
1444
2045
|
productDescription: {
|
|
1445
2046
|
name: await storageContext.get('deviceName'),
|
|
1446
2047
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1447
2048
|
},
|
|
2049
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2050
|
+
// Optional: If Omitted some development defaults are used
|
|
1448
2051
|
basicInformation: {
|
|
1449
2052
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1450
2053
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1461,14 +2064,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1461
2064
|
reachable: true,
|
|
1462
2065
|
},
|
|
1463
2066
|
});
|
|
2067
|
+
/**
|
|
2068
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2069
|
+
* This means: It is added to the first fabric.
|
|
2070
|
+
*/
|
|
1464
2071
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1465
2072
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1466
2073
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1467
2074
|
});
|
|
2075
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1468
2076
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1469
2077
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1470
2078
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1471
2079
|
});
|
|
2080
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1472
2081
|
serverNode.lifecycle.online.on(async () => {
|
|
1473
2082
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1474
2083
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1476,6 +2085,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
2085
|
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
1477
2086
|
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1478
2087
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2088
|
+
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
1479
2089
|
this.startEndAdvertiseTimer(serverNode);
|
|
1480
2090
|
}
|
|
1481
2091
|
else {
|
|
@@ -1486,14 +2096,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1486
2096
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1487
2097
|
this.emit('online', storeId);
|
|
1488
2098
|
});
|
|
2099
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1489
2100
|
serverNode.lifecycle.offline.on(() => {
|
|
1490
2101
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1491
|
-
this.matterbridgeInformation.matterbridgeEndAdvertise = true;
|
|
2102
|
+
this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
|
|
1492
2103
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1493
2104
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1494
2105
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1495
2106
|
this.emit('offline', storeId);
|
|
1496
2107
|
});
|
|
2108
|
+
/**
|
|
2109
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2110
|
+
* information is needed.
|
|
2111
|
+
*/
|
|
1497
2112
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1498
2113
|
let action = '';
|
|
1499
2114
|
switch (fabricAction) {
|
|
@@ -1510,14 +2125,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1510
2125
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1511
2126
|
this.frontend.wssSendRefreshRequired('fabrics');
|
|
1512
2127
|
});
|
|
2128
|
+
/**
|
|
2129
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2130
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2131
|
+
*/
|
|
1513
2132
|
serverNode.events.sessions.opened.on((session) => {
|
|
1514
2133
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1515
2134
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1516
2135
|
});
|
|
2136
|
+
/**
|
|
2137
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2138
|
+
*/
|
|
1517
2139
|
serverNode.events.sessions.closed.on((session) => {
|
|
1518
2140
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1519
2141
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1520
2142
|
});
|
|
2143
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1521
2144
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1522
2145
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1523
2146
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
@@ -1525,6 +2148,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1525
2148
|
this.log.info(`Created server node for ${storeId}`);
|
|
1526
2149
|
return serverNode;
|
|
1527
2150
|
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2153
|
+
*
|
|
2154
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2155
|
+
*/
|
|
1528
2156
|
startEndAdvertiseTimer(matterServerNode) {
|
|
1529
2157
|
if (this.endAdvertiseTimeout) {
|
|
1530
2158
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -1543,12 +2171,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1543
2171
|
this.log.notice(`Advertising on server node for ${matterServerNode.id} stopped. Restart to commission.`);
|
|
1544
2172
|
}, 15 * 60 * 1000).unref();
|
|
1545
2173
|
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Starts the specified server node.
|
|
2176
|
+
*
|
|
2177
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2178
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2179
|
+
*/
|
|
1546
2180
|
async startServerNode(matterServerNode) {
|
|
1547
2181
|
if (!matterServerNode)
|
|
1548
2182
|
return;
|
|
1549
2183
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1550
2184
|
await matterServerNode.start();
|
|
1551
2185
|
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Stops the specified server node.
|
|
2188
|
+
*
|
|
2189
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2190
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2191
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2192
|
+
*/
|
|
1552
2193
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1553
2194
|
if (!matterServerNode)
|
|
1554
2195
|
return;
|
|
@@ -1561,6 +2202,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1561
2202
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1562
2203
|
}
|
|
1563
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Advertises the specified server node.
|
|
2207
|
+
*
|
|
2208
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2209
|
+
* @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
|
|
2210
|
+
*/
|
|
1564
2211
|
async advertiseServerNode(matterServerNode) {
|
|
1565
2212
|
if (matterServerNode) {
|
|
1566
2213
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1569,19 +2216,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1569
2216
|
return { qrPairingCode, manualPairingCode };
|
|
1570
2217
|
}
|
|
1571
2218
|
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Stop advertise the specified server node.
|
|
2221
|
+
*
|
|
2222
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2223
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2224
|
+
*/
|
|
1572
2225
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1573
2226
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1574
2227
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1575
2228
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1576
2229
|
}
|
|
1577
2230
|
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Creates an aggregator node with the specified storage context.
|
|
2233
|
+
*
|
|
2234
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2235
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2236
|
+
*/
|
|
1578
2237
|
async createAggregatorNode(storageContext) {
|
|
1579
2238
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1580
2239
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1581
2240
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1582
2241
|
return aggregatorNode;
|
|
1583
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2245
|
+
*
|
|
2246
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2247
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2248
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2249
|
+
*/
|
|
1584
2250
|
async addBridgedEndpoint(pluginName, device) {
|
|
2251
|
+
// Check if the plugin is registered
|
|
1585
2252
|
const plugin = this.plugins.get(pluginName);
|
|
1586
2253
|
if (!plugin) {
|
|
1587
2254
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1601,6 +2268,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1601
2268
|
}
|
|
1602
2269
|
else if (this.bridgeMode === 'bridge') {
|
|
1603
2270
|
if (device.mode === 'matter') {
|
|
2271
|
+
// Register and add the device to the matterbridge server node
|
|
1604
2272
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1605
2273
|
if (!this.serverNode) {
|
|
1606
2274
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1617,6 +2285,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1617
2285
|
}
|
|
1618
2286
|
}
|
|
1619
2287
|
else {
|
|
2288
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1620
2289
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1621
2290
|
if (!this.aggregatorNode) {
|
|
1622
2291
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1634,6 +2303,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1634
2303
|
}
|
|
1635
2304
|
}
|
|
1636
2305
|
else if (this.bridgeMode === 'childbridge') {
|
|
2306
|
+
// Register and add the device to the plugin server node
|
|
1637
2307
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1638
2308
|
try {
|
|
1639
2309
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1657,10 +2327,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1657
2327
|
return;
|
|
1658
2328
|
}
|
|
1659
2329
|
}
|
|
2330
|
+
// Register and add the device to the plugin aggregator node
|
|
1660
2331
|
if (plugin.type === 'DynamicPlatform') {
|
|
1661
2332
|
try {
|
|
1662
2333
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1663
2334
|
await this.createDynamicPlugin(plugin);
|
|
2335
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1664
2336
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1665
2337
|
if (!plugin.aggregatorNode) {
|
|
1666
2338
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1683,17 +2355,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1683
2355
|
plugin.registeredDevices++;
|
|
1684
2356
|
if (plugin.addedDevices !== undefined)
|
|
1685
2357
|
plugin.addedDevices++;
|
|
2358
|
+
// Add the device to the DeviceManager
|
|
1686
2359
|
this.devices.set(device);
|
|
2360
|
+
// Subscribe to the reachable$Changed event
|
|
1687
2361
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1688
2362
|
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}`);
|
|
1689
2363
|
}
|
|
2364
|
+
/**
|
|
2365
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2366
|
+
*
|
|
2367
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2368
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2369
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2370
|
+
*/
|
|
1690
2371
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1691
2372
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2373
|
+
// Check if the plugin is registered
|
|
1692
2374
|
const plugin = this.plugins.get(pluginName);
|
|
1693
2375
|
if (!plugin) {
|
|
1694
2376
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1695
2377
|
return;
|
|
1696
2378
|
}
|
|
2379
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1697
2380
|
if (this.bridgeMode === 'bridge') {
|
|
1698
2381
|
if (!this.aggregatorNode) {
|
|
1699
2382
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1708,6 +2391,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1708
2391
|
}
|
|
1709
2392
|
else if (this.bridgeMode === 'childbridge') {
|
|
1710
2393
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2394
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1711
2395
|
}
|
|
1712
2396
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1713
2397
|
if (!plugin.aggregatorNode) {
|
|
@@ -1722,8 +2406,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1722
2406
|
if (plugin.addedDevices !== undefined)
|
|
1723
2407
|
plugin.addedDevices--;
|
|
1724
2408
|
}
|
|
2409
|
+
// Remove the device from the DeviceManager
|
|
1725
2410
|
this.devices.remove(device);
|
|
1726
2411
|
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2414
|
+
*
|
|
2415
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2416
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2417
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2418
|
+
*
|
|
2419
|
+
* @remarks
|
|
2420
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2421
|
+
* It also applies a delay between each removal if specified.
|
|
2422
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2423
|
+
*/
|
|
1727
2424
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1728
2425
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1729
2426
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1734,6 +2431,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1734
2431
|
if (delay > 0)
|
|
1735
2432
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1736
2433
|
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2436
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2437
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2438
|
+
*
|
|
2439
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2440
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2441
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2442
|
+
*/
|
|
1737
2443
|
async subscribeAttributeChanged(plugin, device) {
|
|
1738
2444
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1739
2445
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -1749,6 +2455,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1749
2455
|
});
|
|
1750
2456
|
}
|
|
1751
2457
|
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2460
|
+
*
|
|
2461
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2462
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2463
|
+
*/
|
|
1752
2464
|
sanitizeFabricInformations(fabricInfo) {
|
|
1753
2465
|
return fabricInfo.map((info) => {
|
|
1754
2466
|
return {
|
|
@@ -1762,6 +2474,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1762
2474
|
};
|
|
1763
2475
|
});
|
|
1764
2476
|
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2479
|
+
*
|
|
2480
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2481
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2482
|
+
*/
|
|
1765
2483
|
sanitizeSessionInformation(sessions) {
|
|
1766
2484
|
return sessions
|
|
1767
2485
|
.filter((session) => session.isPeerActive)
|
|
@@ -1788,7 +2506,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1788
2506
|
};
|
|
1789
2507
|
});
|
|
1790
2508
|
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2511
|
+
*
|
|
2512
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2513
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2514
|
+
*/
|
|
2515
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1791
2516
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2517
|
+
/*
|
|
2518
|
+
for (const child of aggregatorNode.parts) {
|
|
2519
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2520
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2521
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2522
|
+
}
|
|
2523
|
+
*/
|
|
1792
2524
|
}
|
|
1793
2525
|
getVendorIdName = (vendorId) => {
|
|
1794
2526
|
if (!vendorId)
|
|
@@ -1828,10 +2560,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1828
2560
|
case 0x1488:
|
|
1829
2561
|
vendorName = '(ShortcutLabsFlic)';
|
|
1830
2562
|
break;
|
|
1831
|
-
case 65521:
|
|
2563
|
+
case 65521: // 0xFFF1
|
|
1832
2564
|
vendorName = '(MatterTest)';
|
|
1833
2565
|
break;
|
|
1834
2566
|
}
|
|
1835
2567
|
return vendorName;
|
|
1836
2568
|
};
|
|
1837
2569
|
}
|
|
2570
|
+
//# sourceMappingURL=matterbridge.js.map
|