matterbridge 2.1.3-dev.1 → 2.1.3
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 +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +110 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +231 -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 -0
- 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 +409 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +748 -40
- 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 +834 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2262 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +96 -0
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +152 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +111 -3
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +167 -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 +230 -3
- package/dist/pluginManager.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/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/export.d.ts +3 -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/utils.d.ts +221 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +251 -7
- package/dist/utils/utils.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
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 { fileURLToPath } from 'url';
|
|
2
25
|
import { promises as fs } from 'fs';
|
|
3
26
|
import { exec, spawn } from 'child_process';
|
|
@@ -5,20 +28,27 @@ import EventEmitter from 'events';
|
|
|
5
28
|
import os from 'os';
|
|
6
29
|
import path from 'path';
|
|
7
30
|
import { randomBytes } from 'crypto';
|
|
31
|
+
// NodeStorage and AnsiLogger modules
|
|
8
32
|
import { NodeStorageManager } from './storage/export.js';
|
|
9
33
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from './logger/export.js';
|
|
34
|
+
// Matterbridge
|
|
10
35
|
import { logInterfaces, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
11
36
|
import { PluginManager } from './pluginManager.js';
|
|
12
37
|
import { DeviceManager } from './deviceManager.js';
|
|
13
38
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
14
39
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
15
40
|
import { Frontend } from './frontend.js';
|
|
41
|
+
// @matter
|
|
16
42
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
17
43
|
import { DeviceCommissioner, FabricAction, PaseClient } from '@matter/main/protocol';
|
|
18
44
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
45
|
+
// Default colors
|
|
19
46
|
const plg = '\u001B[38;5;33m';
|
|
20
47
|
const dev = '\u001B[38;5;79m';
|
|
21
48
|
const typ = '\u001B[38;5;207m';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
22
52
|
export class Matterbridge extends EventEmitter {
|
|
23
53
|
systemInformation = {
|
|
24
54
|
interfaceName: '',
|
|
@@ -55,7 +85,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
55
85
|
restartMode: '',
|
|
56
86
|
readOnly: hasParameter('readonly'),
|
|
57
87
|
profile: getParameter('profile'),
|
|
58
|
-
loggerLevel: "info"
|
|
88
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
59
89
|
fileLogger: false,
|
|
60
90
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
61
91
|
matterFileLogger: false,
|
|
@@ -90,9 +120,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
90
120
|
plugins;
|
|
91
121
|
devices;
|
|
92
122
|
frontend = new Frontend(this);
|
|
123
|
+
// Matterbridge storage
|
|
93
124
|
nodeStorage;
|
|
94
125
|
nodeContext;
|
|
95
126
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
127
|
+
// Cleanup
|
|
96
128
|
hasCleanupStarted = false;
|
|
97
129
|
initialized = false;
|
|
98
130
|
execRunningCount = 0;
|
|
@@ -104,34 +136,57 @@ export class Matterbridge extends EventEmitter {
|
|
|
104
136
|
sigtermHandler;
|
|
105
137
|
exceptionHandler;
|
|
106
138
|
rejectionHandler;
|
|
139
|
+
// Matter environment
|
|
107
140
|
environment = Environment.default;
|
|
141
|
+
// Matter storage
|
|
108
142
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
109
143
|
matterStorageService;
|
|
110
144
|
matterStorageManager;
|
|
111
145
|
matterbridgeContext;
|
|
112
146
|
mattercontrollerContext;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
// Matter parameters
|
|
148
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
149
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
150
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
151
|
+
port; // first server node port
|
|
152
|
+
passcode; // first server node passcode
|
|
153
|
+
discriminator; // first server node discriminator
|
|
119
154
|
serverNode;
|
|
120
155
|
aggregatorNode;
|
|
121
156
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
122
157
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
123
158
|
static instance;
|
|
159
|
+
// We load asyncronously so is private
|
|
124
160
|
constructor() {
|
|
125
161
|
super();
|
|
126
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Retrieves the list of Matterbridge devices.
|
|
165
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
166
|
+
*/
|
|
127
167
|
getDevices() {
|
|
128
168
|
return this.devices.array();
|
|
129
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Retrieves the list of registered plugins.
|
|
172
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
173
|
+
*/
|
|
130
174
|
getPlugins() {
|
|
131
175
|
return this.plugins.array();
|
|
132
176
|
}
|
|
177
|
+
/** ***********************************************************************************************************************************/
|
|
178
|
+
/** loadInstance() and cleanup() methods */
|
|
179
|
+
/** ***********************************************************************************************************************************/
|
|
180
|
+
/**
|
|
181
|
+
* Loads an instance of the Matterbridge class.
|
|
182
|
+
* If an instance already exists, return that instance.
|
|
183
|
+
*
|
|
184
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
185
|
+
* @returns The loaded Matterbridge instance.
|
|
186
|
+
*/
|
|
133
187
|
static async loadInstance(initialize = false) {
|
|
134
188
|
if (!Matterbridge.instance) {
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
135
190
|
if (hasParameter('debug'))
|
|
136
191
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
137
192
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -140,51 +195,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
140
195
|
}
|
|
141
196
|
return Matterbridge.instance;
|
|
142
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Call cleanup().
|
|
200
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
201
|
+
*
|
|
202
|
+
*/
|
|
143
203
|
async destroyInstance() {
|
|
144
204
|
await this.cleanup('destroying instance...', false);
|
|
205
|
+
// await matterServerNode.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
206
|
+
// this.log.info(`Closed ${matterServerNode.id} MdnsService`);
|
|
145
207
|
await new Promise((resolve) => {
|
|
146
208
|
setTimeout(resolve, 1000);
|
|
147
209
|
});
|
|
148
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Initializes the Matterbridge application.
|
|
213
|
+
*
|
|
214
|
+
* @remarks
|
|
215
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
216
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
217
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
218
|
+
*
|
|
219
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
220
|
+
*/
|
|
149
221
|
async initialize() {
|
|
222
|
+
// Set the restart mode
|
|
150
223
|
if (hasParameter('service'))
|
|
151
224
|
this.restartMode = 'service';
|
|
152
225
|
if (hasParameter('docker'))
|
|
153
226
|
this.restartMode = 'docker';
|
|
227
|
+
// Set the matterbridge directory
|
|
154
228
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
155
229
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
230
|
+
// Setup the matter environment
|
|
156
231
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
157
232
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
158
233
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
159
234
|
this.environment.vars.set('runtime.signals', false);
|
|
160
235
|
this.environment.vars.set('runtime.exitcode', false);
|
|
161
|
-
|
|
236
|
+
// Create the matterbridge logger
|
|
237
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
238
|
+
// Register process handlers
|
|
162
239
|
this.registerProcessHandlers();
|
|
240
|
+
// Initialize nodeStorage and nodeContext
|
|
163
241
|
try {
|
|
164
242
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
165
243
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
166
244
|
this.log.debug('Creating node storage context for matterbridge');
|
|
167
245
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
246
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
248
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
169
249
|
for (const key of keys) {
|
|
170
250
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
252
|
await this.nodeStorage?.storage.get(key);
|
|
172
253
|
}
|
|
173
254
|
const storages = await this.nodeStorage.getStorageNames();
|
|
174
255
|
for (const storage of storages) {
|
|
175
256
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
176
257
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
258
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
259
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
260
|
const keys = (await nodeContext?.storage.keys());
|
|
178
261
|
keys.forEach(async (key) => {
|
|
179
262
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
180
263
|
await nodeContext?.get(key);
|
|
181
264
|
});
|
|
182
265
|
}
|
|
266
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
183
267
|
this.log.debug('Creating node storage backup...');
|
|
184
268
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
185
269
|
this.log.debug('Created node storage backup');
|
|
186
270
|
}
|
|
187
271
|
catch (error) {
|
|
272
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
188
273
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
189
274
|
if (hasParameter('norestore')) {
|
|
190
275
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -199,45 +284,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
199
284
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
200
285
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
201
286
|
}
|
|
287
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
202
288
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
289
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
203
290
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
291
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
204
292
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
205
293
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
294
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
206
295
|
if (hasParameter('logger')) {
|
|
207
296
|
const level = getParameter('logger');
|
|
208
297
|
if (level === 'debug') {
|
|
209
|
-
this.log.logLevel = "debug"
|
|
298
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
210
299
|
}
|
|
211
300
|
else if (level === 'info') {
|
|
212
|
-
this.log.logLevel = "info"
|
|
301
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
213
302
|
}
|
|
214
303
|
else if (level === 'notice') {
|
|
215
|
-
this.log.logLevel = "notice"
|
|
304
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
216
305
|
}
|
|
217
306
|
else if (level === 'warn') {
|
|
218
|
-
this.log.logLevel = "warn"
|
|
307
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
219
308
|
}
|
|
220
309
|
else if (level === 'error') {
|
|
221
|
-
this.log.logLevel = "error"
|
|
310
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
222
311
|
}
|
|
223
312
|
else if (level === 'fatal') {
|
|
224
|
-
this.log.logLevel = "fatal"
|
|
313
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
225
314
|
}
|
|
226
315
|
else {
|
|
227
316
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
228
|
-
this.log.logLevel = "info"
|
|
317
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
229
318
|
}
|
|
230
319
|
}
|
|
231
320
|
else {
|
|
232
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
321
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
233
322
|
}
|
|
234
323
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
324
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
235
325
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
236
326
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
237
327
|
this.matterbridgeInformation.fileLogger = true;
|
|
238
328
|
}
|
|
239
329
|
this.log.notice('Matterbridge is starting...');
|
|
240
330
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
331
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
241
332
|
if (hasParameter('matterlogger')) {
|
|
242
333
|
const level = getParameter('matterlogger');
|
|
243
334
|
if (level === 'debug') {
|
|
@@ -268,6 +359,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
268
359
|
}
|
|
269
360
|
Logger.format = MatterLogFormat.ANSI;
|
|
270
361
|
Logger.setLogger('default', this.createMatterLogger());
|
|
362
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
271
363
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
272
364
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
273
365
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -276,6 +368,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
276
368
|
});
|
|
277
369
|
}
|
|
278
370
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
371
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
279
372
|
if (hasParameter('mdnsinterface')) {
|
|
280
373
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
281
374
|
}
|
|
@@ -284,6 +377,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
284
377
|
if (this.mdnsInterface === '')
|
|
285
378
|
this.mdnsInterface = undefined;
|
|
286
379
|
}
|
|
380
|
+
// Validate mdnsInterface
|
|
287
381
|
if (this.mdnsInterface) {
|
|
288
382
|
const networkInterfaces = os.networkInterfaces();
|
|
289
383
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -297,6 +391,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
297
391
|
}
|
|
298
392
|
if (this.mdnsInterface)
|
|
299
393
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
394
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
300
395
|
if (hasParameter('ipv4address')) {
|
|
301
396
|
this.ipv4address = getParameter('ipv4address');
|
|
302
397
|
}
|
|
@@ -305,6 +400,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
305
400
|
if (this.ipv4address === '')
|
|
306
401
|
this.ipv4address = undefined;
|
|
307
402
|
}
|
|
403
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
308
404
|
if (hasParameter('ipv6address')) {
|
|
309
405
|
this.ipv6address = getParameter('ipv6address');
|
|
310
406
|
}
|
|
@@ -313,14 +409,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
313
409
|
if (this.ipv6address === '')
|
|
314
410
|
this.ipv6address = undefined;
|
|
315
411
|
}
|
|
412
|
+
// Initialize PluginManager
|
|
316
413
|
this.plugins = new PluginManager(this);
|
|
317
414
|
await this.plugins.loadFromStorage();
|
|
318
415
|
this.plugins.logLevel = this.log.logLevel;
|
|
416
|
+
// Initialize DeviceManager
|
|
319
417
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
320
418
|
this.devices.logLevel = this.log.logLevel;
|
|
419
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
321
420
|
for (const plugin of this.plugins) {
|
|
322
421
|
const packageJson = await this.plugins.parse(plugin);
|
|
323
422
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
423
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
424
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
324
425
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
325
426
|
try {
|
|
326
427
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -342,6 +443,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
342
443
|
await plugin.nodeContext.set('description', plugin.description);
|
|
343
444
|
await plugin.nodeContext.set('author', plugin.author);
|
|
344
445
|
}
|
|
446
|
+
// Log system info and create .matterbridge directory
|
|
345
447
|
await this.logNodeAndSystemInfo();
|
|
346
448
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
347
449
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -349,6 +451,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
349
451
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
350
452
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
351
453
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
454
|
+
// Check node version and throw error
|
|
352
455
|
const minNodeVersion = 18;
|
|
353
456
|
const nodeVersion = process.versions.node;
|
|
354
457
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -356,9 +459,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
356
459
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
357
460
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
358
461
|
}
|
|
462
|
+
// Parse command line
|
|
359
463
|
await this.parseCommandLine();
|
|
360
464
|
this.initialized = true;
|
|
361
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
468
|
+
* @private
|
|
469
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
470
|
+
*/
|
|
362
471
|
async parseCommandLine() {
|
|
363
472
|
if (hasParameter('help')) {
|
|
364
473
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -468,6 +577,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
468
577
|
await this.shutdownProcessAndFactoryReset();
|
|
469
578
|
return;
|
|
470
579
|
}
|
|
580
|
+
// Start the matter storage and create the matterbridge context
|
|
471
581
|
try {
|
|
472
582
|
await this.startMatterStorage();
|
|
473
583
|
}
|
|
@@ -475,10 +585,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
475
585
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
476
586
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
477
587
|
}
|
|
588
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
478
589
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
479
590
|
await this.shutdownProcessAndReset();
|
|
480
591
|
return;
|
|
481
592
|
}
|
|
593
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
482
594
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
483
595
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
484
596
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -500,9 +612,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
500
612
|
this.emit('shutdown');
|
|
501
613
|
return;
|
|
502
614
|
}
|
|
615
|
+
// Initialize frontend
|
|
503
616
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
504
617
|
await this.frontend.start(getIntParameter('frontend'));
|
|
505
618
|
this.frontend.logLevel = this.log.logLevel;
|
|
619
|
+
// Check each 60 minutes the latest versions
|
|
506
620
|
this.checkUpdateInterval = setInterval(() => {
|
|
507
621
|
this.getMatterbridgeLatestVersion();
|
|
508
622
|
for (const plugin of this.plugins) {
|
|
@@ -510,20 +624,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
510
624
|
}
|
|
511
625
|
this.frontend.wssSendRefreshRequired();
|
|
512
626
|
}, 60 * 60 * 1000);
|
|
627
|
+
// Start the matterbridge in mode test
|
|
513
628
|
if (hasParameter('test')) {
|
|
514
629
|
this.bridgeMode = 'bridge';
|
|
515
630
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
516
631
|
return;
|
|
517
632
|
}
|
|
633
|
+
// Start the matterbridge in mode controller
|
|
518
634
|
if (hasParameter('controller')) {
|
|
519
635
|
this.bridgeMode = 'controller';
|
|
520
636
|
await this.startController();
|
|
521
637
|
return;
|
|
522
638
|
}
|
|
639
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
523
640
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
524
641
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
525
642
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
526
643
|
}
|
|
644
|
+
// Start matterbridge in bridge mode
|
|
527
645
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
528
646
|
this.bridgeMode = 'bridge';
|
|
529
647
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -531,6 +649,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
531
649
|
await this.startBridge();
|
|
532
650
|
return;
|
|
533
651
|
}
|
|
652
|
+
// Start matterbridge in childbridge mode
|
|
534
653
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
535
654
|
this.bridgeMode = 'childbridge';
|
|
536
655
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -539,16 +658,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
539
658
|
return;
|
|
540
659
|
}
|
|
541
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Asynchronously loads and starts the registered plugins.
|
|
663
|
+
*
|
|
664
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
665
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
666
|
+
*
|
|
667
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
668
|
+
*/
|
|
542
669
|
async startPlugins() {
|
|
670
|
+
// Check, load and start the plugins
|
|
543
671
|
for (const plugin of this.plugins) {
|
|
544
672
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
545
673
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
674
|
+
// Check if the plugin is available
|
|
546
675
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
547
676
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
548
677
|
plugin.enabled = false;
|
|
549
678
|
plugin.error = true;
|
|
550
679
|
continue;
|
|
551
680
|
}
|
|
681
|
+
// Check if the plugin has a new version
|
|
682
|
+
// this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
552
683
|
if (!plugin.enabled) {
|
|
553
684
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
554
685
|
continue;
|
|
@@ -562,20 +693,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
562
693
|
plugin.addedDevices = undefined;
|
|
563
694
|
plugin.qrPairingCode = undefined;
|
|
564
695
|
plugin.manualPairingCode = undefined;
|
|
565
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
696
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
566
697
|
}
|
|
567
698
|
this.frontend.wssSendRefreshRequired();
|
|
568
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
702
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
703
|
+
*/
|
|
569
704
|
registerProcessHandlers() {
|
|
570
705
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
571
706
|
process.removeAllListeners('uncaughtException');
|
|
572
707
|
process.removeAllListeners('unhandledRejection');
|
|
573
708
|
this.exceptionHandler = async (error) => {
|
|
574
709
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
710
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
575
711
|
};
|
|
576
712
|
process.on('uncaughtException', this.exceptionHandler);
|
|
577
713
|
this.rejectionHandler = async (reason, promise) => {
|
|
578
714
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
715
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
579
716
|
};
|
|
580
717
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
581
718
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -588,6 +725,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
588
725
|
};
|
|
589
726
|
process.on('SIGTERM', this.sigtermHandler);
|
|
590
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
730
|
+
*/
|
|
591
731
|
deregisterProcesslHandlers() {
|
|
592
732
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
593
733
|
if (this.exceptionHandler)
|
|
@@ -604,12 +744,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
604
744
|
process.off('SIGTERM', this.sigtermHandler);
|
|
605
745
|
this.sigtermHandler = undefined;
|
|
606
746
|
}
|
|
747
|
+
/**
|
|
748
|
+
* Logs the node and system information.
|
|
749
|
+
*/
|
|
607
750
|
async logNodeAndSystemInfo() {
|
|
751
|
+
// IP address information
|
|
608
752
|
const networkInterfaces = os.networkInterfaces();
|
|
609
753
|
this.systemInformation.interfaceName = '';
|
|
610
754
|
this.systemInformation.ipv4Address = '';
|
|
611
755
|
this.systemInformation.ipv6Address = '';
|
|
612
756
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
757
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
613
758
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
614
759
|
continue;
|
|
615
760
|
if (!interfaceDetails) {
|
|
@@ -635,19 +780,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
635
780
|
break;
|
|
636
781
|
}
|
|
637
782
|
}
|
|
783
|
+
// Node information
|
|
638
784
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
639
785
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
640
786
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
641
787
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
788
|
+
// Host system information
|
|
642
789
|
this.systemInformation.hostname = os.hostname();
|
|
643
790
|
this.systemInformation.user = os.userInfo().username;
|
|
644
|
-
this.systemInformation.osType = os.type();
|
|
645
|
-
this.systemInformation.osRelease = os.release();
|
|
646
|
-
this.systemInformation.osPlatform = os.platform();
|
|
647
|
-
this.systemInformation.osArch = os.arch();
|
|
648
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
649
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
650
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
791
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
792
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
793
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
794
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
795
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
796
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
797
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
798
|
+
// Log the system information
|
|
651
799
|
this.log.debug('Host System Information:');
|
|
652
800
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
653
801
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -663,15 +811,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
663
811
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
664
812
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
665
813
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
814
|
+
// Home directory
|
|
666
815
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
667
816
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
668
817
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
818
|
+
// Package root directory
|
|
669
819
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
670
820
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
671
821
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
672
822
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
823
|
+
// Global node_modules directory
|
|
673
824
|
if (this.nodeContext)
|
|
674
825
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
826
|
+
// First run of Matterbridge so the node storage is empty
|
|
675
827
|
if (this.globalModulesDirectory === '') {
|
|
676
828
|
try {
|
|
677
829
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -685,6 +837,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
685
837
|
}
|
|
686
838
|
else
|
|
687
839
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
840
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
841
|
+
else {
|
|
842
|
+
this.getGlobalNodeModules()
|
|
843
|
+
.then(async (globalModulesDirectory) => {
|
|
844
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
845
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
846
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
847
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
848
|
+
})
|
|
849
|
+
.catch((error) => {
|
|
850
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
851
|
+
});
|
|
852
|
+
}*/
|
|
853
|
+
// Create the data directory .matterbridge in the home directory
|
|
688
854
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
689
855
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
690
856
|
try {
|
|
@@ -708,6 +874,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
708
874
|
}
|
|
709
875
|
}
|
|
710
876
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
877
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
711
878
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
712
879
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
713
880
|
try {
|
|
@@ -731,18 +898,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
731
898
|
}
|
|
732
899
|
}
|
|
733
900
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
901
|
+
// Matterbridge version
|
|
734
902
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
735
903
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
736
904
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
737
905
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
906
|
+
// Matterbridge latest version
|
|
738
907
|
if (this.nodeContext)
|
|
739
908
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
740
909
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
910
|
+
// this.getMatterbridgeLatestVersion();
|
|
911
|
+
// Current working directory
|
|
741
912
|
const currentDir = process.cwd();
|
|
742
913
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
914
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
743
915
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
744
916
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
745
917
|
}
|
|
918
|
+
/**
|
|
919
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
920
|
+
* @param packageName - The name of the package.
|
|
921
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
922
|
+
*/
|
|
746
923
|
async getLatestVersion(packageName) {
|
|
747
924
|
return new Promise((resolve, reject) => {
|
|
748
925
|
this.execRunningCount++;
|
|
@@ -757,6 +934,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
757
934
|
});
|
|
758
935
|
});
|
|
759
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
939
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
940
|
+
*/
|
|
760
941
|
async getGlobalNodeModules() {
|
|
761
942
|
return new Promise((resolve, reject) => {
|
|
762
943
|
this.execRunningCount++;
|
|
@@ -771,6 +952,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
771
952
|
});
|
|
772
953
|
});
|
|
773
954
|
}
|
|
955
|
+
/**
|
|
956
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
957
|
+
* @private
|
|
958
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
959
|
+
*/
|
|
774
960
|
async getMatterbridgeLatestVersion() {
|
|
775
961
|
this.getLatestVersion('matterbridge')
|
|
776
962
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -787,8 +973,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
787
973
|
})
|
|
788
974
|
.catch((error) => {
|
|
789
975
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
976
|
+
// error.stack && this.log.debug(error.stack);
|
|
790
977
|
});
|
|
791
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
981
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
982
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
983
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
984
|
+
*
|
|
985
|
+
* @private
|
|
986
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
987
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
988
|
+
*/
|
|
792
989
|
async getPluginLatestVersion(plugin) {
|
|
793
990
|
this.getLatestVersion(plugin.name)
|
|
794
991
|
.then(async (latestVersion) => {
|
|
@@ -800,40 +997,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
800
997
|
})
|
|
801
998
|
.catch((error) => {
|
|
802
999
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1000
|
+
// error.stack && this.log.debug(error.stack);
|
|
803
1001
|
});
|
|
804
1002
|
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1005
|
+
*
|
|
1006
|
+
* @returns {Function} The MatterLogger function.
|
|
1007
|
+
*/
|
|
805
1008
|
createMatterLogger() {
|
|
806
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1009
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
807
1010
|
return (_level, formattedLog) => {
|
|
808
1011
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
809
1012
|
const message = formattedLog.slice(65);
|
|
810
1013
|
matterLogger.logName = logger;
|
|
811
1014
|
switch (_level) {
|
|
812
1015
|
case MatterLogLevel.DEBUG:
|
|
813
|
-
matterLogger.log("debug"
|
|
1016
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
814
1017
|
break;
|
|
815
1018
|
case MatterLogLevel.INFO:
|
|
816
|
-
matterLogger.log("info"
|
|
1019
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
817
1020
|
break;
|
|
818
1021
|
case MatterLogLevel.NOTICE:
|
|
819
|
-
matterLogger.log("notice"
|
|
1022
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
820
1023
|
break;
|
|
821
1024
|
case MatterLogLevel.WARN:
|
|
822
|
-
matterLogger.log("warn"
|
|
1025
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
823
1026
|
break;
|
|
824
1027
|
case MatterLogLevel.ERROR:
|
|
825
|
-
matterLogger.log("error"
|
|
1028
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
826
1029
|
break;
|
|
827
1030
|
case MatterLogLevel.FATAL:
|
|
828
|
-
matterLogger.log("fatal"
|
|
1031
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
829
1032
|
break;
|
|
830
1033
|
default:
|
|
831
|
-
matterLogger.log("debug"
|
|
1034
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
832
1035
|
break;
|
|
833
1036
|
}
|
|
834
1037
|
};
|
|
835
1038
|
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Creates a Matter File Logger.
|
|
1041
|
+
*
|
|
1042
|
+
* @param {string} filePath - The path to the log file.
|
|
1043
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1044
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1045
|
+
*/
|
|
836
1046
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1047
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
837
1048
|
let fileSize = 0;
|
|
838
1049
|
if (unlink) {
|
|
839
1050
|
try {
|
|
@@ -882,12 +1093,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
882
1093
|
}
|
|
883
1094
|
};
|
|
884
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1098
|
+
*/
|
|
885
1099
|
async restartProcess() {
|
|
886
1100
|
await this.cleanup('restarting...', true);
|
|
887
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Shut down the process by exiting the current process.
|
|
1104
|
+
*/
|
|
888
1105
|
async shutdownProcess() {
|
|
889
1106
|
await this.cleanup('shutting down...', false);
|
|
890
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Update matterbridge and and shut down the process.
|
|
1110
|
+
*/
|
|
891
1111
|
async updateProcess() {
|
|
892
1112
|
this.log.info('Updating matterbridge...');
|
|
893
1113
|
try {
|
|
@@ -900,6 +1120,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
900
1120
|
this.frontend.wssSendRestartRequired();
|
|
901
1121
|
await this.cleanup('updating...', false);
|
|
902
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Unregister all devices and shut down the process.
|
|
1125
|
+
*/
|
|
903
1126
|
async unregisterAndShutdownProcess() {
|
|
904
1127
|
this.log.info('Unregistering all devices and shutting down...');
|
|
905
1128
|
for (const plugin of this.plugins) {
|
|
@@ -907,6 +1130,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
907
1130
|
}
|
|
908
1131
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
909
1132
|
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Reset commissioning and shut down the process.
|
|
1135
|
+
*/
|
|
910
1136
|
async shutdownProcessAndReset() {
|
|
911
1137
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
912
1138
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -918,8 +1144,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
918
1144
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
919
1145
|
await this.cleanup('shutting down with reset...', false);
|
|
920
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Factory reset and shut down the process.
|
|
1149
|
+
*/
|
|
921
1150
|
async shutdownProcessAndFactoryReset() {
|
|
922
1151
|
try {
|
|
1152
|
+
// Delete old matter storage file and backup
|
|
923
1153
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
924
1154
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
925
1155
|
await fs.unlink(file);
|
|
@@ -933,6 +1163,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
933
1163
|
}
|
|
934
1164
|
}
|
|
935
1165
|
try {
|
|
1166
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
936
1167
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
937
1168
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
938
1169
|
await fs.rm(dir, { recursive: true });
|
|
@@ -946,6 +1177,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
946
1177
|
}
|
|
947
1178
|
}
|
|
948
1179
|
try {
|
|
1180
|
+
// Delete node storage directory with its subdirectories and backup
|
|
949
1181
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
950
1182
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
951
1183
|
await fs.rm(dir, { recursive: true });
|
|
@@ -965,30 +1197,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
965
1197
|
this.devices.clear();
|
|
966
1198
|
await this.cleanup('shutting down with factory reset...', false);
|
|
967
1199
|
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Cleans up the Matterbridge instance.
|
|
1202
|
+
* @param message - The cleanup message.
|
|
1203
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1204
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1205
|
+
*/
|
|
968
1206
|
async cleanup(message, restart = false) {
|
|
969
1207
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
970
1208
|
this.hasCleanupStarted = true;
|
|
971
1209
|
this.log.info(message);
|
|
1210
|
+
// Clear the start matter interval
|
|
972
1211
|
if (this.startMatterInterval) {
|
|
973
1212
|
clearInterval(this.startMatterInterval);
|
|
974
1213
|
this.startMatterInterval = undefined;
|
|
975
1214
|
this.log.debug('Start matter interval cleared');
|
|
976
1215
|
}
|
|
1216
|
+
// Clear the check update interval
|
|
977
1217
|
if (this.checkUpdateInterval) {
|
|
978
1218
|
clearInterval(this.checkUpdateInterval);
|
|
979
1219
|
this.checkUpdateInterval = undefined;
|
|
980
1220
|
this.log.debug('Check update interval cleared');
|
|
981
1221
|
}
|
|
1222
|
+
// Clear the configure timeout
|
|
982
1223
|
if (this.configureTimeout) {
|
|
983
1224
|
clearTimeout(this.configureTimeout);
|
|
984
1225
|
this.configureTimeout = undefined;
|
|
985
1226
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
986
1227
|
}
|
|
1228
|
+
// Clear the reachability timeout
|
|
987
1229
|
if (this.reachabilityTimeout) {
|
|
988
1230
|
clearTimeout(this.reachabilityTimeout);
|
|
989
1231
|
this.reachabilityTimeout = undefined;
|
|
990
1232
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
991
1233
|
}
|
|
1234
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
992
1235
|
for (const plugin of this.plugins) {
|
|
993
1236
|
if (!plugin.enabled || plugin.error)
|
|
994
1237
|
continue;
|
|
@@ -999,7 +1242,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
999
1242
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1000
1243
|
}
|
|
1001
1244
|
}
|
|
1002
|
-
|
|
1245
|
+
// Stopping matter server nodes
|
|
1003
1246
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1004
1247
|
if (this.bridgeMode === 'bridge') {
|
|
1005
1248
|
if (this.serverNode) {
|
|
@@ -1016,16 +1259,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1016
1259
|
}
|
|
1017
1260
|
}
|
|
1018
1261
|
this.log.notice('Stopped matter server nodes');
|
|
1262
|
+
// Stop matter storage
|
|
1019
1263
|
await this.stopMatterStorage();
|
|
1264
|
+
// Stop the frontend
|
|
1265
|
+
await this.frontend.stop();
|
|
1266
|
+
// Remove the matterfilelogger
|
|
1020
1267
|
try {
|
|
1021
1268
|
Logger.removeLogger('matterfilelogger');
|
|
1269
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1022
1270
|
}
|
|
1023
1271
|
catch (error) {
|
|
1272
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1024
1273
|
}
|
|
1274
|
+
// Serialize registeredDevices
|
|
1025
1275
|
if (this.nodeStorage && this.nodeContext) {
|
|
1276
|
+
/*
|
|
1277
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1278
|
+
this.log.info('Saving registered devices...');
|
|
1279
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1280
|
+
this.devices.forEach(async (device) => {
|
|
1281
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1282
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1283
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1284
|
+
});
|
|
1285
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1286
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1287
|
+
*/
|
|
1288
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1026
1289
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1027
1290
|
await this.nodeContext.close();
|
|
1028
1291
|
this.nodeContext = undefined;
|
|
1292
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1029
1293
|
for (const plugin of this.plugins) {
|
|
1030
1294
|
if (plugin.nodeContext) {
|
|
1031
1295
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1042,12 +1306,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1042
1306
|
}
|
|
1043
1307
|
this.plugins.clear();
|
|
1044
1308
|
this.devices.clear();
|
|
1309
|
+
// Deregisters the process handlers
|
|
1045
1310
|
this.deregisterProcesslHandlers();
|
|
1046
1311
|
if (restart) {
|
|
1047
1312
|
if (message === 'updating...') {
|
|
1048
1313
|
this.log.info('Cleanup completed. Updating...');
|
|
1049
1314
|
Matterbridge.instance = undefined;
|
|
1050
|
-
this.emit('update');
|
|
1315
|
+
this.emit('update'); // Restart the process but the update has been done before
|
|
1051
1316
|
}
|
|
1052
1317
|
else if (message === 'restarting...') {
|
|
1053
1318
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1064,6 +1329,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1064
1329
|
this.initialized = false;
|
|
1065
1330
|
}
|
|
1066
1331
|
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1334
|
+
*
|
|
1335
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1336
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1337
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1338
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1339
|
+
*/
|
|
1067
1340
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1068
1341
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1069
1342
|
plugin.locked = true;
|
|
@@ -1075,6 +1348,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1075
1348
|
await this.startServerNode(plugin.serverNode);
|
|
1076
1349
|
}
|
|
1077
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1353
|
+
*
|
|
1354
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1355
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1356
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1357
|
+
*/
|
|
1078
1358
|
async createDynamicPlugin(plugin, start = false) {
|
|
1079
1359
|
if (!plugin.locked) {
|
|
1080
1360
|
plugin.locked = true;
|
|
@@ -1086,7 +1366,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1086
1366
|
await this.startServerNode(plugin.serverNode);
|
|
1087
1367
|
}
|
|
1088
1368
|
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Starts the Matterbridge in bridge mode.
|
|
1371
|
+
* @private
|
|
1372
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1373
|
+
*/
|
|
1089
1374
|
async startBridge() {
|
|
1375
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1090
1376
|
if (!this.matterStorageManager)
|
|
1091
1377
|
throw new Error('No storage manager initialized');
|
|
1092
1378
|
if (!this.matterbridgeContext)
|
|
@@ -1123,7 +1409,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1123
1409
|
clearInterval(this.startMatterInterval);
|
|
1124
1410
|
this.startMatterInterval = undefined;
|
|
1125
1411
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1412
|
+
// Start the Matter server node
|
|
1126
1413
|
this.startServerNode(this.serverNode);
|
|
1414
|
+
// Configure the plugins
|
|
1127
1415
|
this.configureTimeout = setTimeout(async () => {
|
|
1128
1416
|
for (const plugin of this.plugins) {
|
|
1129
1417
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1138,6 +1426,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1138
1426
|
}
|
|
1139
1427
|
this.frontend.wssSendRefreshRequired();
|
|
1140
1428
|
}, 30 * 1000);
|
|
1429
|
+
// Setting reachability to true
|
|
1141
1430
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1142
1431
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1143
1432
|
if (this.serverNode)
|
|
@@ -1148,7 +1437,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1148
1437
|
}, 60 * 1000);
|
|
1149
1438
|
}, 1000);
|
|
1150
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1442
|
+
* @private
|
|
1443
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1444
|
+
*/
|
|
1151
1445
|
async startChildbridge() {
|
|
1446
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1447
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1152
1448
|
if (!this.matterStorageManager)
|
|
1153
1449
|
throw new Error('No storage manager initialized');
|
|
1154
1450
|
for (const plugin of this.plugins) {
|
|
@@ -1195,12 +1491,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1195
1491
|
clearInterval(this.startMatterInterval);
|
|
1196
1492
|
this.startMatterInterval = undefined;
|
|
1197
1493
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1494
|
+
// Configure the plugins
|
|
1198
1495
|
this.configureTimeout = setTimeout(async () => {
|
|
1199
1496
|
for (const plugin of this.plugins) {
|
|
1200
1497
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1201
1498
|
continue;
|
|
1202
1499
|
try {
|
|
1203
|
-
await this.plugins.configure(plugin);
|
|
1500
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1204
1501
|
}
|
|
1205
1502
|
catch (error) {
|
|
1206
1503
|
plugin.error = true;
|
|
@@ -1228,7 +1525,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1228
1525
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1229
1526
|
continue;
|
|
1230
1527
|
}
|
|
1528
|
+
// Start the Matter server node
|
|
1231
1529
|
this.startServerNode(plugin.serverNode);
|
|
1530
|
+
// Setting reachability to true
|
|
1232
1531
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1233
1532
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1234
1533
|
if (plugin.serverNode)
|
|
@@ -1242,9 +1541,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1242
1541
|
}
|
|
1243
1542
|
}, 1000);
|
|
1244
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Starts the Matterbridge controller.
|
|
1546
|
+
* @private
|
|
1547
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1548
|
+
*/
|
|
1245
1549
|
async startController() {
|
|
1550
|
+
/*
|
|
1551
|
+
if (!this.storageManager) {
|
|
1552
|
+
this.log.error('No storage manager initialized');
|
|
1553
|
+
await this.cleanup('No storage manager initialized');
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1557
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1558
|
+
if (!this.mattercontrollerContext) {
|
|
1559
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1560
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1565
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1566
|
+
this.log.info('Creating matter commissioning controller');
|
|
1567
|
+
this.commissioningController = new CommissioningController({
|
|
1568
|
+
autoConnect: false,
|
|
1569
|
+
});
|
|
1570
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1571
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1572
|
+
|
|
1573
|
+
this.log.info('Starting matter server');
|
|
1574
|
+
await this.matterServer.start();
|
|
1575
|
+
this.log.info('Matter server started');
|
|
1576
|
+
|
|
1577
|
+
if (hasParameter('pairingcode')) {
|
|
1578
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1579
|
+
const pairingCode = getParameter('pairingcode');
|
|
1580
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1581
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1582
|
+
|
|
1583
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1584
|
+
if (pairingCode !== undefined) {
|
|
1585
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1586
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1587
|
+
longDiscriminator = undefined;
|
|
1588
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1589
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1590
|
+
} else {
|
|
1591
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1592
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1593
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1594
|
+
}
|
|
1595
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1596
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1600
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1601
|
+
regulatoryCountryCode: 'XX',
|
|
1602
|
+
};
|
|
1603
|
+
const options = {
|
|
1604
|
+
commissioning: commissioningOptions,
|
|
1605
|
+
discovery: {
|
|
1606
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1607
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1608
|
+
},
|
|
1609
|
+
passcode: setupPin,
|
|
1610
|
+
} as NodeCommissioningOptions;
|
|
1611
|
+
this.log.info('Commissioning with options:', options);
|
|
1612
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1613
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1614
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1615
|
+
} // (hasParameter('pairingcode'))
|
|
1616
|
+
|
|
1617
|
+
if (hasParameter('unpairall')) {
|
|
1618
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1619
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1620
|
+
for (const nodeId of nodeIds) {
|
|
1621
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1622
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1623
|
+
}
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
if (hasParameter('discover')) {
|
|
1628
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1629
|
+
// console.log(discover);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1633
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1638
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1639
|
+
for (const nodeId of nodeIds) {
|
|
1640
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1641
|
+
|
|
1642
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1643
|
+
autoSubscribe: false,
|
|
1644
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1645
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1646
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1647
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1648
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1649
|
+
switch (info) {
|
|
1650
|
+
case NodeStateInformation.Connected:
|
|
1651
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1652
|
+
break;
|
|
1653
|
+
case NodeStateInformation.Disconnected:
|
|
1654
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1655
|
+
break;
|
|
1656
|
+
case NodeStateInformation.Reconnecting:
|
|
1657
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1658
|
+
break;
|
|
1659
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1660
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1661
|
+
break;
|
|
1662
|
+
case NodeStateInformation.StructureChanged:
|
|
1663
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1664
|
+
break;
|
|
1665
|
+
case NodeStateInformation.Decommissioned:
|
|
1666
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1667
|
+
break;
|
|
1668
|
+
default:
|
|
1669
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1670
|
+
break;
|
|
1671
|
+
}
|
|
1672
|
+
},
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
node.logStructure();
|
|
1676
|
+
|
|
1677
|
+
// Get the interaction client
|
|
1678
|
+
this.log.info('Getting the interaction client');
|
|
1679
|
+
const interactionClient = await node.getInteractionClient();
|
|
1680
|
+
let cluster;
|
|
1681
|
+
let attributes;
|
|
1682
|
+
|
|
1683
|
+
// Log BasicInformationCluster
|
|
1684
|
+
cluster = BasicInformationCluster;
|
|
1685
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1686
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1687
|
+
});
|
|
1688
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1689
|
+
attributes.forEach((attribute) => {
|
|
1690
|
+
this.log.info(
|
|
1691
|
+
`- 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}`,
|
|
1692
|
+
);
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
// Log PowerSourceCluster
|
|
1696
|
+
cluster = PowerSourceCluster;
|
|
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 ThreadNetworkDiagnostics
|
|
1708
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
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 SwitchCluster
|
|
1720
|
+
cluster = SwitchCluster;
|
|
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
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1732
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1733
|
+
ignoreInitialTriggers: false,
|
|
1734
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1735
|
+
this.log.info(
|
|
1736
|
+
`***${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}`,
|
|
1737
|
+
),
|
|
1738
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1739
|
+
this.log.info(
|
|
1740
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1741
|
+
);
|
|
1742
|
+
},
|
|
1743
|
+
});
|
|
1744
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1745
|
+
}
|
|
1746
|
+
*/
|
|
1246
1747
|
}
|
|
1748
|
+
/** ***********************************************************************************************************************************/
|
|
1749
|
+
/** Matter.js methods */
|
|
1750
|
+
/** ***********************************************************************************************************************************/
|
|
1751
|
+
/**
|
|
1752
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1753
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1754
|
+
*/
|
|
1247
1755
|
async startMatterStorage() {
|
|
1756
|
+
// Setup Matter storage
|
|
1248
1757
|
this.log.info(`Starting matter node storage...`);
|
|
1249
1758
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1250
1759
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1252,13 +1761,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1252
1761
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1253
1762
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1254
1763
|
this.log.info('Matter node storage started');
|
|
1764
|
+
// Backup matter storage since it is created/opened correctly
|
|
1255
1765
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1256
1766
|
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1769
|
+
*
|
|
1770
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1771
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1772
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1773
|
+
*/
|
|
1257
1774
|
async backupMatterStorage(storageName, backupName) {
|
|
1258
1775
|
this.log.info('Creating matter node storage backup...');
|
|
1259
1776
|
await copyDirectory(storageName, backupName);
|
|
1260
1777
|
this.log.info('Created matter node storage backup');
|
|
1261
1778
|
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Stops the matter storage.
|
|
1781
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1782
|
+
*/
|
|
1262
1783
|
async stopMatterStorage() {
|
|
1263
1784
|
this.log.info('Closing matter node storage...');
|
|
1264
1785
|
this.matterStorageManager?.close();
|
|
@@ -1267,6 +1788,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1267
1788
|
this.matterbridgeContext = undefined;
|
|
1268
1789
|
this.log.info('Matter node storage closed');
|
|
1269
1790
|
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Creates a server node storage context.
|
|
1793
|
+
*
|
|
1794
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1795
|
+
* @param {string} deviceName - The name of the device.
|
|
1796
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1797
|
+
* @param {number} vendorId - The vendor ID.
|
|
1798
|
+
* @param {string} vendorName - The vendor name.
|
|
1799
|
+
* @param {number} productId - The product ID.
|
|
1800
|
+
* @param {string} productName - The product name.
|
|
1801
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1802
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1803
|
+
*/
|
|
1270
1804
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1271
1805
|
if (!this.matterStorageService)
|
|
1272
1806
|
throw new Error('No storage service initialized');
|
|
@@ -1299,6 +1833,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1299
1833
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1300
1834
|
return storageContext;
|
|
1301
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Creates a server node.
|
|
1838
|
+
*
|
|
1839
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1840
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1841
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1842
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1843
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1844
|
+
*/
|
|
1302
1845
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1303
1846
|
const storeId = await storageContext.get('storeId');
|
|
1304
1847
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1308,21 +1851,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1308
1851
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1309
1852
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1310
1853
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1854
|
+
/**
|
|
1855
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1856
|
+
*/
|
|
1311
1857
|
const serverNode = await ServerNode.create({
|
|
1858
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1312
1859
|
id: storeId,
|
|
1860
|
+
// Provide Network relevant configuration like the port
|
|
1861
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1313
1862
|
network: {
|
|
1314
1863
|
listeningAddressIpv4: this.ipv4address,
|
|
1315
1864
|
listeningAddressIpv6: this.ipv6address,
|
|
1316
1865
|
port,
|
|
1317
1866
|
},
|
|
1867
|
+
// Provide Commissioning relevant settings
|
|
1868
|
+
// Optional for development/testing purposes
|
|
1318
1869
|
commissioning: {
|
|
1319
1870
|
passcode,
|
|
1320
1871
|
discriminator,
|
|
1321
1872
|
},
|
|
1873
|
+
// Provide Node announcement settings
|
|
1874
|
+
// Optional: If Ommitted some development defaults are used
|
|
1322
1875
|
productDescription: {
|
|
1323
1876
|
name: await storageContext.get('deviceName'),
|
|
1324
1877
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1325
1878
|
},
|
|
1879
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1880
|
+
// Optional: If Omitted some development defaults are used
|
|
1326
1881
|
basicInformation: {
|
|
1327
1882
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1328
1883
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1339,12 +1894,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1339
1894
|
},
|
|
1340
1895
|
});
|
|
1341
1896
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1897
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1342
1898
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1343
1899
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1344
1900
|
if (this.bridgeMode === 'bridge') {
|
|
1345
1901
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1346
1902
|
if (resetSessions)
|
|
1347
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1903
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1348
1904
|
this.matterbridgePaired = true;
|
|
1349
1905
|
}
|
|
1350
1906
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1352,13 +1908,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1352
1908
|
if (plugin) {
|
|
1353
1909
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1354
1910
|
if (resetSessions)
|
|
1355
|
-
plugin.sessionInformations = undefined;
|
|
1911
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1356
1912
|
plugin.paired = true;
|
|
1357
1913
|
}
|
|
1358
1914
|
}
|
|
1359
1915
|
};
|
|
1916
|
+
/**
|
|
1917
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1918
|
+
* This means: It is added to the first fabric.
|
|
1919
|
+
*/
|
|
1360
1920
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1921
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1361
1922
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1923
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1362
1924
|
serverNode.lifecycle.online.on(async () => {
|
|
1363
1925
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1364
1926
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1404,6 +1966,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1404
1966
|
}
|
|
1405
1967
|
this.frontend.wssSendRefreshRequired();
|
|
1406
1968
|
});
|
|
1969
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1407
1970
|
serverNode.lifecycle.offline.on(() => {
|
|
1408
1971
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1409
1972
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1425,6 +1988,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1425
1988
|
}
|
|
1426
1989
|
this.frontend.wssSendRefreshRequired();
|
|
1427
1990
|
});
|
|
1991
|
+
/**
|
|
1992
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
1993
|
+
* information is needed.
|
|
1994
|
+
*/
|
|
1428
1995
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1429
1996
|
let action = '';
|
|
1430
1997
|
switch (fabricAction) {
|
|
@@ -1458,16 +2025,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1458
2025
|
}
|
|
1459
2026
|
}
|
|
1460
2027
|
};
|
|
2028
|
+
/**
|
|
2029
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2030
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2031
|
+
*/
|
|
1461
2032
|
serverNode.events.sessions.opened.on((session) => {
|
|
1462
2033
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1463
2034
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1464
2035
|
this.frontend.wssSendRefreshRequired();
|
|
1465
2036
|
});
|
|
2037
|
+
/**
|
|
2038
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2039
|
+
*/
|
|
1466
2040
|
serverNode.events.sessions.closed.on((session) => {
|
|
1467
2041
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1468
2042
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1469
2043
|
this.frontend.wssSendRefreshRequired();
|
|
1470
2044
|
});
|
|
2045
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1471
2046
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1472
2047
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1473
2048
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1476,38 +2051,61 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
2051
|
this.log.info(`Created server node for ${storeId}`);
|
|
1477
2052
|
return serverNode;
|
|
1478
2053
|
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Starts the specified server node.
|
|
2056
|
+
*
|
|
2057
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2058
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2059
|
+
*/
|
|
1479
2060
|
async startServerNode(matterServerNode) {
|
|
1480
2061
|
if (!matterServerNode)
|
|
1481
2062
|
return;
|
|
1482
2063
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1483
2064
|
await matterServerNode.start();
|
|
1484
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Stops the specified server node.
|
|
2068
|
+
*
|
|
2069
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2070
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2071
|
+
*/
|
|
1485
2072
|
async stopServerNode(matterServerNode) {
|
|
1486
2073
|
if (!matterServerNode)
|
|
1487
2074
|
return;
|
|
1488
2075
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2076
|
+
/*
|
|
2077
|
+
await matterServerNode.close();
|
|
2078
|
+
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
2079
|
+
*/
|
|
2080
|
+
// Helper function to add a timeout to a promise
|
|
1489
2081
|
const withTimeout = (promise, ms) => {
|
|
1490
2082
|
return new Promise((resolve, reject) => {
|
|
1491
2083
|
const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
|
|
1492
2084
|
promise
|
|
1493
2085
|
.then((result) => {
|
|
1494
|
-
clearTimeout(timer);
|
|
2086
|
+
clearTimeout(timer); // Prevent memory leak
|
|
1495
2087
|
resolve(result);
|
|
1496
2088
|
})
|
|
1497
2089
|
.catch((error) => {
|
|
1498
|
-
clearTimeout(timer);
|
|
2090
|
+
clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
|
|
1499
2091
|
reject(error);
|
|
1500
2092
|
});
|
|
1501
2093
|
});
|
|
1502
2094
|
};
|
|
1503
2095
|
try {
|
|
1504
|
-
await withTimeout(matterServerNode.close(), 5000);
|
|
2096
|
+
await withTimeout(matterServerNode.close(), 5000); // 5 seconds timeout
|
|
1505
2097
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1506
2098
|
}
|
|
1507
2099
|
catch (error) {
|
|
1508
2100
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1509
2101
|
}
|
|
1510
2102
|
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Advertises the specified server node if it is commissioned.
|
|
2105
|
+
*
|
|
2106
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2107
|
+
* @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.
|
|
2108
|
+
*/
|
|
1511
2109
|
async advertiseServerNode(matterServerNode) {
|
|
1512
2110
|
if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
|
|
1513
2111
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1517,17 +2115,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1517
2115
|
}
|
|
1518
2116
|
return undefined;
|
|
1519
2117
|
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Creates an aggregator node with the specified storage context.
|
|
2120
|
+
*
|
|
2121
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2122
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2123
|
+
*/
|
|
1520
2124
|
async createAggregatorNode(storageContext) {
|
|
1521
2125
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1522
2126
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1523
2127
|
return aggregatorNode;
|
|
1524
2128
|
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2131
|
+
*
|
|
2132
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2133
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2134
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2135
|
+
*/
|
|
1525
2136
|
async addBridgedEndpoint(pluginName, device) {
|
|
2137
|
+
// Check if the plugin is registered
|
|
1526
2138
|
const plugin = this.plugins.get(pluginName);
|
|
1527
2139
|
if (!plugin) {
|
|
1528
2140
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1529
2141
|
return;
|
|
1530
2142
|
}
|
|
2143
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1531
2144
|
if (this.bridgeMode === 'bridge') {
|
|
1532
2145
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1533
2146
|
if (!this.aggregatorNode)
|
|
@@ -1550,16 +2163,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1550
2163
|
plugin.registeredDevices++;
|
|
1551
2164
|
if (plugin.addedDevices !== undefined)
|
|
1552
2165
|
plugin.addedDevices++;
|
|
2166
|
+
// Add the device to the DeviceManager
|
|
1553
2167
|
this.devices.set(device);
|
|
1554
2168
|
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}`);
|
|
1555
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2172
|
+
*
|
|
2173
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2174
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2175
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2176
|
+
*/
|
|
1556
2177
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1557
2178
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2179
|
+
// Check if the plugin is registered
|
|
1558
2180
|
const plugin = this.plugins.get(pluginName);
|
|
1559
2181
|
if (!plugin) {
|
|
1560
2182
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1561
2183
|
return;
|
|
1562
2184
|
}
|
|
2185
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1563
2186
|
if (this.bridgeMode === 'bridge') {
|
|
1564
2187
|
if (!this.aggregatorNode) {
|
|
1565
2188
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1574,6 +2197,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1574
2197
|
}
|
|
1575
2198
|
else if (this.bridgeMode === 'childbridge') {
|
|
1576
2199
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2200
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1577
2201
|
}
|
|
1578
2202
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1579
2203
|
if (!plugin.aggregatorNode) {
|
|
@@ -1587,6 +2211,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1587
2211
|
plugin.registeredDevices--;
|
|
1588
2212
|
if (plugin.addedDevices !== undefined)
|
|
1589
2213
|
plugin.addedDevices--;
|
|
2214
|
+
// Close the server node TODO check if this is correct
|
|
1590
2215
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1591
2216
|
if (plugin.serverNode) {
|
|
1592
2217
|
await this.stopServerNode(plugin.serverNode);
|
|
@@ -1597,14 +2222,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1597
2222
|
}
|
|
1598
2223
|
}
|
|
1599
2224
|
}
|
|
2225
|
+
// Remove the device from the DeviceManager
|
|
1600
2226
|
this.devices.remove(device);
|
|
1601
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2230
|
+
*
|
|
2231
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2232
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2233
|
+
*/
|
|
1602
2234
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1603
2235
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1604
2236
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1605
2237
|
await this.removeBridgedEndpoint(pluginName, device);
|
|
1606
2238
|
}
|
|
1607
2239
|
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2242
|
+
*
|
|
2243
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2244
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2245
|
+
*/
|
|
1608
2246
|
sanitizeFabricInformations(fabricInfo) {
|
|
1609
2247
|
return fabricInfo.map((info) => {
|
|
1610
2248
|
return {
|
|
@@ -1618,6 +2256,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1618
2256
|
};
|
|
1619
2257
|
});
|
|
1620
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2261
|
+
*
|
|
2262
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2263
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2264
|
+
*/
|
|
1621
2265
|
sanitizeSessionInformation(sessionInfo) {
|
|
1622
2266
|
return sessionInfo
|
|
1623
2267
|
.filter((session) => session.isPeerActive)
|
|
@@ -1645,11 +2289,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1645
2289
|
};
|
|
1646
2290
|
});
|
|
1647
2291
|
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Sets the reachability of a matter server node and trigger ReachableChanged event.
|
|
2294
|
+
*
|
|
2295
|
+
* @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
|
|
2296
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2297
|
+
*/
|
|
2298
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1648
2299
|
setServerNodeReachability(serverNode, reachable) {
|
|
2300
|
+
/*
|
|
2301
|
+
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2302
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2303
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2304
|
+
*/
|
|
1649
2305
|
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2308
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
|
|
2309
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2310
|
+
*/
|
|
2311
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1650
2312
|
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2313
|
+
/*
|
|
2314
|
+
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2315
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2316
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2317
|
+
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2318
|
+
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2319
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2320
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2321
|
+
});
|
|
2322
|
+
*/
|
|
1651
2323
|
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Sets the reachability of a device and trigger.
|
|
2326
|
+
*
|
|
2327
|
+
* @param {MatterbridgeEndpoint} device - The device to set the reachability for.
|
|
2328
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2329
|
+
*/
|
|
2330
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1652
2331
|
setDeviceReachability(device, reachable) {
|
|
2332
|
+
/*
|
|
2333
|
+
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2334
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2335
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2336
|
+
*/
|
|
1653
2337
|
}
|
|
1654
2338
|
getVendorIdName = (vendorId) => {
|
|
1655
2339
|
if (!vendorId)
|
|
@@ -1692,13 +2376,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1692
2376
|
}
|
|
1693
2377
|
return vendorName;
|
|
1694
2378
|
};
|
|
2379
|
+
/**
|
|
2380
|
+
* Spawns a child process with the given command and arguments.
|
|
2381
|
+
* @param {string} command - The command to execute.
|
|
2382
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2383
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2384
|
+
*/
|
|
1695
2385
|
async spawnCommand(command, args = []) {
|
|
2386
|
+
/*
|
|
2387
|
+
npm > npm.cmd on windows
|
|
2388
|
+
cmd.exe ['dir'] on windows
|
|
2389
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2390
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2391
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2395
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2396
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2397
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2398
|
+
*/
|
|
1696
2399
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1697
2400
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2401
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1698
2402
|
const argstring = 'npm ' + args.join(' ');
|
|
1699
2403
|
args.splice(0, args.length, '/c', argstring);
|
|
1700
2404
|
command = 'cmd.exe';
|
|
1701
2405
|
}
|
|
2406
|
+
// Decide when using sudo on linux
|
|
2407
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2408
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1702
2409
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1703
2410
|
args.unshift(command);
|
|
1704
2411
|
command = 'sudo';
|
|
@@ -1757,3 +2464,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1757
2464
|
});
|
|
1758
2465
|
}
|
|
1759
2466
|
}
|
|
2467
|
+
//# sourceMappingURL=matterbridge.js.map
|