matterbridge 3.0.1 → 3.0.2-dev-20250508-7214e17
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 +25 -0
- package/README.md +1 -1
- package/dist/cli.js +2 -37
- package/dist/cluster/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -23
- package/dist/deviceManager.js +1 -94
- package/dist/frontend.js +15 -328
- package/dist/index.js +2 -28
- 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 +80 -747
- package/dist/matterbridgeAccessoryPlatform.js +0 -34
- package/dist/matterbridgeBehaviors.js +109 -48
- package/dist/matterbridgeDeviceTypes.js +12 -431
- package/dist/matterbridgeDynamicPlatform.js +0 -34
- package/dist/matterbridgeEndpoint.js +13 -814
- package/dist/matterbridgeEndpointHelpers.js +29 -148
- package/dist/matterbridgePlatform.js +7 -225
- package/dist/matterbridgeTypes.js +0 -24
- package/dist/pluginManager.js +3 -264
- package/dist/roboticVacuumCleaner.js +87 -0
- package/dist/shelly.js +6 -146
- package/dist/storage/export.js +0 -1
- package/dist/update.js +1 -53
- package/dist/utils/colorUtils.js +2 -205
- package/dist/utils/{parameter.js → commandLine.js} +1 -54
- package/dist/utils/copyDirectory.js +1 -37
- package/dist/utils/createZip.js +2 -42
- package/dist/utils/deepCopy.js +8 -43
- package/dist/utils/deepEqual.js +7 -69
- package/dist/utils/export.js +2 -2
- package/dist/utils/hex.js +27 -0
- package/dist/utils/isvalid.js +3 -93
- package/dist/utils/network.js +7 -78
- package/dist/utils/wait.js +5 -48
- 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 -240
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.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 -433
- 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 -1166
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -494
- 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 -956
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -2706
- 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 -187
- 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/shelly.d.ts +0 -92
- 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 -32
- 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/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 -10
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -95
- 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/parameter.d.ts +0 -58
- package/dist/utils/parameter.d.ts.map +0 -1
- package/dist/utils/parameter.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -43
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.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.2
|
|
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 { PluginManager } from './pluginManager.js';
|
|
@@ -38,19 +12,14 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
38
12
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
39
13
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
40
14
|
import { Frontend } from './frontend.js';
|
|
41
|
-
// @matter
|
|
42
15
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
|
|
43
16
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
44
17
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
45
18
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
46
19
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
47
|
-
// Default colors
|
|
48
20
|
const plg = '\u001B[38;5;33m';
|
|
49
21
|
const dev = '\u001B[38;5;79m';
|
|
50
22
|
const typ = '\u001B[38;5;207m';
|
|
51
|
-
/**
|
|
52
|
-
* Represents the Matterbridge application.
|
|
53
|
-
*/
|
|
54
23
|
export class Matterbridge extends EventEmitter {
|
|
55
24
|
systemInformation = {
|
|
56
25
|
interfaceName: '',
|
|
@@ -97,7 +66,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
97
66
|
shellySysUpdate: false,
|
|
98
67
|
shellyMainUpdate: false,
|
|
99
68
|
profile: getParameter('profile'),
|
|
100
|
-
loggerLevel: "info"
|
|
69
|
+
loggerLevel: "info",
|
|
101
70
|
fileLogger: false,
|
|
102
71
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
103
72
|
matterFileLogger: false,
|
|
@@ -136,11 +105,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
136
105
|
plugins;
|
|
137
106
|
devices;
|
|
138
107
|
frontend = new Frontend(this);
|
|
139
|
-
// Matterbridge storage
|
|
140
108
|
nodeStorage;
|
|
141
109
|
nodeContext;
|
|
142
110
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
143
|
-
// Cleanup
|
|
144
111
|
hasCleanupStarted = false;
|
|
145
112
|
initialized = false;
|
|
146
113
|
execRunningCount = 0;
|
|
@@ -153,21 +120,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
153
120
|
sigtermHandler;
|
|
154
121
|
exceptionHandler;
|
|
155
122
|
rejectionHandler;
|
|
156
|
-
// Matter environment
|
|
157
123
|
environment = Environment.default;
|
|
158
|
-
// Matter storage
|
|
159
124
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
160
125
|
matterStorageService;
|
|
161
126
|
matterStorageManager;
|
|
162
127
|
matterbridgeContext;
|
|
163
128
|
controllerContext;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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.
|
|
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 initialize - Whether to initialize the Matterbridge instance after loading.
|
|
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
|
-
* @deprecated This method is deprecated and is only used for jest tests.
|
|
271
|
-
*
|
|
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,81 +202,55 @@ 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, 1000);
|
|
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
|
-
// Set the restart mode
|
|
312
216
|
if (hasParameter('service'))
|
|
313
217
|
this.restartMode = 'service';
|
|
314
218
|
if (hasParameter('docker'))
|
|
315
219
|
this.restartMode = 'docker';
|
|
316
|
-
// Set the matterbridge directory
|
|
317
220
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
318
221
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
319
|
-
// Setup the matter environment
|
|
320
222
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
321
223
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
322
224
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
323
225
|
this.environment.vars.set('runtime.signals', false);
|
|
324
226
|
this.environment.vars.set('runtime.exitcode', false);
|
|
325
|
-
|
|
326
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
327
|
-
// Register process handlers
|
|
227
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
328
228
|
this.registerProcessHandlers();
|
|
329
|
-
// Initialize nodeStorage and nodeContext
|
|
330
229
|
try {
|
|
331
230
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
332
231
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
333
232
|
this.log.debug('Creating node storage context for matterbridge');
|
|
334
233
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
335
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
336
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
337
234
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
338
235
|
for (const key of keys) {
|
|
339
236
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
340
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
341
237
|
await this.nodeStorage?.storage.get(key);
|
|
342
238
|
}
|
|
343
239
|
const storages = await this.nodeStorage.getStorageNames();
|
|
344
240
|
for (const storage of storages) {
|
|
345
241
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
346
242
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
347
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
348
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
349
243
|
const keys = (await nodeContext?.storage.keys());
|
|
350
244
|
keys.forEach(async (key) => {
|
|
351
245
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
352
246
|
await nodeContext?.get(key);
|
|
353
247
|
});
|
|
354
248
|
}
|
|
355
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
356
249
|
this.log.debug('Creating node storage backup...');
|
|
357
250
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
358
251
|
this.log.debug('Created node storage backup');
|
|
359
252
|
}
|
|
360
253
|
catch (error) {
|
|
361
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
362
254
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
363
255
|
if (hasParameter('norestore')) {
|
|
364
256
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -373,46 +265,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
373
265
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
374
266
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
375
267
|
}
|
|
376
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
377
268
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
378
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
379
269
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
380
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
381
270
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
271
|
+
const pairingFilePath = path.join(this.homeDirectory, '.mattercert', 'pairing.json');
|
|
272
|
+
try {
|
|
273
|
+
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
274
|
+
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
275
|
+
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
276
|
+
if (pairingFileJson.passcode && pairingFileJson.discriminator) {
|
|
277
|
+
this.passcode = pairingFileJson.passcode;
|
|
278
|
+
this.discriminator = pairingFileJson.discriminator;
|
|
279
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf}`);
|
|
280
|
+
}
|
|
281
|
+
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
282
|
+
const hexStringToUint8Array = (hexString) => {
|
|
283
|
+
const matches = hexString.match(/.{1,2}/g);
|
|
284
|
+
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
285
|
+
};
|
|
286
|
+
this.certification = {
|
|
287
|
+
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
288
|
+
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
289
|
+
intermediateCertificate: hexStringToUint8Array(pairingFileJson.intermediateCertificate),
|
|
290
|
+
declaration: hexStringToUint8Array(pairingFileJson.declaration),
|
|
291
|
+
};
|
|
292
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using privateKey, certificate, intermediateCertificate and declaration from pairing file.`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
297
|
+
}
|
|
298
|
+
await this.nodeContext.set('matterport', this.port);
|
|
299
|
+
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
300
|
+
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
382
301
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
383
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
384
302
|
if (hasParameter('logger')) {
|
|
385
303
|
const level = getParameter('logger');
|
|
386
304
|
if (level === 'debug') {
|
|
387
|
-
this.log.logLevel = "debug"
|
|
305
|
+
this.log.logLevel = "debug";
|
|
388
306
|
}
|
|
389
307
|
else if (level === 'info') {
|
|
390
|
-
this.log.logLevel = "info"
|
|
308
|
+
this.log.logLevel = "info";
|
|
391
309
|
}
|
|
392
310
|
else if (level === 'notice') {
|
|
393
|
-
this.log.logLevel = "notice"
|
|
311
|
+
this.log.logLevel = "notice";
|
|
394
312
|
}
|
|
395
313
|
else if (level === 'warn') {
|
|
396
|
-
this.log.logLevel = "warn"
|
|
314
|
+
this.log.logLevel = "warn";
|
|
397
315
|
}
|
|
398
316
|
else if (level === 'error') {
|
|
399
|
-
this.log.logLevel = "error"
|
|
317
|
+
this.log.logLevel = "error";
|
|
400
318
|
}
|
|
401
319
|
else if (level === 'fatal') {
|
|
402
|
-
this.log.logLevel = "fatal"
|
|
320
|
+
this.log.logLevel = "fatal";
|
|
403
321
|
}
|
|
404
322
|
else {
|
|
405
323
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
406
|
-
this.log.logLevel = "info"
|
|
324
|
+
this.log.logLevel = "info";
|
|
407
325
|
}
|
|
408
326
|
}
|
|
409
327
|
else {
|
|
410
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice"
|
|
328
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
411
329
|
}
|
|
412
330
|
this.frontend.logLevel = this.log.logLevel;
|
|
413
331
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
414
332
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
415
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
416
333
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
417
334
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
418
335
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -421,7 +338,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
421
338
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
422
339
|
if (this.profile !== undefined)
|
|
423
340
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
424
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
425
341
|
if (hasParameter('matterlogger')) {
|
|
426
342
|
const level = getParameter('matterlogger');
|
|
427
343
|
if (level === 'debug') {
|
|
@@ -453,7 +369,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
453
369
|
Logger.format = MatterLogFormat.ANSI;
|
|
454
370
|
Logger.setLogger('default', this.createMatterLogger());
|
|
455
371
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
456
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
457
372
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
458
373
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
459
374
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -462,7 +377,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
462
377
|
});
|
|
463
378
|
}
|
|
464
379
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
465
|
-
// Log network interfaces
|
|
466
380
|
const networkInterfaces = os.networkInterfaces();
|
|
467
381
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
468
382
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -474,7 +388,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
474
388
|
});
|
|
475
389
|
}
|
|
476
390
|
}
|
|
477
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
478
391
|
if (hasParameter('mdnsinterface')) {
|
|
479
392
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
480
393
|
}
|
|
@@ -483,7 +396,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
483
396
|
if (this.mdnsInterface === '')
|
|
484
397
|
this.mdnsInterface = undefined;
|
|
485
398
|
}
|
|
486
|
-
// Validate mdnsInterface
|
|
487
399
|
if (this.mdnsInterface) {
|
|
488
400
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
489
401
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -496,7 +408,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
496
408
|
}
|
|
497
409
|
if (this.mdnsInterface)
|
|
498
410
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
499
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
500
411
|
if (hasParameter('ipv4address')) {
|
|
501
412
|
this.ipv4address = getParameter('ipv4address');
|
|
502
413
|
}
|
|
@@ -505,7 +416,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
505
416
|
if (this.ipv4address === '')
|
|
506
417
|
this.ipv4address = undefined;
|
|
507
418
|
}
|
|
508
|
-
// Validate ipv4address
|
|
509
419
|
if (this.ipv4address) {
|
|
510
420
|
let isValid = false;
|
|
511
421
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -521,7 +431,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
521
431
|
await this.nodeContext.remove('matteripv4address');
|
|
522
432
|
}
|
|
523
433
|
}
|
|
524
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
525
434
|
if (hasParameter('ipv6address')) {
|
|
526
435
|
this.ipv6address = getParameter('ipv6address');
|
|
527
436
|
}
|
|
@@ -530,7 +439,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
530
439
|
if (this.ipv6address === '')
|
|
531
440
|
this.ipv6address = undefined;
|
|
532
441
|
}
|
|
533
|
-
// Validate ipv6address
|
|
534
442
|
if (this.ipv6address) {
|
|
535
443
|
let isValid = false;
|
|
536
444
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -551,19 +459,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
551
459
|
await this.nodeContext.remove('matteripv6address');
|
|
552
460
|
}
|
|
553
461
|
}
|
|
554
|
-
// Initialize PluginManager
|
|
555
462
|
this.plugins = new PluginManager(this);
|
|
556
463
|
await this.plugins.loadFromStorage();
|
|
557
464
|
this.plugins.logLevel = this.log.logLevel;
|
|
558
|
-
// Initialize DeviceManager
|
|
559
465
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
560
466
|
this.devices.logLevel = this.log.logLevel;
|
|
561
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
562
467
|
for (const plugin of this.plugins) {
|
|
563
468
|
const packageJson = await this.plugins.parse(plugin);
|
|
564
469
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
565
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
566
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
567
470
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
568
471
|
try {
|
|
569
472
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -585,7 +488,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
585
488
|
await plugin.nodeContext.set('description', plugin.description);
|
|
586
489
|
await plugin.nodeContext.set('author', plugin.author);
|
|
587
490
|
}
|
|
588
|
-
// Log system info and create .matterbridge directory
|
|
589
491
|
await this.logNodeAndSystemInfo();
|
|
590
492
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
591
493
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -593,7 +495,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
593
495
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
594
496
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
595
497
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
596
|
-
// Check node version and throw error
|
|
597
498
|
const minNodeVersion = 18;
|
|
598
499
|
const nodeVersion = process.versions.node;
|
|
599
500
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -601,15 +502,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
601
502
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
602
503
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
603
504
|
}
|
|
604
|
-
// Parse command line
|
|
605
505
|
await this.parseCommandLine();
|
|
606
506
|
this.initialized = true;
|
|
607
507
|
}
|
|
608
|
-
/**
|
|
609
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
610
|
-
* @private
|
|
611
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
612
|
-
*/
|
|
613
508
|
async parseCommandLine() {
|
|
614
509
|
if (hasParameter('help')) {
|
|
615
510
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -728,7 +623,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
728
623
|
this.shutdown = true;
|
|
729
624
|
return;
|
|
730
625
|
}
|
|
731
|
-
// Start the matter storage and create the matterbridge context
|
|
732
626
|
try {
|
|
733
627
|
await this.startMatterStorage();
|
|
734
628
|
}
|
|
@@ -736,14 +630,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
736
630
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
737
631
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
738
632
|
}
|
|
739
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
740
633
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
741
634
|
this.initialized = true;
|
|
742
635
|
await this.shutdownProcessAndReset();
|
|
743
636
|
this.shutdown = true;
|
|
744
637
|
return;
|
|
745
638
|
}
|
|
746
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
747
639
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
748
640
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
749
641
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -768,37 +660,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
768
660
|
this.shutdown = true;
|
|
769
661
|
return;
|
|
770
662
|
}
|
|
771
|
-
// Initialize frontend
|
|
772
663
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
773
664
|
await this.frontend.start(getIntParameter('frontend'));
|
|
774
|
-
// Check in 30 seconds the latest versions
|
|
775
665
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
776
666
|
const { checkUpdates } = await import('./update.js');
|
|
777
667
|
checkUpdates(this);
|
|
778
668
|
}, 30 * 1000).unref();
|
|
779
|
-
// Check each 24 hours the latest versions
|
|
780
669
|
this.checkUpdateInterval = setInterval(async () => {
|
|
781
670
|
const { checkUpdates } = await import('./update.js');
|
|
782
671
|
checkUpdates(this);
|
|
783
672
|
}, 12 * 60 * 60 * 1000).unref();
|
|
784
|
-
// Start the matterbridge in mode test
|
|
785
673
|
if (hasParameter('test')) {
|
|
786
674
|
this.bridgeMode = 'bridge';
|
|
787
675
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
788
676
|
return;
|
|
789
677
|
}
|
|
790
|
-
// Start the matterbridge in mode controller
|
|
791
678
|
if (hasParameter('controller')) {
|
|
792
679
|
this.bridgeMode = 'controller';
|
|
793
680
|
await this.startController();
|
|
794
681
|
return;
|
|
795
682
|
}
|
|
796
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
797
683
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
798
684
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
799
685
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
800
686
|
}
|
|
801
|
-
// Start matterbridge in bridge mode
|
|
802
687
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
803
688
|
this.bridgeMode = 'bridge';
|
|
804
689
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -806,7 +691,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
806
691
|
await this.startBridge();
|
|
807
692
|
return;
|
|
808
693
|
}
|
|
809
|
-
// Start matterbridge in childbridge mode
|
|
810
694
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
811
695
|
this.bridgeMode = 'childbridge';
|
|
812
696
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -815,20 +699,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
815
699
|
return;
|
|
816
700
|
}
|
|
817
701
|
}
|
|
818
|
-
/**
|
|
819
|
-
* Asynchronously loads and starts the registered plugins.
|
|
820
|
-
*
|
|
821
|
-
* This method is responsible for initializing and staarting all enabled plugins.
|
|
822
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
823
|
-
*
|
|
824
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
825
|
-
*/
|
|
826
702
|
async startPlugins() {
|
|
827
|
-
// Check, load and start the plugins
|
|
828
703
|
for (const plugin of this.plugins) {
|
|
829
704
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
830
705
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
831
|
-
// Check if the plugin is available
|
|
832
706
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
833
707
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
834
708
|
plugin.enabled = false;
|
|
@@ -848,14 +722,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
848
722
|
plugin.addedDevices = undefined;
|
|
849
723
|
plugin.qrPairingCode = undefined;
|
|
850
724
|
plugin.manualPairingCode = undefined;
|
|
851
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
725
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
852
726
|
}
|
|
853
727
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
854
728
|
}
|
|
855
|
-
/**
|
|
856
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
857
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
858
|
-
*/
|
|
859
729
|
registerProcessHandlers() {
|
|
860
730
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
861
731
|
process.removeAllListeners('uncaughtException');
|
|
@@ -882,9 +752,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
882
752
|
};
|
|
883
753
|
process.on('SIGTERM', this.sigtermHandler);
|
|
884
754
|
}
|
|
885
|
-
/**
|
|
886
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
887
|
-
*/
|
|
888
755
|
deregisterProcesslHandlers() {
|
|
889
756
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
890
757
|
if (this.exceptionHandler)
|
|
@@ -901,17 +768,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
901
768
|
process.off('SIGTERM', this.sigtermHandler);
|
|
902
769
|
this.sigtermHandler = undefined;
|
|
903
770
|
}
|
|
904
|
-
/**
|
|
905
|
-
* Logs the node and system information.
|
|
906
|
-
*/
|
|
907
771
|
async logNodeAndSystemInfo() {
|
|
908
|
-
// IP address information
|
|
909
772
|
const networkInterfaces = os.networkInterfaces();
|
|
910
773
|
this.systemInformation.interfaceName = '';
|
|
911
774
|
this.systemInformation.ipv4Address = '';
|
|
912
775
|
this.systemInformation.ipv6Address = '';
|
|
913
776
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
914
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
915
777
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
916
778
|
continue;
|
|
917
779
|
if (!interfaceDetails) {
|
|
@@ -937,22 +799,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
937
799
|
break;
|
|
938
800
|
}
|
|
939
801
|
}
|
|
940
|
-
// Node information
|
|
941
802
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
942
803
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
943
804
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
944
805
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
945
|
-
// Host system information
|
|
946
806
|
this.systemInformation.hostname = os.hostname();
|
|
947
807
|
this.systemInformation.user = os.userInfo().username;
|
|
948
|
-
this.systemInformation.osType = os.type();
|
|
949
|
-
this.systemInformation.osRelease = os.release();
|
|
950
|
-
this.systemInformation.osPlatform = os.platform();
|
|
951
|
-
this.systemInformation.osArch = os.arch();
|
|
952
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
953
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
954
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
955
|
-
// Log the system information
|
|
808
|
+
this.systemInformation.osType = os.type();
|
|
809
|
+
this.systemInformation.osRelease = os.release();
|
|
810
|
+
this.systemInformation.osPlatform = os.platform();
|
|
811
|
+
this.systemInformation.osArch = os.arch();
|
|
812
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
813
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
814
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
956
815
|
this.log.debug('Host System Information:');
|
|
957
816
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
958
817
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -968,20 +827,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
968
827
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
969
828
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
970
829
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
971
|
-
// Home directory
|
|
972
830
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
973
831
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
974
832
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
975
|
-
// Package root directory
|
|
976
833
|
const { fileURLToPath } = await import('node:url');
|
|
977
834
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
978
835
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
979
836
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
980
837
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
981
|
-
// Global node_modules directory
|
|
982
838
|
if (this.nodeContext)
|
|
983
839
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
984
|
-
// First run of Matterbridge so the node storage is empty
|
|
985
840
|
if (this.globalModulesDirectory === '') {
|
|
986
841
|
try {
|
|
987
842
|
this.execRunningCount++;
|
|
@@ -997,20 +852,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
997
852
|
}
|
|
998
853
|
else
|
|
999
854
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1000
|
-
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
1001
|
-
else {
|
|
1002
|
-
this.getGlobalNodeModules()
|
|
1003
|
-
.then(async (globalModulesDirectory) => {
|
|
1004
|
-
this.globalModulesDirectory = globalModulesDirectory;
|
|
1005
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1006
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1007
|
-
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1008
|
-
})
|
|
1009
|
-
.catch((error) => {
|
|
1010
|
-
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1011
|
-
});
|
|
1012
|
-
}*/
|
|
1013
|
-
// Create the data directory .matterbridge in the home directory
|
|
1014
855
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
1015
856
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
1016
857
|
try {
|
|
@@ -1034,7 +875,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1034
875
|
}
|
|
1035
876
|
}
|
|
1036
877
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1037
|
-
// Create the plugin directory Matterbridge in the home directory
|
|
1038
878
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
1039
879
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
1040
880
|
try {
|
|
@@ -1058,7 +898,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1058
898
|
}
|
|
1059
899
|
}
|
|
1060
900
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1061
|
-
// Create the matter cert directory in the home directory
|
|
1062
901
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
1063
902
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
1064
903
|
try {
|
|
@@ -1082,68 +921,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
921
|
}
|
|
1083
922
|
}
|
|
1084
923
|
this.log.debug(`Matterbridge Matter Cert Directory: ${this.matterbridgeCertDirectory}`);
|
|
1085
|
-
// Matterbridge version
|
|
1086
924
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1087
925
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
1088
926
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
1089
927
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
-
// Matterbridge latest version
|
|
1091
928
|
if (this.nodeContext)
|
|
1092
929
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1093
930
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
-
// this.getMatterbridgeLatestVersion();
|
|
1095
|
-
// Current working directory
|
|
1096
931
|
const currentDir = process.cwd();
|
|
1097
932
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1098
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1099
933
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1100
934
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1101
935
|
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1104
|
-
*
|
|
1105
|
-
* @returns {Function} The MatterLogger function.
|
|
1106
|
-
*/
|
|
1107
936
|
createMatterLogger() {
|
|
1108
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
937
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1109
938
|
return (_level, formattedLog) => {
|
|
1110
939
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1111
940
|
const message = formattedLog.slice(65);
|
|
1112
941
|
matterLogger.logName = logger;
|
|
1113
942
|
switch (_level) {
|
|
1114
943
|
case MatterLogLevel.DEBUG:
|
|
1115
|
-
matterLogger.log("debug"
|
|
944
|
+
matterLogger.log("debug", message);
|
|
1116
945
|
break;
|
|
1117
946
|
case MatterLogLevel.INFO:
|
|
1118
|
-
matterLogger.log("info"
|
|
947
|
+
matterLogger.log("info", message);
|
|
1119
948
|
break;
|
|
1120
949
|
case MatterLogLevel.NOTICE:
|
|
1121
|
-
matterLogger.log("notice"
|
|
950
|
+
matterLogger.log("notice", message);
|
|
1122
951
|
break;
|
|
1123
952
|
case MatterLogLevel.WARN:
|
|
1124
|
-
matterLogger.log("warn"
|
|
953
|
+
matterLogger.log("warn", message);
|
|
1125
954
|
break;
|
|
1126
955
|
case MatterLogLevel.ERROR:
|
|
1127
|
-
matterLogger.log("error"
|
|
956
|
+
matterLogger.log("error", message);
|
|
1128
957
|
break;
|
|
1129
958
|
case MatterLogLevel.FATAL:
|
|
1130
|
-
matterLogger.log("fatal"
|
|
959
|
+
matterLogger.log("fatal", message);
|
|
1131
960
|
break;
|
|
1132
961
|
default:
|
|
1133
|
-
matterLogger.log("debug"
|
|
962
|
+
matterLogger.log("debug", message);
|
|
1134
963
|
break;
|
|
1135
964
|
}
|
|
1136
965
|
};
|
|
1137
966
|
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Creates a Matter File Logger.
|
|
1140
|
-
*
|
|
1141
|
-
* @param {string} filePath - The path to the log file.
|
|
1142
|
-
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1143
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1144
|
-
*/
|
|
1145
967
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1146
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1147
968
|
let fileSize = 0;
|
|
1148
969
|
if (unlink) {
|
|
1149
970
|
try {
|
|
@@ -1192,21 +1013,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1192
1013
|
}
|
|
1193
1014
|
};
|
|
1194
1015
|
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1197
|
-
*/
|
|
1198
1016
|
async restartProcess() {
|
|
1199
1017
|
await this.cleanup('restarting...', true);
|
|
1200
1018
|
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Shut down the process by exiting the current process.
|
|
1203
|
-
*/
|
|
1204
1019
|
async shutdownProcess() {
|
|
1205
1020
|
await this.cleanup('shutting down...', false);
|
|
1206
1021
|
}
|
|
1207
|
-
/**
|
|
1208
|
-
* Update matterbridge and and shut down the process.
|
|
1209
|
-
*/
|
|
1210
1022
|
async updateProcess() {
|
|
1211
1023
|
this.log.info('Updating matterbridge...');
|
|
1212
1024
|
try {
|
|
@@ -1219,73 +1031,52 @@ export class Matterbridge extends EventEmitter {
|
|
|
1219
1031
|
this.frontend.wssSendRestartRequired();
|
|
1220
1032
|
await this.cleanup('updating...', false);
|
|
1221
1033
|
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Unregister all devices and shut down the process.
|
|
1224
|
-
*/
|
|
1225
1034
|
async unregisterAndShutdownProcess() {
|
|
1226
1035
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1227
1036
|
for (const plugin of this.plugins) {
|
|
1228
1037
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1229
1038
|
}
|
|
1230
1039
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1231
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1040
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1232
1041
|
this.log.debug('Cleaning up and shutting down...');
|
|
1233
1042
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1234
1043
|
}
|
|
1235
|
-
/**
|
|
1236
|
-
* Reset commissioning and shut down the process.
|
|
1237
|
-
*/
|
|
1238
1044
|
async shutdownProcessAndReset() {
|
|
1239
1045
|
await this.cleanup('shutting down with reset...', false);
|
|
1240
1046
|
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Factory reset and shut down the process.
|
|
1243
|
-
*/
|
|
1244
1047
|
async shutdownProcessAndFactoryReset() {
|
|
1245
1048
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1246
1049
|
}
|
|
1247
|
-
/**
|
|
1248
|
-
* Cleans up the Matterbridge instance.
|
|
1249
|
-
* @param message - The cleanup message.
|
|
1250
|
-
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1251
|
-
* @returns A promise that resolves when the cleanup is completed.
|
|
1252
|
-
*/
|
|
1253
1050
|
async cleanup(message, restart = false) {
|
|
1254
1051
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1255
1052
|
this.emit('cleanup_started');
|
|
1256
1053
|
this.hasCleanupStarted = true;
|
|
1257
1054
|
this.log.info(message);
|
|
1258
|
-
// Clear the start matter interval
|
|
1259
1055
|
if (this.startMatterInterval) {
|
|
1260
1056
|
clearInterval(this.startMatterInterval);
|
|
1261
1057
|
this.startMatterInterval = undefined;
|
|
1262
1058
|
this.log.debug('Start matter interval cleared');
|
|
1263
1059
|
}
|
|
1264
|
-
// Clear the check update timeout
|
|
1265
1060
|
if (this.checkUpdateTimeout) {
|
|
1266
1061
|
clearInterval(this.checkUpdateTimeout);
|
|
1267
1062
|
this.checkUpdateTimeout = undefined;
|
|
1268
1063
|
this.log.debug('Check update timeout cleared');
|
|
1269
1064
|
}
|
|
1270
|
-
// Clear the check update interval
|
|
1271
1065
|
if (this.checkUpdateInterval) {
|
|
1272
1066
|
clearInterval(this.checkUpdateInterval);
|
|
1273
1067
|
this.checkUpdateInterval = undefined;
|
|
1274
1068
|
this.log.debug('Check update interval cleared');
|
|
1275
1069
|
}
|
|
1276
|
-
// Clear the configure timeout
|
|
1277
1070
|
if (this.configureTimeout) {
|
|
1278
1071
|
clearTimeout(this.configureTimeout);
|
|
1279
1072
|
this.configureTimeout = undefined;
|
|
1280
1073
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1281
1074
|
}
|
|
1282
|
-
// Clear the reachability timeout
|
|
1283
1075
|
if (this.reachabilityTimeout) {
|
|
1284
1076
|
clearTimeout(this.reachabilityTimeout);
|
|
1285
1077
|
this.reachabilityTimeout = undefined;
|
|
1286
1078
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1287
1079
|
}
|
|
1288
|
-
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1289
1080
|
for (const plugin of this.plugins) {
|
|
1290
1081
|
if (!plugin.enabled || plugin.error)
|
|
1291
1082
|
continue;
|
|
@@ -1296,10 +1087,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1296
1087
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1297
1088
|
}
|
|
1298
1089
|
}
|
|
1299
|
-
// Stop matter server nodes
|
|
1300
1090
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1301
1091
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1302
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1092
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1303
1093
|
if (this.bridgeMode === 'bridge') {
|
|
1304
1094
|
if (this.serverNode) {
|
|
1305
1095
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1315,7 +1105,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1315
1105
|
}
|
|
1316
1106
|
}
|
|
1317
1107
|
this.log.notice('Stopped matter server nodes');
|
|
1318
|
-
// Matter commisioning reset
|
|
1319
1108
|
if (message === 'shutting down with reset...') {
|
|
1320
1109
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1321
1110
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1325,37 +1114,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1325
1114
|
await this.matterbridgeContext?.clearAll();
|
|
1326
1115
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1327
1116
|
}
|
|
1328
|
-
// Stop matter storage
|
|
1329
1117
|
await this.stopMatterStorage();
|
|
1330
|
-
// Stop the frontend
|
|
1331
1118
|
await this.frontend.stop();
|
|
1332
|
-
// Remove the matterfilelogger
|
|
1333
1119
|
try {
|
|
1334
1120
|
Logger.removeLogger('matterfilelogger');
|
|
1335
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1336
1121
|
}
|
|
1337
1122
|
catch (error) {
|
|
1338
|
-
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1339
1123
|
}
|
|
1340
|
-
// Serialize registeredDevices
|
|
1341
1124
|
if (this.nodeStorage && this.nodeContext) {
|
|
1342
|
-
/*
|
|
1343
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1344
|
-
this.log.info('Saving registered devices...');
|
|
1345
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1346
|
-
this.devices.forEach(async (device) => {
|
|
1347
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1348
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1349
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1350
|
-
});
|
|
1351
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1352
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1353
|
-
*/
|
|
1354
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1355
1125
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1356
1126
|
await this.nodeContext.close();
|
|
1357
1127
|
this.nodeContext = undefined;
|
|
1358
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1359
1128
|
for (const plugin of this.plugins) {
|
|
1360
1129
|
if (plugin.nodeContext) {
|
|
1361
1130
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1372,10 +1141,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1372
1141
|
}
|
|
1373
1142
|
this.plugins.clear();
|
|
1374
1143
|
this.devices.clear();
|
|
1375
|
-
// Factory reset
|
|
1376
1144
|
if (message === 'shutting down with factory reset...') {
|
|
1377
1145
|
try {
|
|
1378
|
-
// Delete old matter storage file and backup
|
|
1379
1146
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1380
1147
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1381
1148
|
await fs.unlink(file);
|
|
@@ -1389,7 +1156,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1156
|
}
|
|
1390
1157
|
}
|
|
1391
1158
|
try {
|
|
1392
|
-
// Delete matter node storage directory with its subdirectories and backup
|
|
1393
1159
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1394
1160
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1395
1161
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1403,7 +1169,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1403
1169
|
}
|
|
1404
1170
|
}
|
|
1405
1171
|
try {
|
|
1406
|
-
// Delete node storage directory with its subdirectories and backup
|
|
1407
1172
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1408
1173
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1409
1174
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1418,13 +1183,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1418
1183
|
}
|
|
1419
1184
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1420
1185
|
}
|
|
1421
|
-
// Deregisters the process handlers
|
|
1422
1186
|
this.deregisterProcesslHandlers();
|
|
1423
1187
|
if (restart) {
|
|
1424
1188
|
if (message === 'updating...') {
|
|
1425
1189
|
this.log.info('Cleanup completed. Updating...');
|
|
1426
1190
|
Matterbridge.instance = undefined;
|
|
1427
|
-
this.emit('update');
|
|
1191
|
+
this.emit('update');
|
|
1428
1192
|
}
|
|
1429
1193
|
else if (message === 'restarting...') {
|
|
1430
1194
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1445,14 +1209,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1445
1209
|
this.log.debug('Cleanup already started...');
|
|
1446
1210
|
}
|
|
1447
1211
|
}
|
|
1448
|
-
/**
|
|
1449
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1450
|
-
*
|
|
1451
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1452
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1453
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1454
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1455
|
-
*/
|
|
1456
1212
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1457
1213
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1458
1214
|
plugin.locked = true;
|
|
@@ -1466,13 +1222,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1466
1222
|
await this.startServerNode(plugin.serverNode);
|
|
1467
1223
|
}
|
|
1468
1224
|
}
|
|
1469
|
-
/**
|
|
1470
|
-
* Creates and configures the server node for a dynamic plugin.
|
|
1471
|
-
*
|
|
1472
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1473
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1474
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1475
|
-
*/
|
|
1476
1225
|
async createDynamicPlugin(plugin, start = false) {
|
|
1477
1226
|
if (!plugin.locked) {
|
|
1478
1227
|
plugin.locked = true;
|
|
@@ -1485,13 +1234,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1485
1234
|
await this.startServerNode(plugin.serverNode);
|
|
1486
1235
|
}
|
|
1487
1236
|
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Starts the Matterbridge in bridge mode.
|
|
1490
|
-
* @private
|
|
1491
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1492
|
-
*/
|
|
1493
1237
|
async startBridge() {
|
|
1494
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1495
1238
|
if (!this.matterStorageManager)
|
|
1496
1239
|
throw new Error('No storage manager initialized');
|
|
1497
1240
|
if (!this.matterbridgeContext)
|
|
@@ -1529,9 +1272,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1529
1272
|
clearInterval(this.startMatterInterval);
|
|
1530
1273
|
this.startMatterInterval = undefined;
|
|
1531
1274
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1532
|
-
// Start the Matter server node
|
|
1533
1275
|
this.startServerNode(this.serverNode);
|
|
1534
|
-
// Configure the plugins
|
|
1535
1276
|
this.configureTimeout = setTimeout(async () => {
|
|
1536
1277
|
for (const plugin of this.plugins) {
|
|
1537
1278
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1549,7 +1290,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1549
1290
|
}
|
|
1550
1291
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1551
1292
|
}, 30 * 1000);
|
|
1552
|
-
// Setting reachability to true
|
|
1553
1293
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1554
1294
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1555
1295
|
if (this.aggregatorNode)
|
|
@@ -1558,11 +1298,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1558
1298
|
}, 60 * 1000);
|
|
1559
1299
|
}, 1000);
|
|
1560
1300
|
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1563
|
-
* @private
|
|
1564
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1565
|
-
*/
|
|
1566
1301
|
async startChildbridge() {
|
|
1567
1302
|
if (!this.matterStorageManager)
|
|
1568
1303
|
throw new Error('No storage manager initialized');
|
|
@@ -1600,7 +1335,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1600
1335
|
clearInterval(this.startMatterInterval);
|
|
1601
1336
|
this.startMatterInterval = undefined;
|
|
1602
1337
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1603
|
-
// Configure the plugins
|
|
1604
1338
|
this.configureTimeout = setTimeout(async () => {
|
|
1605
1339
|
for (const plugin of this.plugins) {
|
|
1606
1340
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1637,9 +1371,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1637
1371
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1638
1372
|
continue;
|
|
1639
1373
|
}
|
|
1640
|
-
// Start the Matter server node
|
|
1641
1374
|
this.startServerNode(plugin.serverNode);
|
|
1642
|
-
// Setting reachability to true
|
|
1643
1375
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1644
1376
|
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}`);
|
|
1645
1377
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1649,11 +1381,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1649
1381
|
}
|
|
1650
1382
|
}, 1000);
|
|
1651
1383
|
}
|
|
1652
|
-
/**
|
|
1653
|
-
* Starts the Matterbridge controller.
|
|
1654
|
-
* @private
|
|
1655
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1656
|
-
*/
|
|
1657
1384
|
async startController() {
|
|
1658
1385
|
if (!this.matterStorageManager) {
|
|
1659
1386
|
this.log.error('No storage manager initialized');
|
|
@@ -1668,207 +1395,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1668
1395
|
return;
|
|
1669
1396
|
}
|
|
1670
1397
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1671
|
-
/*
|
|
1672
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1673
|
-
this.log.info('Creating matter commissioning controller');
|
|
1674
|
-
this.commissioningController = new CommissioningController({
|
|
1675
|
-
autoConnect: false,
|
|
1676
|
-
});
|
|
1677
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1678
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1679
|
-
|
|
1680
|
-
this.log.info('Starting matter server');
|
|
1681
|
-
await this.matterServer.start();
|
|
1682
|
-
this.log.info('Matter server started');
|
|
1683
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1684
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1685
|
-
regulatoryCountryCode: 'XX',
|
|
1686
|
-
};
|
|
1687
|
-
const commissioningController = new CommissioningController({
|
|
1688
|
-
environment: {
|
|
1689
|
-
environment,
|
|
1690
|
-
id: uniqueId,
|
|
1691
|
-
},
|
|
1692
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1693
|
-
adminFabricLabel,
|
|
1694
|
-
});
|
|
1695
|
-
|
|
1696
|
-
if (hasParameter('pairingcode')) {
|
|
1697
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1698
|
-
const pairingCode = getParameter('pairingcode');
|
|
1699
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1700
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1701
|
-
|
|
1702
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1703
|
-
if (pairingCode !== undefined) {
|
|
1704
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1705
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1706
|
-
longDiscriminator = undefined;
|
|
1707
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1708
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1709
|
-
} else {
|
|
1710
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1711
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1712
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1713
|
-
}
|
|
1714
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1715
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
const options = {
|
|
1719
|
-
commissioning: commissioningOptions,
|
|
1720
|
-
discovery: {
|
|
1721
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1722
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1723
|
-
},
|
|
1724
|
-
passcode: setupPin,
|
|
1725
|
-
} as NodeCommissioningOptions;
|
|
1726
|
-
this.log.info('Commissioning with options:', options);
|
|
1727
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1728
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1729
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1730
|
-
} // (hasParameter('pairingcode'))
|
|
1731
|
-
|
|
1732
|
-
if (hasParameter('unpairall')) {
|
|
1733
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1734
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1735
|
-
for (const nodeId of nodeIds) {
|
|
1736
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1737
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1738
|
-
}
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
if (hasParameter('discover')) {
|
|
1743
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1744
|
-
// console.log(discover);
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1748
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1749
|
-
return;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1753
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1754
|
-
for (const nodeId of nodeIds) {
|
|
1755
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1756
|
-
|
|
1757
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1758
|
-
autoSubscribe: false,
|
|
1759
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1760
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1761
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1762
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1763
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1764
|
-
switch (info) {
|
|
1765
|
-
case NodeStateInformation.Connected:
|
|
1766
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1767
|
-
break;
|
|
1768
|
-
case NodeStateInformation.Disconnected:
|
|
1769
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1770
|
-
break;
|
|
1771
|
-
case NodeStateInformation.Reconnecting:
|
|
1772
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1773
|
-
break;
|
|
1774
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1775
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1776
|
-
break;
|
|
1777
|
-
case NodeStateInformation.StructureChanged:
|
|
1778
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1779
|
-
break;
|
|
1780
|
-
case NodeStateInformation.Decommissioned:
|
|
1781
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1782
|
-
break;
|
|
1783
|
-
default:
|
|
1784
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1785
|
-
break;
|
|
1786
|
-
}
|
|
1787
|
-
},
|
|
1788
|
-
});
|
|
1789
|
-
|
|
1790
|
-
node.logStructure();
|
|
1791
|
-
|
|
1792
|
-
// Get the interaction client
|
|
1793
|
-
this.log.info('Getting the interaction client');
|
|
1794
|
-
const interactionClient = await node.getInteractionClient();
|
|
1795
|
-
let cluster;
|
|
1796
|
-
let attributes;
|
|
1797
|
-
|
|
1798
|
-
// Log BasicInformationCluster
|
|
1799
|
-
cluster = BasicInformationCluster;
|
|
1800
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1801
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1802
|
-
});
|
|
1803
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1804
|
-
attributes.forEach((attribute) => {
|
|
1805
|
-
this.log.info(
|
|
1806
|
-
`- 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}`,
|
|
1807
|
-
);
|
|
1808
|
-
});
|
|
1809
|
-
|
|
1810
|
-
// Log PowerSourceCluster
|
|
1811
|
-
cluster = PowerSourceCluster;
|
|
1812
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1813
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1814
|
-
});
|
|
1815
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1816
|
-
attributes.forEach((attribute) => {
|
|
1817
|
-
this.log.info(
|
|
1818
|
-
`- 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}`,
|
|
1819
|
-
);
|
|
1820
|
-
});
|
|
1821
|
-
|
|
1822
|
-
// Log ThreadNetworkDiagnostics
|
|
1823
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1824
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1825
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1826
|
-
});
|
|
1827
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1828
|
-
attributes.forEach((attribute) => {
|
|
1829
|
-
this.log.info(
|
|
1830
|
-
`- 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}`,
|
|
1831
|
-
);
|
|
1832
|
-
});
|
|
1833
|
-
|
|
1834
|
-
// Log SwitchCluster
|
|
1835
|
-
cluster = SwitchCluster;
|
|
1836
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1837
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1838
|
-
});
|
|
1839
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1840
|
-
attributes.forEach((attribute) => {
|
|
1841
|
-
this.log.info(
|
|
1842
|
-
`- 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}`,
|
|
1843
|
-
);
|
|
1844
|
-
});
|
|
1845
|
-
|
|
1846
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1847
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1848
|
-
ignoreInitialTriggers: false,
|
|
1849
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1850
|
-
this.log.info(
|
|
1851
|
-
`***${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}`,
|
|
1852
|
-
),
|
|
1853
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1854
|
-
this.log.info(
|
|
1855
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1856
|
-
);
|
|
1857
|
-
},
|
|
1858
|
-
});
|
|
1859
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1860
|
-
}
|
|
1861
|
-
*/
|
|
1862
1398
|
}
|
|
1863
|
-
/** ***********************************************************************************************************************************/
|
|
1864
|
-
/** Matter.js methods */
|
|
1865
|
-
/** ***********************************************************************************************************************************/
|
|
1866
|
-
/**
|
|
1867
|
-
* Starts the matter storage process with name Matterbridge.
|
|
1868
|
-
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1869
|
-
*/
|
|
1870
1399
|
async startMatterStorage() {
|
|
1871
|
-
// Setup Matter storage
|
|
1872
1400
|
this.log.info(`Starting matter node storage...`);
|
|
1873
1401
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1874
1402
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1877,25 +1405,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1877
1405
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1878
1406
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1879
1407
|
this.log.info('Matter node storage started');
|
|
1880
|
-
// Backup matter storage since it is created/opened correctly
|
|
1881
1408
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1882
1409
|
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1885
|
-
*
|
|
1886
|
-
* @param storageName - The name of the storage directory to be backed up.
|
|
1887
|
-
* @param backupName - The name of the backup directory to be created.
|
|
1888
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1889
|
-
*/
|
|
1890
1410
|
async backupMatterStorage(storageName, backupName) {
|
|
1891
1411
|
this.log.info('Creating matter node storage backup...');
|
|
1892
1412
|
await copyDirectory(storageName, backupName);
|
|
1893
1413
|
this.log.info('Created matter node storage backup');
|
|
1894
1414
|
}
|
|
1895
|
-
/**
|
|
1896
|
-
* Stops the matter storage.
|
|
1897
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1898
|
-
*/
|
|
1899
1415
|
async stopMatterStorage() {
|
|
1900
1416
|
this.log.info('Closing matter node storage...');
|
|
1901
1417
|
await this.matterStorageManager?.close();
|
|
@@ -1904,19 +1420,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1904
1420
|
this.matterbridgeContext = undefined;
|
|
1905
1421
|
this.log.info('Matter node storage closed');
|
|
1906
1422
|
}
|
|
1907
|
-
/**
|
|
1908
|
-
* Creates a server node storage context.
|
|
1909
|
-
*
|
|
1910
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1911
|
-
* @param {string} deviceName - The name of the device.
|
|
1912
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1913
|
-
* @param {number} vendorId - The vendor ID.
|
|
1914
|
-
* @param {string} vendorName - The vendor name.
|
|
1915
|
-
* @param {number} productId - The product ID.
|
|
1916
|
-
* @param {string} productName - The product name.
|
|
1917
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1918
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1919
|
-
*/
|
|
1920
1423
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1921
1424
|
const { randomBytes } = await import('node:crypto');
|
|
1922
1425
|
if (!this.matterStorageService)
|
|
@@ -1950,15 +1453,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1950
1453
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1951
1454
|
return storageContext;
|
|
1952
1455
|
}
|
|
1953
|
-
/**
|
|
1954
|
-
* Creates a server node.
|
|
1955
|
-
*
|
|
1956
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1957
|
-
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1958
|
-
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1959
|
-
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1960
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1961
|
-
*/
|
|
1962
1456
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1963
1457
|
const storeId = await storageContext.get('storeId');
|
|
1964
1458
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1968,33 +1462,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1968
1462
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1969
1463
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1970
1464
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1971
|
-
/**
|
|
1972
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1973
|
-
*/
|
|
1974
1465
|
const serverNode = await ServerNode.create({
|
|
1975
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1976
1466
|
id: storeId,
|
|
1977
|
-
// Provide Network relevant configuration like the port
|
|
1978
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
1979
1467
|
network: {
|
|
1980
1468
|
listeningAddressIpv4: this.ipv4address,
|
|
1981
1469
|
listeningAddressIpv6: this.ipv6address,
|
|
1982
1470
|
port,
|
|
1983
1471
|
},
|
|
1984
|
-
|
|
1985
|
-
|
|
1472
|
+
operationalCredentials: {
|
|
1473
|
+
certification: this.certification,
|
|
1474
|
+
},
|
|
1986
1475
|
commissioning: {
|
|
1987
1476
|
passcode,
|
|
1988
1477
|
discriminator,
|
|
1989
1478
|
},
|
|
1990
|
-
// Provide Node announcement settings
|
|
1991
|
-
// Optional: If Ommitted some development defaults are used
|
|
1992
1479
|
productDescription: {
|
|
1993
1480
|
name: await storageContext.get('deviceName'),
|
|
1994
1481
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1995
1482
|
},
|
|
1996
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
-
// Optional: If Omitted some development defaults are used
|
|
1998
1483
|
basicInformation: {
|
|
1999
1484
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2000
1485
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2012,13 +1497,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2012
1497
|
},
|
|
2013
1498
|
});
|
|
2014
1499
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2015
|
-
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
2016
1500
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
2017
1501
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
2018
1502
|
if (this.bridgeMode === 'bridge') {
|
|
2019
1503
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
2020
1504
|
if (resetSessions)
|
|
2021
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1505
|
+
this.matterbridgeSessionInformations = undefined;
|
|
2022
1506
|
this.matterbridgePaired = true;
|
|
2023
1507
|
}
|
|
2024
1508
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -2026,19 +1510,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2026
1510
|
if (plugin) {
|
|
2027
1511
|
plugin.fabricInformations = sanitizedFabrics;
|
|
2028
1512
|
if (resetSessions)
|
|
2029
|
-
plugin.sessionInformations = undefined;
|
|
1513
|
+
plugin.sessionInformations = undefined;
|
|
2030
1514
|
plugin.paired = true;
|
|
2031
1515
|
}
|
|
2032
1516
|
}
|
|
2033
1517
|
};
|
|
2034
|
-
/**
|
|
2035
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2036
|
-
* This means: It is added to the first fabric.
|
|
2037
|
-
*/
|
|
2038
1518
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
2039
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2040
1519
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2041
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2042
1520
|
serverNode.lifecycle.online.on(async () => {
|
|
2043
1521
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2044
1522
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2107,7 +1585,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2107
1585
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2108
1586
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2109
1587
|
});
|
|
2110
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2111
1588
|
serverNode.lifecycle.offline.on(() => {
|
|
2112
1589
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2113
1590
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -2131,10 +1608,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2131
1608
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2132
1609
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2133
1610
|
});
|
|
2134
|
-
/**
|
|
2135
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2136
|
-
* information is needed.
|
|
2137
|
-
*/
|
|
2138
1611
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2139
1612
|
let action = '';
|
|
2140
1613
|
switch (fabricAction) {
|
|
@@ -2168,24 +1641,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2168
1641
|
}
|
|
2169
1642
|
}
|
|
2170
1643
|
};
|
|
2171
|
-
/**
|
|
2172
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2173
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2174
|
-
*/
|
|
2175
1644
|
serverNode.events.sessions.opened.on((session) => {
|
|
2176
1645
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2177
1646
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2178
1647
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2179
1648
|
});
|
|
2180
|
-
/**
|
|
2181
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2182
|
-
*/
|
|
2183
1649
|
serverNode.events.sessions.closed.on((session) => {
|
|
2184
1650
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2185
1651
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2186
1652
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2187
1653
|
});
|
|
2188
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2189
1654
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2190
1655
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2191
1656
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -2194,42 +1659,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2194
1659
|
this.log.info(`Created server node for ${storeId}`);
|
|
2195
1660
|
return serverNode;
|
|
2196
1661
|
}
|
|
2197
|
-
/**
|
|
2198
|
-
* Starts the specified server node.
|
|
2199
|
-
*
|
|
2200
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2201
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2202
|
-
*/
|
|
2203
1662
|
async startServerNode(matterServerNode) {
|
|
2204
1663
|
if (!matterServerNode)
|
|
2205
1664
|
return;
|
|
2206
1665
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2207
1666
|
await matterServerNode.start();
|
|
2208
1667
|
}
|
|
2209
|
-
/**
|
|
2210
|
-
* Stops the specified server node.
|
|
2211
|
-
*
|
|
2212
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2213
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2214
|
-
*/
|
|
2215
1668
|
async stopServerNode(matterServerNode) {
|
|
2216
1669
|
if (!matterServerNode)
|
|
2217
1670
|
return;
|
|
2218
1671
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2219
1672
|
try {
|
|
2220
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
1673
|
+
await withTimeout(matterServerNode.close(), 30000);
|
|
2221
1674
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
2222
1675
|
}
|
|
2223
1676
|
catch (error) {
|
|
2224
1677
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2225
1678
|
}
|
|
2226
1679
|
}
|
|
2227
|
-
/**
|
|
2228
|
-
* Advertises the specified server node.
|
|
2229
|
-
*
|
|
2230
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2231
|
-
* @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.
|
|
2232
|
-
*/
|
|
2233
1680
|
async advertiseServerNode(matterServerNode) {
|
|
2234
1681
|
if (matterServerNode) {
|
|
2235
1682
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2238,45 +1685,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2238
1685
|
return { qrPairingCode, manualPairingCode };
|
|
2239
1686
|
}
|
|
2240
1687
|
}
|
|
2241
|
-
/**
|
|
2242
|
-
* Stop advertise the specified server node.
|
|
2243
|
-
*
|
|
2244
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2245
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2246
|
-
*/
|
|
2247
1688
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2248
1689
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2249
1690
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2250
1691
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2251
1692
|
}
|
|
2252
1693
|
}
|
|
2253
|
-
/**
|
|
2254
|
-
* Creates an aggregator node with the specified storage context.
|
|
2255
|
-
*
|
|
2256
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2257
|
-
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2258
|
-
*/
|
|
2259
1694
|
async createAggregatorNode(storageContext) {
|
|
2260
1695
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
2261
1696
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2262
1697
|
return aggregatorNode;
|
|
2263
1698
|
}
|
|
2264
|
-
/**
|
|
2265
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2266
|
-
*
|
|
2267
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2268
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2269
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2270
|
-
*/
|
|
2271
1699
|
async addBridgedEndpoint(pluginName, device) {
|
|
2272
|
-
// Check if the plugin is registered
|
|
2273
1700
|
const plugin = this.plugins.get(pluginName);
|
|
2274
1701
|
if (!plugin) {
|
|
2275
1702
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
2276
1703
|
return;
|
|
2277
1704
|
}
|
|
2278
1705
|
if (this.bridgeMode === 'bridge') {
|
|
2279
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2280
1706
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2281
1707
|
if (!this.aggregatorNode) {
|
|
2282
1708
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2293,7 +1719,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2293
1719
|
}
|
|
2294
1720
|
}
|
|
2295
1721
|
else if (this.bridgeMode === 'childbridge') {
|
|
2296
|
-
// Register and add the device to the plugin server node
|
|
2297
1722
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2298
1723
|
try {
|
|
2299
1724
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2310,12 +1735,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2310
1735
|
return;
|
|
2311
1736
|
}
|
|
2312
1737
|
}
|
|
2313
|
-
// Register and add the device to the plugin aggregator node
|
|
2314
1738
|
if (plugin.type === 'DynamicPlatform') {
|
|
2315
1739
|
try {
|
|
2316
1740
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2317
1741
|
await this.createDynamicPlugin(plugin);
|
|
2318
|
-
// Fast plugins can add another device before the server node is created
|
|
2319
1742
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2320
1743
|
if (!plugin.aggregatorNode) {
|
|
2321
1744
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2335,28 +1758,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2335
1758
|
plugin.registeredDevices++;
|
|
2336
1759
|
if (plugin.addedDevices !== undefined)
|
|
2337
1760
|
plugin.addedDevices++;
|
|
2338
|
-
// Add the device to the DeviceManager
|
|
2339
1761
|
this.devices.set(device);
|
|
2340
|
-
// Subscribe to the reachable$Changed event
|
|
2341
1762
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2342
1763
|
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}`);
|
|
2343
1764
|
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2346
|
-
*
|
|
2347
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2348
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2349
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2350
|
-
*/
|
|
2351
1765
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2352
1766
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2353
|
-
// Check if the plugin is registered
|
|
2354
1767
|
const plugin = this.plugins.get(pluginName);
|
|
2355
1768
|
if (!plugin) {
|
|
2356
1769
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2357
1770
|
return;
|
|
2358
1771
|
}
|
|
2359
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2360
1772
|
if (this.bridgeMode === 'bridge') {
|
|
2361
1773
|
if (!this.aggregatorNode) {
|
|
2362
1774
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2371,7 +1783,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2371
1783
|
}
|
|
2372
1784
|
else if (this.bridgeMode === 'childbridge') {
|
|
2373
1785
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2374
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2375
1786
|
}
|
|
2376
1787
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2377
1788
|
if (!plugin.aggregatorNode) {
|
|
@@ -2386,21 +1797,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2386
1797
|
if (plugin.addedDevices !== undefined)
|
|
2387
1798
|
plugin.addedDevices--;
|
|
2388
1799
|
}
|
|
2389
|
-
// Remove the device from the DeviceManager
|
|
2390
1800
|
this.devices.remove(device);
|
|
2391
1801
|
}
|
|
2392
|
-
/**
|
|
2393
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2394
|
-
*
|
|
2395
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2396
|
-
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2397
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2398
|
-
*
|
|
2399
|
-
* @remarks
|
|
2400
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2401
|
-
* It also applies a delay between each removal if specified.
|
|
2402
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2403
|
-
*/
|
|
2404
1802
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2405
1803
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2406
1804
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2411,25 +1809,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2411
1809
|
if (delay > 0)
|
|
2412
1810
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2413
1811
|
}
|
|
2414
|
-
/**
|
|
2415
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2416
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2417
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2418
|
-
*
|
|
2419
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2420
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2421
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2422
|
-
*/
|
|
2423
1812
|
async subscribeAttributeChanged(plugin, device) {
|
|
2424
1813
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2425
1814
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2426
|
-
/*
|
|
2427
|
-
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
|
|
2428
|
-
setTimeout(async () => {
|
|
2429
|
-
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
|
|
2430
|
-
await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
|
|
2431
|
-
}, 60000).unref();
|
|
2432
|
-
*/
|
|
2433
1815
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
2434
1816
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2435
1817
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
@@ -2442,12 +1824,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2442
1824
|
});
|
|
2443
1825
|
}
|
|
2444
1826
|
}
|
|
2445
|
-
/**
|
|
2446
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2447
|
-
*
|
|
2448
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2449
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2450
|
-
*/
|
|
2451
1827
|
sanitizeFabricInformations(fabricInfo) {
|
|
2452
1828
|
return fabricInfo.map((info) => {
|
|
2453
1829
|
return {
|
|
@@ -2461,12 +1837,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2461
1837
|
};
|
|
2462
1838
|
});
|
|
2463
1839
|
}
|
|
2464
|
-
/**
|
|
2465
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2466
|
-
*
|
|
2467
|
-
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2468
|
-
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2469
|
-
*/
|
|
2470
1840
|
sanitizeSessionInformation(sessionInfo) {
|
|
2471
1841
|
return sessionInfo
|
|
2472
1842
|
.filter((session) => session.isPeerActive)
|
|
@@ -2494,20 +1864,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2494
1864
|
};
|
|
2495
1865
|
});
|
|
2496
1866
|
}
|
|
2497
|
-
/**
|
|
2498
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2499
|
-
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2500
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2501
|
-
*/
|
|
2502
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2503
1867
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2504
|
-
/*
|
|
2505
|
-
for (const child of aggregatorNode.parts) {
|
|
2506
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2507
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2508
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2509
|
-
}
|
|
2510
|
-
*/
|
|
2511
1868
|
}
|
|
2512
1869
|
getVendorIdName = (vendorId) => {
|
|
2513
1870
|
if (!vendorId)
|
|
@@ -2550,37 +1907,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2550
1907
|
}
|
|
2551
1908
|
return vendorName;
|
|
2552
1909
|
};
|
|
2553
|
-
/**
|
|
2554
|
-
* Spawns a child process with the given command and arguments.
|
|
2555
|
-
* @param {string} command - The command to execute.
|
|
2556
|
-
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2557
|
-
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2558
|
-
*/
|
|
2559
1910
|
async spawnCommand(command, args = []) {
|
|
2560
1911
|
const { spawn } = await import('node:child_process');
|
|
2561
|
-
/*
|
|
2562
|
-
npm > npm.cmd on windows
|
|
2563
|
-
cmd.exe ['dir'] on windows
|
|
2564
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2565
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
2566
|
-
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2567
|
-
});
|
|
2568
|
-
|
|
2569
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2570
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2571
|
-
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2572
|
-
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2573
|
-
*/
|
|
2574
1912
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2575
1913
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2576
|
-
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2577
1914
|
const argstring = 'npm ' + args.join(' ');
|
|
2578
1915
|
args.splice(0, args.length, '/c', argstring);
|
|
2579
1916
|
command = 'cmd.exe';
|
|
2580
1917
|
}
|
|
2581
|
-
// Decide when using sudo on linux
|
|
2582
|
-
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2583
|
-
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2584
1918
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2585
1919
|
args.unshift(command);
|
|
2586
1920
|
command = 'sudo';
|
|
@@ -2639,4 +1973,3 @@ export class Matterbridge extends EventEmitter {
|
|
|
2639
1973
|
});
|
|
2640
1974
|
}
|
|
2641
1975
|
}
|
|
2642
|
-
//# sourceMappingURL=matterbridge.js.map
|