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