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