matterbridge 2.2.9 → 3.0.0-edge.10
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 +44 -0
- package/dist/cli.js +6 -38
- package/dist/cluster/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -23
- package/dist/deviceManager.js +1 -94
- package/dist/frontend.js +20 -328
- package/dist/index.js +1 -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 +104 -761
- package/dist/matterbridgeAccessoryPlatform.js +0 -33
- package/dist/matterbridgeBehaviors.js +46 -72
- package/dist/matterbridgeDeviceTypes.js +249 -233
- package/dist/matterbridgeDynamicPlatform.js +0 -33
- package/dist/matterbridgeEndpoint.js +85 -759
- package/dist/matterbridgeEndpointHelpers.js +30 -136
- package/dist/matterbridgePlatform.js +9 -218
- package/dist/matterbridgeTypes.js +0 -24
- package/dist/pluginManager.js +3 -262
- package/dist/shelly.js +6 -146
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -45
- package/dist/utils/colorUtils.js +2 -205
- package/dist/utils/copyDirectory.js +1 -37
- package/dist/utils/createZip.js +2 -42
- package/dist/utils/deepCopy.js +0 -40
- package/dist/utils/deepEqual.js +1 -65
- package/dist/utils/export.js +0 -1
- package/dist/utils/isvalid.js +0 -86
- package/dist/utils/network.js +5 -76
- package/dist/utils/parameter.js +0 -41
- package/dist/utils/wait.js +5 -48
- package/npm-shrinkwrap.json +289 -351
- package/package.json +3 -4
- 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 -221
- 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 -425
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -39
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -1056
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -178
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -39
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -867
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -2275
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -285
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -183
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -271
- 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 -87
- 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 -44
- 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,35 +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
6
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
30
|
-
// NodeStorage module
|
|
31
7
|
import { NodeStorageManager } from './storage/export.js';
|
|
32
|
-
// Matterbridge
|
|
33
8
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
|
|
34
9
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
35
10
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -37,19 +12,14 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
37
12
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
38
13
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
39
14
|
import { Frontend } from './frontend.js';
|
|
40
|
-
// @matter
|
|
41
15
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
42
16
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
43
17
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
44
|
-
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
45
18
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
46
|
-
|
|
19
|
+
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
47
20
|
const plg = '\u001B[38;5;33m';
|
|
48
21
|
const dev = '\u001B[38;5;79m';
|
|
49
22
|
const typ = '\u001B[38;5;207m';
|
|
50
|
-
/**
|
|
51
|
-
* Represents the Matterbridge application.
|
|
52
|
-
*/
|
|
53
23
|
export class Matterbridge extends EventEmitter {
|
|
54
24
|
systemInformation = {
|
|
55
25
|
interfaceName: '',
|
|
@@ -94,7 +64,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
94
64
|
shellySysUpdate: false,
|
|
95
65
|
shellyMainUpdate: false,
|
|
96
66
|
profile: getParameter('profile'),
|
|
97
|
-
loggerLevel: "info"
|
|
67
|
+
loggerLevel: "info",
|
|
98
68
|
fileLogger: false,
|
|
99
69
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
100
70
|
matterFileLogger: false,
|
|
@@ -132,11 +102,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
132
102
|
plugins;
|
|
133
103
|
devices;
|
|
134
104
|
frontend = new Frontend(this);
|
|
135
|
-
// Matterbridge storage
|
|
136
105
|
nodeStorage;
|
|
137
106
|
nodeContext;
|
|
138
107
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
139
|
-
// Cleanup
|
|
140
108
|
hasCleanupStarted = false;
|
|
141
109
|
initialized = false;
|
|
142
110
|
execRunningCount = 0;
|
|
@@ -149,21 +117,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
149
117
|
sigtermHandler;
|
|
150
118
|
exceptionHandler;
|
|
151
119
|
rejectionHandler;
|
|
152
|
-
// Matter environment
|
|
153
120
|
environment = Environment.default;
|
|
154
|
-
// Matter storage
|
|
155
121
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
156
122
|
matterStorageService;
|
|
157
123
|
matterStorageManager;
|
|
158
124
|
matterbridgeContext;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
discriminator; // first server node discriminator
|
|
125
|
+
controllerContext;
|
|
126
|
+
mdnsInterface;
|
|
127
|
+
ipv4address;
|
|
128
|
+
ipv6address;
|
|
129
|
+
port;
|
|
130
|
+
passcode;
|
|
131
|
+
discriminator;
|
|
167
132
|
serverNode;
|
|
168
133
|
aggregatorNode;
|
|
169
134
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -171,50 +136,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
171
136
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
172
137
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
173
138
|
static instance;
|
|
174
|
-
// We load asyncronously so is private
|
|
175
139
|
constructor() {
|
|
176
140
|
super();
|
|
177
141
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Emits an event of the specified type with the provided arguments.
|
|
180
|
-
*
|
|
181
|
-
* @template K - The type of the event.
|
|
182
|
-
* @param {K} eventName - The name of the event to emit.
|
|
183
|
-
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
184
|
-
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
185
|
-
*/
|
|
186
142
|
emit(eventName, ...args) {
|
|
187
143
|
return super.emit(eventName, ...args);
|
|
188
144
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Registers an event listener for the specified event type.
|
|
191
|
-
*
|
|
192
|
-
* @template K - The type of the event.
|
|
193
|
-
* @param {K} eventName - The name of the event to listen for.
|
|
194
|
-
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
195
|
-
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
196
|
-
*/
|
|
197
145
|
on(eventName, listener) {
|
|
198
146
|
return super.on(eventName, listener);
|
|
199
147
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Retrieves the list of Matterbridge devices.
|
|
202
|
-
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
203
|
-
*/
|
|
204
148
|
getDevices() {
|
|
205
149
|
return this.devices.array();
|
|
206
150
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Retrieves the list of registered plugins.
|
|
209
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
210
|
-
*/
|
|
211
151
|
getPlugins() {
|
|
212
152
|
return this.plugins.array();
|
|
213
153
|
}
|
|
214
|
-
/**
|
|
215
|
-
* Set the logger logLevel for the Matterbridge classes.
|
|
216
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
217
|
-
*/
|
|
218
154
|
async setLogLevel(logLevel) {
|
|
219
155
|
if (this.log)
|
|
220
156
|
this.log.logLevel = logLevel;
|
|
@@ -228,31 +164,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
228
164
|
for (const plugin of this.plugins) {
|
|
229
165
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
230
166
|
continue;
|
|
231
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug"
|
|
232
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug"
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
167
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
168
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
169
|
+
}
|
|
170
|
+
let callbackLogLevel = "notice";
|
|
171
|
+
if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
172
|
+
callbackLogLevel = "info";
|
|
173
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
174
|
+
callbackLogLevel = "debug";
|
|
240
175
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
241
176
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
242
177
|
}
|
|
243
|
-
/** ***********************************************************************************************************************************/
|
|
244
|
-
/** loadInstance() and cleanup() methods */
|
|
245
|
-
/** ***********************************************************************************************************************************/
|
|
246
|
-
/**
|
|
247
|
-
* Loads an instance of the Matterbridge class.
|
|
248
|
-
* If an instance already exists, return that instance.
|
|
249
|
-
*
|
|
250
|
-
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
251
|
-
* @returns The loaded Matterbridge instance.
|
|
252
|
-
*/
|
|
253
178
|
static async loadInstance(initialize = false) {
|
|
254
179
|
if (!Matterbridge.instance) {
|
|
255
|
-
// eslint-disable-next-line no-console
|
|
256
180
|
if (hasParameter('debug'))
|
|
257
181
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
258
182
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -261,14 +185,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
261
185
|
}
|
|
262
186
|
return Matterbridge.instance;
|
|
263
187
|
}
|
|
264
|
-
/**
|
|
265
|
-
* Call cleanup().
|
|
266
|
-
* @deprecated This method is deprecated and is only used for jest tests.
|
|
267
|
-
*
|
|
268
|
-
*/
|
|
269
188
|
async destroyInstance() {
|
|
270
189
|
this.log.info(`Destroy instance...`);
|
|
271
|
-
// Save server nodes to close
|
|
272
190
|
const servers = [];
|
|
273
191
|
if (this.bridgeMode === 'bridge') {
|
|
274
192
|
if (this.serverNode)
|
|
@@ -280,81 +198,55 @@ export class Matterbridge extends EventEmitter {
|
|
|
280
198
|
servers.push(plugin.serverNode);
|
|
281
199
|
}
|
|
282
200
|
}
|
|
283
|
-
// Cleanup
|
|
284
201
|
await this.cleanup('destroying instance...', false);
|
|
285
|
-
// Close servers mdns service
|
|
286
202
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
287
203
|
for (const server of servers) {
|
|
288
204
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
289
205
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
290
206
|
}
|
|
291
|
-
// Wait for the cleanup to finish
|
|
292
207
|
await new Promise((resolve) => {
|
|
293
208
|
setTimeout(resolve, 1000);
|
|
294
209
|
});
|
|
295
210
|
}
|
|
296
|
-
/**
|
|
297
|
-
* Initializes the Matterbridge application.
|
|
298
|
-
*
|
|
299
|
-
* @remarks
|
|
300
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
301
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
302
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
303
|
-
*
|
|
304
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
305
|
-
*/
|
|
306
211
|
async initialize() {
|
|
307
|
-
// Set the restart mode
|
|
308
212
|
if (hasParameter('service'))
|
|
309
213
|
this.restartMode = 'service';
|
|
310
214
|
if (hasParameter('docker'))
|
|
311
215
|
this.restartMode = 'docker';
|
|
312
|
-
// Set the matterbridge directory
|
|
313
216
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
314
217
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
315
|
-
// Setup the matter environment
|
|
316
218
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
317
219
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
318
220
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
319
221
|
this.environment.vars.set('runtime.signals', false);
|
|
320
222
|
this.environment.vars.set('runtime.exitcode', false);
|
|
321
|
-
|
|
322
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
323
|
-
// Register process handlers
|
|
223
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
324
224
|
this.registerProcessHandlers();
|
|
325
|
-
// Initialize nodeStorage and nodeContext
|
|
326
225
|
try {
|
|
327
226
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
328
227
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
329
228
|
this.log.debug('Creating node storage context for matterbridge');
|
|
330
229
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
331
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
332
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
333
230
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
334
231
|
for (const key of keys) {
|
|
335
232
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
336
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
337
233
|
await this.nodeStorage?.storage.get(key);
|
|
338
234
|
}
|
|
339
235
|
const storages = await this.nodeStorage.getStorageNames();
|
|
340
236
|
for (const storage of storages) {
|
|
341
237
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
342
238
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
343
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
344
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
345
239
|
const keys = (await nodeContext?.storage.keys());
|
|
346
240
|
keys.forEach(async (key) => {
|
|
347
241
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
348
242
|
await nodeContext?.get(key);
|
|
349
243
|
});
|
|
350
244
|
}
|
|
351
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
352
245
|
this.log.debug('Creating node storage backup...');
|
|
353
246
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
354
247
|
this.log.debug('Created node storage backup');
|
|
355
248
|
}
|
|
356
249
|
catch (error) {
|
|
357
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
358
250
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
359
251
|
if (hasParameter('norestore')) {
|
|
360
252
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -369,46 +261,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
369
261
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
370
262
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
371
263
|
}
|
|
372
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
373
264
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
374
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
375
265
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
376
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
377
266
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
378
267
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
379
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
380
268
|
if (hasParameter('logger')) {
|
|
381
269
|
const level = getParameter('logger');
|
|
382
270
|
if (level === 'debug') {
|
|
383
|
-
this.log.logLevel = "debug"
|
|
271
|
+
this.log.logLevel = "debug";
|
|
384
272
|
}
|
|
385
273
|
else if (level === 'info') {
|
|
386
|
-
this.log.logLevel = "info"
|
|
274
|
+
this.log.logLevel = "info";
|
|
387
275
|
}
|
|
388
276
|
else if (level === 'notice') {
|
|
389
|
-
this.log.logLevel = "notice"
|
|
277
|
+
this.log.logLevel = "notice";
|
|
390
278
|
}
|
|
391
279
|
else if (level === 'warn') {
|
|
392
|
-
this.log.logLevel = "warn"
|
|
280
|
+
this.log.logLevel = "warn";
|
|
393
281
|
}
|
|
394
282
|
else if (level === 'error') {
|
|
395
|
-
this.log.logLevel = "error"
|
|
283
|
+
this.log.logLevel = "error";
|
|
396
284
|
}
|
|
397
285
|
else if (level === 'fatal') {
|
|
398
|
-
this.log.logLevel = "fatal"
|
|
286
|
+
this.log.logLevel = "fatal";
|
|
399
287
|
}
|
|
400
288
|
else {
|
|
401
289
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
402
|
-
this.log.logLevel = "info"
|
|
290
|
+
this.log.logLevel = "info";
|
|
403
291
|
}
|
|
404
292
|
}
|
|
405
293
|
else {
|
|
406
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice"
|
|
294
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
407
295
|
}
|
|
408
296
|
this.frontend.logLevel = this.log.logLevel;
|
|
409
297
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
410
298
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
411
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
412
299
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
413
300
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
414
301
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -417,39 +304,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
417
304
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
418
305
|
if (this.profile !== undefined)
|
|
419
306
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
420
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
421
307
|
if (hasParameter('matterlogger')) {
|
|
422
308
|
const level = getParameter('matterlogger');
|
|
423
309
|
if (level === 'debug') {
|
|
424
|
-
Logger.
|
|
310
|
+
Logger.level = MatterLogLevel.DEBUG;
|
|
425
311
|
}
|
|
426
312
|
else if (level === 'info') {
|
|
427
|
-
Logger.
|
|
313
|
+
Logger.level = MatterLogLevel.INFO;
|
|
428
314
|
}
|
|
429
315
|
else if (level === 'notice') {
|
|
430
|
-
Logger.
|
|
316
|
+
Logger.level = MatterLogLevel.NOTICE;
|
|
431
317
|
}
|
|
432
318
|
else if (level === 'warn') {
|
|
433
|
-
Logger.
|
|
319
|
+
Logger.level = MatterLogLevel.WARN;
|
|
434
320
|
}
|
|
435
321
|
else if (level === 'error') {
|
|
436
|
-
Logger.
|
|
322
|
+
Logger.level = MatterLogLevel.ERROR;
|
|
437
323
|
}
|
|
438
324
|
else if (level === 'fatal') {
|
|
439
|
-
Logger.
|
|
325
|
+
Logger.level = MatterLogLevel.FATAL;
|
|
440
326
|
}
|
|
441
327
|
else {
|
|
442
328
|
this.log.warn(`Invalid matter.js logger level: ${level}. Using default level "info".`);
|
|
443
|
-
Logger.
|
|
329
|
+
Logger.level = MatterLogLevel.INFO;
|
|
444
330
|
}
|
|
445
331
|
}
|
|
446
332
|
else {
|
|
447
|
-
Logger.
|
|
333
|
+
Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
448
334
|
}
|
|
449
335
|
Logger.format = MatterLogFormat.ANSI;
|
|
450
336
|
Logger.setLogger('default', this.createMatterLogger());
|
|
451
337
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
452
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
453
338
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
454
339
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
455
340
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -458,7 +343,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
458
343
|
});
|
|
459
344
|
}
|
|
460
345
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
461
|
-
// Log network interfaces
|
|
462
346
|
const networkInterfaces = os.networkInterfaces();
|
|
463
347
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
464
348
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -470,7 +354,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
470
354
|
});
|
|
471
355
|
}
|
|
472
356
|
}
|
|
473
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
474
357
|
if (hasParameter('mdnsinterface')) {
|
|
475
358
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
476
359
|
}
|
|
@@ -479,7 +362,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
479
362
|
if (this.mdnsInterface === '')
|
|
480
363
|
this.mdnsInterface = undefined;
|
|
481
364
|
}
|
|
482
|
-
// Validate mdnsInterface
|
|
483
365
|
if (this.mdnsInterface) {
|
|
484
366
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
485
367
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -491,7 +373,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
491
373
|
}
|
|
492
374
|
if (this.mdnsInterface)
|
|
493
375
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
494
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
495
376
|
if (hasParameter('ipv4address')) {
|
|
496
377
|
this.ipv4address = getParameter('ipv4address');
|
|
497
378
|
}
|
|
@@ -500,7 +381,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
500
381
|
if (this.ipv4address === '')
|
|
501
382
|
this.ipv4address = undefined;
|
|
502
383
|
}
|
|
503
|
-
// Validate ipv4address
|
|
504
384
|
if (this.ipv4address) {
|
|
505
385
|
let isValid = false;
|
|
506
386
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -515,7 +395,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
515
395
|
this.ipv4address = undefined;
|
|
516
396
|
}
|
|
517
397
|
}
|
|
518
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
519
398
|
if (hasParameter('ipv6address')) {
|
|
520
399
|
this.ipv6address = getParameter('ipv6address');
|
|
521
400
|
}
|
|
@@ -524,7 +403,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
524
403
|
if (this.ipv6address === '')
|
|
525
404
|
this.ipv6address = undefined;
|
|
526
405
|
}
|
|
527
|
-
// Validate ipv6address
|
|
528
406
|
if (this.ipv6address) {
|
|
529
407
|
let isValid = false;
|
|
530
408
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -544,19 +422,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
544
422
|
this.ipv6address = undefined;
|
|
545
423
|
}
|
|
546
424
|
}
|
|
547
|
-
// Initialize PluginManager
|
|
548
425
|
this.plugins = new PluginManager(this);
|
|
549
426
|
await this.plugins.loadFromStorage();
|
|
550
427
|
this.plugins.logLevel = this.log.logLevel;
|
|
551
|
-
// Initialize DeviceManager
|
|
552
428
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
553
429
|
this.devices.logLevel = this.log.logLevel;
|
|
554
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
555
430
|
for (const plugin of this.plugins) {
|
|
556
431
|
const packageJson = await this.plugins.parse(plugin);
|
|
557
432
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
558
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
559
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
560
433
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
561
434
|
try {
|
|
562
435
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -578,7 +451,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
578
451
|
await plugin.nodeContext.set('description', plugin.description);
|
|
579
452
|
await plugin.nodeContext.set('author', plugin.author);
|
|
580
453
|
}
|
|
581
|
-
// Log system info and create .matterbridge directory
|
|
582
454
|
await this.logNodeAndSystemInfo();
|
|
583
455
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
584
456
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -586,7 +458,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
586
458
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
587
459
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
588
460
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
589
|
-
// Check node version and throw error
|
|
590
461
|
const minNodeVersion = 18;
|
|
591
462
|
const nodeVersion = process.versions.node;
|
|
592
463
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -594,15 +465,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
594
465
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
595
466
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
596
467
|
}
|
|
597
|
-
// Parse command line
|
|
598
468
|
await this.parseCommandLine();
|
|
599
469
|
this.initialized = true;
|
|
600
470
|
}
|
|
601
|
-
/**
|
|
602
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
603
|
-
* @private
|
|
604
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
605
|
-
*/
|
|
606
471
|
async parseCommandLine() {
|
|
607
472
|
if (hasParameter('help')) {
|
|
608
473
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -714,7 +579,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
714
579
|
this.shutdown = true;
|
|
715
580
|
return;
|
|
716
581
|
}
|
|
717
|
-
// Start the matter storage and create the matterbridge context
|
|
718
582
|
try {
|
|
719
583
|
await this.startMatterStorage();
|
|
720
584
|
}
|
|
@@ -722,14 +586,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
722
586
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
723
587
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
724
588
|
}
|
|
725
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
726
589
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
727
590
|
this.initialized = true;
|
|
728
591
|
await this.shutdownProcessAndReset();
|
|
729
592
|
this.shutdown = true;
|
|
730
593
|
return;
|
|
731
594
|
}
|
|
732
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
733
595
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
734
596
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
735
597
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -754,37 +616,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
754
616
|
this.shutdown = true;
|
|
755
617
|
return;
|
|
756
618
|
}
|
|
757
|
-
// Initialize frontend
|
|
758
619
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
759
620
|
await this.frontend.start(getIntParameter('frontend'));
|
|
760
|
-
// Check in 30 seconds the latest versions
|
|
761
621
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
762
622
|
const { checkUpdates } = await import('./update.js');
|
|
763
623
|
checkUpdates(this);
|
|
764
624
|
}, 30 * 1000).unref();
|
|
765
|
-
// Check each 24 hours the latest versions
|
|
766
625
|
this.checkUpdateInterval = setInterval(async () => {
|
|
767
626
|
const { checkUpdates } = await import('./update.js');
|
|
768
627
|
checkUpdates(this);
|
|
769
628
|
}, 24 * 60 * 60 * 1000).unref();
|
|
770
|
-
// Start the matterbridge in mode test
|
|
771
629
|
if (hasParameter('test')) {
|
|
772
630
|
this.bridgeMode = 'bridge';
|
|
773
631
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
774
632
|
return;
|
|
775
633
|
}
|
|
776
|
-
// Start the matterbridge in mode controller
|
|
777
634
|
if (hasParameter('controller')) {
|
|
778
635
|
this.bridgeMode = 'controller';
|
|
779
636
|
await this.startController();
|
|
780
637
|
return;
|
|
781
638
|
}
|
|
782
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
783
639
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
784
640
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
785
641
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
786
642
|
}
|
|
787
|
-
// Start matterbridge in bridge mode
|
|
788
643
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
789
644
|
this.bridgeMode = 'bridge';
|
|
790
645
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -792,7 +647,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
792
647
|
await this.startBridge();
|
|
793
648
|
return;
|
|
794
649
|
}
|
|
795
|
-
// Start matterbridge in childbridge mode
|
|
796
650
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
797
651
|
this.bridgeMode = 'childbridge';
|
|
798
652
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -801,20 +655,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
655
|
return;
|
|
802
656
|
}
|
|
803
657
|
}
|
|
804
|
-
/**
|
|
805
|
-
* Asynchronously loads and starts the registered plugins.
|
|
806
|
-
*
|
|
807
|
-
* This method is responsible for initializing and staarting all enabled plugins.
|
|
808
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
809
|
-
*
|
|
810
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
811
|
-
*/
|
|
812
658
|
async startPlugins() {
|
|
813
|
-
// Check, load and start the plugins
|
|
814
659
|
for (const plugin of this.plugins) {
|
|
815
660
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
816
661
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
817
|
-
// Check if the plugin is available
|
|
818
662
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
819
663
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
820
664
|
plugin.enabled = false;
|
|
@@ -834,26 +678,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
834
678
|
plugin.addedDevices = undefined;
|
|
835
679
|
plugin.qrPairingCode = undefined;
|
|
836
680
|
plugin.manualPairingCode = undefined;
|
|
837
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
681
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
838
682
|
}
|
|
839
683
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
840
684
|
}
|
|
841
|
-
/**
|
|
842
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
843
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
844
|
-
*/
|
|
845
685
|
registerProcessHandlers() {
|
|
846
686
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
847
687
|
process.removeAllListeners('uncaughtException');
|
|
848
688
|
process.removeAllListeners('unhandledRejection');
|
|
849
689
|
this.exceptionHandler = async (error) => {
|
|
850
690
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
851
|
-
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
852
691
|
};
|
|
853
692
|
process.on('uncaughtException', this.exceptionHandler);
|
|
854
693
|
this.rejectionHandler = async (reason, promise) => {
|
|
855
694
|
this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
856
|
-
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
857
695
|
};
|
|
858
696
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
859
697
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -866,9 +704,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
866
704
|
};
|
|
867
705
|
process.on('SIGTERM', this.sigtermHandler);
|
|
868
706
|
}
|
|
869
|
-
/**
|
|
870
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
871
|
-
*/
|
|
872
707
|
deregisterProcesslHandlers() {
|
|
873
708
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
874
709
|
if (this.exceptionHandler)
|
|
@@ -885,17 +720,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
885
720
|
process.off('SIGTERM', this.sigtermHandler);
|
|
886
721
|
this.sigtermHandler = undefined;
|
|
887
722
|
}
|
|
888
|
-
/**
|
|
889
|
-
* Logs the node and system information.
|
|
890
|
-
*/
|
|
891
723
|
async logNodeAndSystemInfo() {
|
|
892
|
-
// IP address information
|
|
893
724
|
const networkInterfaces = os.networkInterfaces();
|
|
894
725
|
this.systemInformation.interfaceName = '';
|
|
895
726
|
this.systemInformation.ipv4Address = '';
|
|
896
727
|
this.systemInformation.ipv6Address = '';
|
|
897
728
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
898
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
899
729
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
900
730
|
continue;
|
|
901
731
|
if (!interfaceDetails) {
|
|
@@ -921,22 +751,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
921
751
|
break;
|
|
922
752
|
}
|
|
923
753
|
}
|
|
924
|
-
// Node information
|
|
925
754
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
926
755
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
927
756
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
928
757
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
929
|
-
// Host system information
|
|
930
758
|
this.systemInformation.hostname = os.hostname();
|
|
931
759
|
this.systemInformation.user = os.userInfo().username;
|
|
932
|
-
this.systemInformation.osType = os.type();
|
|
933
|
-
this.systemInformation.osRelease = os.release();
|
|
934
|
-
this.systemInformation.osPlatform = os.platform();
|
|
935
|
-
this.systemInformation.osArch = os.arch();
|
|
936
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
937
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
938
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
939
|
-
// Log the system information
|
|
760
|
+
this.systemInformation.osType = os.type();
|
|
761
|
+
this.systemInformation.osRelease = os.release();
|
|
762
|
+
this.systemInformation.osPlatform = os.platform();
|
|
763
|
+
this.systemInformation.osArch = os.arch();
|
|
764
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
765
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
766
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
940
767
|
this.log.debug('Host System Information:');
|
|
941
768
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
942
769
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -952,20 +779,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
952
779
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
953
780
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
954
781
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
955
|
-
// Home directory
|
|
956
782
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
957
783
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
958
784
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
959
|
-
// Package root directory
|
|
960
785
|
const { fileURLToPath } = await import('node:url');
|
|
961
786
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
962
787
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
963
788
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
964
789
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
965
|
-
// Global node_modules directory
|
|
966
790
|
if (this.nodeContext)
|
|
967
791
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
968
|
-
// First run of Matterbridge so the node storage is empty
|
|
969
792
|
if (this.globalModulesDirectory === '') {
|
|
970
793
|
try {
|
|
971
794
|
this.execRunningCount++;
|
|
@@ -981,20 +804,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
981
804
|
}
|
|
982
805
|
else
|
|
983
806
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
984
|
-
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
985
|
-
else {
|
|
986
|
-
this.getGlobalNodeModules()
|
|
987
|
-
.then(async (globalModulesDirectory) => {
|
|
988
|
-
this.globalModulesDirectory = globalModulesDirectory;
|
|
989
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
990
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
991
|
-
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
992
|
-
})
|
|
993
|
-
.catch((error) => {
|
|
994
|
-
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
995
|
-
});
|
|
996
|
-
}*/
|
|
997
|
-
// Create the data directory .matterbridge in the home directory
|
|
998
807
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
999
808
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
1000
809
|
try {
|
|
@@ -1018,7 +827,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1018
827
|
}
|
|
1019
828
|
}
|
|
1020
829
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1021
|
-
// Create the plugin directory Matterbridge in the home directory
|
|
1022
830
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
1023
831
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
1024
832
|
try {
|
|
@@ -1042,7 +850,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1042
850
|
}
|
|
1043
851
|
}
|
|
1044
852
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1045
|
-
// Create the matter cert directory in the home directory
|
|
1046
853
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
1047
854
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
1048
855
|
try {
|
|
@@ -1066,68 +873,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
1066
873
|
}
|
|
1067
874
|
}
|
|
1068
875
|
this.log.debug(`Matterbridge Matter Cert Directory: ${this.matterbridgeCertDirectory}`);
|
|
1069
|
-
// Matterbridge version
|
|
1070
876
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1071
877
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
1072
878
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
1073
879
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1074
|
-
// Matterbridge latest version
|
|
1075
880
|
if (this.nodeContext)
|
|
1076
881
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1077
882
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1078
|
-
// this.getMatterbridgeLatestVersion();
|
|
1079
|
-
// Current working directory
|
|
1080
883
|
const currentDir = process.cwd();
|
|
1081
884
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1082
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1083
885
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1084
886
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1085
887
|
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1088
|
-
*
|
|
1089
|
-
* @returns {Function} The MatterLogger function.
|
|
1090
|
-
*/
|
|
1091
888
|
createMatterLogger() {
|
|
1092
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
889
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1093
890
|
return (_level, formattedLog) => {
|
|
1094
891
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1095
892
|
const message = formattedLog.slice(65);
|
|
1096
893
|
matterLogger.logName = logger;
|
|
1097
894
|
switch (_level) {
|
|
1098
895
|
case MatterLogLevel.DEBUG:
|
|
1099
|
-
matterLogger.log("debug"
|
|
896
|
+
matterLogger.log("debug", message);
|
|
1100
897
|
break;
|
|
1101
898
|
case MatterLogLevel.INFO:
|
|
1102
|
-
matterLogger.log("info"
|
|
899
|
+
matterLogger.log("info", message);
|
|
1103
900
|
break;
|
|
1104
901
|
case MatterLogLevel.NOTICE:
|
|
1105
|
-
matterLogger.log("notice"
|
|
902
|
+
matterLogger.log("notice", message);
|
|
1106
903
|
break;
|
|
1107
904
|
case MatterLogLevel.WARN:
|
|
1108
|
-
matterLogger.log("warn"
|
|
905
|
+
matterLogger.log("warn", message);
|
|
1109
906
|
break;
|
|
1110
907
|
case MatterLogLevel.ERROR:
|
|
1111
|
-
matterLogger.log("error"
|
|
908
|
+
matterLogger.log("error", message);
|
|
1112
909
|
break;
|
|
1113
910
|
case MatterLogLevel.FATAL:
|
|
1114
|
-
matterLogger.log("fatal"
|
|
911
|
+
matterLogger.log("fatal", message);
|
|
1115
912
|
break;
|
|
1116
913
|
default:
|
|
1117
|
-
matterLogger.log("debug"
|
|
914
|
+
matterLogger.log("debug", message);
|
|
1118
915
|
break;
|
|
1119
916
|
}
|
|
1120
917
|
};
|
|
1121
918
|
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Creates a Matter File Logger.
|
|
1124
|
-
*
|
|
1125
|
-
* @param {string} filePath - The path to the log file.
|
|
1126
|
-
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1127
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1128
|
-
*/
|
|
1129
919
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1130
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1131
920
|
let fileSize = 0;
|
|
1132
921
|
if (unlink) {
|
|
1133
922
|
try {
|
|
@@ -1176,21 +965,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1176
965
|
}
|
|
1177
966
|
};
|
|
1178
967
|
}
|
|
1179
|
-
/**
|
|
1180
|
-
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1181
|
-
*/
|
|
1182
968
|
async restartProcess() {
|
|
1183
969
|
await this.cleanup('restarting...', true);
|
|
1184
970
|
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Shut down the process by exiting the current process.
|
|
1187
|
-
*/
|
|
1188
971
|
async shutdownProcess() {
|
|
1189
972
|
await this.cleanup('shutting down...', false);
|
|
1190
973
|
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Update matterbridge and and shut down the process.
|
|
1193
|
-
*/
|
|
1194
974
|
async updateProcess() {
|
|
1195
975
|
this.log.info('Updating matterbridge...');
|
|
1196
976
|
try {
|
|
@@ -1203,72 +983,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1203
983
|
this.frontend.wssSendRestartRequired();
|
|
1204
984
|
await this.cleanup('updating...', false);
|
|
1205
985
|
}
|
|
1206
|
-
/**
|
|
1207
|
-
* Unregister all devices and shut down the process.
|
|
1208
|
-
*/
|
|
1209
986
|
async unregisterAndShutdownProcess() {
|
|
1210
987
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1211
988
|
for (const plugin of this.plugins) {
|
|
1212
989
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1213
990
|
}
|
|
1214
991
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1215
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
992
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1216
993
|
this.log.debug('Cleaning up and shutting down...');
|
|
1217
994
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1218
995
|
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Reset commissioning and shut down the process.
|
|
1221
|
-
*/
|
|
1222
996
|
async shutdownProcessAndReset() {
|
|
1223
997
|
await this.cleanup('shutting down with reset...', false);
|
|
1224
998
|
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Factory reset and shut down the process.
|
|
1227
|
-
*/
|
|
1228
999
|
async shutdownProcessAndFactoryReset() {
|
|
1229
1000
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1230
1001
|
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Cleans up the Matterbridge instance.
|
|
1233
|
-
* @param message - The cleanup message.
|
|
1234
|
-
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1235
|
-
* @returns A promise that resolves when the cleanup is completed.
|
|
1236
|
-
*/
|
|
1237
1002
|
async cleanup(message, restart = false) {
|
|
1238
1003
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1239
1004
|
this.hasCleanupStarted = true;
|
|
1240
1005
|
this.log.info(message);
|
|
1241
|
-
// Clear the start matter interval
|
|
1242
1006
|
if (this.startMatterInterval) {
|
|
1243
1007
|
clearInterval(this.startMatterInterval);
|
|
1244
1008
|
this.startMatterInterval = undefined;
|
|
1245
1009
|
this.log.debug('Start matter interval cleared');
|
|
1246
1010
|
}
|
|
1247
|
-
// Clear the check update timeout
|
|
1248
1011
|
if (this.checkUpdateTimeout) {
|
|
1249
1012
|
clearInterval(this.checkUpdateTimeout);
|
|
1250
1013
|
this.checkUpdateTimeout = undefined;
|
|
1251
1014
|
this.log.debug('Check update timeout cleared');
|
|
1252
1015
|
}
|
|
1253
|
-
// Clear the check update interval
|
|
1254
1016
|
if (this.checkUpdateInterval) {
|
|
1255
1017
|
clearInterval(this.checkUpdateInterval);
|
|
1256
1018
|
this.checkUpdateInterval = undefined;
|
|
1257
1019
|
this.log.debug('Check update interval cleared');
|
|
1258
1020
|
}
|
|
1259
|
-
// Clear the configure timeout
|
|
1260
1021
|
if (this.configureTimeout) {
|
|
1261
1022
|
clearTimeout(this.configureTimeout);
|
|
1262
1023
|
this.configureTimeout = undefined;
|
|
1263
1024
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1264
1025
|
}
|
|
1265
|
-
// Clear the reachability timeout
|
|
1266
1026
|
if (this.reachabilityTimeout) {
|
|
1267
1027
|
clearTimeout(this.reachabilityTimeout);
|
|
1268
1028
|
this.reachabilityTimeout = undefined;
|
|
1269
1029
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1270
1030
|
}
|
|
1271
|
-
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1272
1031
|
for (const plugin of this.plugins) {
|
|
1273
1032
|
if (!plugin.enabled || plugin.error)
|
|
1274
1033
|
continue;
|
|
@@ -1279,10 +1038,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1279
1038
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1280
1039
|
}
|
|
1281
1040
|
}
|
|
1282
|
-
// Stopping matter server nodes
|
|
1283
1041
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1284
1042
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1285
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1043
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1286
1044
|
if (this.bridgeMode === 'bridge') {
|
|
1287
1045
|
if (this.serverNode) {
|
|
1288
1046
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1298,7 +1056,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1298
1056
|
}
|
|
1299
1057
|
}
|
|
1300
1058
|
this.log.notice('Stopped matter server nodes');
|
|
1301
|
-
// Matter commisioning reset
|
|
1302
1059
|
if (message === 'shutting down with reset...') {
|
|
1303
1060
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1304
1061
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1308,37 +1065,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1308
1065
|
await this.matterbridgeContext?.clearAll();
|
|
1309
1066
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1310
1067
|
}
|
|
1311
|
-
// Stop matter storage
|
|
1312
1068
|
await this.stopMatterStorage();
|
|
1313
|
-
// Stop the frontend
|
|
1314
1069
|
await this.frontend.stop();
|
|
1315
|
-
// Remove the matterfilelogger
|
|
1316
1070
|
try {
|
|
1317
1071
|
Logger.removeLogger('matterfilelogger');
|
|
1318
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1319
1072
|
}
|
|
1320
1073
|
catch (error) {
|
|
1321
|
-
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1322
1074
|
}
|
|
1323
|
-
// Serialize registeredDevices
|
|
1324
1075
|
if (this.nodeStorage && this.nodeContext) {
|
|
1325
|
-
/*
|
|
1326
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1327
|
-
this.log.info('Saving registered devices...');
|
|
1328
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1329
|
-
this.devices.forEach(async (device) => {
|
|
1330
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1331
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1332
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1333
|
-
});
|
|
1334
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1335
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1336
|
-
*/
|
|
1337
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1338
1076
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1339
1077
|
await this.nodeContext.close();
|
|
1340
1078
|
this.nodeContext = undefined;
|
|
1341
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1342
1079
|
for (const plugin of this.plugins) {
|
|
1343
1080
|
if (plugin.nodeContext) {
|
|
1344
1081
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1355,10 +1092,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1355
1092
|
}
|
|
1356
1093
|
this.plugins.clear();
|
|
1357
1094
|
this.devices.clear();
|
|
1358
|
-
// Factory reset
|
|
1359
1095
|
if (message === 'shutting down with factory reset...') {
|
|
1360
1096
|
try {
|
|
1361
|
-
// Delete old matter storage file and backup
|
|
1362
1097
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1363
1098
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1364
1099
|
await fs.unlink(file);
|
|
@@ -1372,7 +1107,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1372
1107
|
}
|
|
1373
1108
|
}
|
|
1374
1109
|
try {
|
|
1375
|
-
// Delete matter node storage directory with its subdirectories and backup
|
|
1376
1110
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1377
1111
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1378
1112
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1386,7 +1120,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1386
1120
|
}
|
|
1387
1121
|
}
|
|
1388
1122
|
try {
|
|
1389
|
-
// Delete node storage directory with its subdirectories and backup
|
|
1390
1123
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1391
1124
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1392
1125
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1401,13 +1134,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1401
1134
|
}
|
|
1402
1135
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1403
1136
|
}
|
|
1404
|
-
// Deregisters the process handlers
|
|
1405
1137
|
this.deregisterProcesslHandlers();
|
|
1406
1138
|
if (restart) {
|
|
1407
1139
|
if (message === 'updating...') {
|
|
1408
1140
|
this.log.info('Cleanup completed. Updating...');
|
|
1409
1141
|
Matterbridge.instance = undefined;
|
|
1410
|
-
this.emit('update');
|
|
1142
|
+
this.emit('update');
|
|
1411
1143
|
}
|
|
1412
1144
|
else if (message === 'restarting...') {
|
|
1413
1145
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1427,14 +1159,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1427
1159
|
this.log.debug('Cleanup already started...');
|
|
1428
1160
|
}
|
|
1429
1161
|
}
|
|
1430
|
-
/**
|
|
1431
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1432
|
-
*
|
|
1433
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1434
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1435
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1436
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1437
|
-
*/
|
|
1438
1162
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1439
1163
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1440
1164
|
plugin.locked = true;
|
|
@@ -1447,13 +1171,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1447
1171
|
await this.startServerNode(plugin.serverNode);
|
|
1448
1172
|
}
|
|
1449
1173
|
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Creates and configures the server node for a dynamic plugin.
|
|
1452
|
-
*
|
|
1453
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1454
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1455
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1456
|
-
*/
|
|
1457
1174
|
async createDynamicPlugin(plugin, start = false) {
|
|
1458
1175
|
if (!plugin.locked) {
|
|
1459
1176
|
plugin.locked = true;
|
|
@@ -1465,13 +1182,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1465
1182
|
await this.startServerNode(plugin.serverNode);
|
|
1466
1183
|
}
|
|
1467
1184
|
}
|
|
1468
|
-
/**
|
|
1469
|
-
* Starts the Matterbridge in bridge mode.
|
|
1470
|
-
* @private
|
|
1471
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1472
|
-
*/
|
|
1473
1185
|
async startBridge() {
|
|
1474
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1475
1186
|
if (!this.matterStorageManager)
|
|
1476
1187
|
throw new Error('No storage manager initialized');
|
|
1477
1188
|
if (!this.matterbridgeContext)
|
|
@@ -1509,9 +1220,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1509
1220
|
clearInterval(this.startMatterInterval);
|
|
1510
1221
|
this.startMatterInterval = undefined;
|
|
1511
1222
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1512
|
-
// Start the Matter server node
|
|
1513
1223
|
this.startServerNode(this.serverNode);
|
|
1514
|
-
// Configure the plugins
|
|
1515
1224
|
this.configureTimeout = setTimeout(async () => {
|
|
1516
1225
|
for (const plugin of this.plugins) {
|
|
1517
1226
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1529,7 +1238,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1529
1238
|
}
|
|
1530
1239
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1531
1240
|
}, 30 * 1000);
|
|
1532
|
-
// Setting reachability to true
|
|
1533
1241
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1534
1242
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1535
1243
|
if (this.aggregatorNode)
|
|
@@ -1538,11 +1246,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1538
1246
|
}, 60 * 1000);
|
|
1539
1247
|
}, 1000);
|
|
1540
1248
|
}
|
|
1541
|
-
/**
|
|
1542
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1543
|
-
* @private
|
|
1544
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1545
|
-
*/
|
|
1546
1249
|
async startChildbridge() {
|
|
1547
1250
|
if (!this.matterStorageManager)
|
|
1548
1251
|
throw new Error('No storage manager initialized');
|
|
@@ -1587,7 +1290,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1587
1290
|
clearInterval(this.startMatterInterval);
|
|
1588
1291
|
this.startMatterInterval = undefined;
|
|
1589
1292
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1590
|
-
// Configure the plugins
|
|
1591
1293
|
this.configureTimeout = setTimeout(async () => {
|
|
1592
1294
|
for (const plugin of this.plugins) {
|
|
1593
1295
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1624,11 +1326,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1624
1326
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1625
1327
|
continue;
|
|
1626
1328
|
}
|
|
1627
|
-
// Start the Matter server node
|
|
1628
1329
|
this.startServerNode(plugin.serverNode);
|
|
1629
|
-
// Setting reachability to true
|
|
1630
1330
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1631
|
-
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${
|
|
1331
|
+
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}`);
|
|
1632
1332
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1633
1333
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1634
1334
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
@@ -1636,219 +1336,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1636
1336
|
}
|
|
1637
1337
|
}, 1000);
|
|
1638
1338
|
}
|
|
1639
|
-
/**
|
|
1640
|
-
* Starts the Matterbridge controller.
|
|
1641
|
-
* @private
|
|
1642
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1643
|
-
*/
|
|
1644
1339
|
async startController() {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
return;
|
|
1340
|
+
if (!this.matterStorageManager) {
|
|
1341
|
+
this.log.error('No storage manager initialized');
|
|
1342
|
+
await this.cleanup('No storage manager initialized');
|
|
1343
|
+
return;
|
|
1650
1344
|
}
|
|
1651
1345
|
this.log.info('Creating context: mattercontrollerContext');
|
|
1652
|
-
this.
|
|
1653
|
-
if (!this.
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1346
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1347
|
+
if (!this.controllerContext) {
|
|
1348
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1349
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1350
|
+
return;
|
|
1657
1351
|
}
|
|
1658
|
-
|
|
1659
1352
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1660
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1661
|
-
this.log.info('Creating matter commissioning controller');
|
|
1662
|
-
this.commissioningController = new CommissioningController({
|
|
1663
|
-
autoConnect: false,
|
|
1664
|
-
});
|
|
1665
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1666
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1667
|
-
|
|
1668
|
-
this.log.info('Starting matter server');
|
|
1669
|
-
await this.matterServer.start();
|
|
1670
|
-
this.log.info('Matter server started');
|
|
1671
|
-
|
|
1672
|
-
if (hasParameter('pairingcode')) {
|
|
1673
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1674
|
-
const pairingCode = getParameter('pairingcode');
|
|
1675
|
-
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1676
|
-
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1677
|
-
|
|
1678
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1679
|
-
if (pairingCode !== undefined) {
|
|
1680
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1681
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1682
|
-
longDiscriminator = undefined;
|
|
1683
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1684
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1685
|
-
} else {
|
|
1686
|
-
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1687
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1688
|
-
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1689
|
-
}
|
|
1690
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1691
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1695
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1696
|
-
regulatoryCountryCode: 'XX',
|
|
1697
|
-
};
|
|
1698
|
-
const options = {
|
|
1699
|
-
commissioning: commissioningOptions,
|
|
1700
|
-
discovery: {
|
|
1701
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1702
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1703
|
-
},
|
|
1704
|
-
passcode: setupPin,
|
|
1705
|
-
} as NodeCommissioningOptions;
|
|
1706
|
-
this.log.info('Commissioning with options:', options);
|
|
1707
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1708
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1709
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1710
|
-
} // (hasParameter('pairingcode'))
|
|
1711
|
-
|
|
1712
|
-
if (hasParameter('unpairall')) {
|
|
1713
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1714
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1715
|
-
for (const nodeId of nodeIds) {
|
|
1716
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1717
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1718
|
-
}
|
|
1719
|
-
return;
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
if (hasParameter('discover')) {
|
|
1723
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1724
|
-
// console.log(discover);
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1728
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1729
|
-
return;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1733
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1734
|
-
for (const nodeId of nodeIds) {
|
|
1735
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1736
|
-
|
|
1737
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1738
|
-
autoSubscribe: false,
|
|
1739
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1740
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1741
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1742
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1743
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1744
|
-
switch (info) {
|
|
1745
|
-
case NodeStateInformation.Connected:
|
|
1746
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1747
|
-
break;
|
|
1748
|
-
case NodeStateInformation.Disconnected:
|
|
1749
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1750
|
-
break;
|
|
1751
|
-
case NodeStateInformation.Reconnecting:
|
|
1752
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1753
|
-
break;
|
|
1754
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1755
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1756
|
-
break;
|
|
1757
|
-
case NodeStateInformation.StructureChanged:
|
|
1758
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1759
|
-
break;
|
|
1760
|
-
case NodeStateInformation.Decommissioned:
|
|
1761
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1762
|
-
break;
|
|
1763
|
-
default:
|
|
1764
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1765
|
-
break;
|
|
1766
|
-
}
|
|
1767
|
-
},
|
|
1768
|
-
});
|
|
1769
|
-
|
|
1770
|
-
node.logStructure();
|
|
1771
|
-
|
|
1772
|
-
// Get the interaction client
|
|
1773
|
-
this.log.info('Getting the interaction client');
|
|
1774
|
-
const interactionClient = await node.getInteractionClient();
|
|
1775
|
-
let cluster;
|
|
1776
|
-
let attributes;
|
|
1777
|
-
|
|
1778
|
-
// Log BasicInformationCluster
|
|
1779
|
-
cluster = BasicInformationCluster;
|
|
1780
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1781
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1782
|
-
});
|
|
1783
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1784
|
-
attributes.forEach((attribute) => {
|
|
1785
|
-
this.log.info(
|
|
1786
|
-
`- 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}`,
|
|
1787
|
-
);
|
|
1788
|
-
});
|
|
1789
|
-
|
|
1790
|
-
// Log PowerSourceCluster
|
|
1791
|
-
cluster = PowerSourceCluster;
|
|
1792
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1793
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1794
|
-
});
|
|
1795
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1796
|
-
attributes.forEach((attribute) => {
|
|
1797
|
-
this.log.info(
|
|
1798
|
-
`- 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}`,
|
|
1799
|
-
);
|
|
1800
|
-
});
|
|
1801
|
-
|
|
1802
|
-
// Log ThreadNetworkDiagnostics
|
|
1803
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1804
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1805
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1806
|
-
});
|
|
1807
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1808
|
-
attributes.forEach((attribute) => {
|
|
1809
|
-
this.log.info(
|
|
1810
|
-
`- 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}`,
|
|
1811
|
-
);
|
|
1812
|
-
});
|
|
1813
|
-
|
|
1814
|
-
// Log SwitchCluster
|
|
1815
|
-
cluster = SwitchCluster;
|
|
1816
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1817
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1818
|
-
});
|
|
1819
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1820
|
-
attributes.forEach((attribute) => {
|
|
1821
|
-
this.log.info(
|
|
1822
|
-
`- 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}`,
|
|
1823
|
-
);
|
|
1824
|
-
});
|
|
1825
|
-
|
|
1826
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1827
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1828
|
-
ignoreInitialTriggers: false,
|
|
1829
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1830
|
-
this.log.info(
|
|
1831
|
-
`***${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}`,
|
|
1832
|
-
),
|
|
1833
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1834
|
-
this.log.info(
|
|
1835
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1836
|
-
);
|
|
1837
|
-
},
|
|
1838
|
-
});
|
|
1839
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1840
|
-
}
|
|
1841
|
-
*/
|
|
1842
1353
|
}
|
|
1843
|
-
/** ***********************************************************************************************************************************/
|
|
1844
|
-
/** Matter.js methods */
|
|
1845
|
-
/** ***********************************************************************************************************************************/
|
|
1846
|
-
/**
|
|
1847
|
-
* Starts the matter storage process with name Matterbridge.
|
|
1848
|
-
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1849
|
-
*/
|
|
1850
1354
|
async startMatterStorage() {
|
|
1851
|
-
// Setup Matter storage
|
|
1852
1355
|
this.log.info(`Starting matter node storage...`);
|
|
1853
1356
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1854
1357
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1856,25 +1359,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1856
1359
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1857
1360
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1858
1361
|
this.log.info('Matter node storage started');
|
|
1859
|
-
// Backup matter storage since it is created/opened correctly
|
|
1860
1362
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1861
1363
|
}
|
|
1862
|
-
/**
|
|
1863
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1864
|
-
*
|
|
1865
|
-
* @param storageName - The name of the storage directory to be backed up.
|
|
1866
|
-
* @param backupName - The name of the backup directory to be created.
|
|
1867
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1868
|
-
*/
|
|
1869
1364
|
async backupMatterStorage(storageName, backupName) {
|
|
1870
1365
|
this.log.info('Creating matter node storage backup...');
|
|
1871
1366
|
await copyDirectory(storageName, backupName);
|
|
1872
1367
|
this.log.info('Created matter node storage backup');
|
|
1873
1368
|
}
|
|
1874
|
-
/**
|
|
1875
|
-
* Stops the matter storage.
|
|
1876
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1877
|
-
*/
|
|
1878
1369
|
async stopMatterStorage() {
|
|
1879
1370
|
this.log.info('Closing matter node storage...');
|
|
1880
1371
|
this.matterStorageManager?.close();
|
|
@@ -1883,19 +1374,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1883
1374
|
this.matterbridgeContext = undefined;
|
|
1884
1375
|
this.log.info('Matter node storage closed');
|
|
1885
1376
|
}
|
|
1886
|
-
/**
|
|
1887
|
-
* Creates a server node storage context.
|
|
1888
|
-
*
|
|
1889
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1890
|
-
* @param {string} deviceName - The name of the device.
|
|
1891
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1892
|
-
* @param {number} vendorId - The vendor ID.
|
|
1893
|
-
* @param {string} vendorName - The vendor name.
|
|
1894
|
-
* @param {number} productId - The product ID.
|
|
1895
|
-
* @param {string} productName - The product name.
|
|
1896
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1897
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1898
|
-
*/
|
|
1899
1377
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1900
1378
|
const { randomBytes } = await import('node:crypto');
|
|
1901
1379
|
if (!this.matterStorageService)
|
|
@@ -1929,15 +1407,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1929
1407
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1930
1408
|
return storageContext;
|
|
1931
1409
|
}
|
|
1932
|
-
/**
|
|
1933
|
-
* Creates a server node.
|
|
1934
|
-
*
|
|
1935
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1936
|
-
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1937
|
-
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1938
|
-
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1939
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1940
|
-
*/
|
|
1941
1410
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1942
1411
|
const storeId = await storageContext.get('storeId');
|
|
1943
1412
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1947,33 +1416,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1947
1416
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1948
1417
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1949
1418
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1950
|
-
/**
|
|
1951
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1952
|
-
*/
|
|
1953
1419
|
const serverNode = await ServerNode.create({
|
|
1954
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1955
1420
|
id: storeId,
|
|
1956
|
-
// Provide Network relevant configuration like the port
|
|
1957
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
1958
1421
|
network: {
|
|
1959
1422
|
listeningAddressIpv4: this.ipv4address,
|
|
1960
1423
|
listeningAddressIpv6: this.ipv6address,
|
|
1961
1424
|
port,
|
|
1962
1425
|
},
|
|
1963
|
-
// Provide Commissioning relevant settings
|
|
1964
|
-
// Optional for development/testing purposes
|
|
1965
1426
|
commissioning: {
|
|
1966
1427
|
passcode,
|
|
1967
1428
|
discriminator,
|
|
1968
1429
|
},
|
|
1969
|
-
// Provide Node announcement settings
|
|
1970
|
-
// Optional: If Ommitted some development defaults are used
|
|
1971
1430
|
productDescription: {
|
|
1972
1431
|
name: await storageContext.get('deviceName'),
|
|
1973
1432
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1974
1433
|
},
|
|
1975
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1976
|
-
// Optional: If Omitted some development defaults are used
|
|
1977
1434
|
basicInformation: {
|
|
1978
1435
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1979
1436
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1987,16 +1444,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1987
1444
|
softwareVersionString: await storageContext.get('softwareVersionString'),
|
|
1988
1445
|
hardwareVersion: await storageContext.get('hardwareVersion'),
|
|
1989
1446
|
hardwareVersionString: await storageContext.get('hardwareVersionString'),
|
|
1447
|
+
reachable: true,
|
|
1990
1448
|
},
|
|
1991
1449
|
});
|
|
1992
1450
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1993
|
-
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1994
1451
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1995
1452
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1996
1453
|
if (this.bridgeMode === 'bridge') {
|
|
1997
1454
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1998
1455
|
if (resetSessions)
|
|
1999
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1456
|
+
this.matterbridgeSessionInformations = undefined;
|
|
2000
1457
|
this.matterbridgePaired = true;
|
|
2001
1458
|
}
|
|
2002
1459
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -2004,19 +1461,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2004
1461
|
if (plugin) {
|
|
2005
1462
|
plugin.fabricInformations = sanitizedFabrics;
|
|
2006
1463
|
if (resetSessions)
|
|
2007
|
-
plugin.sessionInformations = undefined;
|
|
1464
|
+
plugin.sessionInformations = undefined;
|
|
2008
1465
|
plugin.paired = true;
|
|
2009
1466
|
}
|
|
2010
1467
|
}
|
|
2011
1468
|
};
|
|
2012
|
-
/**
|
|
2013
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2014
|
-
* This means: It is added to the first fabric.
|
|
2015
|
-
*/
|
|
2016
1469
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
2017
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2018
1470
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2019
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2020
1471
|
serverNode.lifecycle.online.on(async () => {
|
|
2021
1472
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2022
1473
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2067,11 +1518,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2067
1518
|
if (plugin) {
|
|
2068
1519
|
plugin.qrPairingCode = undefined;
|
|
2069
1520
|
plugin.manualPairingCode = undefined;
|
|
2070
|
-
this.frontend.wssSendRefreshRequired('plugins');
|
|
2071
1521
|
}
|
|
2072
1522
|
}
|
|
2073
|
-
this.frontend.wssSendRefreshRequired('settings');
|
|
2074
1523
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1524
|
+
this.frontend.wssSendRefreshRequired('settings');
|
|
2075
1525
|
this.frontend.wssSendRefreshRequired('fabrics');
|
|
2076
1526
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2077
1527
|
this.frontend.wssSendSnackbarMessage(`Advertising on server node for ${storeId} stopped. Restart to commission.`, 0);
|
|
@@ -2086,7 +1536,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2086
1536
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2087
1537
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2088
1538
|
});
|
|
2089
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2090
1539
|
serverNode.lifecycle.offline.on(() => {
|
|
2091
1540
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2092
1541
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -2110,10 +1559,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2110
1559
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2111
1560
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2112
1561
|
});
|
|
2113
|
-
/**
|
|
2114
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2115
|
-
* information is needed.
|
|
2116
|
-
*/
|
|
2117
1562
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2118
1563
|
let action = '';
|
|
2119
1564
|
switch (fabricAction) {
|
|
@@ -2147,24 +1592,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2147
1592
|
}
|
|
2148
1593
|
}
|
|
2149
1594
|
};
|
|
2150
|
-
/**
|
|
2151
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2152
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2153
|
-
*/
|
|
2154
1595
|
serverNode.events.sessions.opened.on((session) => {
|
|
2155
1596
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2156
1597
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2157
1598
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2158
1599
|
});
|
|
2159
|
-
/**
|
|
2160
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2161
|
-
*/
|
|
2162
1600
|
serverNode.events.sessions.closed.on((session) => {
|
|
2163
1601
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2164
1602
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2165
1603
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2166
1604
|
});
|
|
2167
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2168
1605
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2169
1606
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2170
1607
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -2173,42 +1610,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2173
1610
|
this.log.info(`Created server node for ${storeId}`);
|
|
2174
1611
|
return serverNode;
|
|
2175
1612
|
}
|
|
2176
|
-
/**
|
|
2177
|
-
* Starts the specified server node.
|
|
2178
|
-
*
|
|
2179
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2180
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2181
|
-
*/
|
|
2182
1613
|
async startServerNode(matterServerNode) {
|
|
2183
1614
|
if (!matterServerNode)
|
|
2184
1615
|
return;
|
|
2185
1616
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2186
1617
|
await matterServerNode.start();
|
|
2187
1618
|
}
|
|
2188
|
-
/**
|
|
2189
|
-
* Stops the specified server node.
|
|
2190
|
-
*
|
|
2191
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2192
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2193
|
-
*/
|
|
2194
1619
|
async stopServerNode(matterServerNode) {
|
|
2195
1620
|
if (!matterServerNode)
|
|
2196
1621
|
return;
|
|
2197
1622
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2198
1623
|
try {
|
|
2199
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
1624
|
+
await withTimeout(matterServerNode.close(), 30000);
|
|
2200
1625
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
2201
1626
|
}
|
|
2202
1627
|
catch (error) {
|
|
2203
1628
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2204
1629
|
}
|
|
2205
1630
|
}
|
|
2206
|
-
/**
|
|
2207
|
-
* Advertises the specified server node.
|
|
2208
|
-
*
|
|
2209
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2210
|
-
* @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.
|
|
2211
|
-
*/
|
|
2212
1631
|
async advertiseServerNode(matterServerNode) {
|
|
2213
1632
|
if (matterServerNode) {
|
|
2214
1633
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2217,88 +1636,84 @@ export class Matterbridge extends EventEmitter {
|
|
|
2217
1636
|
return { qrPairingCode, manualPairingCode };
|
|
2218
1637
|
}
|
|
2219
1638
|
}
|
|
2220
|
-
/**
|
|
2221
|
-
* Stop advertise the specified server node.
|
|
2222
|
-
*
|
|
2223
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2224
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2225
|
-
*/
|
|
2226
1639
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2227
1640
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2228
1641
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2229
1642
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2230
1643
|
}
|
|
2231
1644
|
}
|
|
2232
|
-
/**
|
|
2233
|
-
* Creates an aggregator node with the specified storage context.
|
|
2234
|
-
*
|
|
2235
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2236
|
-
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2237
|
-
*/
|
|
2238
1645
|
async createAggregatorNode(storageContext) {
|
|
2239
1646
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
2240
1647
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2241
1648
|
return aggregatorNode;
|
|
2242
1649
|
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2245
|
-
*
|
|
2246
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2247
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2248
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2249
|
-
*/
|
|
2250
1650
|
async addBridgedEndpoint(pluginName, device) {
|
|
2251
|
-
// Check if the plugin is registered
|
|
2252
1651
|
const plugin = this.plugins.get(pluginName);
|
|
2253
1652
|
if (!plugin) {
|
|
2254
1653
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
2255
1654
|
return;
|
|
2256
1655
|
}
|
|
2257
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2258
1656
|
if (this.bridgeMode === 'bridge') {
|
|
2259
1657
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2260
1658
|
if (!this.aggregatorNode)
|
|
2261
1659
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
2262
|
-
|
|
1660
|
+
try {
|
|
1661
|
+
await this.aggregatorNode?.add(device);
|
|
1662
|
+
}
|
|
1663
|
+
catch (error) {
|
|
1664
|
+
const errorMessage = error instanceof Error ? error.message : '';
|
|
1665
|
+
const errorStack = error instanceof Error ? error.stack : '';
|
|
1666
|
+
const errorDebug = inspect(error, { depth: 10 });
|
|
1667
|
+
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) for plugin ${plg}${pluginName}${er}: ${error} ${errorMessage} ${errorStack} ${errorDebug}`);
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
2263
1670
|
}
|
|
2264
1671
|
else if (this.bridgeMode === 'childbridge') {
|
|
2265
1672
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2266
|
-
|
|
1673
|
+
try {
|
|
1674
|
+
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
1675
|
+
await this.createAccessoryPlugin(plugin, device);
|
|
1676
|
+
}
|
|
1677
|
+
catch (error) {
|
|
1678
|
+
const errorMessage = error instanceof Error ? error.message : '';
|
|
1679
|
+
const errorStack = error instanceof Error ? error.stack : '';
|
|
1680
|
+
const errorDebug = inspect(error, { depth: 10 });
|
|
1681
|
+
this.log.error(`Error creating endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) for AccessoryPlatform plugin ${plg}${pluginName}${er} server node: ${error} ${errorMessage} ${errorStack} ${errorDebug}`);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
2267
1684
|
}
|
|
2268
1685
|
if (plugin.type === 'DynamicPlatform') {
|
|
2269
1686
|
plugin.locked = true;
|
|
2270
|
-
this.log.debug(`Adding bridged endpoint ${
|
|
1687
|
+
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2271
1688
|
if (!plugin.aggregatorNode)
|
|
2272
1689
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${db}`);
|
|
2273
|
-
|
|
1690
|
+
try {
|
|
1691
|
+
await plugin.aggregatorNode?.add(device);
|
|
1692
|
+
}
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
const errorMessage = error instanceof Error ? error.message : '';
|
|
1695
|
+
const errorStack = error instanceof Error ? error.stack : '';
|
|
1696
|
+
const errorDebug = inspect(error, { depth: 10 });
|
|
1697
|
+
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) for DynamicPlatform plugin ${plg}${pluginName}${er} aggregator node: ${error} ${errorMessage} ${errorStack} ${errorDebug}`);
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
2274
1700
|
}
|
|
2275
1701
|
}
|
|
2276
1702
|
if (plugin.registeredDevices !== undefined)
|
|
2277
1703
|
plugin.registeredDevices++;
|
|
2278
1704
|
if (plugin.addedDevices !== undefined)
|
|
2279
1705
|
plugin.addedDevices++;
|
|
2280
|
-
// Add the device to the DeviceManager
|
|
2281
1706
|
this.devices.set(device);
|
|
2282
|
-
// Subscribe to the reachable$Changed event
|
|
2283
1707
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2284
1708
|
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}`);
|
|
2285
1709
|
}
|
|
2286
|
-
/**
|
|
2287
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2288
|
-
*
|
|
2289
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2290
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2291
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2292
|
-
*/
|
|
2293
1710
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2294
1711
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2295
|
-
// Check if the plugin is registered
|
|
2296
1712
|
const plugin = this.plugins.get(pluginName);
|
|
2297
1713
|
if (!plugin) {
|
|
2298
1714
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2299
1715
|
return;
|
|
2300
1716
|
}
|
|
2301
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2302
1717
|
if (this.bridgeMode === 'bridge') {
|
|
2303
1718
|
if (!this.aggregatorNode) {
|
|
2304
1719
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2313,7 +1728,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2313
1728
|
}
|
|
2314
1729
|
else if (this.bridgeMode === 'childbridge') {
|
|
2315
1730
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2316
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2317
1731
|
}
|
|
2318
1732
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2319
1733
|
if (!plugin.aggregatorNode) {
|
|
@@ -2328,43 +1742,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
2328
1742
|
if (plugin.addedDevices !== undefined)
|
|
2329
1743
|
plugin.addedDevices--;
|
|
2330
1744
|
}
|
|
2331
|
-
// Remove the device from the DeviceManager
|
|
2332
1745
|
this.devices.remove(device);
|
|
2333
1746
|
}
|
|
2334
|
-
/**
|
|
2335
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2336
|
-
*
|
|
2337
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2338
|
-
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2339
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2340
|
-
*/
|
|
2341
1747
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2342
|
-
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1748
|
+
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2343
1749
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
2344
1750
|
await this.removeBridgedEndpoint(pluginName, device);
|
|
2345
1751
|
if (delay > 0)
|
|
2346
1752
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2347
1753
|
}
|
|
1754
|
+
if (delay > 0)
|
|
1755
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2348
1756
|
}
|
|
2349
|
-
/**
|
|
2350
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2351
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2352
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2353
|
-
*
|
|
2354
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2355
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2356
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2357
|
-
*/
|
|
2358
1757
|
async subscribeAttributeChanged(plugin, device) {
|
|
2359
1758
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2360
1759
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2361
|
-
/*
|
|
2362
|
-
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
|
|
2363
|
-
setTimeout(async () => {
|
|
2364
|
-
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
|
|
2365
|
-
await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
|
|
2366
|
-
}, 60000).unref();
|
|
2367
|
-
*/
|
|
2368
1760
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
2369
1761
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2370
1762
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
@@ -2377,12 +1769,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2377
1769
|
});
|
|
2378
1770
|
}
|
|
2379
1771
|
}
|
|
2380
|
-
/**
|
|
2381
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2382
|
-
*
|
|
2383
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2384
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2385
|
-
*/
|
|
2386
1772
|
sanitizeFabricInformations(fabricInfo) {
|
|
2387
1773
|
return fabricInfo.map((info) => {
|
|
2388
1774
|
return {
|
|
@@ -2396,12 +1782,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2396
1782
|
};
|
|
2397
1783
|
});
|
|
2398
1784
|
}
|
|
2399
|
-
/**
|
|
2400
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2401
|
-
*
|
|
2402
|
-
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2403
|
-
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2404
|
-
*/
|
|
2405
1785
|
sanitizeSessionInformation(sessionInfo) {
|
|
2406
1786
|
return sessionInfo
|
|
2407
1787
|
.filter((session) => session.isPeerActive)
|
|
@@ -2429,20 +1809,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2429
1809
|
};
|
|
2430
1810
|
});
|
|
2431
1811
|
}
|
|
2432
|
-
/**
|
|
2433
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2434
|
-
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2435
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2436
|
-
*/
|
|
2437
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2438
1812
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2439
|
-
/*
|
|
2440
|
-
for (const child of aggregatorNode.parts) {
|
|
2441
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2442
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2443
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2444
|
-
}
|
|
2445
|
-
*/
|
|
2446
1813
|
}
|
|
2447
1814
|
getVendorIdName = (vendorId) => {
|
|
2448
1815
|
if (!vendorId)
|
|
@@ -2485,37 +1852,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2485
1852
|
}
|
|
2486
1853
|
return vendorName;
|
|
2487
1854
|
};
|
|
2488
|
-
/**
|
|
2489
|
-
* Spawns a child process with the given command and arguments.
|
|
2490
|
-
* @param {string} command - The command to execute.
|
|
2491
|
-
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2492
|
-
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2493
|
-
*/
|
|
2494
1855
|
async spawnCommand(command, args = []) {
|
|
2495
1856
|
const { spawn } = await import('node:child_process');
|
|
2496
|
-
/*
|
|
2497
|
-
npm > npm.cmd on windows
|
|
2498
|
-
cmd.exe ['dir'] on windows
|
|
2499
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2500
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
2501
|
-
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2502
|
-
});
|
|
2503
|
-
|
|
2504
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2505
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2506
|
-
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2507
|
-
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2508
|
-
*/
|
|
2509
1857
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2510
1858
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2511
|
-
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2512
1859
|
const argstring = 'npm ' + args.join(' ');
|
|
2513
1860
|
args.splice(0, args.length, '/c', argstring);
|
|
2514
1861
|
command = 'cmd.exe';
|
|
2515
1862
|
}
|
|
2516
|
-
// Decide when using sudo on linux
|
|
2517
|
-
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2518
|
-
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2519
1863
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2520
1864
|
args.unshift(command);
|
|
2521
1865
|
command = 'sudo';
|
|
@@ -2574,4 +1918,3 @@ export class Matterbridge extends EventEmitter {
|
|
|
2574
1918
|
});
|
|
2575
1919
|
}
|
|
2576
1920
|
}
|
|
2577
|
-
//# sourceMappingURL=matterbridge.js.map
|