matterbridge 2.2.6-dev.5 → 2.2.6
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 +2 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +37 -2
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -2
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +221 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +325 -19
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +425 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +753 -47
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +118 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +285 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +216 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +179 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +229 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +92 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +146 -6
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +32 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +45 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +32 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +42 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +40 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +65 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +10 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/isvalid.d.ts +87 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +86 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +69 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -8
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/parameter.d.ts +44 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +41 -0
- package/dist/utils/parameter.js.map +1 -0
- package/dist/utils/wait.d.ts +43 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +48 -5
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
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
|
|
1
24
|
import os from 'node:os';
|
|
2
25
|
import path from 'node:path';
|
|
3
26
|
import { promises as fs } from 'node:fs';
|
|
4
27
|
import EventEmitter from 'node:events';
|
|
28
|
+
// AnsiLogger module
|
|
5
29
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
30
|
+
// NodeStorage module
|
|
6
31
|
import { NodeStorageManager } from './storage/export.js';
|
|
32
|
+
// Matterbridge
|
|
7
33
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
|
|
8
34
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
9
35
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -11,14 +37,19 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
11
37
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
12
38
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
13
39
|
import { Frontend } from './frontend.js';
|
|
40
|
+
// @matter
|
|
14
41
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
15
42
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
16
43
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
17
44
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
18
45
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
46
|
+
// Default colors
|
|
19
47
|
const plg = '\u001B[38;5;33m';
|
|
20
48
|
const dev = '\u001B[38;5;79m';
|
|
21
49
|
const typ = '\u001B[38;5;207m';
|
|
50
|
+
/**
|
|
51
|
+
* Represents the Matterbridge application.
|
|
52
|
+
*/
|
|
22
53
|
export class Matterbridge extends EventEmitter {
|
|
23
54
|
systemInformation = {
|
|
24
55
|
interfaceName: '',
|
|
@@ -63,7 +94,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
63
94
|
shellySysUpdate: false,
|
|
64
95
|
shellyMainUpdate: false,
|
|
65
96
|
profile: getParameter('profile'),
|
|
66
|
-
loggerLevel: "info"
|
|
97
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
67
98
|
fileLogger: false,
|
|
68
99
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
69
100
|
matterFileLogger: false,
|
|
@@ -101,9 +132,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
101
132
|
plugins;
|
|
102
133
|
devices;
|
|
103
134
|
frontend = new Frontend(this);
|
|
135
|
+
// Matterbridge storage
|
|
104
136
|
nodeStorage;
|
|
105
137
|
nodeContext;
|
|
106
138
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
139
|
+
// Cleanup
|
|
107
140
|
hasCleanupStarted = false;
|
|
108
141
|
initialized = false;
|
|
109
142
|
execRunningCount = 0;
|
|
@@ -116,38 +149,72 @@ export class Matterbridge extends EventEmitter {
|
|
|
116
149
|
sigtermHandler;
|
|
117
150
|
exceptionHandler;
|
|
118
151
|
rejectionHandler;
|
|
152
|
+
// Matter environment
|
|
119
153
|
environment = Environment.default;
|
|
154
|
+
// Matter storage
|
|
120
155
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
121
156
|
matterStorageService;
|
|
122
157
|
matterStorageManager;
|
|
123
158
|
matterbridgeContext;
|
|
124
159
|
mattercontrollerContext;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
160
|
+
// Matter parameters
|
|
161
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
162
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
163
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
164
|
+
port; // first server node port
|
|
165
|
+
passcode; // first server node passcode
|
|
166
|
+
discriminator; // first server node discriminator
|
|
131
167
|
serverNode;
|
|
132
168
|
aggregatorNode;
|
|
133
169
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
170
|
+
aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
|
|
134
171
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
172
|
+
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
135
173
|
static instance;
|
|
174
|
+
// We load asyncronously so is private
|
|
136
175
|
constructor() {
|
|
137
176
|
super();
|
|
138
177
|
}
|
|
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
|
+
*/
|
|
139
186
|
emit(eventName, ...args) {
|
|
140
187
|
return super.emit(eventName, ...args);
|
|
141
188
|
}
|
|
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
|
+
*/
|
|
142
197
|
on(eventName, listener) {
|
|
143
198
|
return super.on(eventName, listener);
|
|
144
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Retrieves the list of Matterbridge devices.
|
|
202
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
203
|
+
*/
|
|
145
204
|
getDevices() {
|
|
146
205
|
return this.devices.array();
|
|
147
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Retrieves the list of registered plugins.
|
|
209
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
210
|
+
*/
|
|
148
211
|
getPlugins() {
|
|
149
212
|
return this.plugins.array();
|
|
150
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Set the logger logLevel for the Matterbridge classes.
|
|
216
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
217
|
+
*/
|
|
151
218
|
async setLogLevel(logLevel) {
|
|
152
219
|
if (this.log)
|
|
153
220
|
this.log.logLevel = logLevel;
|
|
@@ -161,19 +228,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
161
228
|
for (const plugin of this.plugins) {
|
|
162
229
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
163
230
|
continue;
|
|
164
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
165
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
231
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
232
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
233
|
+
}
|
|
234
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
235
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
236
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
237
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
238
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
239
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
172
240
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
173
241
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
174
242
|
}
|
|
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
|
+
*/
|
|
175
253
|
static async loadInstance(initialize = false) {
|
|
176
254
|
if (!Matterbridge.instance) {
|
|
255
|
+
// eslint-disable-next-line no-console
|
|
177
256
|
if (hasParameter('debug'))
|
|
178
257
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
179
258
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -182,8 +261,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
182
261
|
}
|
|
183
262
|
return Matterbridge.instance;
|
|
184
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Call cleanup().
|
|
266
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
267
|
+
*
|
|
268
|
+
*/
|
|
185
269
|
async destroyInstance() {
|
|
186
270
|
this.log.info(`Destroy instance...`);
|
|
271
|
+
// Save server nodes to close
|
|
187
272
|
const servers = [];
|
|
188
273
|
if (this.bridgeMode === 'bridge') {
|
|
189
274
|
if (this.serverNode)
|
|
@@ -195,55 +280,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
195
280
|
servers.push(plugin.serverNode);
|
|
196
281
|
}
|
|
197
282
|
}
|
|
283
|
+
// Cleanup
|
|
198
284
|
await this.cleanup('destroying instance...', false);
|
|
285
|
+
// Close servers mdns service
|
|
199
286
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
200
287
|
for (const server of servers) {
|
|
201
288
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
202
289
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
203
290
|
}
|
|
291
|
+
// Wait for the cleanup to finish
|
|
204
292
|
await new Promise((resolve) => {
|
|
205
293
|
setTimeout(resolve, 1000);
|
|
206
294
|
});
|
|
207
295
|
}
|
|
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
|
+
*/
|
|
208
306
|
async initialize() {
|
|
307
|
+
// Set the restart mode
|
|
209
308
|
if (hasParameter('service'))
|
|
210
309
|
this.restartMode = 'service';
|
|
211
310
|
if (hasParameter('docker'))
|
|
212
311
|
this.restartMode = 'docker';
|
|
312
|
+
// Set the matterbridge directory
|
|
213
313
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
214
314
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
315
|
+
// Setup the matter environment
|
|
215
316
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
216
317
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
217
318
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
218
319
|
this.environment.vars.set('runtime.signals', false);
|
|
219
320
|
this.environment.vars.set('runtime.exitcode', false);
|
|
220
|
-
|
|
321
|
+
// Create the matterbridge logger
|
|
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
|
|
221
324
|
this.registerProcessHandlers();
|
|
325
|
+
// Initialize nodeStorage and nodeContext
|
|
222
326
|
try {
|
|
223
327
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
224
328
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
225
329
|
this.log.debug('Creating node storage context for matterbridge');
|
|
226
330
|
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
|
|
227
333
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
228
334
|
for (const key of keys) {
|
|
229
335
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
336
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
337
|
await this.nodeStorage?.storage.get(key);
|
|
231
338
|
}
|
|
232
339
|
const storages = await this.nodeStorage.getStorageNames();
|
|
233
340
|
for (const storage of storages) {
|
|
234
341
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
235
342
|
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
|
|
236
345
|
const keys = (await nodeContext?.storage.keys());
|
|
237
346
|
keys.forEach(async (key) => {
|
|
238
347
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
239
348
|
await nodeContext?.get(key);
|
|
240
349
|
});
|
|
241
350
|
}
|
|
351
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
242
352
|
this.log.debug('Creating node storage backup...');
|
|
243
353
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
244
354
|
this.log.debug('Created node storage backup');
|
|
245
355
|
}
|
|
246
356
|
catch (error) {
|
|
357
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
247
358
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
248
359
|
if (hasParameter('norestore')) {
|
|
249
360
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -258,41 +369,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
258
369
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
259
370
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
260
371
|
}
|
|
372
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
261
373
|
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)
|
|
262
375
|
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)
|
|
263
377
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
264
378
|
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)
|
|
265
380
|
if (hasParameter('logger')) {
|
|
266
381
|
const level = getParameter('logger');
|
|
267
382
|
if (level === 'debug') {
|
|
268
|
-
this.log.logLevel = "debug"
|
|
383
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
269
384
|
}
|
|
270
385
|
else if (level === 'info') {
|
|
271
|
-
this.log.logLevel = "info"
|
|
386
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
272
387
|
}
|
|
273
388
|
else if (level === 'notice') {
|
|
274
|
-
this.log.logLevel = "notice"
|
|
389
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
275
390
|
}
|
|
276
391
|
else if (level === 'warn') {
|
|
277
|
-
this.log.logLevel = "warn"
|
|
392
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
278
393
|
}
|
|
279
394
|
else if (level === 'error') {
|
|
280
|
-
this.log.logLevel = "error"
|
|
395
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
281
396
|
}
|
|
282
397
|
else if (level === 'fatal') {
|
|
283
|
-
this.log.logLevel = "fatal"
|
|
398
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
284
399
|
}
|
|
285
400
|
else {
|
|
286
401
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
287
|
-
this.log.logLevel = "info"
|
|
402
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
288
403
|
}
|
|
289
404
|
}
|
|
290
405
|
else {
|
|
291
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
406
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
292
407
|
}
|
|
293
408
|
this.frontend.logLevel = this.log.logLevel;
|
|
294
409
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
295
410
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
411
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
296
412
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
297
413
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
298
414
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -301,6 +417,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
301
417
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
302
418
|
if (this.profile !== undefined)
|
|
303
419
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
420
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
304
421
|
if (hasParameter('matterlogger')) {
|
|
305
422
|
const level = getParameter('matterlogger');
|
|
306
423
|
if (level === 'debug') {
|
|
@@ -332,6 +449,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
332
449
|
Logger.format = MatterLogFormat.ANSI;
|
|
333
450
|
Logger.setLogger('default', this.createMatterLogger());
|
|
334
451
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
452
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
335
453
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
336
454
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
337
455
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -340,6 +458,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
340
458
|
});
|
|
341
459
|
}
|
|
342
460
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
461
|
+
// Log network interfaces
|
|
343
462
|
const networkInterfaces = os.networkInterfaces();
|
|
344
463
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
345
464
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -351,6 +470,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
351
470
|
});
|
|
352
471
|
}
|
|
353
472
|
}
|
|
473
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
354
474
|
if (hasParameter('mdnsinterface')) {
|
|
355
475
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
356
476
|
}
|
|
@@ -359,6 +479,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
359
479
|
if (this.mdnsInterface === '')
|
|
360
480
|
this.mdnsInterface = undefined;
|
|
361
481
|
}
|
|
482
|
+
// Validate mdnsInterface
|
|
362
483
|
if (this.mdnsInterface) {
|
|
363
484
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
364
485
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -370,6 +491,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
370
491
|
}
|
|
371
492
|
if (this.mdnsInterface)
|
|
372
493
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
494
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
373
495
|
if (hasParameter('ipv4address')) {
|
|
374
496
|
this.ipv4address = getParameter('ipv4address');
|
|
375
497
|
}
|
|
@@ -378,6 +500,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
378
500
|
if (this.ipv4address === '')
|
|
379
501
|
this.ipv4address = undefined;
|
|
380
502
|
}
|
|
503
|
+
// Validate ipv4address
|
|
381
504
|
if (this.ipv4address) {
|
|
382
505
|
let isValid = false;
|
|
383
506
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -392,6 +515,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
392
515
|
this.ipv4address = undefined;
|
|
393
516
|
}
|
|
394
517
|
}
|
|
518
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
395
519
|
if (hasParameter('ipv6address')) {
|
|
396
520
|
this.ipv6address = getParameter('ipv6address');
|
|
397
521
|
}
|
|
@@ -400,6 +524,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
400
524
|
if (this.ipv6address === '')
|
|
401
525
|
this.ipv6address = undefined;
|
|
402
526
|
}
|
|
527
|
+
// Validate ipv6address
|
|
403
528
|
if (this.ipv6address) {
|
|
404
529
|
let isValid = false;
|
|
405
530
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -419,14 +544,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
419
544
|
this.ipv6address = undefined;
|
|
420
545
|
}
|
|
421
546
|
}
|
|
547
|
+
// Initialize PluginManager
|
|
422
548
|
this.plugins = new PluginManager(this);
|
|
423
549
|
await this.plugins.loadFromStorage();
|
|
424
550
|
this.plugins.logLevel = this.log.logLevel;
|
|
551
|
+
// Initialize DeviceManager
|
|
425
552
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
426
553
|
this.devices.logLevel = this.log.logLevel;
|
|
554
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
427
555
|
for (const plugin of this.plugins) {
|
|
428
556
|
const packageJson = await this.plugins.parse(plugin);
|
|
429
557
|
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
|
|
430
560
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
431
561
|
try {
|
|
432
562
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -448,6 +578,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
448
578
|
await plugin.nodeContext.set('description', plugin.description);
|
|
449
579
|
await plugin.nodeContext.set('author', plugin.author);
|
|
450
580
|
}
|
|
581
|
+
// Log system info and create .matterbridge directory
|
|
451
582
|
await this.logNodeAndSystemInfo();
|
|
452
583
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
453
584
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -455,6 +586,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
455
586
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
456
587
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
457
588
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
589
|
+
// Check node version and throw error
|
|
458
590
|
const minNodeVersion = 18;
|
|
459
591
|
const nodeVersion = process.versions.node;
|
|
460
592
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -462,9 +594,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
462
594
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
463
595
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
464
596
|
}
|
|
597
|
+
// Parse command line
|
|
465
598
|
await this.parseCommandLine();
|
|
466
599
|
this.initialized = true;
|
|
467
600
|
}
|
|
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
|
+
*/
|
|
468
606
|
async parseCommandLine() {
|
|
469
607
|
if (hasParameter('help')) {
|
|
470
608
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -576,6 +714,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
576
714
|
this.shutdown = true;
|
|
577
715
|
return;
|
|
578
716
|
}
|
|
717
|
+
// Start the matter storage and create the matterbridge context
|
|
579
718
|
try {
|
|
580
719
|
await this.startMatterStorage();
|
|
581
720
|
}
|
|
@@ -583,12 +722,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
583
722
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
584
723
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
585
724
|
}
|
|
725
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
586
726
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
587
727
|
this.initialized = true;
|
|
588
728
|
await this.shutdownProcessAndReset();
|
|
589
729
|
this.shutdown = true;
|
|
590
730
|
return;
|
|
591
731
|
}
|
|
732
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
592
733
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
593
734
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
594
735
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -613,30 +754,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
613
754
|
this.shutdown = true;
|
|
614
755
|
return;
|
|
615
756
|
}
|
|
757
|
+
// Initialize frontend
|
|
616
758
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
617
759
|
await this.frontend.start(getIntParameter('frontend'));
|
|
760
|
+
// Check in 30 seconds the latest versions
|
|
618
761
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
619
762
|
const { checkUpdates } = await import('./update.js');
|
|
620
763
|
checkUpdates(this);
|
|
621
764
|
}, 30 * 1000).unref();
|
|
765
|
+
// Check each 24 hours the latest versions
|
|
622
766
|
this.checkUpdateInterval = setInterval(async () => {
|
|
623
767
|
const { checkUpdates } = await import('./update.js');
|
|
624
768
|
checkUpdates(this);
|
|
625
769
|
}, 24 * 60 * 60 * 1000).unref();
|
|
770
|
+
// Start the matterbridge in mode test
|
|
626
771
|
if (hasParameter('test')) {
|
|
627
772
|
this.bridgeMode = 'bridge';
|
|
628
773
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
629
774
|
return;
|
|
630
775
|
}
|
|
776
|
+
// Start the matterbridge in mode controller
|
|
631
777
|
if (hasParameter('controller')) {
|
|
632
778
|
this.bridgeMode = 'controller';
|
|
633
779
|
await this.startController();
|
|
634
780
|
return;
|
|
635
781
|
}
|
|
782
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
636
783
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
637
784
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
638
785
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
639
786
|
}
|
|
787
|
+
// Start matterbridge in bridge mode
|
|
640
788
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
641
789
|
this.bridgeMode = 'bridge';
|
|
642
790
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -644,6 +792,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
644
792
|
await this.startBridge();
|
|
645
793
|
return;
|
|
646
794
|
}
|
|
795
|
+
// Start matterbridge in childbridge mode
|
|
647
796
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
648
797
|
this.bridgeMode = 'childbridge';
|
|
649
798
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -652,10 +801,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
652
801
|
return;
|
|
653
802
|
}
|
|
654
803
|
}
|
|
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
|
+
*/
|
|
655
812
|
async startPlugins() {
|
|
813
|
+
// Check, load and start the plugins
|
|
656
814
|
for (const plugin of this.plugins) {
|
|
657
815
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
658
816
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
817
|
+
// Check if the plugin is available
|
|
659
818
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
660
819
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
661
820
|
plugin.enabled = false;
|
|
@@ -675,20 +834,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
675
834
|
plugin.addedDevices = undefined;
|
|
676
835
|
plugin.qrPairingCode = undefined;
|
|
677
836
|
plugin.manualPairingCode = undefined;
|
|
678
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
837
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
679
838
|
}
|
|
680
839
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
681
840
|
}
|
|
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
|
+
*/
|
|
682
845
|
registerProcessHandlers() {
|
|
683
846
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
684
847
|
process.removeAllListeners('uncaughtException');
|
|
685
848
|
process.removeAllListeners('unhandledRejection');
|
|
686
849
|
this.exceptionHandler = async (error) => {
|
|
687
850
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
851
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
688
852
|
};
|
|
689
853
|
process.on('uncaughtException', this.exceptionHandler);
|
|
690
854
|
this.rejectionHandler = async (reason, promise) => {
|
|
691
855
|
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...');
|
|
692
857
|
};
|
|
693
858
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
694
859
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -701,6 +866,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
701
866
|
};
|
|
702
867
|
process.on('SIGTERM', this.sigtermHandler);
|
|
703
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
871
|
+
*/
|
|
704
872
|
deregisterProcesslHandlers() {
|
|
705
873
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
706
874
|
if (this.exceptionHandler)
|
|
@@ -717,12 +885,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
717
885
|
process.off('SIGTERM', this.sigtermHandler);
|
|
718
886
|
this.sigtermHandler = undefined;
|
|
719
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* Logs the node and system information.
|
|
890
|
+
*/
|
|
720
891
|
async logNodeAndSystemInfo() {
|
|
892
|
+
// IP address information
|
|
721
893
|
const networkInterfaces = os.networkInterfaces();
|
|
722
894
|
this.systemInformation.interfaceName = '';
|
|
723
895
|
this.systemInformation.ipv4Address = '';
|
|
724
896
|
this.systemInformation.ipv6Address = '';
|
|
725
897
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
898
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
726
899
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
727
900
|
continue;
|
|
728
901
|
if (!interfaceDetails) {
|
|
@@ -748,19 +921,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
748
921
|
break;
|
|
749
922
|
}
|
|
750
923
|
}
|
|
924
|
+
// Node information
|
|
751
925
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
752
926
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
753
927
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
754
928
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
929
|
+
// Host system information
|
|
755
930
|
this.systemInformation.hostname = os.hostname();
|
|
756
931
|
this.systemInformation.user = os.userInfo().username;
|
|
757
|
-
this.systemInformation.osType = os.type();
|
|
758
|
-
this.systemInformation.osRelease = os.release();
|
|
759
|
-
this.systemInformation.osPlatform = os.platform();
|
|
760
|
-
this.systemInformation.osArch = os.arch();
|
|
761
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
762
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
763
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
932
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
933
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
934
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
935
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
936
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
937
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
938
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
939
|
+
// Log the system information
|
|
764
940
|
this.log.debug('Host System Information:');
|
|
765
941
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
766
942
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -776,16 +952,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
776
952
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
777
953
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
778
954
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
955
|
+
// Home directory
|
|
779
956
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
780
957
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
781
958
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
959
|
+
// Package root directory
|
|
782
960
|
const { fileURLToPath } = await import('node:url');
|
|
783
961
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
784
962
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
785
963
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
786
964
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
965
|
+
// Global node_modules directory
|
|
787
966
|
if (this.nodeContext)
|
|
788
967
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
968
|
+
// First run of Matterbridge so the node storage is empty
|
|
789
969
|
if (this.globalModulesDirectory === '') {
|
|
790
970
|
try {
|
|
791
971
|
this.execRunningCount++;
|
|
@@ -801,6 +981,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
981
|
}
|
|
802
982
|
else
|
|
803
983
|
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
|
|
804
998
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
805
999
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
806
1000
|
try {
|
|
@@ -824,6 +1018,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
824
1018
|
}
|
|
825
1019
|
}
|
|
826
1020
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1021
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
827
1022
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
828
1023
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
829
1024
|
try {
|
|
@@ -847,6 +1042,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
847
1042
|
}
|
|
848
1043
|
}
|
|
849
1044
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1045
|
+
// Create the matter cert directory in the home directory
|
|
850
1046
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
851
1047
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
852
1048
|
try {
|
|
@@ -870,50 +1066,68 @@ export class Matterbridge extends EventEmitter {
|
|
|
870
1066
|
}
|
|
871
1067
|
}
|
|
872
1068
|
this.log.debug(`Matterbridge Matter Cert Directory: ${this.matterbridgeCertDirectory}`);
|
|
1069
|
+
// Matterbridge version
|
|
873
1070
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
874
1071
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
875
1072
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
876
1073
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1074
|
+
// Matterbridge latest version
|
|
877
1075
|
if (this.nodeContext)
|
|
878
1076
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
879
1077
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1078
|
+
// this.getMatterbridgeLatestVersion();
|
|
1079
|
+
// Current working directory
|
|
880
1080
|
const currentDir = process.cwd();
|
|
881
1081
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1082
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
882
1083
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
883
1084
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
884
1085
|
}
|
|
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
|
+
*/
|
|
885
1091
|
createMatterLogger() {
|
|
886
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1092
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
887
1093
|
return (_level, formattedLog) => {
|
|
888
1094
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
889
1095
|
const message = formattedLog.slice(65);
|
|
890
1096
|
matterLogger.logName = logger;
|
|
891
1097
|
switch (_level) {
|
|
892
1098
|
case MatterLogLevel.DEBUG:
|
|
893
|
-
matterLogger.log("debug"
|
|
1099
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
894
1100
|
break;
|
|
895
1101
|
case MatterLogLevel.INFO:
|
|
896
|
-
matterLogger.log("info"
|
|
1102
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
897
1103
|
break;
|
|
898
1104
|
case MatterLogLevel.NOTICE:
|
|
899
|
-
matterLogger.log("notice"
|
|
1105
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
900
1106
|
break;
|
|
901
1107
|
case MatterLogLevel.WARN:
|
|
902
|
-
matterLogger.log("warn"
|
|
1108
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
903
1109
|
break;
|
|
904
1110
|
case MatterLogLevel.ERROR:
|
|
905
|
-
matterLogger.log("error"
|
|
1111
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
906
1112
|
break;
|
|
907
1113
|
case MatterLogLevel.FATAL:
|
|
908
|
-
matterLogger.log("fatal"
|
|
1114
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
909
1115
|
break;
|
|
910
1116
|
default:
|
|
911
|
-
matterLogger.log("debug"
|
|
1117
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
912
1118
|
break;
|
|
913
1119
|
}
|
|
914
1120
|
};
|
|
915
1121
|
}
|
|
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
|
+
*/
|
|
916
1129
|
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
|
|
917
1131
|
let fileSize = 0;
|
|
918
1132
|
if (unlink) {
|
|
919
1133
|
try {
|
|
@@ -962,12 +1176,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
962
1176
|
}
|
|
963
1177
|
};
|
|
964
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1181
|
+
*/
|
|
965
1182
|
async restartProcess() {
|
|
966
1183
|
await this.cleanup('restarting...', true);
|
|
967
1184
|
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Shut down the process by exiting the current process.
|
|
1187
|
+
*/
|
|
968
1188
|
async shutdownProcess() {
|
|
969
1189
|
await this.cleanup('shutting down...', false);
|
|
970
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Update matterbridge and and shut down the process.
|
|
1193
|
+
*/
|
|
971
1194
|
async updateProcess() {
|
|
972
1195
|
this.log.info('Updating matterbridge...');
|
|
973
1196
|
try {
|
|
@@ -980,51 +1203,72 @@ export class Matterbridge extends EventEmitter {
|
|
|
980
1203
|
this.frontend.wssSendRestartRequired();
|
|
981
1204
|
await this.cleanup('updating...', false);
|
|
982
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Unregister all devices and shut down the process.
|
|
1208
|
+
*/
|
|
983
1209
|
async unregisterAndShutdownProcess() {
|
|
984
1210
|
this.log.info('Unregistering all devices and shutting down...');
|
|
985
1211
|
for (const plugin of this.plugins) {
|
|
986
1212
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
987
1213
|
}
|
|
988
1214
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
989
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1215
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
990
1216
|
this.log.debug('Cleaning up and shutting down...');
|
|
991
1217
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
992
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Reset commissioning and shut down the process.
|
|
1221
|
+
*/
|
|
993
1222
|
async shutdownProcessAndReset() {
|
|
994
1223
|
await this.cleanup('shutting down with reset...', false);
|
|
995
1224
|
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Factory reset and shut down the process.
|
|
1227
|
+
*/
|
|
996
1228
|
async shutdownProcessAndFactoryReset() {
|
|
997
1229
|
await this.cleanup('shutting down with factory reset...', false);
|
|
998
1230
|
}
|
|
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
|
+
*/
|
|
999
1237
|
async cleanup(message, restart = false) {
|
|
1000
1238
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1001
1239
|
this.hasCleanupStarted = true;
|
|
1002
1240
|
this.log.info(message);
|
|
1241
|
+
// Clear the start matter interval
|
|
1003
1242
|
if (this.startMatterInterval) {
|
|
1004
1243
|
clearInterval(this.startMatterInterval);
|
|
1005
1244
|
this.startMatterInterval = undefined;
|
|
1006
1245
|
this.log.debug('Start matter interval cleared');
|
|
1007
1246
|
}
|
|
1247
|
+
// Clear the check update timeout
|
|
1008
1248
|
if (this.checkUpdateTimeout) {
|
|
1009
1249
|
clearInterval(this.checkUpdateTimeout);
|
|
1010
1250
|
this.checkUpdateTimeout = undefined;
|
|
1011
1251
|
this.log.debug('Check update timeout cleared');
|
|
1012
1252
|
}
|
|
1253
|
+
// Clear the check update interval
|
|
1013
1254
|
if (this.checkUpdateInterval) {
|
|
1014
1255
|
clearInterval(this.checkUpdateInterval);
|
|
1015
1256
|
this.checkUpdateInterval = undefined;
|
|
1016
1257
|
this.log.debug('Check update interval cleared');
|
|
1017
1258
|
}
|
|
1259
|
+
// Clear the configure timeout
|
|
1018
1260
|
if (this.configureTimeout) {
|
|
1019
1261
|
clearTimeout(this.configureTimeout);
|
|
1020
1262
|
this.configureTimeout = undefined;
|
|
1021
1263
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1022
1264
|
}
|
|
1265
|
+
// Clear the reachability timeout
|
|
1023
1266
|
if (this.reachabilityTimeout) {
|
|
1024
1267
|
clearTimeout(this.reachabilityTimeout);
|
|
1025
1268
|
this.reachabilityTimeout = undefined;
|
|
1026
1269
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1027
1270
|
}
|
|
1271
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1028
1272
|
for (const plugin of this.plugins) {
|
|
1029
1273
|
if (!plugin.enabled || plugin.error)
|
|
1030
1274
|
continue;
|
|
@@ -1035,9 +1279,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1035
1279
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1036
1280
|
}
|
|
1037
1281
|
}
|
|
1282
|
+
// Stopping matter server nodes
|
|
1038
1283
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1039
1284
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1040
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1285
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
1041
1286
|
if (this.bridgeMode === 'bridge') {
|
|
1042
1287
|
if (this.serverNode) {
|
|
1043
1288
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1053,6 +1298,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1053
1298
|
}
|
|
1054
1299
|
}
|
|
1055
1300
|
this.log.notice('Stopped matter server nodes');
|
|
1301
|
+
// Matter commisioning reset
|
|
1056
1302
|
if (message === 'shutting down with reset...') {
|
|
1057
1303
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1058
1304
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1062,17 +1308,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1062
1308
|
await this.matterbridgeContext?.clearAll();
|
|
1063
1309
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1064
1310
|
}
|
|
1311
|
+
// Stop matter storage
|
|
1065
1312
|
await this.stopMatterStorage();
|
|
1313
|
+
// Stop the frontend
|
|
1066
1314
|
await this.frontend.stop();
|
|
1315
|
+
// Remove the matterfilelogger
|
|
1067
1316
|
try {
|
|
1068
1317
|
Logger.removeLogger('matterfilelogger');
|
|
1318
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1069
1319
|
}
|
|
1070
1320
|
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}`);
|
|
1071
1322
|
}
|
|
1323
|
+
// Serialize registeredDevices
|
|
1072
1324
|
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)
|
|
1073
1338
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1074
1339
|
await this.nodeContext.close();
|
|
1075
1340
|
this.nodeContext = undefined;
|
|
1341
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1076
1342
|
for (const plugin of this.plugins) {
|
|
1077
1343
|
if (plugin.nodeContext) {
|
|
1078
1344
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1089,8 +1355,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1089
1355
|
}
|
|
1090
1356
|
this.plugins.clear();
|
|
1091
1357
|
this.devices.clear();
|
|
1358
|
+
// Factory reset
|
|
1092
1359
|
if (message === 'shutting down with factory reset...') {
|
|
1093
1360
|
try {
|
|
1361
|
+
// Delete old matter storage file and backup
|
|
1094
1362
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1095
1363
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1096
1364
|
await fs.unlink(file);
|
|
@@ -1104,6 +1372,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1104
1372
|
}
|
|
1105
1373
|
}
|
|
1106
1374
|
try {
|
|
1375
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1107
1376
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1108
1377
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1109
1378
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1117,6 +1386,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1117
1386
|
}
|
|
1118
1387
|
}
|
|
1119
1388
|
try {
|
|
1389
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1120
1390
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1121
1391
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1122
1392
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1131,12 +1401,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1131
1401
|
}
|
|
1132
1402
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1133
1403
|
}
|
|
1404
|
+
// Deregisters the process handlers
|
|
1134
1405
|
this.deregisterProcesslHandlers();
|
|
1135
1406
|
if (restart) {
|
|
1136
1407
|
if (message === 'updating...') {
|
|
1137
1408
|
this.log.info('Cleanup completed. Updating...');
|
|
1138
1409
|
Matterbridge.instance = undefined;
|
|
1139
|
-
this.emit('update');
|
|
1410
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1140
1411
|
}
|
|
1141
1412
|
else if (message === 'restarting...') {
|
|
1142
1413
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1156,6 +1427,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1156
1427
|
this.log.debug('Cleanup already started...');
|
|
1157
1428
|
}
|
|
1158
1429
|
}
|
|
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
|
+
*/
|
|
1159
1438
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1160
1439
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1161
1440
|
plugin.locked = true;
|
|
@@ -1168,6 +1447,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1168
1447
|
await this.startServerNode(plugin.serverNode);
|
|
1169
1448
|
}
|
|
1170
1449
|
}
|
|
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
|
+
*/
|
|
1171
1457
|
async createDynamicPlugin(plugin, start = false) {
|
|
1172
1458
|
if (!plugin.locked) {
|
|
1173
1459
|
plugin.locked = true;
|
|
@@ -1179,7 +1465,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1179
1465
|
await this.startServerNode(plugin.serverNode);
|
|
1180
1466
|
}
|
|
1181
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Starts the Matterbridge in bridge mode.
|
|
1470
|
+
* @private
|
|
1471
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1472
|
+
*/
|
|
1182
1473
|
async startBridge() {
|
|
1474
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1183
1475
|
if (!this.matterStorageManager)
|
|
1184
1476
|
throw new Error('No storage manager initialized');
|
|
1185
1477
|
if (!this.matterbridgeContext)
|
|
@@ -1217,7 +1509,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1217
1509
|
clearInterval(this.startMatterInterval);
|
|
1218
1510
|
this.startMatterInterval = undefined;
|
|
1219
1511
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1512
|
+
// Start the Matter server node
|
|
1220
1513
|
this.startServerNode(this.serverNode);
|
|
1514
|
+
// Configure the plugins
|
|
1221
1515
|
this.configureTimeout = setTimeout(async () => {
|
|
1222
1516
|
for (const plugin of this.plugins) {
|
|
1223
1517
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1235,6 +1529,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1235
1529
|
}
|
|
1236
1530
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1237
1531
|
}, 30 * 1000);
|
|
1532
|
+
// Setting reachability to true
|
|
1238
1533
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1239
1534
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1240
1535
|
if (this.aggregatorNode)
|
|
@@ -1243,6 +1538,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1243
1538
|
}, 60 * 1000);
|
|
1244
1539
|
}, 1000);
|
|
1245
1540
|
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1543
|
+
* @private
|
|
1544
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1545
|
+
*/
|
|
1246
1546
|
async startChildbridge() {
|
|
1247
1547
|
if (!this.matterStorageManager)
|
|
1248
1548
|
throw new Error('No storage manager initialized');
|
|
@@ -1287,6 +1587,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1287
1587
|
clearInterval(this.startMatterInterval);
|
|
1288
1588
|
this.startMatterInterval = undefined;
|
|
1289
1589
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1590
|
+
// Configure the plugins
|
|
1290
1591
|
this.configureTimeout = setTimeout(async () => {
|
|
1291
1592
|
for (const plugin of this.plugins) {
|
|
1292
1593
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1323,7 +1624,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1323
1624
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1324
1625
|
continue;
|
|
1325
1626
|
}
|
|
1627
|
+
// Start the Matter server node
|
|
1326
1628
|
this.startServerNode(plugin.serverNode);
|
|
1629
|
+
// Setting reachability to true
|
|
1327
1630
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1328
1631
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggragator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1329
1632
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1333,23 +1636,245 @@ export class Matterbridge extends EventEmitter {
|
|
|
1333
1636
|
}
|
|
1334
1637
|
}, 1000);
|
|
1335
1638
|
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Starts the Matterbridge controller.
|
|
1641
|
+
* @private
|
|
1642
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1643
|
+
*/
|
|
1336
1644
|
async startController() {
|
|
1645
|
+
/*
|
|
1646
|
+
if (!this.storageManager) {
|
|
1647
|
+
this.log.error('No storage manager initialized');
|
|
1648
|
+
await this.cleanup('No storage manager initialized');
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1652
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1653
|
+
if (!this.mattercontrollerContext) {
|
|
1654
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1655
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
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
|
+
*/
|
|
1337
1842
|
}
|
|
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
|
+
*/
|
|
1338
1850
|
async startMatterStorage() {
|
|
1851
|
+
// Setup Matter storage
|
|
1339
1852
|
this.log.info(`Starting matter node storage...`);
|
|
1340
1853
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1341
1854
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
1342
1855
|
this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
|
|
1343
1856
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1344
|
-
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId,
|
|
1857
|
+
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1345
1858
|
this.log.info('Matter node storage started');
|
|
1859
|
+
// Backup matter storage since it is created/opened correctly
|
|
1346
1860
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1347
1861
|
}
|
|
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
|
+
*/
|
|
1348
1869
|
async backupMatterStorage(storageName, backupName) {
|
|
1349
1870
|
this.log.info('Creating matter node storage backup...');
|
|
1350
1871
|
await copyDirectory(storageName, backupName);
|
|
1351
1872
|
this.log.info('Created matter node storage backup');
|
|
1352
1873
|
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Stops the matter storage.
|
|
1876
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1877
|
+
*/
|
|
1353
1878
|
async stopMatterStorage() {
|
|
1354
1879
|
this.log.info('Closing matter node storage...');
|
|
1355
1880
|
this.matterStorageManager?.close();
|
|
@@ -1358,6 +1883,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1358
1883
|
this.matterbridgeContext = undefined;
|
|
1359
1884
|
this.log.info('Matter node storage closed');
|
|
1360
1885
|
}
|
|
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
|
+
*/
|
|
1361
1899
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1362
1900
|
const { randomBytes } = await import('node:crypto');
|
|
1363
1901
|
if (!this.matterStorageService)
|
|
@@ -1391,6 +1929,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1391
1929
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1392
1930
|
return storageContext;
|
|
1393
1931
|
}
|
|
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
|
+
*/
|
|
1394
1941
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1395
1942
|
const storeId = await storageContext.get('storeId');
|
|
1396
1943
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1400,21 +1947,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1400
1947
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1401
1948
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1402
1949
|
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
|
+
*/
|
|
1403
1953
|
const serverNode = await ServerNode.create({
|
|
1954
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1404
1955
|
id: storeId,
|
|
1956
|
+
// Provide Network relevant configuration like the port
|
|
1957
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1405
1958
|
network: {
|
|
1406
1959
|
listeningAddressIpv4: this.ipv4address,
|
|
1407
1960
|
listeningAddressIpv6: this.ipv6address,
|
|
1408
1961
|
port,
|
|
1409
1962
|
},
|
|
1963
|
+
// Provide Commissioning relevant settings
|
|
1964
|
+
// Optional for development/testing purposes
|
|
1410
1965
|
commissioning: {
|
|
1411
1966
|
passcode,
|
|
1412
1967
|
discriminator,
|
|
1413
1968
|
},
|
|
1969
|
+
// Provide Node announcement settings
|
|
1970
|
+
// Optional: If Ommitted some development defaults are used
|
|
1414
1971
|
productDescription: {
|
|
1415
1972
|
name: await storageContext.get('deviceName'),
|
|
1416
1973
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1417
1974
|
},
|
|
1975
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1976
|
+
// Optional: If Omitted some development defaults are used
|
|
1418
1977
|
basicInformation: {
|
|
1419
1978
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1420
1979
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1431,12 +1990,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1431
1990
|
},
|
|
1432
1991
|
});
|
|
1433
1992
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1993
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1434
1994
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1435
1995
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1436
1996
|
if (this.bridgeMode === 'bridge') {
|
|
1437
1997
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1438
1998
|
if (resetSessions)
|
|
1439
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1999
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1440
2000
|
this.matterbridgePaired = true;
|
|
1441
2001
|
}
|
|
1442
2002
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1444,13 +2004,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1444
2004
|
if (plugin) {
|
|
1445
2005
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1446
2006
|
if (resetSessions)
|
|
1447
|
-
plugin.sessionInformations = undefined;
|
|
2007
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1448
2008
|
plugin.paired = true;
|
|
1449
2009
|
}
|
|
1450
2010
|
}
|
|
1451
2011
|
};
|
|
2012
|
+
/**
|
|
2013
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2014
|
+
* This means: It is added to the first fabric.
|
|
2015
|
+
*/
|
|
1452
2016
|
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. */
|
|
1453
2018
|
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. */
|
|
1454
2020
|
serverNode.lifecycle.online.on(async () => {
|
|
1455
2021
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1456
2022
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1515,6 +2081,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1515
2081
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1516
2082
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1517
2083
|
});
|
|
2084
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1518
2085
|
serverNode.lifecycle.offline.on(() => {
|
|
1519
2086
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1520
2087
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1538,6 +2105,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1538
2105
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1539
2106
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1540
2107
|
});
|
|
2108
|
+
/**
|
|
2109
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2110
|
+
* information is needed.
|
|
2111
|
+
*/
|
|
1541
2112
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1542
2113
|
let action = '';
|
|
1543
2114
|
switch (fabricAction) {
|
|
@@ -1571,16 +2142,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1571
2142
|
}
|
|
1572
2143
|
}
|
|
1573
2144
|
};
|
|
2145
|
+
/**
|
|
2146
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2147
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2148
|
+
*/
|
|
1574
2149
|
serverNode.events.sessions.opened.on((session) => {
|
|
1575
2150
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1576
2151
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1577
2152
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1578
2153
|
});
|
|
2154
|
+
/**
|
|
2155
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2156
|
+
*/
|
|
1579
2157
|
serverNode.events.sessions.closed.on((session) => {
|
|
1580
2158
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1581
2159
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1582
2160
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1583
2161
|
});
|
|
2162
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1584
2163
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1585
2164
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1586
2165
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1589,24 +2168,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1589
2168
|
this.log.info(`Created server node for ${storeId}`);
|
|
1590
2169
|
return serverNode;
|
|
1591
2170
|
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Starts the specified server node.
|
|
2173
|
+
*
|
|
2174
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2175
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2176
|
+
*/
|
|
1592
2177
|
async startServerNode(matterServerNode) {
|
|
1593
2178
|
if (!matterServerNode)
|
|
1594
2179
|
return;
|
|
1595
2180
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1596
2181
|
await matterServerNode.start();
|
|
1597
2182
|
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Stops the specified server node.
|
|
2185
|
+
*
|
|
2186
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2187
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2188
|
+
*/
|
|
1598
2189
|
async stopServerNode(matterServerNode) {
|
|
1599
2190
|
if (!matterServerNode)
|
|
1600
2191
|
return;
|
|
1601
2192
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1602
2193
|
try {
|
|
1603
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2194
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1604
2195
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1605
2196
|
}
|
|
1606
2197
|
catch (error) {
|
|
1607
2198
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1608
2199
|
}
|
|
1609
2200
|
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Advertises the specified server node.
|
|
2203
|
+
*
|
|
2204
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2205
|
+
* @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.
|
|
2206
|
+
*/
|
|
1610
2207
|
async advertiseServerNode(matterServerNode) {
|
|
1611
2208
|
if (matterServerNode) {
|
|
1612
2209
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1615,23 +2212,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
2212
|
return { qrPairingCode, manualPairingCode };
|
|
1616
2213
|
}
|
|
1617
2214
|
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Stop advertise the specified server node.
|
|
2217
|
+
*
|
|
2218
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2219
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2220
|
+
*/
|
|
1618
2221
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1619
2222
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1620
2223
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1621
2224
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1622
2225
|
}
|
|
1623
2226
|
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Creates an aggregator node with the specified storage context.
|
|
2229
|
+
*
|
|
2230
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2231
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2232
|
+
*/
|
|
1624
2233
|
async createAggregatorNode(storageContext) {
|
|
1625
2234
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1626
2235
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1627
2236
|
return aggregatorNode;
|
|
1628
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2240
|
+
*
|
|
2241
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2242
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2243
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2244
|
+
*/
|
|
1629
2245
|
async addBridgedEndpoint(pluginName, device) {
|
|
2246
|
+
// Check if the plugin is registered
|
|
1630
2247
|
const plugin = this.plugins.get(pluginName);
|
|
1631
2248
|
if (!plugin) {
|
|
1632
2249
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1633
2250
|
return;
|
|
1634
2251
|
}
|
|
2252
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1635
2253
|
if (this.bridgeMode === 'bridge') {
|
|
1636
2254
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1637
2255
|
if (!this.aggregatorNode)
|
|
@@ -1654,17 +2272,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1654
2272
|
plugin.registeredDevices++;
|
|
1655
2273
|
if (plugin.addedDevices !== undefined)
|
|
1656
2274
|
plugin.addedDevices++;
|
|
2275
|
+
// Add the device to the DeviceManager
|
|
1657
2276
|
this.devices.set(device);
|
|
2277
|
+
// Subscribe to the reachable$Changed event
|
|
1658
2278
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1659
2279
|
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}`);
|
|
1660
2280
|
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2283
|
+
*
|
|
2284
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2285
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2286
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2287
|
+
*/
|
|
1661
2288
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1662
2289
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2290
|
+
// Check if the plugin is registered
|
|
1663
2291
|
const plugin = this.plugins.get(pluginName);
|
|
1664
2292
|
if (!plugin) {
|
|
1665
2293
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1666
2294
|
return;
|
|
1667
2295
|
}
|
|
2296
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1668
2297
|
if (this.bridgeMode === 'bridge') {
|
|
1669
2298
|
if (!this.aggregatorNode) {
|
|
1670
2299
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1679,6 +2308,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1679
2308
|
}
|
|
1680
2309
|
else if (this.bridgeMode === 'childbridge') {
|
|
1681
2310
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2311
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1682
2312
|
}
|
|
1683
2313
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1684
2314
|
if (!plugin.aggregatorNode) {
|
|
@@ -1693,8 +2323,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1693
2323
|
if (plugin.addedDevices !== undefined)
|
|
1694
2324
|
plugin.addedDevices--;
|
|
1695
2325
|
}
|
|
2326
|
+
// Remove the device from the DeviceManager
|
|
1696
2327
|
this.devices.remove(device);
|
|
1697
2328
|
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2331
|
+
*
|
|
2332
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2333
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2334
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2335
|
+
*/
|
|
1698
2336
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1699
2337
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1700
2338
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1703,9 +2341,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1703
2341
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1704
2342
|
}
|
|
1705
2343
|
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2346
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2347
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2348
|
+
*
|
|
2349
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2350
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2351
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2352
|
+
*/
|
|
1706
2353
|
async subscribeAttributeChanged(plugin, device) {
|
|
1707
2354
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1708
2355
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2356
|
+
/*
|
|
2357
|
+
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
|
|
2358
|
+
setTimeout(async () => {
|
|
2359
|
+
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
|
|
2360
|
+
await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
|
|
2361
|
+
}, 60000).unref();
|
|
2362
|
+
*/
|
|
1709
2363
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1710
2364
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
1711
2365
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
@@ -1718,6 +2372,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1718
2372
|
});
|
|
1719
2373
|
}
|
|
1720
2374
|
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2377
|
+
*
|
|
2378
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2379
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2380
|
+
*/
|
|
1721
2381
|
sanitizeFabricInformations(fabricInfo) {
|
|
1722
2382
|
return fabricInfo.map((info) => {
|
|
1723
2383
|
return {
|
|
@@ -1731,6 +2391,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1731
2391
|
};
|
|
1732
2392
|
});
|
|
1733
2393
|
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2396
|
+
*
|
|
2397
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2398
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2399
|
+
*/
|
|
1734
2400
|
sanitizeSessionInformation(sessionInfo) {
|
|
1735
2401
|
return sessionInfo
|
|
1736
2402
|
.filter((session) => session.isPeerActive)
|
|
@@ -1758,7 +2424,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1758
2424
|
};
|
|
1759
2425
|
});
|
|
1760
2426
|
}
|
|
2427
|
+
/**
|
|
2428
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2429
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2430
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2431
|
+
*/
|
|
2432
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1761
2433
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2434
|
+
/*
|
|
2435
|
+
for (const child of aggregatorNode.parts) {
|
|
2436
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2437
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2438
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2439
|
+
}
|
|
2440
|
+
*/
|
|
1762
2441
|
}
|
|
1763
2442
|
getVendorIdName = (vendorId) => {
|
|
1764
2443
|
if (!vendorId)
|
|
@@ -1792,20 +2471,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
1792
2471
|
case 4742:
|
|
1793
2472
|
vendorName = '(eWeLink)';
|
|
1794
2473
|
break;
|
|
2474
|
+
case 5264:
|
|
2475
|
+
vendorName = '(Shelly)';
|
|
2476
|
+
break;
|
|
1795
2477
|
case 65521:
|
|
1796
2478
|
vendorName = '(MatterServer)';
|
|
1797
2479
|
break;
|
|
1798
2480
|
}
|
|
1799
2481
|
return vendorName;
|
|
1800
2482
|
};
|
|
2483
|
+
/**
|
|
2484
|
+
* Spawns a child process with the given command and arguments.
|
|
2485
|
+
* @param {string} command - The command to execute.
|
|
2486
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2487
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2488
|
+
*/
|
|
1801
2489
|
async spawnCommand(command, args = []) {
|
|
1802
2490
|
const { spawn } = await import('node:child_process');
|
|
2491
|
+
/*
|
|
2492
|
+
npm > npm.cmd on windows
|
|
2493
|
+
cmd.exe ['dir'] on windows
|
|
2494
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2495
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2496
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2497
|
+
});
|
|
2498
|
+
|
|
2499
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2500
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2501
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2502
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2503
|
+
*/
|
|
1803
2504
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1804
2505
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2506
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1805
2507
|
const argstring = 'npm ' + args.join(' ');
|
|
1806
2508
|
args.splice(0, args.length, '/c', argstring);
|
|
1807
2509
|
command = 'cmd.exe';
|
|
1808
2510
|
}
|
|
2511
|
+
// Decide when using sudo on linux
|
|
2512
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2513
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1809
2514
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1810
2515
|
args.unshift(command);
|
|
1811
2516
|
command = 'sudo';
|
|
@@ -1864,3 +2569,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1864
2569
|
});
|
|
1865
2570
|
}
|
|
1866
2571
|
}
|
|
2572
|
+
//# sourceMappingURL=matterbridge.js.map
|