matterbridge 3.0.4 → 3.0.5-dev-20250528-9314890
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 +17 -0
- package/dist/cli.js +81 -52
- package/dist/cluster/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -23
- package/dist/deviceManager.js +1 -94
- package/dist/frontend.js +16 -363
- package/dist/helpers.js +0 -51
- package/dist/index.js +1 -27
- package/dist/laundryWasher.js +125 -0
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -2
- package/dist/matter/types.js +0 -2
- package/dist/matterbridge.js +47 -747
- package/dist/matterbridgeAccessoryPlatform.js +0 -34
- package/dist/matterbridgeBehaviors.js +4 -63
- package/dist/matterbridgeDeviceTypes.js +15 -563
- package/dist/matterbridgeDynamicPlatform.js +0 -34
- package/dist/matterbridgeEndpoint.js +23 -903
- package/dist/matterbridgeEndpointHelpers.js +11 -173
- package/dist/matterbridgePlatform.js +7 -225
- package/dist/matterbridgeTypes.js +0 -24
- package/dist/pluginManager.js +3 -264
- package/dist/roboticVacuumCleaner.js +3 -78
- package/dist/shelly.js +7 -155
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -53
- package/dist/utils/colorUtils.js +2 -205
- package/dist/utils/commandLine.js +0 -53
- package/dist/utils/copyDirectory.js +1 -37
- package/dist/utils/createZip.js +2 -42
- package/dist/utils/deepCopy.js +0 -38
- package/dist/utils/deepEqual.js +1 -71
- package/dist/utils/export.js +0 -1
- package/dist/utils/hex.js +0 -57
- package/dist/utils/isvalid.js +0 -100
- package/dist/utils/network.js +5 -76
- package/dist/utils/wait.js +9 -58
- package/dist/waterHeater.js +0 -52
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -2
- package/dist/cli.d.ts +0 -29
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cluster/export.d.ts +0 -2
- package/dist/cluster/export.d.ts.map +0 -1
- package/dist/cluster/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -27
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -114
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/frontend.d.ts +0 -256
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/helpers.d.ts +0 -47
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -35
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -445
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -40
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -1398
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -629
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -40
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1053
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -2749
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -294
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -196
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -273
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/roboticVacuumCleaner.d.ts +0 -82
- package/dist/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/roboticVacuumCleaner.js.map +0 -1
- package/dist/shelly.d.ts +0 -153
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -58
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -61
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -58
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -32
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -38
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -31
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -53
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/export.d.ts +0 -11
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -48
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -102
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/network.d.ts +0 -69
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -52
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
- package/dist/waterHeater.d.ts +0 -75
- package/dist/waterHeater.d.ts.map +0 -1
- package/dist/waterHeater.js.map +0 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,36 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file contains the class Matterbridge.
|
|
3
|
-
*
|
|
4
|
-
* @file matterbridge.ts
|
|
5
|
-
* @author Luca Liguori
|
|
6
|
-
* @date 2023-12-29
|
|
7
|
-
* @version 1.5.3
|
|
8
|
-
*
|
|
9
|
-
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
-
*
|
|
11
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
-
* you may not use this file except in compliance with the License.
|
|
13
|
-
* You may obtain a copy of the License at
|
|
14
|
-
*
|
|
15
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
-
*
|
|
17
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
-
* See the License for the specific language governing permissions and
|
|
21
|
-
* limitations under the License. *
|
|
22
|
-
*/
|
|
23
|
-
// Node.js modules
|
|
24
1
|
import os from 'node:os';
|
|
25
2
|
import path from 'node:path';
|
|
26
3
|
import { promises as fs } from 'node:fs';
|
|
27
4
|
import EventEmitter from 'node:events';
|
|
28
5
|
import { inspect } from 'node:util';
|
|
29
|
-
// AnsiLogger module
|
|
30
6
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
31
|
-
// NodeStorage module
|
|
32
7
|
import { NodeStorageManager } from './storage/export.js';
|
|
33
|
-
// Matterbridge
|
|
34
8
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber } from './utils/export.js';
|
|
35
9
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
36
10
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -40,15 +14,11 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
40
14
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
41
15
|
import { Frontend } from './frontend.js';
|
|
42
16
|
import { addVirtualDevices } from './helpers.js';
|
|
43
|
-
// @matter
|
|
44
17
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
|
|
45
18
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
46
19
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
47
20
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
48
21
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
49
|
-
/**
|
|
50
|
-
* Represents the Matterbridge application.
|
|
51
|
-
*/
|
|
52
22
|
export class Matterbridge extends EventEmitter {
|
|
53
23
|
systemInformation = {
|
|
54
24
|
interfaceName: '',
|
|
@@ -96,7 +66,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
96
66
|
shellySysUpdate: false,
|
|
97
67
|
shellyMainUpdate: false,
|
|
98
68
|
profile: getParameter('profile'),
|
|
99
|
-
loggerLevel: "info"
|
|
69
|
+
loggerLevel: "info",
|
|
100
70
|
fileLogger: false,
|
|
101
71
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
102
72
|
matterFileLogger: false,
|
|
@@ -135,11 +105,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
135
105
|
plugins;
|
|
136
106
|
devices;
|
|
137
107
|
frontend = new Frontend(this);
|
|
138
|
-
// Matterbridge storage
|
|
139
108
|
nodeStorage;
|
|
140
109
|
nodeContext;
|
|
141
110
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
142
|
-
// Cleanup
|
|
143
111
|
hasCleanupStarted = false;
|
|
144
112
|
initialized = false;
|
|
145
113
|
execRunningCount = 0;
|
|
@@ -152,22 +120,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
152
120
|
sigtermHandler;
|
|
153
121
|
exceptionHandler;
|
|
154
122
|
rejectionHandler;
|
|
155
|
-
// Matter environment
|
|
156
123
|
environment = Environment.default;
|
|
157
|
-
// Matter storage
|
|
158
124
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
159
125
|
matterStorageService;
|
|
160
126
|
matterStorageManager;
|
|
161
127
|
matterbridgeContext;
|
|
162
128
|
controllerContext;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
certification; // device certification
|
|
129
|
+
mdnsInterface;
|
|
130
|
+
ipv4address;
|
|
131
|
+
ipv6address;
|
|
132
|
+
port;
|
|
133
|
+
passcode;
|
|
134
|
+
discriminator;
|
|
135
|
+
certification;
|
|
171
136
|
serverNode;
|
|
172
137
|
aggregatorNode;
|
|
173
138
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -175,50 +140,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
175
140
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
176
141
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
177
142
|
static instance;
|
|
178
|
-
// We load asyncronously so is private
|
|
179
143
|
constructor() {
|
|
180
144
|
super();
|
|
181
145
|
}
|
|
182
|
-
/**
|
|
183
|
-
* Emits an event of the specified type with the provided arguments.
|
|
184
|
-
*
|
|
185
|
-
* @template K - The type of the event.
|
|
186
|
-
* @param {K} eventName - The name of the event to emit.
|
|
187
|
-
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
188
|
-
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
189
|
-
*/
|
|
190
146
|
emit(eventName, ...args) {
|
|
191
147
|
return super.emit(eventName, ...args);
|
|
192
148
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Registers an event listener for the specified event type.
|
|
195
|
-
*
|
|
196
|
-
* @template K - The type of the event.
|
|
197
|
-
* @param {K} eventName - The name of the event to listen for.
|
|
198
|
-
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
199
|
-
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
200
|
-
*/
|
|
201
149
|
on(eventName, listener) {
|
|
202
150
|
return super.on(eventName, listener);
|
|
203
151
|
}
|
|
204
|
-
/**
|
|
205
|
-
* Retrieves the list of Matterbridge devices.
|
|
206
|
-
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
207
|
-
*/
|
|
208
152
|
getDevices() {
|
|
209
153
|
return this.devices.array();
|
|
210
154
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Retrieves the list of registered plugins.
|
|
213
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
214
|
-
*/
|
|
215
155
|
getPlugins() {
|
|
216
156
|
return this.plugins.array();
|
|
217
157
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
220
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
221
|
-
*/
|
|
222
158
|
async setLogLevel(logLevel) {
|
|
223
159
|
if (this.log)
|
|
224
160
|
this.log.logLevel = logLevel;
|
|
@@ -232,31 +168,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
232
168
|
for (const plugin of this.plugins) {
|
|
233
169
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
234
170
|
continue;
|
|
235
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug"
|
|
236
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug"
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
171
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
172
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
173
|
+
}
|
|
174
|
+
let callbackLogLevel = "notice";
|
|
175
|
+
if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
176
|
+
callbackLogLevel = "info";
|
|
177
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
178
|
+
callbackLogLevel = "debug";
|
|
244
179
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
245
180
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
246
181
|
}
|
|
247
|
-
/** ***********************************************************************************************************************************/
|
|
248
|
-
/** loadInstance() and cleanup() methods */
|
|
249
|
-
/** ***********************************************************************************************************************************/
|
|
250
|
-
/**
|
|
251
|
-
* Loads an instance of the Matterbridge class.
|
|
252
|
-
* If an instance already exists, return that instance.
|
|
253
|
-
*
|
|
254
|
-
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
255
|
-
* @returns The loaded Matterbridge instance.
|
|
256
|
-
*/
|
|
257
182
|
static async loadInstance(initialize = false) {
|
|
258
183
|
if (!Matterbridge.instance) {
|
|
259
|
-
// eslint-disable-next-line no-console
|
|
260
184
|
if (hasParameter('debug'))
|
|
261
185
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
262
186
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -265,14 +189,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
265
189
|
}
|
|
266
190
|
return Matterbridge.instance;
|
|
267
191
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Call cleanup().
|
|
270
|
-
*
|
|
271
|
-
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
272
|
-
*/
|
|
273
192
|
async destroyInstance() {
|
|
274
193
|
this.log.info(`Destroy instance...`);
|
|
275
|
-
// Save server nodes to close
|
|
276
194
|
const servers = [];
|
|
277
195
|
if (this.bridgeMode === 'bridge') {
|
|
278
196
|
if (this.serverNode)
|
|
@@ -284,103 +202,72 @@ export class Matterbridge extends EventEmitter {
|
|
|
284
202
|
servers.push(plugin.serverNode);
|
|
285
203
|
}
|
|
286
204
|
}
|
|
287
|
-
// Cleanup
|
|
288
205
|
await this.cleanup('destroying instance...', false);
|
|
289
|
-
// Close servers mdns service
|
|
290
206
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
291
207
|
for (const server of servers) {
|
|
292
208
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
293
209
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
294
210
|
}
|
|
295
|
-
// Wait for the cleanup to finish
|
|
296
211
|
await new Promise((resolve) => {
|
|
297
212
|
setTimeout(resolve, 500);
|
|
298
213
|
});
|
|
299
214
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Initializes the Matterbridge application.
|
|
302
|
-
*
|
|
303
|
-
* @remarks
|
|
304
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
305
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
306
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
307
|
-
*
|
|
308
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
309
|
-
*/
|
|
310
215
|
async initialize() {
|
|
311
|
-
// Emit the initialize_started event
|
|
312
216
|
this.emit('initialize_started');
|
|
313
|
-
|
|
314
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
315
|
-
// Set the restart mode
|
|
217
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
316
218
|
if (hasParameter('service'))
|
|
317
219
|
this.restartMode = 'service';
|
|
318
220
|
if (hasParameter('docker'))
|
|
319
221
|
this.restartMode = 'docker';
|
|
320
|
-
// Set the matterbridge home directory
|
|
321
222
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
322
223
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
323
224
|
await this.createDirectory(this.homeDirectory, 'Matterbridge Home Directory');
|
|
324
|
-
// Set the matterbridge directory
|
|
325
225
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
326
226
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
327
227
|
await this.createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory');
|
|
328
228
|
await this.createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory');
|
|
329
229
|
await this.createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory');
|
|
330
|
-
// Set the matterbridge plugin directory
|
|
331
230
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
332
231
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
333
232
|
await this.createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory');
|
|
334
|
-
// Set the matterbridge cert directory
|
|
335
233
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
336
234
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
337
235
|
await this.createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory');
|
|
338
|
-
// Set the matterbridge root directory
|
|
339
236
|
const { fileURLToPath } = await import('node:url');
|
|
340
237
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
341
238
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
342
239
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
343
|
-
// Setup the matter environment
|
|
344
240
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
345
241
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
346
242
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
347
243
|
this.environment.vars.set('runtime.signals', false);
|
|
348
244
|
this.environment.vars.set('runtime.exitcode', false);
|
|
349
|
-
// Register process handlers
|
|
350
245
|
this.registerProcessHandlers();
|
|
351
|
-
// Initialize nodeStorage and nodeContext
|
|
352
246
|
try {
|
|
353
247
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
354
248
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
355
249
|
this.log.debug('Creating node storage context for matterbridge');
|
|
356
250
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
357
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
358
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
359
251
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
360
252
|
for (const key of keys) {
|
|
361
253
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
362
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
363
254
|
await this.nodeStorage?.storage.get(key);
|
|
364
255
|
}
|
|
365
256
|
const storages = await this.nodeStorage.getStorageNames();
|
|
366
257
|
for (const storage of storages) {
|
|
367
258
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
368
259
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
369
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
370
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
371
260
|
const keys = (await nodeContext?.storage.keys());
|
|
372
261
|
keys.forEach(async (key) => {
|
|
373
262
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
374
263
|
await nodeContext?.get(key);
|
|
375
264
|
});
|
|
376
265
|
}
|
|
377
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
378
266
|
this.log.debug('Creating node storage backup...');
|
|
379
267
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
380
268
|
this.log.debug('Created node storage backup');
|
|
381
269
|
}
|
|
382
270
|
catch (error) {
|
|
383
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
384
271
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
385
272
|
if (hasParameter('norestore')) {
|
|
386
273
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -395,19 +282,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
395
282
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
396
283
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
397
284
|
}
|
|
398
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
399
285
|
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)
|
|
401
286
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
402
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
403
287
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
404
|
-
// Certificate management
|
|
405
288
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
406
289
|
try {
|
|
407
290
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
408
291
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
409
292
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
410
|
-
// Set the vendorId, vendorName, productId and productName if they are present in the pairing file
|
|
411
293
|
if (isValidNumber(pairingFileJson.vendorId))
|
|
412
294
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
413
295
|
if (isValidString(pairingFileJson.vendorName, 3))
|
|
@@ -416,20 +298,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
416
298
|
this.aggregatorProductId = VendorId(pairingFileJson.productId);
|
|
417
299
|
if (isValidString(pairingFileJson.productName, 3))
|
|
418
300
|
this.aggregatorProductName = pairingFileJson.productName;
|
|
419
|
-
// Override the passcode and discriminator if they are present in the pairing file
|
|
420
301
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
421
302
|
this.passcode = pairingFileJson.passcode;
|
|
422
303
|
this.discriminator = pairingFileJson.discriminator;
|
|
423
304
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
424
305
|
}
|
|
425
|
-
// Set the certification if it is present in the pairing file
|
|
426
306
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
427
307
|
const hexStringToUint8Array = (hexString) => {
|
|
428
308
|
const matches = hexString.match(/.{1,2}/g);
|
|
429
309
|
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
430
310
|
};
|
|
431
|
-
// const hexString = Buffer.from('Test string', 'utf-8').toString('hex');
|
|
432
|
-
// console.log(hexString, Buffer.from(hexStringToUint8Array(hexString)).toString('utf-8'));
|
|
433
311
|
this.certification = {
|
|
434
312
|
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
435
313
|
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
@@ -442,44 +320,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
442
320
|
catch (error) {
|
|
443
321
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
444
322
|
}
|
|
445
|
-
// Store the passcode, discriminator and port in the node context
|
|
446
323
|
await this.nodeContext.set('matterport', this.port);
|
|
447
324
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
448
325
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
449
326
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
450
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
451
327
|
if (hasParameter('logger')) {
|
|
452
328
|
const level = getParameter('logger');
|
|
453
329
|
if (level === 'debug') {
|
|
454
|
-
this.log.logLevel = "debug"
|
|
330
|
+
this.log.logLevel = "debug";
|
|
455
331
|
}
|
|
456
332
|
else if (level === 'info') {
|
|
457
|
-
this.log.logLevel = "info"
|
|
333
|
+
this.log.logLevel = "info";
|
|
458
334
|
}
|
|
459
335
|
else if (level === 'notice') {
|
|
460
|
-
this.log.logLevel = "notice"
|
|
336
|
+
this.log.logLevel = "notice";
|
|
461
337
|
}
|
|
462
338
|
else if (level === 'warn') {
|
|
463
|
-
this.log.logLevel = "warn"
|
|
339
|
+
this.log.logLevel = "warn";
|
|
464
340
|
}
|
|
465
341
|
else if (level === 'error') {
|
|
466
|
-
this.log.logLevel = "error"
|
|
342
|
+
this.log.logLevel = "error";
|
|
467
343
|
}
|
|
468
344
|
else if (level === 'fatal') {
|
|
469
|
-
this.log.logLevel = "fatal"
|
|
345
|
+
this.log.logLevel = "fatal";
|
|
470
346
|
}
|
|
471
347
|
else {
|
|
472
348
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
473
|
-
this.log.logLevel = "info"
|
|
349
|
+
this.log.logLevel = "info";
|
|
474
350
|
}
|
|
475
351
|
}
|
|
476
352
|
else {
|
|
477
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice"
|
|
353
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
478
354
|
}
|
|
479
355
|
this.frontend.logLevel = this.log.logLevel;
|
|
480
356
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
481
357
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
482
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
483
358
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
484
359
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
485
360
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -488,7 +363,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
488
363
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
489
364
|
if (this.profile !== undefined)
|
|
490
365
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
491
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
492
366
|
if (hasParameter('matterlogger')) {
|
|
493
367
|
const level = getParameter('matterlogger');
|
|
494
368
|
if (level === 'debug') {
|
|
@@ -520,7 +394,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
520
394
|
Logger.format = MatterLogFormat.ANSI;
|
|
521
395
|
Logger.setLogger('default', this.createMatterLogger());
|
|
522
396
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
523
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
524
397
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
525
398
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
526
399
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -529,7 +402,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
529
402
|
});
|
|
530
403
|
}
|
|
531
404
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
532
|
-
// Log network interfaces
|
|
533
405
|
const networkInterfaces = os.networkInterfaces();
|
|
534
406
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
535
407
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -541,7 +413,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
541
413
|
});
|
|
542
414
|
}
|
|
543
415
|
}
|
|
544
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
545
416
|
if (hasParameter('mdnsinterface')) {
|
|
546
417
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
547
418
|
}
|
|
@@ -550,7 +421,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
550
421
|
if (this.mdnsInterface === '')
|
|
551
422
|
this.mdnsInterface = undefined;
|
|
552
423
|
}
|
|
553
|
-
// Validate mdnsInterface
|
|
554
424
|
if (this.mdnsInterface) {
|
|
555
425
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
556
426
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -563,7 +433,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
563
433
|
}
|
|
564
434
|
if (this.mdnsInterface)
|
|
565
435
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
566
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
567
436
|
if (hasParameter('ipv4address')) {
|
|
568
437
|
this.ipv4address = getParameter('ipv4address');
|
|
569
438
|
}
|
|
@@ -572,7 +441,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
572
441
|
if (this.ipv4address === '')
|
|
573
442
|
this.ipv4address = undefined;
|
|
574
443
|
}
|
|
575
|
-
// Validate ipv4address
|
|
576
444
|
if (this.ipv4address) {
|
|
577
445
|
let isValid = false;
|
|
578
446
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -588,7 +456,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
588
456
|
await this.nodeContext.remove('matteripv4address');
|
|
589
457
|
}
|
|
590
458
|
}
|
|
591
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
592
459
|
if (hasParameter('ipv6address')) {
|
|
593
460
|
this.ipv6address = getParameter('ipv6address');
|
|
594
461
|
}
|
|
@@ -597,7 +464,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
597
464
|
if (this.ipv6address === '')
|
|
598
465
|
this.ipv6address = undefined;
|
|
599
466
|
}
|
|
600
|
-
// Validate ipv6address
|
|
601
467
|
if (this.ipv6address) {
|
|
602
468
|
let isValid = false;
|
|
603
469
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -618,7 +484,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
618
484
|
await this.nodeContext.remove('matteripv6address');
|
|
619
485
|
}
|
|
620
486
|
}
|
|
621
|
-
// Initialize the virtual mode
|
|
622
487
|
if (hasParameter('novirtual')) {
|
|
623
488
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
624
489
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -627,19 +492,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
627
492
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
628
493
|
}
|
|
629
494
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
630
|
-
// Initialize PluginManager
|
|
631
495
|
this.plugins = new PluginManager(this);
|
|
632
496
|
await this.plugins.loadFromStorage();
|
|
633
497
|
this.plugins.logLevel = this.log.logLevel;
|
|
634
|
-
// Initialize DeviceManager
|
|
635
498
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
636
499
|
this.devices.logLevel = this.log.logLevel;
|
|
637
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
638
500
|
for (const plugin of this.plugins) {
|
|
639
501
|
const packageJson = await this.plugins.parse(plugin);
|
|
640
502
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
641
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
642
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
643
503
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
644
504
|
try {
|
|
645
505
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -661,7 +521,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
661
521
|
await plugin.nodeContext.set('description', plugin.description);
|
|
662
522
|
await plugin.nodeContext.set('author', plugin.author);
|
|
663
523
|
}
|
|
664
|
-
// Log system info and create .matterbridge directory
|
|
665
524
|
await this.logNodeAndSystemInfo();
|
|
666
525
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
667
526
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -669,7 +528,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
669
528
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
670
529
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
671
530
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
672
|
-
// Check node version and throw error
|
|
673
531
|
const minNodeVersion = 18;
|
|
674
532
|
const nodeVersion = process.versions.node;
|
|
675
533
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -677,17 +535,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
677
535
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
678
536
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
679
537
|
}
|
|
680
|
-
// Parse command line
|
|
681
538
|
await this.parseCommandLine();
|
|
682
|
-
// Emit the initialize_completed event
|
|
683
539
|
this.emit('initialize_completed');
|
|
684
540
|
this.initialized = true;
|
|
685
541
|
}
|
|
686
|
-
/**
|
|
687
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
688
|
-
* @private
|
|
689
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
690
|
-
*/
|
|
691
542
|
async parseCommandLine() {
|
|
692
543
|
if (hasParameter('help')) {
|
|
693
544
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -809,7 +660,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
809
660
|
this.shutdown = true;
|
|
810
661
|
return;
|
|
811
662
|
}
|
|
812
|
-
// Start the matter storage and create the matterbridge context
|
|
813
663
|
try {
|
|
814
664
|
await this.startMatterStorage();
|
|
815
665
|
}
|
|
@@ -817,14 +667,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
817
667
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
818
668
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
819
669
|
}
|
|
820
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
821
670
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
822
671
|
this.initialized = true;
|
|
823
672
|
await this.shutdownProcessAndReset();
|
|
824
673
|
this.shutdown = true;
|
|
825
674
|
return;
|
|
826
675
|
}
|
|
827
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
828
676
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
829
677
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
830
678
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -849,37 +697,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
849
697
|
this.shutdown = true;
|
|
850
698
|
return;
|
|
851
699
|
}
|
|
852
|
-
// Initialize frontend
|
|
853
700
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
854
701
|
await this.frontend.start(getIntParameter('frontend'));
|
|
855
|
-
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
856
702
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
857
703
|
const { checkUpdates } = await import('./update.js');
|
|
858
704
|
checkUpdates(this);
|
|
859
705
|
}, 30 * 1000).unref();
|
|
860
|
-
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
861
706
|
this.checkUpdateInterval = setInterval(async () => {
|
|
862
707
|
const { checkUpdates } = await import('./update.js');
|
|
863
708
|
checkUpdates(this);
|
|
864
709
|
}, 12 * 60 * 60 * 1000).unref();
|
|
865
|
-
// Start the matterbridge in mode test
|
|
866
710
|
if (hasParameter('test')) {
|
|
867
711
|
this.bridgeMode = 'bridge';
|
|
868
712
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
869
713
|
return;
|
|
870
714
|
}
|
|
871
|
-
// Start the matterbridge in mode controller
|
|
872
715
|
if (hasParameter('controller')) {
|
|
873
716
|
this.bridgeMode = 'controller';
|
|
874
717
|
await this.startController();
|
|
875
718
|
return;
|
|
876
719
|
}
|
|
877
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
878
720
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
879
721
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
880
722
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
881
723
|
}
|
|
882
|
-
// Start matterbridge in bridge mode
|
|
883
724
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
884
725
|
this.bridgeMode = 'bridge';
|
|
885
726
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -887,7 +728,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
887
728
|
await this.startBridge();
|
|
888
729
|
return;
|
|
889
730
|
}
|
|
890
|
-
// Start matterbridge in childbridge mode
|
|
891
731
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
892
732
|
this.bridgeMode = 'childbridge';
|
|
893
733
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -896,20 +736,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
896
736
|
return;
|
|
897
737
|
}
|
|
898
738
|
}
|
|
899
|
-
/**
|
|
900
|
-
* Asynchronously loads and starts the registered plugins.
|
|
901
|
-
*
|
|
902
|
-
* This method is responsible for initializing and staarting all enabled plugins.
|
|
903
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
904
|
-
*
|
|
905
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
906
|
-
*/
|
|
907
739
|
async startPlugins() {
|
|
908
|
-
// Check, load and start the plugins
|
|
909
740
|
for (const plugin of this.plugins) {
|
|
910
741
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
911
742
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
912
|
-
// Check if the plugin is available
|
|
913
743
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
914
744
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
915
745
|
plugin.enabled = false;
|
|
@@ -929,14 +759,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
929
759
|
plugin.addedDevices = undefined;
|
|
930
760
|
plugin.qrPairingCode = undefined;
|
|
931
761
|
plugin.manualPairingCode = undefined;
|
|
932
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
762
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
933
763
|
}
|
|
934
764
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
935
765
|
}
|
|
936
|
-
/**
|
|
937
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
938
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
939
|
-
*/
|
|
940
766
|
registerProcessHandlers() {
|
|
941
767
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
942
768
|
process.removeAllListeners('uncaughtException');
|
|
@@ -963,9 +789,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
963
789
|
};
|
|
964
790
|
process.on('SIGTERM', this.sigtermHandler);
|
|
965
791
|
}
|
|
966
|
-
/**
|
|
967
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
968
|
-
*/
|
|
969
792
|
deregisterProcessHandlers() {
|
|
970
793
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
971
794
|
if (this.exceptionHandler)
|
|
@@ -982,17 +805,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
982
805
|
process.off('SIGTERM', this.sigtermHandler);
|
|
983
806
|
this.sigtermHandler = undefined;
|
|
984
807
|
}
|
|
985
|
-
/**
|
|
986
|
-
* Logs the node and system information.
|
|
987
|
-
*/
|
|
988
808
|
async logNodeAndSystemInfo() {
|
|
989
|
-
// IP address information
|
|
990
809
|
const networkInterfaces = os.networkInterfaces();
|
|
991
810
|
this.systemInformation.interfaceName = '';
|
|
992
811
|
this.systemInformation.ipv4Address = '';
|
|
993
812
|
this.systemInformation.ipv6Address = '';
|
|
994
813
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
995
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
996
814
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
997
815
|
continue;
|
|
998
816
|
if (!interfaceDetails) {
|
|
@@ -1018,22 +836,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1018
836
|
break;
|
|
1019
837
|
}
|
|
1020
838
|
}
|
|
1021
|
-
// Node information
|
|
1022
839
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
1023
840
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
1024
841
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
1025
842
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1026
|
-
// Host system information
|
|
1027
843
|
this.systemInformation.hostname = os.hostname();
|
|
1028
844
|
this.systemInformation.user = os.userInfo().username;
|
|
1029
|
-
this.systemInformation.osType = os.type();
|
|
1030
|
-
this.systemInformation.osRelease = os.release();
|
|
1031
|
-
this.systemInformation.osPlatform = os.platform();
|
|
1032
|
-
this.systemInformation.osArch = os.arch();
|
|
1033
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
1034
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
1035
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1036
|
-
// Log the system information
|
|
845
|
+
this.systemInformation.osType = os.type();
|
|
846
|
+
this.systemInformation.osRelease = os.release();
|
|
847
|
+
this.systemInformation.osPlatform = os.platform();
|
|
848
|
+
this.systemInformation.osArch = os.arch();
|
|
849
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
850
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
851
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1037
852
|
this.log.debug('Host System Information:');
|
|
1038
853
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
1039
854
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -1049,17 +864,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1049
864
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
1050
865
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
1051
866
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1052
|
-
// Log directories
|
|
1053
867
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1054
868
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1055
869
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1056
870
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1057
871
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1058
|
-
// Global node_modules directory
|
|
1059
872
|
if (this.nodeContext)
|
|
1060
873
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1061
874
|
if (this.globalModulesDirectory === '') {
|
|
1062
|
-
// First run of Matterbridge so the node storage is empty
|
|
1063
875
|
try {
|
|
1064
876
|
this.execRunningCount++;
|
|
1065
877
|
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory = await getGlobalNodeModules();
|
|
@@ -1073,84 +885,53 @@ export class Matterbridge extends EventEmitter {
|
|
|
1073
885
|
}
|
|
1074
886
|
else
|
|
1075
887
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1076
|
-
/* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
|
|
1077
|
-
else {
|
|
1078
|
-
this.getGlobalNodeModules()
|
|
1079
|
-
.then(async (globalModulesDirectory) => {
|
|
1080
|
-
this.globalModulesDirectory = globalModulesDirectory;
|
|
1081
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1082
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1083
|
-
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1084
|
-
})
|
|
1085
|
-
.catch((error) => {
|
|
1086
|
-
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1087
|
-
});
|
|
1088
|
-
}*/
|
|
1089
|
-
// Matterbridge version
|
|
1090
888
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1091
889
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
1092
890
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
1093
891
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1094
|
-
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
1095
892
|
if (this.nodeContext)
|
|
1096
893
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1097
894
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1098
|
-
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
1099
895
|
if (this.nodeContext)
|
|
1100
896
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
1101
897
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1102
|
-
// Current working directory
|
|
1103
898
|
const currentDir = process.cwd();
|
|
1104
899
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1105
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1106
900
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1107
901
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1108
902
|
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1111
|
-
*
|
|
1112
|
-
* @returns {Function} The MatterLogger function.
|
|
1113
|
-
*/
|
|
1114
903
|
createMatterLogger() {
|
|
1115
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
904
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1116
905
|
return (level, formattedLog) => {
|
|
1117
906
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1118
907
|
const message = formattedLog.slice(65);
|
|
1119
908
|
matterLogger.logName = logger;
|
|
1120
909
|
switch (level) {
|
|
1121
910
|
case MatterLogLevel.DEBUG:
|
|
1122
|
-
matterLogger.log("debug"
|
|
911
|
+
matterLogger.log("debug", message);
|
|
1123
912
|
break;
|
|
1124
913
|
case MatterLogLevel.INFO:
|
|
1125
|
-
matterLogger.log("info"
|
|
914
|
+
matterLogger.log("info", message);
|
|
1126
915
|
break;
|
|
1127
916
|
case MatterLogLevel.NOTICE:
|
|
1128
|
-
matterLogger.log("notice"
|
|
917
|
+
matterLogger.log("notice", message);
|
|
1129
918
|
break;
|
|
1130
919
|
case MatterLogLevel.WARN:
|
|
1131
|
-
matterLogger.log("warn"
|
|
920
|
+
matterLogger.log("warn", message);
|
|
1132
921
|
break;
|
|
1133
922
|
case MatterLogLevel.ERROR:
|
|
1134
|
-
matterLogger.log("error"
|
|
923
|
+
matterLogger.log("error", message);
|
|
1135
924
|
break;
|
|
1136
925
|
case MatterLogLevel.FATAL:
|
|
1137
|
-
matterLogger.log("fatal"
|
|
926
|
+
matterLogger.log("fatal", message);
|
|
1138
927
|
break;
|
|
1139
928
|
default:
|
|
1140
|
-
matterLogger.log("debug"
|
|
929
|
+
matterLogger.log("debug", message);
|
|
1141
930
|
break;
|
|
1142
931
|
}
|
|
1143
932
|
};
|
|
1144
933
|
}
|
|
1145
|
-
/**
|
|
1146
|
-
* Creates a Matter File Logger.
|
|
1147
|
-
*
|
|
1148
|
-
* @param {string} filePath - The path to the log file.
|
|
1149
|
-
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1150
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1151
|
-
*/
|
|
1152
934
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1153
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1154
935
|
let fileSize = 0;
|
|
1155
936
|
if (unlink) {
|
|
1156
937
|
try {
|
|
@@ -1199,21 +980,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1199
980
|
}
|
|
1200
981
|
};
|
|
1201
982
|
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1204
|
-
*/
|
|
1205
983
|
async restartProcess() {
|
|
1206
984
|
await this.cleanup('restarting...', true);
|
|
1207
985
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Shut down the process by exiting the current process.
|
|
1210
|
-
*/
|
|
1211
986
|
async shutdownProcess() {
|
|
1212
987
|
await this.cleanup('shutting down...', false);
|
|
1213
988
|
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Update matterbridge and and shut down the process.
|
|
1216
|
-
*/
|
|
1217
989
|
async updateProcess() {
|
|
1218
990
|
this.log.info('Updating matterbridge...');
|
|
1219
991
|
try {
|
|
@@ -1226,73 +998,52 @@ export class Matterbridge extends EventEmitter {
|
|
|
1226
998
|
this.frontend.wssSendRestartRequired();
|
|
1227
999
|
await this.cleanup('updating...', false);
|
|
1228
1000
|
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Unregister all devices and shut down the process.
|
|
1231
|
-
*/
|
|
1232
1001
|
async unregisterAndShutdownProcess() {
|
|
1233
1002
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1234
1003
|
for (const plugin of this.plugins) {
|
|
1235
1004
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1236
1005
|
}
|
|
1237
1006
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1238
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1007
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1239
1008
|
this.log.debug('Cleaning up and shutting down...');
|
|
1240
1009
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1241
1010
|
}
|
|
1242
|
-
/**
|
|
1243
|
-
* Reset commissioning and shut down the process.
|
|
1244
|
-
*/
|
|
1245
1011
|
async shutdownProcessAndReset() {
|
|
1246
1012
|
await this.cleanup('shutting down with reset...', false);
|
|
1247
1013
|
}
|
|
1248
|
-
/**
|
|
1249
|
-
* Factory reset and shut down the process.
|
|
1250
|
-
*/
|
|
1251
1014
|
async shutdownProcessAndFactoryReset() {
|
|
1252
1015
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1253
1016
|
}
|
|
1254
|
-
/**
|
|
1255
|
-
* Cleans up the Matterbridge instance.
|
|
1256
|
-
* @param {string} message - The cleanup message.
|
|
1257
|
-
* @param {boolean} [restart=false] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1258
|
-
* @returns A promise that resolves when the cleanup is completed.
|
|
1259
|
-
*/
|
|
1260
1017
|
async cleanup(message, restart = false) {
|
|
1261
1018
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1262
1019
|
this.emit('cleanup_started');
|
|
1263
1020
|
this.hasCleanupStarted = true;
|
|
1264
1021
|
this.log.info(message);
|
|
1265
|
-
// Clear the start matter interval
|
|
1266
1022
|
if (this.startMatterInterval) {
|
|
1267
1023
|
clearInterval(this.startMatterInterval);
|
|
1268
1024
|
this.startMatterInterval = undefined;
|
|
1269
1025
|
this.log.debug('Start matter interval cleared');
|
|
1270
1026
|
}
|
|
1271
|
-
// Clear the check update timeout
|
|
1272
1027
|
if (this.checkUpdateTimeout) {
|
|
1273
1028
|
clearInterval(this.checkUpdateTimeout);
|
|
1274
1029
|
this.checkUpdateTimeout = undefined;
|
|
1275
1030
|
this.log.debug('Check update timeout cleared');
|
|
1276
1031
|
}
|
|
1277
|
-
// Clear the check update interval
|
|
1278
1032
|
if (this.checkUpdateInterval) {
|
|
1279
1033
|
clearInterval(this.checkUpdateInterval);
|
|
1280
1034
|
this.checkUpdateInterval = undefined;
|
|
1281
1035
|
this.log.debug('Check update interval cleared');
|
|
1282
1036
|
}
|
|
1283
|
-
// Clear the configure timeout
|
|
1284
1037
|
if (this.configureTimeout) {
|
|
1285
1038
|
clearTimeout(this.configureTimeout);
|
|
1286
1039
|
this.configureTimeout = undefined;
|
|
1287
1040
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1288
1041
|
}
|
|
1289
|
-
// Clear the reachability timeout
|
|
1290
1042
|
if (this.reachabilityTimeout) {
|
|
1291
1043
|
clearTimeout(this.reachabilityTimeout);
|
|
1292
1044
|
this.reachabilityTimeout = undefined;
|
|
1293
1045
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1294
1046
|
}
|
|
1295
|
-
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1296
1047
|
for (const plugin of this.plugins) {
|
|
1297
1048
|
if (!plugin.enabled || plugin.error)
|
|
1298
1049
|
continue;
|
|
@@ -1303,10 +1054,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1303
1054
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1304
1055
|
}
|
|
1305
1056
|
}
|
|
1306
|
-
// Stop matter server nodes
|
|
1307
1057
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1308
1058
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1309
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1059
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1310
1060
|
if (this.bridgeMode === 'bridge') {
|
|
1311
1061
|
if (this.serverNode) {
|
|
1312
1062
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1322,7 +1072,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1322
1072
|
}
|
|
1323
1073
|
}
|
|
1324
1074
|
this.log.notice('Stopped matter server nodes');
|
|
1325
|
-
// Matter commisioning reset
|
|
1326
1075
|
if (message === 'shutting down with reset...') {
|
|
1327
1076
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1328
1077
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1332,36 +1081,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1332
1081
|
await this.matterbridgeContext?.clearAll();
|
|
1333
1082
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1334
1083
|
}
|
|
1335
|
-
// Stop matter storage
|
|
1336
1084
|
await this.stopMatterStorage();
|
|
1337
|
-
// Stop the frontend
|
|
1338
1085
|
await this.frontend.stop();
|
|
1339
|
-
// Remove the matterfilelogger
|
|
1340
1086
|
try {
|
|
1341
1087
|
Logger.removeLogger('matterfilelogger');
|
|
1342
1088
|
}
|
|
1343
1089
|
catch (error) {
|
|
1344
1090
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1345
1091
|
}
|
|
1346
|
-
// Close the matterbridge node storage and context
|
|
1347
1092
|
if (this.nodeStorage && this.nodeContext) {
|
|
1348
|
-
/*
|
|
1349
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1350
|
-
this.log.info('Saving registered devices...');
|
|
1351
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1352
|
-
this.devices.forEach(async (device) => {
|
|
1353
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1354
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1355
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1356
|
-
});
|
|
1357
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1358
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1359
|
-
*/
|
|
1360
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1361
1093
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1362
1094
|
await this.nodeContext.close();
|
|
1363
1095
|
this.nodeContext = undefined;
|
|
1364
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1365
1096
|
for (const plugin of this.plugins) {
|
|
1366
1097
|
if (plugin.nodeContext) {
|
|
1367
1098
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1378,10 +1109,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1378
1109
|
}
|
|
1379
1110
|
this.plugins.clear();
|
|
1380
1111
|
this.devices.clear();
|
|
1381
|
-
// Factory reset
|
|
1382
1112
|
if (message === 'shutting down with factory reset...') {
|
|
1383
1113
|
try {
|
|
1384
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1385
1114
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1386
1115
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1387
1116
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1395,7 +1124,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1395
1124
|
}
|
|
1396
1125
|
}
|
|
1397
1126
|
try {
|
|
1398
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1399
1127
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1400
1128
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1401
1129
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1410,13 +1138,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1410
1138
|
}
|
|
1411
1139
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1412
1140
|
}
|
|
1413
|
-
// Deregisters the process handlers
|
|
1414
1141
|
this.deregisterProcessHandlers();
|
|
1415
1142
|
if (restart) {
|
|
1416
1143
|
if (message === 'updating...') {
|
|
1417
1144
|
this.log.info('Cleanup completed. Updating...');
|
|
1418
1145
|
Matterbridge.instance = undefined;
|
|
1419
|
-
this.emit('update');
|
|
1146
|
+
this.emit('update');
|
|
1420
1147
|
}
|
|
1421
1148
|
else if (message === 'restarting...') {
|
|
1422
1149
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1437,14 +1164,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1437
1164
|
this.log.debug('Cleanup already started...');
|
|
1438
1165
|
}
|
|
1439
1166
|
}
|
|
1440
|
-
/**
|
|
1441
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1442
|
-
*
|
|
1443
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1444
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1445
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1446
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1447
|
-
*/
|
|
1448
1167
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1449
1168
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1450
1169
|
plugin.locked = true;
|
|
@@ -1458,13 +1177,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1458
1177
|
await this.startServerNode(plugin.serverNode);
|
|
1459
1178
|
}
|
|
1460
1179
|
}
|
|
1461
|
-
/**
|
|
1462
|
-
* Creates and configures the server node for a dynamic plugin.
|
|
1463
|
-
*
|
|
1464
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1465
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1466
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1467
|
-
*/
|
|
1468
1180
|
async createDynamicPlugin(plugin, start = false) {
|
|
1469
1181
|
if (!plugin.locked) {
|
|
1470
1182
|
plugin.locked = true;
|
|
@@ -1477,13 +1189,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1477
1189
|
await this.startServerNode(plugin.serverNode);
|
|
1478
1190
|
}
|
|
1479
1191
|
}
|
|
1480
|
-
/**
|
|
1481
|
-
* Starts the Matterbridge in bridge mode.
|
|
1482
|
-
* @private
|
|
1483
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1484
|
-
*/
|
|
1485
1192
|
async startBridge() {
|
|
1486
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1487
1193
|
if (!this.matterStorageManager)
|
|
1488
1194
|
throw new Error('No storage manager initialized');
|
|
1489
1195
|
if (!this.matterbridgeContext)
|
|
@@ -1522,9 +1228,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1522
1228
|
clearInterval(this.startMatterInterval);
|
|
1523
1229
|
this.startMatterInterval = undefined;
|
|
1524
1230
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1525
|
-
// Start the Matter server node
|
|
1526
1231
|
this.startServerNode(this.serverNode);
|
|
1527
|
-
// Configure the plugins
|
|
1528
1232
|
this.configureTimeout = setTimeout(async () => {
|
|
1529
1233
|
for (const plugin of this.plugins) {
|
|
1530
1234
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1542,7 +1246,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1542
1246
|
}
|
|
1543
1247
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1544
1248
|
}, 30 * 1000);
|
|
1545
|
-
// Setting reachability to true
|
|
1546
1249
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1547
1250
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1548
1251
|
if (this.aggregatorNode)
|
|
@@ -1551,11 +1254,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1551
1254
|
}, 60 * 1000);
|
|
1552
1255
|
}, 1000);
|
|
1553
1256
|
}
|
|
1554
|
-
/**
|
|
1555
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1556
|
-
* @private
|
|
1557
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1558
|
-
*/
|
|
1559
1257
|
async startChildbridge() {
|
|
1560
1258
|
if (!this.matterStorageManager)
|
|
1561
1259
|
throw new Error('No storage manager initialized');
|
|
@@ -1593,7 +1291,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1593
1291
|
clearInterval(this.startMatterInterval);
|
|
1594
1292
|
this.startMatterInterval = undefined;
|
|
1595
1293
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1596
|
-
// Configure the plugins
|
|
1597
1294
|
this.configureTimeout = setTimeout(async () => {
|
|
1598
1295
|
for (const plugin of this.plugins) {
|
|
1599
1296
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1630,9 +1327,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1630
1327
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1631
1328
|
continue;
|
|
1632
1329
|
}
|
|
1633
|
-
// Start the Matter server node
|
|
1634
1330
|
this.startServerNode(plugin.serverNode);
|
|
1635
|
-
// Setting reachability to true
|
|
1636
1331
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1637
1332
|
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}`);
|
|
1638
1333
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1642,11 +1337,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
1337
|
}
|
|
1643
1338
|
}, 1000);
|
|
1644
1339
|
}
|
|
1645
|
-
/**
|
|
1646
|
-
* Starts the Matterbridge controller.
|
|
1647
|
-
* @private
|
|
1648
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1649
|
-
*/
|
|
1650
1340
|
async startController() {
|
|
1651
1341
|
if (!this.matterStorageManager) {
|
|
1652
1342
|
this.log.error('No storage manager initialized');
|
|
@@ -1661,207 +1351,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1661
1351
|
return;
|
|
1662
1352
|
}
|
|
1663
1353
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1664
|
-
/*
|
|
1665
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1666
|
-
this.log.info('Creating matter commissioning controller');
|
|
1667
|
-
this.commissioningController = new CommissioningController({
|
|
1668
|
-
autoConnect: false,
|
|
1669
|
-
});
|
|
1670
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1671
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1672
|
-
|
|
1673
|
-
this.log.info('Starting matter server');
|
|
1674
|
-
await this.matterServer.start();
|
|
1675
|
-
this.log.info('Matter server started');
|
|
1676
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1677
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1678
|
-
regulatoryCountryCode: 'XX',
|
|
1679
|
-
};
|
|
1680
|
-
const commissioningController = new CommissioningController({
|
|
1681
|
-
environment: {
|
|
1682
|
-
environment,
|
|
1683
|
-
id: uniqueId,
|
|
1684
|
-
},
|
|
1685
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1686
|
-
adminFabricLabel,
|
|
1687
|
-
});
|
|
1688
|
-
|
|
1689
|
-
if (hasParameter('pairingcode')) {
|
|
1690
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1691
|
-
const pairingCode = getParameter('pairingcode');
|
|
1692
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1693
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1694
|
-
|
|
1695
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1696
|
-
if (pairingCode !== undefined) {
|
|
1697
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1698
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1699
|
-
longDiscriminator = undefined;
|
|
1700
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1701
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1702
|
-
} else {
|
|
1703
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1704
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1705
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1706
|
-
}
|
|
1707
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1708
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
const options = {
|
|
1712
|
-
commissioning: commissioningOptions,
|
|
1713
|
-
discovery: {
|
|
1714
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1715
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1716
|
-
},
|
|
1717
|
-
passcode: setupPin,
|
|
1718
|
-
} as NodeCommissioningOptions;
|
|
1719
|
-
this.log.info('Commissioning with options:', options);
|
|
1720
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1721
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1722
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1723
|
-
} // (hasParameter('pairingcode'))
|
|
1724
|
-
|
|
1725
|
-
if (hasParameter('unpairall')) {
|
|
1726
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1727
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1728
|
-
for (const nodeId of nodeIds) {
|
|
1729
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1730
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1731
|
-
}
|
|
1732
|
-
return;
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
if (hasParameter('discover')) {
|
|
1736
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1737
|
-
// console.log(discover);
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1741
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1746
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1747
|
-
for (const nodeId of nodeIds) {
|
|
1748
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1749
|
-
|
|
1750
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1751
|
-
autoSubscribe: false,
|
|
1752
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1753
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1754
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1755
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1756
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1757
|
-
switch (info) {
|
|
1758
|
-
case NodeStateInformation.Connected:
|
|
1759
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1760
|
-
break;
|
|
1761
|
-
case NodeStateInformation.Disconnected:
|
|
1762
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1763
|
-
break;
|
|
1764
|
-
case NodeStateInformation.Reconnecting:
|
|
1765
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1766
|
-
break;
|
|
1767
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1768
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1769
|
-
break;
|
|
1770
|
-
case NodeStateInformation.StructureChanged:
|
|
1771
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1772
|
-
break;
|
|
1773
|
-
case NodeStateInformation.Decommissioned:
|
|
1774
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1775
|
-
break;
|
|
1776
|
-
default:
|
|
1777
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1778
|
-
break;
|
|
1779
|
-
}
|
|
1780
|
-
},
|
|
1781
|
-
});
|
|
1782
|
-
|
|
1783
|
-
node.logStructure();
|
|
1784
|
-
|
|
1785
|
-
// Get the interaction client
|
|
1786
|
-
this.log.info('Getting the interaction client');
|
|
1787
|
-
const interactionClient = await node.getInteractionClient();
|
|
1788
|
-
let cluster;
|
|
1789
|
-
let attributes;
|
|
1790
|
-
|
|
1791
|
-
// Log BasicInformationCluster
|
|
1792
|
-
cluster = BasicInformationCluster;
|
|
1793
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1794
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1795
|
-
});
|
|
1796
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1797
|
-
attributes.forEach((attribute) => {
|
|
1798
|
-
this.log.info(
|
|
1799
|
-
`- 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}`,
|
|
1800
|
-
);
|
|
1801
|
-
});
|
|
1802
|
-
|
|
1803
|
-
// Log PowerSourceCluster
|
|
1804
|
-
cluster = PowerSourceCluster;
|
|
1805
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1806
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1807
|
-
});
|
|
1808
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1809
|
-
attributes.forEach((attribute) => {
|
|
1810
|
-
this.log.info(
|
|
1811
|
-
`- 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}`,
|
|
1812
|
-
);
|
|
1813
|
-
});
|
|
1814
|
-
|
|
1815
|
-
// Log ThreadNetworkDiagnostics
|
|
1816
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1817
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1818
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1819
|
-
});
|
|
1820
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1821
|
-
attributes.forEach((attribute) => {
|
|
1822
|
-
this.log.info(
|
|
1823
|
-
`- 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}`,
|
|
1824
|
-
);
|
|
1825
|
-
});
|
|
1826
|
-
|
|
1827
|
-
// Log SwitchCluster
|
|
1828
|
-
cluster = SwitchCluster;
|
|
1829
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1830
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1831
|
-
});
|
|
1832
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1833
|
-
attributes.forEach((attribute) => {
|
|
1834
|
-
this.log.info(
|
|
1835
|
-
`- 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}`,
|
|
1836
|
-
);
|
|
1837
|
-
});
|
|
1838
|
-
|
|
1839
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1840
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1841
|
-
ignoreInitialTriggers: false,
|
|
1842
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1843
|
-
this.log.info(
|
|
1844
|
-
`***${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}`,
|
|
1845
|
-
),
|
|
1846
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1847
|
-
this.log.info(
|
|
1848
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1849
|
-
);
|
|
1850
|
-
},
|
|
1851
|
-
});
|
|
1852
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1853
|
-
}
|
|
1854
|
-
*/
|
|
1855
1354
|
}
|
|
1856
|
-
/** ***********************************************************************************************************************************/
|
|
1857
|
-
/** Matter.js methods */
|
|
1858
|
-
/** ***********************************************************************************************************************************/
|
|
1859
|
-
/**
|
|
1860
|
-
* Starts the matter storage process with name Matterbridge.
|
|
1861
|
-
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1862
|
-
*/
|
|
1863
1355
|
async startMatterStorage() {
|
|
1864
|
-
// Setup Matter storage
|
|
1865
1356
|
this.log.info(`Starting matter node storage...`);
|
|
1866
1357
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1867
1358
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1870,25 +1361,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1870
1361
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1871
1362
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1872
1363
|
this.log.info('Matter node storage started');
|
|
1873
|
-
// Backup matter storage since it is created/opened correctly
|
|
1874
1364
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1875
1365
|
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1878
|
-
*
|
|
1879
|
-
* @param storageName - The name of the storage directory to be backed up.
|
|
1880
|
-
* @param backupName - The name of the backup directory to be created.
|
|
1881
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1882
|
-
*/
|
|
1883
1366
|
async backupMatterStorage(storageName, backupName) {
|
|
1884
1367
|
this.log.info('Creating matter node storage backup...');
|
|
1885
1368
|
await copyDirectory(storageName, backupName);
|
|
1886
1369
|
this.log.info('Created matter node storage backup');
|
|
1887
1370
|
}
|
|
1888
|
-
/**
|
|
1889
|
-
* Stops the matter storage.
|
|
1890
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1891
|
-
*/
|
|
1892
1371
|
async stopMatterStorage() {
|
|
1893
1372
|
this.log.info('Closing matter node storage...');
|
|
1894
1373
|
await this.matterStorageManager?.close();
|
|
@@ -1897,19 +1376,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1897
1376
|
this.matterbridgeContext = undefined;
|
|
1898
1377
|
this.log.info('Matter node storage closed');
|
|
1899
1378
|
}
|
|
1900
|
-
/**
|
|
1901
|
-
* Creates a server node storage context.
|
|
1902
|
-
*
|
|
1903
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1904
|
-
* @param {string} deviceName - The name of the device.
|
|
1905
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1906
|
-
* @param {number} vendorId - The vendor ID.
|
|
1907
|
-
* @param {string} vendorName - The vendor name.
|
|
1908
|
-
* @param {number} productId - The product ID.
|
|
1909
|
-
* @param {string} productName - The product name.
|
|
1910
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1911
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1912
|
-
*/
|
|
1913
1379
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1914
1380
|
const { randomBytes } = await import('node:crypto');
|
|
1915
1381
|
if (!this.matterStorageService)
|
|
@@ -1943,15 +1409,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1943
1409
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1944
1410
|
return storageContext;
|
|
1945
1411
|
}
|
|
1946
|
-
/**
|
|
1947
|
-
* Creates a server node.
|
|
1948
|
-
*
|
|
1949
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1950
|
-
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1951
|
-
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1952
|
-
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1953
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1954
|
-
*/
|
|
1955
1412
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1956
1413
|
const storeId = await storageContext.get('storeId');
|
|
1957
1414
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1961,37 +1418,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1961
1418
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1962
1419
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1963
1420
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1964
|
-
/**
|
|
1965
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1966
|
-
*/
|
|
1967
1421
|
const serverNode = await ServerNode.create({
|
|
1968
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1969
1422
|
id: storeId,
|
|
1970
|
-
// Provide Network relevant configuration like the port
|
|
1971
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
1972
1423
|
network: {
|
|
1973
1424
|
listeningAddressIpv4: this.ipv4address,
|
|
1974
1425
|
listeningAddressIpv6: this.ipv6address,
|
|
1975
1426
|
port,
|
|
1976
1427
|
},
|
|
1977
|
-
// Provide the certificate for the device
|
|
1978
1428
|
operationalCredentials: {
|
|
1979
1429
|
certification: this.certification,
|
|
1980
1430
|
},
|
|
1981
|
-
// Provide Commissioning relevant settings
|
|
1982
|
-
// Optional for development/testing purposes
|
|
1983
1431
|
commissioning: {
|
|
1984
1432
|
passcode,
|
|
1985
1433
|
discriminator,
|
|
1986
1434
|
},
|
|
1987
|
-
// Provide Node announcement settings
|
|
1988
|
-
// Optional: If Ommitted some development defaults are used
|
|
1989
1435
|
productDescription: {
|
|
1990
1436
|
name: await storageContext.get('deviceName'),
|
|
1991
1437
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1992
1438
|
},
|
|
1993
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1994
|
-
// Optional: If Omitted some development defaults are used
|
|
1995
1439
|
basicInformation: {
|
|
1996
1440
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1997
1441
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2009,13 +1453,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2009
1453
|
},
|
|
2010
1454
|
});
|
|
2011
1455
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2012
|
-
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
2013
1456
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
2014
1457
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
2015
1458
|
if (this.bridgeMode === 'bridge') {
|
|
2016
1459
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
2017
1460
|
if (resetSessions)
|
|
2018
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1461
|
+
this.matterbridgeSessionInformations = undefined;
|
|
2019
1462
|
this.matterbridgePaired = true;
|
|
2020
1463
|
}
|
|
2021
1464
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -2023,19 +1466,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2023
1466
|
if (plugin) {
|
|
2024
1467
|
plugin.fabricInformations = sanitizedFabrics;
|
|
2025
1468
|
if (resetSessions)
|
|
2026
|
-
plugin.sessionInformations = undefined;
|
|
1469
|
+
plugin.sessionInformations = undefined;
|
|
2027
1470
|
plugin.paired = true;
|
|
2028
1471
|
}
|
|
2029
1472
|
}
|
|
2030
1473
|
};
|
|
2031
|
-
/**
|
|
2032
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2033
|
-
* This means: It is added to the first fabric.
|
|
2034
|
-
*/
|
|
2035
1474
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
2036
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2037
1475
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2038
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2039
1476
|
serverNode.lifecycle.online.on(async () => {
|
|
2040
1477
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2041
1478
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2104,7 +1541,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2104
1541
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2105
1542
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2106
1543
|
});
|
|
2107
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2108
1544
|
serverNode.lifecycle.offline.on(() => {
|
|
2109
1545
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2110
1546
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -2128,10 +1564,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2128
1564
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2129
1565
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2130
1566
|
});
|
|
2131
|
-
/**
|
|
2132
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2133
|
-
* information is needed.
|
|
2134
|
-
*/
|
|
2135
1567
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2136
1568
|
let action = '';
|
|
2137
1569
|
switch (fabricAction) {
|
|
@@ -2165,24 +1597,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2165
1597
|
}
|
|
2166
1598
|
}
|
|
2167
1599
|
};
|
|
2168
|
-
/**
|
|
2169
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2170
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2171
|
-
*/
|
|
2172
1600
|
serverNode.events.sessions.opened.on((session) => {
|
|
2173
1601
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2174
1602
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2175
1603
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2176
1604
|
});
|
|
2177
|
-
/**
|
|
2178
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2179
|
-
*/
|
|
2180
1605
|
serverNode.events.sessions.closed.on((session) => {
|
|
2181
1606
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2182
1607
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2183
1608
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2184
1609
|
});
|
|
2185
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2186
1610
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2187
1611
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2188
1612
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -2191,42 +1615,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2191
1615
|
this.log.info(`Created server node for ${storeId}`);
|
|
2192
1616
|
return serverNode;
|
|
2193
1617
|
}
|
|
2194
|
-
/**
|
|
2195
|
-
* Starts the specified server node.
|
|
2196
|
-
*
|
|
2197
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2198
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2199
|
-
*/
|
|
2200
1618
|
async startServerNode(matterServerNode) {
|
|
2201
1619
|
if (!matterServerNode)
|
|
2202
1620
|
return;
|
|
2203
1621
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2204
1622
|
await matterServerNode.start();
|
|
2205
1623
|
}
|
|
2206
|
-
/**
|
|
2207
|
-
* Stops the specified server node.
|
|
2208
|
-
*
|
|
2209
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2210
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2211
|
-
*/
|
|
2212
1624
|
async stopServerNode(matterServerNode) {
|
|
2213
1625
|
if (!matterServerNode)
|
|
2214
1626
|
return;
|
|
2215
1627
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2216
1628
|
try {
|
|
2217
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
1629
|
+
await withTimeout(matterServerNode.close(), 30000);
|
|
2218
1630
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
2219
1631
|
}
|
|
2220
1632
|
catch (error) {
|
|
2221
1633
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2222
1634
|
}
|
|
2223
1635
|
}
|
|
2224
|
-
/**
|
|
2225
|
-
* Advertises the specified server node.
|
|
2226
|
-
*
|
|
2227
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2228
|
-
* @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.
|
|
2229
|
-
*/
|
|
2230
1636
|
async advertiseServerNode(matterServerNode) {
|
|
2231
1637
|
if (matterServerNode) {
|
|
2232
1638
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2235,46 +1641,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
2235
1641
|
return { qrPairingCode, manualPairingCode };
|
|
2236
1642
|
}
|
|
2237
1643
|
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Stop advertise the specified server node.
|
|
2240
|
-
*
|
|
2241
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2242
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2243
|
-
*/
|
|
2244
1644
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2245
1645
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2246
1646
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2247
1647
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2248
1648
|
}
|
|
2249
1649
|
}
|
|
2250
|
-
/**
|
|
2251
|
-
* Creates an aggregator node with the specified storage context.
|
|
2252
|
-
*
|
|
2253
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2254
|
-
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2255
|
-
*/
|
|
2256
1650
|
async createAggregatorNode(storageContext) {
|
|
2257
1651
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2258
1652
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2259
1653
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2260
1654
|
return aggregatorNode;
|
|
2261
1655
|
}
|
|
2262
|
-
/**
|
|
2263
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2264
|
-
*
|
|
2265
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2266
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2267
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2268
|
-
*/
|
|
2269
1656
|
async addBridgedEndpoint(pluginName, device) {
|
|
2270
|
-
// Check if the plugin is registered
|
|
2271
1657
|
const plugin = this.plugins.get(pluginName);
|
|
2272
1658
|
if (!plugin) {
|
|
2273
1659
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
2274
1660
|
return;
|
|
2275
1661
|
}
|
|
2276
1662
|
if (this.bridgeMode === 'bridge') {
|
|
2277
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2278
1663
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2279
1664
|
if (!this.aggregatorNode) {
|
|
2280
1665
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2291,7 +1676,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2291
1676
|
}
|
|
2292
1677
|
}
|
|
2293
1678
|
else if (this.bridgeMode === 'childbridge') {
|
|
2294
|
-
// Register and add the device to the plugin server node
|
|
2295
1679
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2296
1680
|
try {
|
|
2297
1681
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2308,12 +1692,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2308
1692
|
return;
|
|
2309
1693
|
}
|
|
2310
1694
|
}
|
|
2311
|
-
// Register and add the device to the plugin aggregator node
|
|
2312
1695
|
if (plugin.type === 'DynamicPlatform') {
|
|
2313
1696
|
try {
|
|
2314
1697
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2315
1698
|
await this.createDynamicPlugin(plugin);
|
|
2316
|
-
// Fast plugins can add another device before the server node is created
|
|
2317
1699
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2318
1700
|
if (!plugin.aggregatorNode) {
|
|
2319
1701
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2333,28 +1715,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2333
1715
|
plugin.registeredDevices++;
|
|
2334
1716
|
if (plugin.addedDevices !== undefined)
|
|
2335
1717
|
plugin.addedDevices++;
|
|
2336
|
-
// Add the device to the DeviceManager
|
|
2337
1718
|
this.devices.set(device);
|
|
2338
|
-
// Subscribe to the reachable$Changed event
|
|
2339
1719
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2340
1720
|
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}`);
|
|
2341
1721
|
}
|
|
2342
|
-
/**
|
|
2343
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2344
|
-
*
|
|
2345
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2346
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2347
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2348
|
-
*/
|
|
2349
1722
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2350
1723
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2351
|
-
// Check if the plugin is registered
|
|
2352
1724
|
const plugin = this.plugins.get(pluginName);
|
|
2353
1725
|
if (!plugin) {
|
|
2354
1726
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2355
1727
|
return;
|
|
2356
1728
|
}
|
|
2357
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2358
1729
|
if (this.bridgeMode === 'bridge') {
|
|
2359
1730
|
if (!this.aggregatorNode) {
|
|
2360
1731
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2369,7 +1740,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2369
1740
|
}
|
|
2370
1741
|
else if (this.bridgeMode === 'childbridge') {
|
|
2371
1742
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2372
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2373
1743
|
}
|
|
2374
1744
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2375
1745
|
if (!plugin.aggregatorNode) {
|
|
@@ -2384,21 +1754,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2384
1754
|
if (plugin.addedDevices !== undefined)
|
|
2385
1755
|
plugin.addedDevices--;
|
|
2386
1756
|
}
|
|
2387
|
-
// Remove the device from the DeviceManager
|
|
2388
1757
|
this.devices.remove(device);
|
|
2389
1758
|
}
|
|
2390
|
-
/**
|
|
2391
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2392
|
-
*
|
|
2393
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2394
|
-
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2395
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2396
|
-
*
|
|
2397
|
-
* @remarks
|
|
2398
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2399
|
-
* It also applies a delay between each removal if specified.
|
|
2400
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2401
|
-
*/
|
|
2402
1759
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2403
1760
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2404
1761
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2409,15 +1766,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2409
1766
|
if (delay > 0)
|
|
2410
1767
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2411
1768
|
}
|
|
2412
|
-
/**
|
|
2413
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2414
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2415
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2416
|
-
*
|
|
2417
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2418
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2419
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2420
|
-
*/
|
|
2421
1769
|
async subscribeAttributeChanged(plugin, device) {
|
|
2422
1770
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2423
1771
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -2433,12 +1781,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2433
1781
|
});
|
|
2434
1782
|
}
|
|
2435
1783
|
}
|
|
2436
|
-
/**
|
|
2437
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2438
|
-
*
|
|
2439
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2440
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2441
|
-
*/
|
|
2442
1784
|
sanitizeFabricInformations(fabricInfo) {
|
|
2443
1785
|
return fabricInfo.map((info) => {
|
|
2444
1786
|
return {
|
|
@@ -2452,12 +1794,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2452
1794
|
};
|
|
2453
1795
|
});
|
|
2454
1796
|
}
|
|
2455
|
-
/**
|
|
2456
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2457
|
-
*
|
|
2458
|
-
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2459
|
-
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2460
|
-
*/
|
|
2461
1797
|
sanitizeSessionInformation(sessionInfo) {
|
|
2462
1798
|
return sessionInfo
|
|
2463
1799
|
.filter((session) => session.isPeerActive)
|
|
@@ -2485,20 +1821,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2485
1821
|
};
|
|
2486
1822
|
});
|
|
2487
1823
|
}
|
|
2488
|
-
/**
|
|
2489
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2490
|
-
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2491
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2492
|
-
*/
|
|
2493
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2494
1824
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2495
|
-
/*
|
|
2496
|
-
for (const child of aggregatorNode.parts) {
|
|
2497
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2498
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2499
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2500
|
-
}
|
|
2501
|
-
*/
|
|
2502
1825
|
}
|
|
2503
1826
|
getVendorIdName = (vendorId) => {
|
|
2504
1827
|
if (!vendorId)
|
|
@@ -2541,29 +1864,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2541
1864
|
}
|
|
2542
1865
|
return vendorName;
|
|
2543
1866
|
};
|
|
2544
|
-
/**
|
|
2545
|
-
* Spawns a child process with the given command and arguments.
|
|
2546
|
-
* @param {string} command - The command to execute.
|
|
2547
|
-
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2548
|
-
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2549
|
-
*/
|
|
2550
1867
|
async spawnCommand(command, args = []) {
|
|
2551
1868
|
const { spawn } = await import('node:child_process');
|
|
2552
|
-
/*
|
|
2553
|
-
npm > npm.cmd on windows
|
|
2554
|
-
cmd.exe ['dir'] on windows
|
|
2555
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2556
|
-
*/
|
|
2557
1869
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2558
1870
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2559
|
-
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2560
1871
|
const argstring = 'npm ' + args.join(' ');
|
|
2561
1872
|
args.splice(0, args.length, '/c', argstring);
|
|
2562
1873
|
command = 'cmd.exe';
|
|
2563
1874
|
}
|
|
2564
|
-
// Decide when using sudo on linux
|
|
2565
|
-
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2566
|
-
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2567
1875
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2568
1876
|
args.unshift(command);
|
|
2569
1877
|
command = 'sudo';
|
|
@@ -2621,13 +1929,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2621
1929
|
}
|
|
2622
1930
|
});
|
|
2623
1931
|
}
|
|
2624
|
-
/**
|
|
2625
|
-
* Creates a directory at the specified path if it doesn't already exist.
|
|
2626
|
-
*
|
|
2627
|
-
* @param {string} path - The path to the directory to create.
|
|
2628
|
-
* @param {string} name - The name of the directory.
|
|
2629
|
-
* @returns {Promise<void>} A promise that resolves when the directory has been created or already exists.
|
|
2630
|
-
*/
|
|
2631
1932
|
async createDirectory(path, name) {
|
|
2632
1933
|
try {
|
|
2633
1934
|
await fs.access(path);
|
|
@@ -2649,4 +1950,3 @@ export class Matterbridge extends EventEmitter {
|
|
|
2649
1950
|
}
|
|
2650
1951
|
}
|
|
2651
1952
|
}
|
|
2652
|
-
//# sourceMappingURL=matterbridge.js.map
|