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