matterbridge 2.2.0-dev.8 → 2.2.0
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 +1 -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 +24 -6
- 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 +172 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +270 -23
- 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 +411 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +719 -47
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +692 -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 +181 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +140 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +174 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +229 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +77 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +121 -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 +70 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -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,13 +37,18 @@ 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';
|
|
45
|
+
// Default colors
|
|
18
46
|
const plg = '\u001B[38;5;33m';
|
|
19
47
|
const dev = '\u001B[38;5;79m';
|
|
20
48
|
const typ = '\u001B[38;5;207m';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
21
52
|
export class Matterbridge extends EventEmitter {
|
|
22
53
|
systemInformation = {
|
|
23
54
|
interfaceName: '',
|
|
@@ -61,7 +92,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
61
92
|
shellySysUpdate: false,
|
|
62
93
|
shellyMainUpdate: false,
|
|
63
94
|
profile: getParameter('profile'),
|
|
64
|
-
loggerLevel: "info"
|
|
95
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
65
96
|
fileLogger: false,
|
|
66
97
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
67
98
|
matterFileLogger: false,
|
|
@@ -97,9 +128,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
97
128
|
plugins;
|
|
98
129
|
devices;
|
|
99
130
|
frontend = new Frontend(this);
|
|
131
|
+
// Matterbridge storage
|
|
100
132
|
nodeStorage;
|
|
101
133
|
nodeContext;
|
|
102
134
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
135
|
+
// Cleanup
|
|
103
136
|
hasCleanupStarted = false;
|
|
104
137
|
initialized = false;
|
|
105
138
|
execRunningCount = 0;
|
|
@@ -112,38 +145,70 @@ export class Matterbridge extends EventEmitter {
|
|
|
112
145
|
sigtermHandler;
|
|
113
146
|
exceptionHandler;
|
|
114
147
|
rejectionHandler;
|
|
148
|
+
// Matter environment
|
|
115
149
|
environment = Environment.default;
|
|
150
|
+
// Matter storage
|
|
116
151
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
117
152
|
matterStorageService;
|
|
118
153
|
matterStorageManager;
|
|
119
154
|
matterbridgeContext;
|
|
120
155
|
mattercontrollerContext;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
// Matter parameters
|
|
157
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
158
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
159
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
160
|
+
port; // first server node port
|
|
161
|
+
passcode; // first server node passcode
|
|
162
|
+
discriminator; // first server node discriminator
|
|
127
163
|
serverNode;
|
|
128
164
|
aggregatorNode;
|
|
129
165
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
130
166
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
131
167
|
static instance;
|
|
168
|
+
// We load asyncronously so is private
|
|
132
169
|
constructor() {
|
|
133
170
|
super();
|
|
134
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Emits an event of the specified type with the provided arguments.
|
|
174
|
+
*
|
|
175
|
+
* @template K - The type of the event.
|
|
176
|
+
* @param {K} eventName - The name of the event to emit.
|
|
177
|
+
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
178
|
+
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
179
|
+
*/
|
|
135
180
|
emit(eventName, ...args) {
|
|
136
181
|
return super.emit(eventName, ...args);
|
|
137
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Registers an event listener for the specified event type.
|
|
185
|
+
*
|
|
186
|
+
* @template K - The type of the event.
|
|
187
|
+
* @param {K} eventName - The name of the event to listen for.
|
|
188
|
+
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
189
|
+
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
190
|
+
*/
|
|
138
191
|
on(eventName, listener) {
|
|
139
192
|
return super.on(eventName, listener);
|
|
140
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Retrieves the list of Matterbridge devices.
|
|
196
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
197
|
+
*/
|
|
141
198
|
getDevices() {
|
|
142
199
|
return this.devices.array();
|
|
143
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Retrieves the list of registered plugins.
|
|
203
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
204
|
+
*/
|
|
144
205
|
getPlugins() {
|
|
145
206
|
return this.plugins.array();
|
|
146
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Set the logger logLevel for the Matterbridge classes.
|
|
210
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
211
|
+
*/
|
|
147
212
|
async setLogLevel(logLevel) {
|
|
148
213
|
if (this.log)
|
|
149
214
|
this.log.logLevel = logLevel;
|
|
@@ -157,19 +222,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
157
222
|
for (const plugin of this.plugins) {
|
|
158
223
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
159
224
|
continue;
|
|
160
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
161
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
225
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
226
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
227
|
+
}
|
|
228
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
229
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
230
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
231
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
232
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
233
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
168
234
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
169
235
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
170
236
|
}
|
|
237
|
+
/** ***********************************************************************************************************************************/
|
|
238
|
+
/** loadInstance() and cleanup() methods */
|
|
239
|
+
/** ***********************************************************************************************************************************/
|
|
240
|
+
/**
|
|
241
|
+
* Loads an instance of the Matterbridge class.
|
|
242
|
+
* If an instance already exists, return that instance.
|
|
243
|
+
*
|
|
244
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
245
|
+
* @returns The loaded Matterbridge instance.
|
|
246
|
+
*/
|
|
171
247
|
static async loadInstance(initialize = false) {
|
|
172
248
|
if (!Matterbridge.instance) {
|
|
249
|
+
// eslint-disable-next-line no-console
|
|
173
250
|
if (hasParameter('debug'))
|
|
174
251
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
175
252
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -178,8 +255,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
178
255
|
}
|
|
179
256
|
return Matterbridge.instance;
|
|
180
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Call cleanup().
|
|
260
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
261
|
+
*
|
|
262
|
+
*/
|
|
181
263
|
async destroyInstance() {
|
|
182
264
|
this.log.info(`Destroy instance...`);
|
|
265
|
+
// Save server nodes to close
|
|
183
266
|
const servers = [];
|
|
184
267
|
if (this.bridgeMode === 'bridge') {
|
|
185
268
|
if (this.serverNode)
|
|
@@ -191,55 +274,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
191
274
|
servers.push(plugin.serverNode);
|
|
192
275
|
}
|
|
193
276
|
}
|
|
277
|
+
// Cleanup
|
|
194
278
|
await this.cleanup('destroying instance...', false);
|
|
279
|
+
// Close servers mdns service
|
|
195
280
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
196
281
|
for (const server of servers) {
|
|
197
282
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
198
283
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
199
284
|
}
|
|
285
|
+
// Wait for the cleanup to finish
|
|
200
286
|
await new Promise((resolve) => {
|
|
201
287
|
setTimeout(resolve, 1000);
|
|
202
288
|
});
|
|
203
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Initializes the Matterbridge application.
|
|
292
|
+
*
|
|
293
|
+
* @remarks
|
|
294
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
295
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
296
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
297
|
+
*
|
|
298
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
299
|
+
*/
|
|
204
300
|
async initialize() {
|
|
301
|
+
// Set the restart mode
|
|
205
302
|
if (hasParameter('service'))
|
|
206
303
|
this.restartMode = 'service';
|
|
207
304
|
if (hasParameter('docker'))
|
|
208
305
|
this.restartMode = 'docker';
|
|
306
|
+
// Set the matterbridge directory
|
|
209
307
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
210
308
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
309
|
+
// Setup the matter environment
|
|
211
310
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
212
311
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
213
312
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
214
313
|
this.environment.vars.set('runtime.signals', false);
|
|
215
314
|
this.environment.vars.set('runtime.exitcode', false);
|
|
216
|
-
|
|
315
|
+
// Create the matterbridge logger
|
|
316
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
317
|
+
// Register process handlers
|
|
217
318
|
this.registerProcessHandlers();
|
|
319
|
+
// Initialize nodeStorage and nodeContext
|
|
218
320
|
try {
|
|
219
321
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
220
322
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
221
323
|
this.log.debug('Creating node storage context for matterbridge');
|
|
222
324
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
325
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
223
327
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
224
328
|
for (const key of keys) {
|
|
225
329
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
330
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
226
331
|
await this.nodeStorage?.storage.get(key);
|
|
227
332
|
}
|
|
228
333
|
const storages = await this.nodeStorage.getStorageNames();
|
|
229
334
|
for (const storage of storages) {
|
|
230
335
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
231
336
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
337
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
338
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
339
|
const keys = (await nodeContext?.storage.keys());
|
|
233
340
|
keys.forEach(async (key) => {
|
|
234
341
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
235
342
|
await nodeContext?.get(key);
|
|
236
343
|
});
|
|
237
344
|
}
|
|
345
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
238
346
|
this.log.debug('Creating node storage backup...');
|
|
239
347
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
240
348
|
this.log.debug('Created node storage backup');
|
|
241
349
|
}
|
|
242
350
|
catch (error) {
|
|
351
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
243
352
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
244
353
|
if (hasParameter('norestore')) {
|
|
245
354
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -254,41 +363,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
254
363
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
255
364
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
256
365
|
}
|
|
366
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
257
367
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
368
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
258
369
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
370
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
259
371
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
260
372
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
373
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
261
374
|
if (hasParameter('logger')) {
|
|
262
375
|
const level = getParameter('logger');
|
|
263
376
|
if (level === 'debug') {
|
|
264
|
-
this.log.logLevel = "debug"
|
|
377
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
265
378
|
}
|
|
266
379
|
else if (level === 'info') {
|
|
267
|
-
this.log.logLevel = "info"
|
|
380
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
268
381
|
}
|
|
269
382
|
else if (level === 'notice') {
|
|
270
|
-
this.log.logLevel = "notice"
|
|
383
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
271
384
|
}
|
|
272
385
|
else if (level === 'warn') {
|
|
273
|
-
this.log.logLevel = "warn"
|
|
386
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
274
387
|
}
|
|
275
388
|
else if (level === 'error') {
|
|
276
|
-
this.log.logLevel = "error"
|
|
389
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
277
390
|
}
|
|
278
391
|
else if (level === 'fatal') {
|
|
279
|
-
this.log.logLevel = "fatal"
|
|
392
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
280
393
|
}
|
|
281
394
|
else {
|
|
282
395
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
283
|
-
this.log.logLevel = "info"
|
|
396
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
284
397
|
}
|
|
285
398
|
}
|
|
286
399
|
else {
|
|
287
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
400
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
288
401
|
}
|
|
289
402
|
this.frontend.logLevel = this.log.logLevel;
|
|
290
403
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
291
404
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
405
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
292
406
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
293
407
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
294
408
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -297,6 +411,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
297
411
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
298
412
|
if (this.profile !== undefined)
|
|
299
413
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
414
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
300
415
|
if (hasParameter('matterlogger')) {
|
|
301
416
|
const level = getParameter('matterlogger');
|
|
302
417
|
if (level === 'debug') {
|
|
@@ -328,6 +443,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
328
443
|
Logger.format = MatterLogFormat.ANSI;
|
|
329
444
|
Logger.setLogger('default', this.createMatterLogger());
|
|
330
445
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
446
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
331
447
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
332
448
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
333
449
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -336,6 +452,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
336
452
|
});
|
|
337
453
|
}
|
|
338
454
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
455
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
339
456
|
if (hasParameter('mdnsinterface')) {
|
|
340
457
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
341
458
|
}
|
|
@@ -344,6 +461,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
344
461
|
if (this.mdnsInterface === '')
|
|
345
462
|
this.mdnsInterface = undefined;
|
|
346
463
|
}
|
|
464
|
+
// Validate mdnsInterface
|
|
347
465
|
if (this.mdnsInterface) {
|
|
348
466
|
const networkInterfaces = os.networkInterfaces();
|
|
349
467
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -357,6 +475,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
357
475
|
}
|
|
358
476
|
if (this.mdnsInterface)
|
|
359
477
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
478
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
360
479
|
if (hasParameter('ipv4address')) {
|
|
361
480
|
this.ipv4address = getParameter('ipv4address');
|
|
362
481
|
}
|
|
@@ -365,6 +484,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
365
484
|
if (this.ipv4address === '')
|
|
366
485
|
this.ipv4address = undefined;
|
|
367
486
|
}
|
|
487
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
368
488
|
if (hasParameter('ipv6address')) {
|
|
369
489
|
this.ipv6address = getParameter('ipv6address');
|
|
370
490
|
}
|
|
@@ -373,14 +493,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
373
493
|
if (this.ipv6address === '')
|
|
374
494
|
this.ipv6address = undefined;
|
|
375
495
|
}
|
|
496
|
+
// Initialize PluginManager
|
|
376
497
|
this.plugins = new PluginManager(this);
|
|
377
498
|
await this.plugins.loadFromStorage();
|
|
378
499
|
this.plugins.logLevel = this.log.logLevel;
|
|
500
|
+
// Initialize DeviceManager
|
|
379
501
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
380
502
|
this.devices.logLevel = this.log.logLevel;
|
|
503
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
381
504
|
for (const plugin of this.plugins) {
|
|
382
505
|
const packageJson = await this.plugins.parse(plugin);
|
|
383
506
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
507
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
508
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
384
509
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
385
510
|
try {
|
|
386
511
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -402,6 +527,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
402
527
|
await plugin.nodeContext.set('description', plugin.description);
|
|
403
528
|
await plugin.nodeContext.set('author', plugin.author);
|
|
404
529
|
}
|
|
530
|
+
// Log system info and create .matterbridge directory
|
|
405
531
|
await this.logNodeAndSystemInfo();
|
|
406
532
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
407
533
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -409,6 +535,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
409
535
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
410
536
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
411
537
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
538
|
+
// Check node version and throw error
|
|
412
539
|
const minNodeVersion = 18;
|
|
413
540
|
const nodeVersion = process.versions.node;
|
|
414
541
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -416,9 +543,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
416
543
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
417
544
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
418
545
|
}
|
|
546
|
+
// Parse command line
|
|
419
547
|
await this.parseCommandLine();
|
|
420
548
|
this.initialized = true;
|
|
421
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
552
|
+
* @private
|
|
553
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
554
|
+
*/
|
|
422
555
|
async parseCommandLine() {
|
|
423
556
|
if (hasParameter('help')) {
|
|
424
557
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -530,6 +663,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
530
663
|
this.shutdown = true;
|
|
531
664
|
return;
|
|
532
665
|
}
|
|
666
|
+
// Start the matter storage and create the matterbridge context
|
|
533
667
|
try {
|
|
534
668
|
await this.startMatterStorage();
|
|
535
669
|
}
|
|
@@ -537,12 +671,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
537
671
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
538
672
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
539
673
|
}
|
|
674
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
540
675
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
541
676
|
this.initialized = true;
|
|
542
677
|
await this.shutdownProcessAndReset();
|
|
543
678
|
this.shutdown = true;
|
|
544
679
|
return;
|
|
545
680
|
}
|
|
681
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
546
682
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
547
683
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
548
684
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -567,30 +703,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
567
703
|
this.shutdown = true;
|
|
568
704
|
return;
|
|
569
705
|
}
|
|
706
|
+
// Initialize frontend
|
|
570
707
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
571
708
|
await this.frontend.start(getIntParameter('frontend'));
|
|
709
|
+
// Check in 30 seconds the latest versions
|
|
572
710
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
573
711
|
const { checkUpdates } = await import('./update.js');
|
|
574
712
|
checkUpdates(this);
|
|
575
713
|
}, 30 * 1000).unref();
|
|
714
|
+
// Check each 24 hours the latest versions
|
|
576
715
|
this.checkUpdateInterval = setInterval(async () => {
|
|
577
716
|
const { checkUpdates } = await import('./update.js');
|
|
578
717
|
checkUpdates(this);
|
|
579
718
|
}, 24 * 60 * 60 * 1000).unref();
|
|
719
|
+
// Start the matterbridge in mode test
|
|
580
720
|
if (hasParameter('test')) {
|
|
581
721
|
this.bridgeMode = 'bridge';
|
|
582
722
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
583
723
|
return;
|
|
584
724
|
}
|
|
725
|
+
// Start the matterbridge in mode controller
|
|
585
726
|
if (hasParameter('controller')) {
|
|
586
727
|
this.bridgeMode = 'controller';
|
|
587
728
|
await this.startController();
|
|
588
729
|
return;
|
|
589
730
|
}
|
|
731
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
590
732
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
591
733
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
592
734
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
593
735
|
}
|
|
736
|
+
// Start matterbridge in bridge mode
|
|
594
737
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
595
738
|
this.bridgeMode = 'bridge';
|
|
596
739
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -598,6 +741,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
598
741
|
await this.startBridge();
|
|
599
742
|
return;
|
|
600
743
|
}
|
|
744
|
+
// Start matterbridge in childbridge mode
|
|
601
745
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
602
746
|
this.bridgeMode = 'childbridge';
|
|
603
747
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -606,10 +750,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
606
750
|
return;
|
|
607
751
|
}
|
|
608
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* Asynchronously loads and starts the registered plugins.
|
|
755
|
+
*
|
|
756
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
757
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
758
|
+
*
|
|
759
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
760
|
+
*/
|
|
609
761
|
async startPlugins() {
|
|
762
|
+
// Check, load and start the plugins
|
|
610
763
|
for (const plugin of this.plugins) {
|
|
611
764
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
612
765
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
766
|
+
// Check if the plugin is available
|
|
613
767
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
614
768
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
615
769
|
plugin.enabled = false;
|
|
@@ -629,20 +783,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
629
783
|
plugin.addedDevices = undefined;
|
|
630
784
|
plugin.qrPairingCode = undefined;
|
|
631
785
|
plugin.manualPairingCode = undefined;
|
|
632
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
786
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
633
787
|
}
|
|
634
788
|
this.frontend.wssSendRefreshRequired();
|
|
635
789
|
}
|
|
790
|
+
/**
|
|
791
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
792
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
793
|
+
*/
|
|
636
794
|
registerProcessHandlers() {
|
|
637
795
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
638
796
|
process.removeAllListeners('uncaughtException');
|
|
639
797
|
process.removeAllListeners('unhandledRejection');
|
|
640
798
|
this.exceptionHandler = async (error) => {
|
|
641
799
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
800
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
642
801
|
};
|
|
643
802
|
process.on('uncaughtException', this.exceptionHandler);
|
|
644
803
|
this.rejectionHandler = async (reason, promise) => {
|
|
645
804
|
this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
805
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
646
806
|
};
|
|
647
807
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
648
808
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -655,6 +815,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
655
815
|
};
|
|
656
816
|
process.on('SIGTERM', this.sigtermHandler);
|
|
657
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
820
|
+
*/
|
|
658
821
|
deregisterProcesslHandlers() {
|
|
659
822
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
660
823
|
if (this.exceptionHandler)
|
|
@@ -671,12 +834,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
671
834
|
process.off('SIGTERM', this.sigtermHandler);
|
|
672
835
|
this.sigtermHandler = undefined;
|
|
673
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Logs the node and system information.
|
|
839
|
+
*/
|
|
674
840
|
async logNodeAndSystemInfo() {
|
|
841
|
+
// IP address information
|
|
675
842
|
const networkInterfaces = os.networkInterfaces();
|
|
676
843
|
this.systemInformation.interfaceName = '';
|
|
677
844
|
this.systemInformation.ipv4Address = '';
|
|
678
845
|
this.systemInformation.ipv6Address = '';
|
|
679
846
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
847
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
680
848
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
681
849
|
continue;
|
|
682
850
|
if (!interfaceDetails) {
|
|
@@ -702,19 +870,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
702
870
|
break;
|
|
703
871
|
}
|
|
704
872
|
}
|
|
873
|
+
// Node information
|
|
705
874
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
706
875
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
707
876
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
708
877
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
878
|
+
// Host system information
|
|
709
879
|
this.systemInformation.hostname = os.hostname();
|
|
710
880
|
this.systemInformation.user = os.userInfo().username;
|
|
711
|
-
this.systemInformation.osType = os.type();
|
|
712
|
-
this.systemInformation.osRelease = os.release();
|
|
713
|
-
this.systemInformation.osPlatform = os.platform();
|
|
714
|
-
this.systemInformation.osArch = os.arch();
|
|
715
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
716
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
717
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
881
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
882
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
883
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
884
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
885
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
886
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
887
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
888
|
+
// Log the system information
|
|
718
889
|
this.log.debug('Host System Information:');
|
|
719
890
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
720
891
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -730,16 +901,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
730
901
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
731
902
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
732
903
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
904
|
+
// Home directory
|
|
733
905
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
734
906
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
735
907
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
908
|
+
// Package root directory
|
|
736
909
|
const { fileURLToPath } = await import('node:url');
|
|
737
910
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
738
911
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
739
912
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
740
913
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
914
|
+
// Global node_modules directory
|
|
741
915
|
if (this.nodeContext)
|
|
742
916
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
917
|
+
// First run of Matterbridge so the node storage is empty
|
|
743
918
|
if (this.globalModulesDirectory === '') {
|
|
744
919
|
try {
|
|
745
920
|
this.execRunningCount++;
|
|
@@ -755,6 +930,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
755
930
|
}
|
|
756
931
|
else
|
|
757
932
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
933
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
934
|
+
else {
|
|
935
|
+
this.getGlobalNodeModules()
|
|
936
|
+
.then(async (globalModulesDirectory) => {
|
|
937
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
938
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
939
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
940
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
941
|
+
})
|
|
942
|
+
.catch((error) => {
|
|
943
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
944
|
+
});
|
|
945
|
+
}*/
|
|
946
|
+
// Create the data directory .matterbridge in the home directory
|
|
758
947
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
759
948
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
760
949
|
try {
|
|
@@ -778,6 +967,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
778
967
|
}
|
|
779
968
|
}
|
|
780
969
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
970
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
781
971
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
782
972
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
783
973
|
try {
|
|
@@ -801,50 +991,68 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
991
|
}
|
|
802
992
|
}
|
|
803
993
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
994
|
+
// Matterbridge version
|
|
804
995
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
805
996
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
806
997
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
807
998
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
999
|
+
// Matterbridge latest version
|
|
808
1000
|
if (this.nodeContext)
|
|
809
1001
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
810
1002
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1003
|
+
// this.getMatterbridgeLatestVersion();
|
|
1004
|
+
// Current working directory
|
|
811
1005
|
const currentDir = process.cwd();
|
|
812
1006
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1007
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
813
1008
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
814
1009
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
815
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1013
|
+
*
|
|
1014
|
+
* @returns {Function} The MatterLogger function.
|
|
1015
|
+
*/
|
|
816
1016
|
createMatterLogger() {
|
|
817
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1017
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
818
1018
|
return (_level, formattedLog) => {
|
|
819
1019
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
820
1020
|
const message = formattedLog.slice(65);
|
|
821
1021
|
matterLogger.logName = logger;
|
|
822
1022
|
switch (_level) {
|
|
823
1023
|
case MatterLogLevel.DEBUG:
|
|
824
|
-
matterLogger.log("debug"
|
|
1024
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
825
1025
|
break;
|
|
826
1026
|
case MatterLogLevel.INFO:
|
|
827
|
-
matterLogger.log("info"
|
|
1027
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
828
1028
|
break;
|
|
829
1029
|
case MatterLogLevel.NOTICE:
|
|
830
|
-
matterLogger.log("notice"
|
|
1030
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
831
1031
|
break;
|
|
832
1032
|
case MatterLogLevel.WARN:
|
|
833
|
-
matterLogger.log("warn"
|
|
1033
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
834
1034
|
break;
|
|
835
1035
|
case MatterLogLevel.ERROR:
|
|
836
|
-
matterLogger.log("error"
|
|
1036
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
837
1037
|
break;
|
|
838
1038
|
case MatterLogLevel.FATAL:
|
|
839
|
-
matterLogger.log("fatal"
|
|
1039
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
840
1040
|
break;
|
|
841
1041
|
default:
|
|
842
|
-
matterLogger.log("debug"
|
|
1042
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
843
1043
|
break;
|
|
844
1044
|
}
|
|
845
1045
|
};
|
|
846
1046
|
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Creates a Matter File Logger.
|
|
1049
|
+
*
|
|
1050
|
+
* @param {string} filePath - The path to the log file.
|
|
1051
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1052
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1053
|
+
*/
|
|
847
1054
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1055
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
848
1056
|
let fileSize = 0;
|
|
849
1057
|
if (unlink) {
|
|
850
1058
|
try {
|
|
@@ -893,12 +1101,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
893
1101
|
}
|
|
894
1102
|
};
|
|
895
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1106
|
+
*/
|
|
896
1107
|
async restartProcess() {
|
|
897
1108
|
await this.cleanup('restarting...', true);
|
|
898
1109
|
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Shut down the process by exiting the current process.
|
|
1112
|
+
*/
|
|
899
1113
|
async shutdownProcess() {
|
|
900
1114
|
await this.cleanup('shutting down...', false);
|
|
901
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Update matterbridge and and shut down the process.
|
|
1118
|
+
*/
|
|
902
1119
|
async updateProcess() {
|
|
903
1120
|
this.log.info('Updating matterbridge...');
|
|
904
1121
|
try {
|
|
@@ -911,51 +1128,72 @@ export class Matterbridge extends EventEmitter {
|
|
|
911
1128
|
this.frontend.wssSendRestartRequired();
|
|
912
1129
|
await this.cleanup('updating...', false);
|
|
913
1130
|
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Unregister all devices and shut down the process.
|
|
1133
|
+
*/
|
|
914
1134
|
async unregisterAndShutdownProcess() {
|
|
915
1135
|
this.log.info('Unregistering all devices and shutting down...');
|
|
916
1136
|
for (const plugin of this.plugins) {
|
|
917
1137
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
918
1138
|
}
|
|
919
1139
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
920
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1140
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
921
1141
|
this.log.debug('Cleaning up and shutting down...');
|
|
922
1142
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
923
1143
|
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Reset commissioning and shut down the process.
|
|
1146
|
+
*/
|
|
924
1147
|
async shutdownProcessAndReset() {
|
|
925
1148
|
await this.cleanup('shutting down with reset...', false);
|
|
926
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Factory reset and shut down the process.
|
|
1152
|
+
*/
|
|
927
1153
|
async shutdownProcessAndFactoryReset() {
|
|
928
1154
|
await this.cleanup('shutting down with factory reset...', false);
|
|
929
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Cleans up the Matterbridge instance.
|
|
1158
|
+
* @param message - The cleanup message.
|
|
1159
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1160
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1161
|
+
*/
|
|
930
1162
|
async cleanup(message, restart = false) {
|
|
931
1163
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
932
1164
|
this.hasCleanupStarted = true;
|
|
933
1165
|
this.log.info(message);
|
|
1166
|
+
// Clear the start matter interval
|
|
934
1167
|
if (this.startMatterInterval) {
|
|
935
1168
|
clearInterval(this.startMatterInterval);
|
|
936
1169
|
this.startMatterInterval = undefined;
|
|
937
1170
|
this.log.debug('Start matter interval cleared');
|
|
938
1171
|
}
|
|
1172
|
+
// Clear the check update timeout
|
|
939
1173
|
if (this.checkUpdateTimeout) {
|
|
940
1174
|
clearInterval(this.checkUpdateTimeout);
|
|
941
1175
|
this.checkUpdateTimeout = undefined;
|
|
942
1176
|
this.log.debug('Check update timeout cleared');
|
|
943
1177
|
}
|
|
1178
|
+
// Clear the check update interval
|
|
944
1179
|
if (this.checkUpdateInterval) {
|
|
945
1180
|
clearInterval(this.checkUpdateInterval);
|
|
946
1181
|
this.checkUpdateInterval = undefined;
|
|
947
1182
|
this.log.debug('Check update interval cleared');
|
|
948
1183
|
}
|
|
1184
|
+
// Clear the configure timeout
|
|
949
1185
|
if (this.configureTimeout) {
|
|
950
1186
|
clearTimeout(this.configureTimeout);
|
|
951
1187
|
this.configureTimeout = undefined;
|
|
952
1188
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
953
1189
|
}
|
|
1190
|
+
// Clear the reachability timeout
|
|
954
1191
|
if (this.reachabilityTimeout) {
|
|
955
1192
|
clearTimeout(this.reachabilityTimeout);
|
|
956
1193
|
this.reachabilityTimeout = undefined;
|
|
957
1194
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
958
1195
|
}
|
|
1196
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
959
1197
|
for (const plugin of this.plugins) {
|
|
960
1198
|
if (!plugin.enabled || plugin.error)
|
|
961
1199
|
continue;
|
|
@@ -966,9 +1204,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
966
1204
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
967
1205
|
}
|
|
968
1206
|
}
|
|
1207
|
+
// Stopping matter server nodes
|
|
969
1208
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
970
1209
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
971
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1210
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
972
1211
|
if (this.bridgeMode === 'bridge') {
|
|
973
1212
|
if (this.serverNode) {
|
|
974
1213
|
await this.stopServerNode(this.serverNode);
|
|
@@ -984,6 +1223,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
1223
|
}
|
|
985
1224
|
}
|
|
986
1225
|
this.log.notice('Stopped matter server nodes');
|
|
1226
|
+
// Matter commisioning reset
|
|
987
1227
|
if (message === 'shutting down with reset...') {
|
|
988
1228
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
989
1229
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -993,17 +1233,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
993
1233
|
await this.matterbridgeContext?.clearAll();
|
|
994
1234
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
995
1235
|
}
|
|
1236
|
+
// Stop matter storage
|
|
996
1237
|
await this.stopMatterStorage();
|
|
1238
|
+
// Stop the frontend
|
|
997
1239
|
await this.frontend.stop();
|
|
1240
|
+
// Remove the matterfilelogger
|
|
998
1241
|
try {
|
|
999
1242
|
Logger.removeLogger('matterfilelogger');
|
|
1243
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1000
1244
|
}
|
|
1001
1245
|
catch (error) {
|
|
1246
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1002
1247
|
}
|
|
1248
|
+
// Serialize registeredDevices
|
|
1003
1249
|
if (this.nodeStorage && this.nodeContext) {
|
|
1250
|
+
/*
|
|
1251
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1252
|
+
this.log.info('Saving registered devices...');
|
|
1253
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1254
|
+
this.devices.forEach(async (device) => {
|
|
1255
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1256
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1257
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1258
|
+
});
|
|
1259
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1260
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1261
|
+
*/
|
|
1262
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1004
1263
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1005
1264
|
await this.nodeContext.close();
|
|
1006
1265
|
this.nodeContext = undefined;
|
|
1266
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1007
1267
|
for (const plugin of this.plugins) {
|
|
1008
1268
|
if (plugin.nodeContext) {
|
|
1009
1269
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1020,8 +1280,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1020
1280
|
}
|
|
1021
1281
|
this.plugins.clear();
|
|
1022
1282
|
this.devices.clear();
|
|
1283
|
+
// Factory reset
|
|
1023
1284
|
if (message === 'shutting down with factory reset...') {
|
|
1024
1285
|
try {
|
|
1286
|
+
// Delete old matter storage file and backup
|
|
1025
1287
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1026
1288
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1027
1289
|
await fs.unlink(file);
|
|
@@ -1035,6 +1297,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1035
1297
|
}
|
|
1036
1298
|
}
|
|
1037
1299
|
try {
|
|
1300
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1038
1301
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1039
1302
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1040
1303
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1048,6 +1311,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1048
1311
|
}
|
|
1049
1312
|
}
|
|
1050
1313
|
try {
|
|
1314
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1051
1315
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1052
1316
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1053
1317
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1062,12 +1326,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1062
1326
|
}
|
|
1063
1327
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1064
1328
|
}
|
|
1329
|
+
// Deregisters the process handlers
|
|
1065
1330
|
this.deregisterProcesslHandlers();
|
|
1066
1331
|
if (restart) {
|
|
1067
1332
|
if (message === 'updating...') {
|
|
1068
1333
|
this.log.info('Cleanup completed. Updating...');
|
|
1069
1334
|
Matterbridge.instance = undefined;
|
|
1070
|
-
this.emit('update');
|
|
1335
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1071
1336
|
}
|
|
1072
1337
|
else if (message === 'restarting...') {
|
|
1073
1338
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1087,6 +1352,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1087
1352
|
this.log.debug('Cleanup already started...');
|
|
1088
1353
|
}
|
|
1089
1354
|
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1357
|
+
*
|
|
1358
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1359
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1360
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1361
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1362
|
+
*/
|
|
1090
1363
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1091
1364
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1092
1365
|
plugin.locked = true;
|
|
@@ -1099,6 +1372,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1099
1372
|
await this.startServerNode(plugin.serverNode);
|
|
1100
1373
|
}
|
|
1101
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1377
|
+
*
|
|
1378
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1379
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1380
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1381
|
+
*/
|
|
1102
1382
|
async createDynamicPlugin(plugin, start = false) {
|
|
1103
1383
|
if (!plugin.locked) {
|
|
1104
1384
|
plugin.locked = true;
|
|
@@ -1110,7 +1390,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1110
1390
|
await this.startServerNode(plugin.serverNode);
|
|
1111
1391
|
}
|
|
1112
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Starts the Matterbridge in bridge mode.
|
|
1395
|
+
* @private
|
|
1396
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1397
|
+
*/
|
|
1113
1398
|
async startBridge() {
|
|
1399
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1114
1400
|
if (!this.matterStorageManager)
|
|
1115
1401
|
throw new Error('No storage manager initialized');
|
|
1116
1402
|
if (!this.matterbridgeContext)
|
|
@@ -1147,7 +1433,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1147
1433
|
clearInterval(this.startMatterInterval);
|
|
1148
1434
|
this.startMatterInterval = undefined;
|
|
1149
1435
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1436
|
+
// Start the Matter server node
|
|
1150
1437
|
this.startServerNode(this.serverNode);
|
|
1438
|
+
// Configure the plugins
|
|
1151
1439
|
this.configureTimeout = setTimeout(async () => {
|
|
1152
1440
|
for (const plugin of this.plugins) {
|
|
1153
1441
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1162,6 +1450,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1162
1450
|
}
|
|
1163
1451
|
this.frontend.wssSendRefreshRequired();
|
|
1164
1452
|
}, 30 * 1000);
|
|
1453
|
+
// Setting reachability to true
|
|
1165
1454
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1166
1455
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1167
1456
|
if (this.aggregatorNode)
|
|
@@ -1170,6 +1459,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1170
1459
|
}, 60 * 1000);
|
|
1171
1460
|
}, 1000);
|
|
1172
1461
|
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1464
|
+
* @private
|
|
1465
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1466
|
+
*/
|
|
1173
1467
|
async startChildbridge() {
|
|
1174
1468
|
if (!this.matterStorageManager)
|
|
1175
1469
|
throw new Error('No storage manager initialized');
|
|
@@ -1213,12 +1507,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1213
1507
|
clearInterval(this.startMatterInterval);
|
|
1214
1508
|
this.startMatterInterval = undefined;
|
|
1215
1509
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1510
|
+
// Configure the plugins
|
|
1216
1511
|
this.configureTimeout = setTimeout(async () => {
|
|
1217
1512
|
for (const plugin of this.plugins) {
|
|
1218
1513
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1219
1514
|
continue;
|
|
1220
1515
|
try {
|
|
1221
|
-
await this.plugins.configure(plugin);
|
|
1516
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1222
1517
|
}
|
|
1223
1518
|
catch (error) {
|
|
1224
1519
|
plugin.error = true;
|
|
@@ -1246,7 +1541,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1246
1541
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1247
1542
|
continue;
|
|
1248
1543
|
}
|
|
1544
|
+
// Start the Matter server node
|
|
1249
1545
|
this.startServerNode(plugin.serverNode);
|
|
1546
|
+
// Setting reachability to true
|
|
1250
1547
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1251
1548
|
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}`);
|
|
1252
1549
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1256,9 +1553,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1256
1553
|
}
|
|
1257
1554
|
}, 1000);
|
|
1258
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Starts the Matterbridge controller.
|
|
1558
|
+
* @private
|
|
1559
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1560
|
+
*/
|
|
1259
1561
|
async startController() {
|
|
1562
|
+
/*
|
|
1563
|
+
if (!this.storageManager) {
|
|
1564
|
+
this.log.error('No storage manager initialized');
|
|
1565
|
+
await this.cleanup('No storage manager initialized');
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1569
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1570
|
+
if (!this.mattercontrollerContext) {
|
|
1571
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1572
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1577
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1578
|
+
this.log.info('Creating matter commissioning controller');
|
|
1579
|
+
this.commissioningController = new CommissioningController({
|
|
1580
|
+
autoConnect: false,
|
|
1581
|
+
});
|
|
1582
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1583
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1584
|
+
|
|
1585
|
+
this.log.info('Starting matter server');
|
|
1586
|
+
await this.matterServer.start();
|
|
1587
|
+
this.log.info('Matter server started');
|
|
1588
|
+
|
|
1589
|
+
if (hasParameter('pairingcode')) {
|
|
1590
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1591
|
+
const pairingCode = getParameter('pairingcode');
|
|
1592
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1593
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1594
|
+
|
|
1595
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1596
|
+
if (pairingCode !== undefined) {
|
|
1597
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1598
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1599
|
+
longDiscriminator = undefined;
|
|
1600
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1601
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1602
|
+
} else {
|
|
1603
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1604
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1605
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1606
|
+
}
|
|
1607
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1608
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1612
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1613
|
+
regulatoryCountryCode: 'XX',
|
|
1614
|
+
};
|
|
1615
|
+
const options = {
|
|
1616
|
+
commissioning: commissioningOptions,
|
|
1617
|
+
discovery: {
|
|
1618
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1619
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1620
|
+
},
|
|
1621
|
+
passcode: setupPin,
|
|
1622
|
+
} as NodeCommissioningOptions;
|
|
1623
|
+
this.log.info('Commissioning with options:', options);
|
|
1624
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1625
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1626
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1627
|
+
} // (hasParameter('pairingcode'))
|
|
1628
|
+
|
|
1629
|
+
if (hasParameter('unpairall')) {
|
|
1630
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1631
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1632
|
+
for (const nodeId of nodeIds) {
|
|
1633
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1634
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1635
|
+
}
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
if (hasParameter('discover')) {
|
|
1640
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1641
|
+
// console.log(discover);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1645
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1650
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1651
|
+
for (const nodeId of nodeIds) {
|
|
1652
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1653
|
+
|
|
1654
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1655
|
+
autoSubscribe: false,
|
|
1656
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1657
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1658
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1659
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1660
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1661
|
+
switch (info) {
|
|
1662
|
+
case NodeStateInformation.Connected:
|
|
1663
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1664
|
+
break;
|
|
1665
|
+
case NodeStateInformation.Disconnected:
|
|
1666
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1667
|
+
break;
|
|
1668
|
+
case NodeStateInformation.Reconnecting:
|
|
1669
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1670
|
+
break;
|
|
1671
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1672
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1673
|
+
break;
|
|
1674
|
+
case NodeStateInformation.StructureChanged:
|
|
1675
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1676
|
+
break;
|
|
1677
|
+
case NodeStateInformation.Decommissioned:
|
|
1678
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1679
|
+
break;
|
|
1680
|
+
default:
|
|
1681
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1682
|
+
break;
|
|
1683
|
+
}
|
|
1684
|
+
},
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
node.logStructure();
|
|
1688
|
+
|
|
1689
|
+
// Get the interaction client
|
|
1690
|
+
this.log.info('Getting the interaction client');
|
|
1691
|
+
const interactionClient = await node.getInteractionClient();
|
|
1692
|
+
let cluster;
|
|
1693
|
+
let attributes;
|
|
1694
|
+
|
|
1695
|
+
// Log BasicInformationCluster
|
|
1696
|
+
cluster = BasicInformationCluster;
|
|
1697
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1698
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1699
|
+
});
|
|
1700
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1701
|
+
attributes.forEach((attribute) => {
|
|
1702
|
+
this.log.info(
|
|
1703
|
+
`- 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}`,
|
|
1704
|
+
);
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
// Log PowerSourceCluster
|
|
1708
|
+
cluster = PowerSourceCluster;
|
|
1709
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1710
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1711
|
+
});
|
|
1712
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1713
|
+
attributes.forEach((attribute) => {
|
|
1714
|
+
this.log.info(
|
|
1715
|
+
`- 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}`,
|
|
1716
|
+
);
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
// Log ThreadNetworkDiagnostics
|
|
1720
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1721
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1722
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1723
|
+
});
|
|
1724
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1725
|
+
attributes.forEach((attribute) => {
|
|
1726
|
+
this.log.info(
|
|
1727
|
+
`- 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}`,
|
|
1728
|
+
);
|
|
1729
|
+
});
|
|
1730
|
+
|
|
1731
|
+
// Log SwitchCluster
|
|
1732
|
+
cluster = SwitchCluster;
|
|
1733
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1734
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1735
|
+
});
|
|
1736
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1737
|
+
attributes.forEach((attribute) => {
|
|
1738
|
+
this.log.info(
|
|
1739
|
+
`- 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}`,
|
|
1740
|
+
);
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1744
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1745
|
+
ignoreInitialTriggers: false,
|
|
1746
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1747
|
+
this.log.info(
|
|
1748
|
+
`***${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}`,
|
|
1749
|
+
),
|
|
1750
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1751
|
+
this.log.info(
|
|
1752
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1753
|
+
);
|
|
1754
|
+
},
|
|
1755
|
+
});
|
|
1756
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1757
|
+
}
|
|
1758
|
+
*/
|
|
1260
1759
|
}
|
|
1760
|
+
/** ***********************************************************************************************************************************/
|
|
1761
|
+
/** Matter.js methods */
|
|
1762
|
+
/** ***********************************************************************************************************************************/
|
|
1763
|
+
/**
|
|
1764
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1765
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1766
|
+
*/
|
|
1261
1767
|
async startMatterStorage() {
|
|
1768
|
+
// Setup Matter storage
|
|
1262
1769
|
this.log.info(`Starting matter node storage...`);
|
|
1263
1770
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1264
1771
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1266,13 +1773,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1266
1773
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1267
1774
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1268
1775
|
this.log.info('Matter node storage started');
|
|
1776
|
+
// Backup matter storage since it is created/opened correctly
|
|
1269
1777
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1270
1778
|
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1781
|
+
*
|
|
1782
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1783
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1784
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1785
|
+
*/
|
|
1271
1786
|
async backupMatterStorage(storageName, backupName) {
|
|
1272
1787
|
this.log.info('Creating matter node storage backup...');
|
|
1273
1788
|
await copyDirectory(storageName, backupName);
|
|
1274
1789
|
this.log.info('Created matter node storage backup');
|
|
1275
1790
|
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Stops the matter storage.
|
|
1793
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1794
|
+
*/
|
|
1276
1795
|
async stopMatterStorage() {
|
|
1277
1796
|
this.log.info('Closing matter node storage...');
|
|
1278
1797
|
this.matterStorageManager?.close();
|
|
@@ -1281,6 +1800,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1281
1800
|
this.matterbridgeContext = undefined;
|
|
1282
1801
|
this.log.info('Matter node storage closed');
|
|
1283
1802
|
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Creates a server node storage context.
|
|
1805
|
+
*
|
|
1806
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1807
|
+
* @param {string} deviceName - The name of the device.
|
|
1808
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1809
|
+
* @param {number} vendorId - The vendor ID.
|
|
1810
|
+
* @param {string} vendorName - The vendor name.
|
|
1811
|
+
* @param {number} productId - The product ID.
|
|
1812
|
+
* @param {string} productName - The product name.
|
|
1813
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1814
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1815
|
+
*/
|
|
1284
1816
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1285
1817
|
const { randomBytes } = await import('node:crypto');
|
|
1286
1818
|
if (!this.matterStorageService)
|
|
@@ -1314,6 +1846,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1314
1846
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1315
1847
|
return storageContext;
|
|
1316
1848
|
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Creates a server node.
|
|
1851
|
+
*
|
|
1852
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1853
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1854
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1855
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1856
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1857
|
+
*/
|
|
1317
1858
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1318
1859
|
const storeId = await storageContext.get('storeId');
|
|
1319
1860
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1323,21 +1864,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1323
1864
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1324
1865
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1325
1866
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1867
|
+
/**
|
|
1868
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1869
|
+
*/
|
|
1326
1870
|
const serverNode = await ServerNode.create({
|
|
1871
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1327
1872
|
id: storeId,
|
|
1873
|
+
// Provide Network relevant configuration like the port
|
|
1874
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1328
1875
|
network: {
|
|
1329
1876
|
listeningAddressIpv4: this.ipv4address,
|
|
1330
1877
|
listeningAddressIpv6: this.ipv6address,
|
|
1331
1878
|
port,
|
|
1332
1879
|
},
|
|
1880
|
+
// Provide Commissioning relevant settings
|
|
1881
|
+
// Optional for development/testing purposes
|
|
1333
1882
|
commissioning: {
|
|
1334
1883
|
passcode,
|
|
1335
1884
|
discriminator,
|
|
1336
1885
|
},
|
|
1886
|
+
// Provide Node announcement settings
|
|
1887
|
+
// Optional: If Ommitted some development defaults are used
|
|
1337
1888
|
productDescription: {
|
|
1338
1889
|
name: await storageContext.get('deviceName'),
|
|
1339
1890
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1340
1891
|
},
|
|
1892
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1893
|
+
// Optional: If Omitted some development defaults are used
|
|
1341
1894
|
basicInformation: {
|
|
1342
1895
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1343
1896
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1354,12 +1907,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1354
1907
|
},
|
|
1355
1908
|
});
|
|
1356
1909
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1910
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1357
1911
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1358
1912
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1359
1913
|
if (this.bridgeMode === 'bridge') {
|
|
1360
1914
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1361
1915
|
if (resetSessions)
|
|
1362
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1916
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1363
1917
|
this.matterbridgePaired = true;
|
|
1364
1918
|
}
|
|
1365
1919
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1367,13 +1921,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1367
1921
|
if (plugin) {
|
|
1368
1922
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1369
1923
|
if (resetSessions)
|
|
1370
|
-
plugin.sessionInformations = undefined;
|
|
1924
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1371
1925
|
plugin.paired = true;
|
|
1372
1926
|
}
|
|
1373
1927
|
}
|
|
1374
1928
|
};
|
|
1929
|
+
/**
|
|
1930
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1931
|
+
* This means: It is added to the first fabric.
|
|
1932
|
+
*/
|
|
1375
1933
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1934
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1376
1935
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1936
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1377
1937
|
serverNode.lifecycle.online.on(async () => {
|
|
1378
1938
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1379
1939
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1420,6 +1980,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1420
1980
|
this.frontend.wssSendRefreshRequired();
|
|
1421
1981
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`);
|
|
1422
1982
|
});
|
|
1983
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1423
1984
|
serverNode.lifecycle.offline.on(() => {
|
|
1424
1985
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1425
1986
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1441,6 +2002,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1441
2002
|
}
|
|
1442
2003
|
this.frontend.wssSendRefreshRequired();
|
|
1443
2004
|
});
|
|
2005
|
+
/**
|
|
2006
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2007
|
+
* information is needed.
|
|
2008
|
+
*/
|
|
1444
2009
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1445
2010
|
let action = '';
|
|
1446
2011
|
switch (fabricAction) {
|
|
@@ -1474,16 +2039,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1474
2039
|
}
|
|
1475
2040
|
}
|
|
1476
2041
|
};
|
|
2042
|
+
/**
|
|
2043
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2044
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2045
|
+
*/
|
|
1477
2046
|
serverNode.events.sessions.opened.on((session) => {
|
|
1478
2047
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1479
2048
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1480
2049
|
this.frontend.wssSendRefreshRequired();
|
|
1481
2050
|
});
|
|
2051
|
+
/**
|
|
2052
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2053
|
+
*/
|
|
1482
2054
|
serverNode.events.sessions.closed.on((session) => {
|
|
1483
2055
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1484
2056
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1485
2057
|
this.frontend.wssSendRefreshRequired();
|
|
1486
2058
|
});
|
|
2059
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1487
2060
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1488
2061
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1489
2062
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1492,24 +2065,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1492
2065
|
this.log.info(`Created server node for ${storeId}`);
|
|
1493
2066
|
return serverNode;
|
|
1494
2067
|
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Starts the specified server node.
|
|
2070
|
+
*
|
|
2071
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2072
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2073
|
+
*/
|
|
1495
2074
|
async startServerNode(matterServerNode) {
|
|
1496
2075
|
if (!matterServerNode)
|
|
1497
2076
|
return;
|
|
1498
2077
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1499
2078
|
await matterServerNode.start();
|
|
1500
2079
|
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Stops the specified server node.
|
|
2082
|
+
*
|
|
2083
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2084
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2085
|
+
*/
|
|
1501
2086
|
async stopServerNode(matterServerNode) {
|
|
1502
2087
|
if (!matterServerNode)
|
|
1503
2088
|
return;
|
|
1504
2089
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1505
2090
|
try {
|
|
1506
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2091
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1507
2092
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1508
2093
|
}
|
|
1509
2094
|
catch (error) {
|
|
1510
2095
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1511
2096
|
}
|
|
1512
2097
|
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Advertises the specified server node.
|
|
2100
|
+
*
|
|
2101
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2102
|
+
* @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.
|
|
2103
|
+
*/
|
|
1513
2104
|
async advertiseServerNode(matterServerNode) {
|
|
1514
2105
|
if (matterServerNode) {
|
|
1515
2106
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1518,23 +2109,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
1518
2109
|
return { qrPairingCode, manualPairingCode };
|
|
1519
2110
|
}
|
|
1520
2111
|
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Stop advertise the specified server node.
|
|
2114
|
+
*
|
|
2115
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2116
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2117
|
+
*/
|
|
1521
2118
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1522
2119
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1523
2120
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1524
2121
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1525
2122
|
}
|
|
1526
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Creates an aggregator node with the specified storage context.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2128
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2129
|
+
*/
|
|
1527
2130
|
async createAggregatorNode(storageContext) {
|
|
1528
2131
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1529
2132
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1530
2133
|
return aggregatorNode;
|
|
1531
2134
|
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2137
|
+
*
|
|
2138
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2139
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2140
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2141
|
+
*/
|
|
1532
2142
|
async addBridgedEndpoint(pluginName, device) {
|
|
2143
|
+
// Check if the plugin is registered
|
|
1533
2144
|
const plugin = this.plugins.get(pluginName);
|
|
1534
2145
|
if (!plugin) {
|
|
1535
2146
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1536
2147
|
return;
|
|
1537
2148
|
}
|
|
2149
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1538
2150
|
if (this.bridgeMode === 'bridge') {
|
|
1539
2151
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1540
2152
|
if (!this.aggregatorNode)
|
|
@@ -1557,16 +2169,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1557
2169
|
plugin.registeredDevices++;
|
|
1558
2170
|
if (plugin.addedDevices !== undefined)
|
|
1559
2171
|
plugin.addedDevices++;
|
|
2172
|
+
// Add the device to the DeviceManager
|
|
1560
2173
|
this.devices.set(device);
|
|
1561
2174
|
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}`);
|
|
1562
2175
|
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2178
|
+
*
|
|
2179
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2180
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2181
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2182
|
+
*/
|
|
1563
2183
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1564
2184
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2185
|
+
// Check if the plugin is registered
|
|
1565
2186
|
const plugin = this.plugins.get(pluginName);
|
|
1566
2187
|
if (!plugin) {
|
|
1567
2188
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1568
2189
|
return;
|
|
1569
2190
|
}
|
|
2191
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1570
2192
|
if (this.bridgeMode === 'bridge') {
|
|
1571
2193
|
if (!this.aggregatorNode) {
|
|
1572
2194
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1581,6 +2203,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1581
2203
|
}
|
|
1582
2204
|
else if (this.bridgeMode === 'childbridge') {
|
|
1583
2205
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2206
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1584
2207
|
}
|
|
1585
2208
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1586
2209
|
if (!plugin.aggregatorNode) {
|
|
@@ -1595,8 +2218,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1595
2218
|
if (plugin.addedDevices !== undefined)
|
|
1596
2219
|
plugin.addedDevices--;
|
|
1597
2220
|
}
|
|
2221
|
+
// Remove the device from the DeviceManager
|
|
1598
2222
|
this.devices.remove(device);
|
|
1599
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2226
|
+
*
|
|
2227
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2228
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2229
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2230
|
+
*/
|
|
1600
2231
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1601
2232
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1602
2233
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1605,6 +2236,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1605
2236
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1606
2237
|
}
|
|
1607
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2241
|
+
*
|
|
2242
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2243
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2244
|
+
*/
|
|
1608
2245
|
sanitizeFabricInformations(fabricInfo) {
|
|
1609
2246
|
return fabricInfo.map((info) => {
|
|
1610
2247
|
return {
|
|
@@ -1618,6 +2255,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1618
2255
|
};
|
|
1619
2256
|
});
|
|
1620
2257
|
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2260
|
+
*
|
|
2261
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2262
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2263
|
+
*/
|
|
1621
2264
|
sanitizeSessionInformation(sessionInfo) {
|
|
1622
2265
|
return sessionInfo
|
|
1623
2266
|
.filter((session) => session.isPeerActive)
|
|
@@ -1645,6 +2288,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1645
2288
|
};
|
|
1646
2289
|
});
|
|
1647
2290
|
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2293
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2294
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2295
|
+
*/
|
|
1648
2296
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
1649
2297
|
for (const child of aggregatorNode.parts) {
|
|
1650
2298
|
this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
|
|
@@ -1690,14 +2338,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1690
2338
|
}
|
|
1691
2339
|
return vendorName;
|
|
1692
2340
|
};
|
|
2341
|
+
/**
|
|
2342
|
+
* Spawns a child process with the given command and arguments.
|
|
2343
|
+
* @param {string} command - The command to execute.
|
|
2344
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2345
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2346
|
+
*/
|
|
1693
2347
|
async spawnCommand(command, args = []) {
|
|
1694
2348
|
const { spawn } = await import('node:child_process');
|
|
2349
|
+
/*
|
|
2350
|
+
npm > npm.cmd on windows
|
|
2351
|
+
cmd.exe ['dir'] on windows
|
|
2352
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2353
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2354
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2358
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2359
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2360
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2361
|
+
*/
|
|
1695
2362
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1696
2363
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2364
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1697
2365
|
const argstring = 'npm ' + args.join(' ');
|
|
1698
2366
|
args.splice(0, args.length, '/c', argstring);
|
|
1699
2367
|
command = 'cmd.exe';
|
|
1700
2368
|
}
|
|
2369
|
+
// Decide when using sudo on linux
|
|
2370
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2371
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1701
2372
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1702
2373
|
args.unshift(command);
|
|
1703
2374
|
command = 'sudo';
|
|
@@ -1756,3 +2427,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1756
2427
|
});
|
|
1757
2428
|
}
|
|
1758
2429
|
}
|
|
2430
|
+
//# sourceMappingURL=matterbridge.js.map
|