matterbridge 2.1.1-dev.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -2
- 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 +46 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +26 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +109 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +227 -22
- 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 +4 -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 +755 -43
- 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 +164 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +123 -3
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +165 -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 +238 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +240 -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
|
-
import { DeviceCommissioner, FabricAction,
|
|
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: '',
|
|
@@ -53,7 +83,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
53
83
|
restartMode: '',
|
|
54
84
|
readOnly: hasParameter('readonly'),
|
|
55
85
|
profile: getParameter('profile'),
|
|
56
|
-
loggerLevel: "info"
|
|
86
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
57
87
|
fileLogger: false,
|
|
58
88
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
59
89
|
matterFileLogger: false,
|
|
@@ -88,9 +118,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
88
118
|
plugins;
|
|
89
119
|
devices;
|
|
90
120
|
frontend = new Frontend(this);
|
|
121
|
+
// Matterbridge storage
|
|
91
122
|
nodeStorage;
|
|
92
123
|
nodeContext;
|
|
93
124
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
125
|
+
// Cleanup
|
|
94
126
|
hasCleanupStarted = false;
|
|
95
127
|
initialized = false;
|
|
96
128
|
execRunningCount = 0;
|
|
@@ -102,34 +134,57 @@ export class Matterbridge extends EventEmitter {
|
|
|
102
134
|
sigtermHandler;
|
|
103
135
|
exceptionHandler;
|
|
104
136
|
rejectionHandler;
|
|
137
|
+
// Matter environment
|
|
105
138
|
environment = Environment.default;
|
|
139
|
+
// Matter storage
|
|
106
140
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
107
141
|
matterStorageService;
|
|
108
142
|
matterStorageManager;
|
|
109
143
|
matterbridgeContext;
|
|
110
144
|
mattercontrollerContext;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
// Matter parameters
|
|
146
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
147
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
148
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
149
|
+
port; // first server node port
|
|
150
|
+
passcode; // first server node passcode
|
|
151
|
+
discriminator; // first server node discriminator
|
|
117
152
|
serverNode;
|
|
118
153
|
aggregatorNode;
|
|
119
154
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
120
155
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
121
156
|
static instance;
|
|
157
|
+
// We load asyncronously so is private
|
|
122
158
|
constructor() {
|
|
123
159
|
super();
|
|
124
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Retrieves the list of Matterbridge devices.
|
|
163
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
164
|
+
*/
|
|
125
165
|
getDevices() {
|
|
126
166
|
return this.devices.array();
|
|
127
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Retrieves the list of registered plugins.
|
|
170
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
171
|
+
*/
|
|
128
172
|
getPlugins() {
|
|
129
173
|
return this.plugins.array();
|
|
130
174
|
}
|
|
175
|
+
/** ***********************************************************************************************************************************/
|
|
176
|
+
/** loadInstance() and cleanup() methods */
|
|
177
|
+
/** ***********************************************************************************************************************************/
|
|
178
|
+
/**
|
|
179
|
+
* Loads an instance of the Matterbridge class.
|
|
180
|
+
* If an instance already exists, return that instance.
|
|
181
|
+
*
|
|
182
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
183
|
+
* @returns The loaded Matterbridge instance.
|
|
184
|
+
*/
|
|
131
185
|
static async loadInstance(initialize = false) {
|
|
132
186
|
if (!Matterbridge.instance) {
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
133
188
|
if (hasParameter('debug'))
|
|
134
189
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
135
190
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -138,48 +193,89 @@ export class Matterbridge extends EventEmitter {
|
|
|
138
193
|
}
|
|
139
194
|
return Matterbridge.instance;
|
|
140
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Call cleanup().
|
|
198
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
199
|
+
*
|
|
200
|
+
*/
|
|
141
201
|
async destroyInstance() {
|
|
142
202
|
await this.cleanup('destroying instance...', false);
|
|
203
|
+
/*
|
|
204
|
+
await waiter(
|
|
205
|
+
'destroying instance...',
|
|
206
|
+
() => {
|
|
207
|
+
return this.initialized === false && this.execRunningCount <= 0 ? true : false;
|
|
208
|
+
},
|
|
209
|
+
false,
|
|
210
|
+
60000,
|
|
211
|
+
100,
|
|
212
|
+
false,
|
|
213
|
+
);
|
|
214
|
+
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
215
|
+
*/
|
|
143
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Initializes the Matterbridge application.
|
|
219
|
+
*
|
|
220
|
+
* @remarks
|
|
221
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
222
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
223
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
224
|
+
*
|
|
225
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
226
|
+
*/
|
|
144
227
|
async initialize() {
|
|
228
|
+
// Set the restart mode
|
|
145
229
|
if (hasParameter('service'))
|
|
146
230
|
this.restartMode = 'service';
|
|
147
231
|
if (hasParameter('docker'))
|
|
148
232
|
this.restartMode = 'docker';
|
|
233
|
+
// Set the matterbridge directory
|
|
149
234
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
150
235
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
236
|
+
// Setup matter environment
|
|
151
237
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
152
238
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
153
239
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
154
240
|
this.environment.vars.set('runtime.signals', false);
|
|
155
241
|
this.environment.vars.set('runtime.exitcode', false);
|
|
156
|
-
|
|
242
|
+
// Create matterbridge logger
|
|
243
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
244
|
+
// Register process handlers
|
|
157
245
|
this.registerProcessHandlers();
|
|
246
|
+
// Initialize nodeStorage and nodeContext
|
|
158
247
|
try {
|
|
159
248
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
160
249
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
161
250
|
this.log.debug('Creating node storage context for matterbridge');
|
|
162
251
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
252
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
254
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
164
255
|
for (const key of keys) {
|
|
165
256
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
258
|
await this.nodeStorage?.storage.get(key);
|
|
167
259
|
}
|
|
168
260
|
const storages = await this.nodeStorage.getStorageNames();
|
|
169
261
|
for (const storage of storages) {
|
|
170
262
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
171
263
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
264
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
265
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
266
|
const keys = (await nodeContext?.storage.keys());
|
|
173
267
|
keys.forEach(async (key) => {
|
|
174
268
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
175
269
|
await nodeContext?.get(key);
|
|
176
270
|
});
|
|
177
271
|
}
|
|
272
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
178
273
|
this.log.debug('Creating node storage backup...');
|
|
179
274
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
180
275
|
this.log.debug('Created node storage backup');
|
|
181
276
|
}
|
|
182
277
|
catch (error) {
|
|
278
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
183
279
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
184
280
|
if (hasParameter('norestore')) {
|
|
185
281
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -194,45 +290,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
194
290
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
195
291
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
196
292
|
}
|
|
293
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
197
294
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
295
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
198
296
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
297
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
199
298
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
200
299
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
300
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
201
301
|
if (hasParameter('logger')) {
|
|
202
302
|
const level = getParameter('logger');
|
|
203
303
|
if (level === 'debug') {
|
|
204
|
-
this.log.logLevel = "debug"
|
|
304
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
205
305
|
}
|
|
206
306
|
else if (level === 'info') {
|
|
207
|
-
this.log.logLevel = "info"
|
|
307
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
208
308
|
}
|
|
209
309
|
else if (level === 'notice') {
|
|
210
|
-
this.log.logLevel = "notice"
|
|
310
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
211
311
|
}
|
|
212
312
|
else if (level === 'warn') {
|
|
213
|
-
this.log.logLevel = "warn"
|
|
313
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
214
314
|
}
|
|
215
315
|
else if (level === 'error') {
|
|
216
|
-
this.log.logLevel = "error"
|
|
316
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
217
317
|
}
|
|
218
318
|
else if (level === 'fatal') {
|
|
219
|
-
this.log.logLevel = "fatal"
|
|
319
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
220
320
|
}
|
|
221
321
|
else {
|
|
222
322
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
223
|
-
this.log.logLevel = "info"
|
|
323
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
224
324
|
}
|
|
225
325
|
}
|
|
226
326
|
else {
|
|
227
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
327
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
228
328
|
}
|
|
229
329
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
330
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
230
331
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
231
332
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
232
333
|
this.matterbridgeInformation.fileLogger = true;
|
|
233
334
|
}
|
|
234
335
|
this.log.notice('Matterbridge is starting...');
|
|
235
336
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
337
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
236
338
|
if (hasParameter('matterlogger')) {
|
|
237
339
|
const level = getParameter('matterlogger');
|
|
238
340
|
if (level === 'debug') {
|
|
@@ -263,6 +365,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
263
365
|
}
|
|
264
366
|
Logger.format = MatterLogFormat.ANSI;
|
|
265
367
|
Logger.setLogger('default', this.createMatterLogger());
|
|
368
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
266
369
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
267
370
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
268
371
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -271,6 +374,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
271
374
|
});
|
|
272
375
|
}
|
|
273
376
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
274
378
|
if (hasParameter('mdnsinterface')) {
|
|
275
379
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
276
380
|
}
|
|
@@ -279,6 +383,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
279
383
|
if (this.mdnsInterface === '')
|
|
280
384
|
this.mdnsInterface = undefined;
|
|
281
385
|
}
|
|
386
|
+
// Validate mdnsInterface
|
|
282
387
|
if (this.mdnsInterface) {
|
|
283
388
|
const networkInterfaces = os.networkInterfaces();
|
|
284
389
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -292,6 +397,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
292
397
|
}
|
|
293
398
|
if (this.mdnsInterface)
|
|
294
399
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
400
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
295
401
|
if (hasParameter('ipv4address')) {
|
|
296
402
|
this.ipv4address = getParameter('ipv4address');
|
|
297
403
|
}
|
|
@@ -300,6 +406,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
300
406
|
if (this.ipv4address === '')
|
|
301
407
|
this.ipv4address = undefined;
|
|
302
408
|
}
|
|
409
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
303
410
|
if (hasParameter('ipv6address')) {
|
|
304
411
|
this.ipv6address = getParameter('ipv6address');
|
|
305
412
|
}
|
|
@@ -308,14 +415,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
308
415
|
if (this.ipv6address === '')
|
|
309
416
|
this.ipv6address = undefined;
|
|
310
417
|
}
|
|
418
|
+
// Initialize PluginManager
|
|
311
419
|
this.plugins = new PluginManager(this);
|
|
312
420
|
await this.plugins.loadFromStorage();
|
|
313
421
|
this.plugins.logLevel = this.log.logLevel;
|
|
422
|
+
// Initialize DeviceManager
|
|
314
423
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
315
424
|
this.devices.logLevel = this.log.logLevel;
|
|
425
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
316
426
|
for (const plugin of this.plugins) {
|
|
317
427
|
const packageJson = await this.plugins.parse(plugin);
|
|
318
428
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
429
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
430
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
319
431
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
320
432
|
try {
|
|
321
433
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -337,6 +449,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
337
449
|
await plugin.nodeContext.set('description', plugin.description);
|
|
338
450
|
await plugin.nodeContext.set('author', plugin.author);
|
|
339
451
|
}
|
|
452
|
+
// Log system info and create .matterbridge directory
|
|
340
453
|
await this.logNodeAndSystemInfo();
|
|
341
454
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
342
455
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -344,6 +457,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
344
457
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
345
458
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
346
459
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
460
|
+
// Check node version and throw error
|
|
347
461
|
const minNodeVersion = 18;
|
|
348
462
|
const nodeVersion = process.versions.node;
|
|
349
463
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -351,9 +465,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
351
465
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
352
466
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
353
467
|
}
|
|
468
|
+
// Parse command line
|
|
354
469
|
await this.parseCommandLine();
|
|
355
470
|
this.initialized = true;
|
|
356
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
474
|
+
* @private
|
|
475
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
476
|
+
*/
|
|
357
477
|
async parseCommandLine() {
|
|
358
478
|
if (hasParameter('help')) {
|
|
359
479
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -463,6 +583,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
463
583
|
await this.shutdownProcessAndFactoryReset();
|
|
464
584
|
return;
|
|
465
585
|
}
|
|
586
|
+
// Start the matter storage and create the matterbridge context
|
|
466
587
|
try {
|
|
467
588
|
await this.startMatterStorage();
|
|
468
589
|
}
|
|
@@ -470,10 +591,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
470
591
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
471
592
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
472
593
|
}
|
|
594
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
473
595
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
474
596
|
await this.shutdownProcessAndReset();
|
|
475
597
|
return;
|
|
476
598
|
}
|
|
599
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
477
600
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
478
601
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
479
602
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -495,9 +618,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
495
618
|
this.emit('shutdown');
|
|
496
619
|
return;
|
|
497
620
|
}
|
|
621
|
+
// Initialize frontend
|
|
498
622
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
499
623
|
await this.frontend.start(getIntParameter('frontend'));
|
|
500
624
|
this.frontend.logLevel = this.log.logLevel;
|
|
625
|
+
// Check each 60 minutes the latest versions
|
|
501
626
|
this.checkUpdateInterval = setInterval(() => {
|
|
502
627
|
this.getMatterbridgeLatestVersion();
|
|
503
628
|
for (const plugin of this.plugins) {
|
|
@@ -505,20 +630,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
505
630
|
}
|
|
506
631
|
this.frontend.wssSendRefreshRequired();
|
|
507
632
|
}, 60 * 60 * 1000);
|
|
633
|
+
// Start the matterbridge in mode test
|
|
508
634
|
if (hasParameter('test')) {
|
|
509
635
|
this.bridgeMode = 'bridge';
|
|
510
636
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
511
637
|
return;
|
|
512
638
|
}
|
|
639
|
+
// Start the matterbridge in mode controller
|
|
513
640
|
if (hasParameter('controller')) {
|
|
514
641
|
this.bridgeMode = 'controller';
|
|
515
642
|
await this.startController();
|
|
516
643
|
return;
|
|
517
644
|
}
|
|
645
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
518
646
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
519
647
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
520
648
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
521
649
|
}
|
|
650
|
+
// Start matterbridge in bridge mode
|
|
522
651
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
523
652
|
this.bridgeMode = 'bridge';
|
|
524
653
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -526,6 +655,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
526
655
|
await this.startBridge();
|
|
527
656
|
return;
|
|
528
657
|
}
|
|
658
|
+
// Start matterbridge in childbridge mode
|
|
529
659
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
530
660
|
this.bridgeMode = 'childbridge';
|
|
531
661
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -534,16 +664,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
534
664
|
return;
|
|
535
665
|
}
|
|
536
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Asynchronously loads and starts the registered plugins.
|
|
669
|
+
*
|
|
670
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
671
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
672
|
+
*
|
|
673
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
674
|
+
*/
|
|
537
675
|
async startPlugins() {
|
|
676
|
+
// Check, load and start the plugins
|
|
538
677
|
for (const plugin of this.plugins) {
|
|
539
678
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
540
679
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
680
|
+
// Check if the plugin is available
|
|
541
681
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
542
682
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
543
683
|
plugin.enabled = false;
|
|
544
684
|
plugin.error = true;
|
|
545
685
|
continue;
|
|
546
686
|
}
|
|
687
|
+
// Check if the plugin has a new version
|
|
688
|
+
// this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
547
689
|
if (!plugin.enabled) {
|
|
548
690
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
549
691
|
continue;
|
|
@@ -557,20 +699,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
557
699
|
plugin.addedDevices = undefined;
|
|
558
700
|
plugin.qrPairingCode = undefined;
|
|
559
701
|
plugin.manualPairingCode = undefined;
|
|
560
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
702
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
561
703
|
}
|
|
562
704
|
this.frontend.wssSendRefreshRequired();
|
|
563
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
708
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
709
|
+
*/
|
|
564
710
|
registerProcessHandlers() {
|
|
565
711
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
566
712
|
process.removeAllListeners('uncaughtException');
|
|
567
713
|
process.removeAllListeners('unhandledRejection');
|
|
568
714
|
this.exceptionHandler = async (error) => {
|
|
569
715
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
716
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
570
717
|
};
|
|
571
718
|
process.on('uncaughtException', this.exceptionHandler);
|
|
572
719
|
this.rejectionHandler = async (reason, promise) => {
|
|
573
720
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
721
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
574
722
|
};
|
|
575
723
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
576
724
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -583,6 +731,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
583
731
|
};
|
|
584
732
|
process.on('SIGTERM', this.sigtermHandler);
|
|
585
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
736
|
+
*/
|
|
586
737
|
deregisterProcesslHandlers() {
|
|
587
738
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
588
739
|
if (this.exceptionHandler)
|
|
@@ -599,12 +750,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
599
750
|
process.off('SIGTERM', this.sigtermHandler);
|
|
600
751
|
this.sigtermHandler = undefined;
|
|
601
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* Logs the node and system information.
|
|
755
|
+
*/
|
|
602
756
|
async logNodeAndSystemInfo() {
|
|
757
|
+
// IP address information
|
|
603
758
|
const networkInterfaces = os.networkInterfaces();
|
|
604
759
|
this.systemInformation.interfaceName = '';
|
|
605
760
|
this.systemInformation.ipv4Address = '';
|
|
606
761
|
this.systemInformation.ipv6Address = '';
|
|
607
762
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
763
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
608
764
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
609
765
|
continue;
|
|
610
766
|
if (!interfaceDetails) {
|
|
@@ -630,19 +786,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
630
786
|
break;
|
|
631
787
|
}
|
|
632
788
|
}
|
|
789
|
+
// Node information
|
|
633
790
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
634
791
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
635
792
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
636
793
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
794
|
+
// Host system information
|
|
637
795
|
this.systemInformation.hostname = os.hostname();
|
|
638
796
|
this.systemInformation.user = os.userInfo().username;
|
|
639
|
-
this.systemInformation.osType = os.type();
|
|
640
|
-
this.systemInformation.osRelease = os.release();
|
|
641
|
-
this.systemInformation.osPlatform = os.platform();
|
|
642
|
-
this.systemInformation.osArch = os.arch();
|
|
643
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
644
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
645
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
797
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
798
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
799
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
800
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
801
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
802
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
803
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
804
|
+
// Log the system information
|
|
646
805
|
this.log.debug('Host System Information:');
|
|
647
806
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
648
807
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -658,15 +817,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
658
817
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
659
818
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
660
819
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
820
|
+
// Home directory
|
|
661
821
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
662
822
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
663
823
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
824
|
+
// Package root directory
|
|
664
825
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
665
826
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
666
827
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
667
828
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
829
|
+
// Global node_modules directory
|
|
668
830
|
if (this.nodeContext)
|
|
669
831
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
832
|
+
// First run of Matterbridge so the node storage is empty
|
|
670
833
|
if (this.globalModulesDirectory === '') {
|
|
671
834
|
try {
|
|
672
835
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -680,6 +843,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
680
843
|
}
|
|
681
844
|
else
|
|
682
845
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
846
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
847
|
+
else {
|
|
848
|
+
this.getGlobalNodeModules()
|
|
849
|
+
.then(async (globalModulesDirectory) => {
|
|
850
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
851
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
852
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
853
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
854
|
+
})
|
|
855
|
+
.catch((error) => {
|
|
856
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
857
|
+
});
|
|
858
|
+
}*/
|
|
859
|
+
// Create the data directory .matterbridge in the home directory
|
|
683
860
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
684
861
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
685
862
|
try {
|
|
@@ -703,6 +880,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
703
880
|
}
|
|
704
881
|
}
|
|
705
882
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
883
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
706
884
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
707
885
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
708
886
|
try {
|
|
@@ -726,18 +904,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
726
904
|
}
|
|
727
905
|
}
|
|
728
906
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
907
|
+
// Matterbridge version
|
|
729
908
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
730
909
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
731
910
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
732
911
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
912
|
+
// Matterbridge latest version
|
|
733
913
|
if (this.nodeContext)
|
|
734
914
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
735
915
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
916
|
+
// this.getMatterbridgeLatestVersion();
|
|
917
|
+
// Current working directory
|
|
736
918
|
const currentDir = process.cwd();
|
|
737
919
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
920
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
738
921
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
739
922
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
740
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
926
|
+
* @param packageName - The name of the package.
|
|
927
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
928
|
+
*/
|
|
741
929
|
async getLatestVersion(packageName) {
|
|
742
930
|
return new Promise((resolve, reject) => {
|
|
743
931
|
this.execRunningCount++;
|
|
@@ -752,6 +940,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
752
940
|
});
|
|
753
941
|
});
|
|
754
942
|
}
|
|
943
|
+
/**
|
|
944
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
945
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
946
|
+
*/
|
|
755
947
|
async getGlobalNodeModules() {
|
|
756
948
|
return new Promise((resolve, reject) => {
|
|
757
949
|
this.execRunningCount++;
|
|
@@ -766,6 +958,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
766
958
|
});
|
|
767
959
|
});
|
|
768
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
963
|
+
* @private
|
|
964
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
965
|
+
*/
|
|
769
966
|
async getMatterbridgeLatestVersion() {
|
|
770
967
|
this.getLatestVersion('matterbridge')
|
|
771
968
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -782,8 +979,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
782
979
|
})
|
|
783
980
|
.catch((error) => {
|
|
784
981
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
982
|
+
// error.stack && this.log.debug(error.stack);
|
|
785
983
|
});
|
|
786
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
987
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
988
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
989
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
990
|
+
*
|
|
991
|
+
* @private
|
|
992
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
993
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
994
|
+
*/
|
|
787
995
|
async getPluginLatestVersion(plugin) {
|
|
788
996
|
this.getLatestVersion(plugin.name)
|
|
789
997
|
.then(async (latestVersion) => {
|
|
@@ -795,40 +1003,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
795
1003
|
})
|
|
796
1004
|
.catch((error) => {
|
|
797
1005
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1006
|
+
// error.stack && this.log.debug(error.stack);
|
|
798
1007
|
});
|
|
799
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1011
|
+
*
|
|
1012
|
+
* @returns {Function} The MatterLogger function.
|
|
1013
|
+
*/
|
|
800
1014
|
createMatterLogger() {
|
|
801
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1015
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
802
1016
|
return (_level, formattedLog) => {
|
|
803
1017
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
804
1018
|
const message = formattedLog.slice(65);
|
|
805
1019
|
matterLogger.logName = logger;
|
|
806
1020
|
switch (_level) {
|
|
807
1021
|
case MatterLogLevel.DEBUG:
|
|
808
|
-
matterLogger.log("debug"
|
|
1022
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
809
1023
|
break;
|
|
810
1024
|
case MatterLogLevel.INFO:
|
|
811
|
-
matterLogger.log("info"
|
|
1025
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
812
1026
|
break;
|
|
813
1027
|
case MatterLogLevel.NOTICE:
|
|
814
|
-
matterLogger.log("notice"
|
|
1028
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
815
1029
|
break;
|
|
816
1030
|
case MatterLogLevel.WARN:
|
|
817
|
-
matterLogger.log("warn"
|
|
1031
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
818
1032
|
break;
|
|
819
1033
|
case MatterLogLevel.ERROR:
|
|
820
|
-
matterLogger.log("error"
|
|
1034
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
821
1035
|
break;
|
|
822
1036
|
case MatterLogLevel.FATAL:
|
|
823
|
-
matterLogger.log("fatal"
|
|
1037
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
824
1038
|
break;
|
|
825
1039
|
default:
|
|
826
|
-
matterLogger.log("debug"
|
|
1040
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
827
1041
|
break;
|
|
828
1042
|
}
|
|
829
1043
|
};
|
|
830
1044
|
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Creates a Matter File Logger.
|
|
1047
|
+
*
|
|
1048
|
+
* @param {string} filePath - The path to the log file.
|
|
1049
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1050
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1051
|
+
*/
|
|
831
1052
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1053
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
832
1054
|
let fileSize = 0;
|
|
833
1055
|
if (unlink) {
|
|
834
1056
|
try {
|
|
@@ -877,12 +1099,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
877
1099
|
}
|
|
878
1100
|
};
|
|
879
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1104
|
+
*/
|
|
880
1105
|
async restartProcess() {
|
|
881
1106
|
await this.cleanup('restarting...', true);
|
|
882
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Shut down the process by exiting the current process.
|
|
1110
|
+
*/
|
|
883
1111
|
async shutdownProcess() {
|
|
884
1112
|
await this.cleanup('shutting down...', false);
|
|
885
1113
|
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Update matterbridge and and shut down the process.
|
|
1116
|
+
*/
|
|
886
1117
|
async updateProcess() {
|
|
887
1118
|
this.log.info('Updating matterbridge...');
|
|
888
1119
|
try {
|
|
@@ -895,6 +1126,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
895
1126
|
this.frontend.wssSendRestartRequired();
|
|
896
1127
|
await this.cleanup('updating...', false);
|
|
897
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Unregister all devices and shut down the process.
|
|
1131
|
+
*/
|
|
898
1132
|
async unregisterAndShutdownProcess() {
|
|
899
1133
|
this.log.info('Unregistering all devices and shutting down...');
|
|
900
1134
|
for (const plugin of this.plugins) {
|
|
@@ -902,6 +1136,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
902
1136
|
}
|
|
903
1137
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
904
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Reset commissioning and shut down the process.
|
|
1141
|
+
*/
|
|
905
1142
|
async shutdownProcessAndReset() {
|
|
906
1143
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
907
1144
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -913,8 +1150,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
913
1150
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
914
1151
|
await this.cleanup('shutting down with reset...', false);
|
|
915
1152
|
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Factory reset and shut down the process.
|
|
1155
|
+
*/
|
|
916
1156
|
async shutdownProcessAndFactoryReset() {
|
|
917
1157
|
try {
|
|
1158
|
+
// Delete old matter storage file and backup
|
|
918
1159
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
919
1160
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
920
1161
|
await fs.unlink(file);
|
|
@@ -928,6 +1169,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
928
1169
|
}
|
|
929
1170
|
}
|
|
930
1171
|
try {
|
|
1172
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
931
1173
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
932
1174
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
933
1175
|
await fs.rm(dir, { recursive: true });
|
|
@@ -941,6 +1183,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
941
1183
|
}
|
|
942
1184
|
}
|
|
943
1185
|
try {
|
|
1186
|
+
// Delete node storage directory with its subdirectories and backup
|
|
944
1187
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
945
1188
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
946
1189
|
await fs.rm(dir, { recursive: true });
|
|
@@ -960,30 +1203,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
960
1203
|
this.devices.clear();
|
|
961
1204
|
await this.cleanup('shutting down with factory reset...', false);
|
|
962
1205
|
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Cleans up the Matterbridge instance.
|
|
1208
|
+
* @param message - The cleanup message.
|
|
1209
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1210
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1211
|
+
*/
|
|
963
1212
|
async cleanup(message, restart = false) {
|
|
964
1213
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
965
1214
|
this.hasCleanupStarted = true;
|
|
966
1215
|
this.log.info(message);
|
|
1216
|
+
// Clear the start matter interval
|
|
967
1217
|
if (this.startMatterInterval) {
|
|
968
1218
|
clearInterval(this.startMatterInterval);
|
|
969
1219
|
this.startMatterInterval = undefined;
|
|
970
1220
|
this.log.debug('Start matter interval cleared');
|
|
971
1221
|
}
|
|
1222
|
+
// Clear the check update interval
|
|
972
1223
|
if (this.checkUpdateInterval) {
|
|
973
1224
|
clearInterval(this.checkUpdateInterval);
|
|
974
1225
|
this.checkUpdateInterval = undefined;
|
|
975
1226
|
this.log.debug('Check update interval cleared');
|
|
976
1227
|
}
|
|
1228
|
+
// Clear the configure timeout
|
|
977
1229
|
if (this.configureTimeout) {
|
|
978
1230
|
clearTimeout(this.configureTimeout);
|
|
979
1231
|
this.configureTimeout = undefined;
|
|
980
1232
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
981
1233
|
}
|
|
1234
|
+
// Clear the reachability timeout
|
|
982
1235
|
if (this.reachabilityTimeout) {
|
|
983
1236
|
clearTimeout(this.reachabilityTimeout);
|
|
984
1237
|
this.reachabilityTimeout = undefined;
|
|
985
1238
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
986
1239
|
}
|
|
1240
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
987
1241
|
for (const plugin of this.plugins) {
|
|
988
1242
|
if (!plugin.enabled || plugin.error)
|
|
989
1243
|
continue;
|
|
@@ -994,13 +1248,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
994
1248
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
995
1249
|
}
|
|
996
1250
|
}
|
|
1251
|
+
// Stop the frontend
|
|
997
1252
|
this.frontend.stop();
|
|
998
|
-
|
|
1253
|
+
// Stopping matter server nodes
|
|
1254
|
+
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
999
1255
|
if (this.bridgeMode === 'bridge') {
|
|
1000
1256
|
if (this.serverNode) {
|
|
1001
1257
|
await this.stopServerNode(this.serverNode);
|
|
1002
1258
|
this.serverNode = undefined;
|
|
1003
|
-
this.log.info(`Stopped matter server node for Matterbridge`);
|
|
1004
1259
|
}
|
|
1005
1260
|
}
|
|
1006
1261
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1008,21 +1263,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1008
1263
|
if (plugin.serverNode) {
|
|
1009
1264
|
await this.stopServerNode(plugin.serverNode);
|
|
1010
1265
|
plugin.serverNode = undefined;
|
|
1011
|
-
this.log.info(`Stopped matter server node for ${plugin.name}`);
|
|
1012
1266
|
}
|
|
1013
1267
|
}
|
|
1014
1268
|
}
|
|
1015
|
-
this.log.
|
|
1269
|
+
this.log.notice('Stopped matter server nodes');
|
|
1270
|
+
// Stop matter storage
|
|
1016
1271
|
await this.stopMatterStorage();
|
|
1272
|
+
// Remove the matterfilelogger
|
|
1017
1273
|
try {
|
|
1018
1274
|
Logger.removeLogger('matterfilelogger');
|
|
1275
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1019
1276
|
}
|
|
1020
1277
|
catch (error) {
|
|
1278
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1021
1279
|
}
|
|
1280
|
+
// Serialize registeredDevices
|
|
1022
1281
|
if (this.nodeStorage && this.nodeContext) {
|
|
1282
|
+
/*
|
|
1283
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1284
|
+
this.log.info('Saving registered devices...');
|
|
1285
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1286
|
+
this.devices.forEach(async (device) => {
|
|
1287
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1288
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1289
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1290
|
+
});
|
|
1291
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1292
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1293
|
+
*/
|
|
1294
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1023
1295
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1024
1296
|
await this.nodeContext.close();
|
|
1025
1297
|
this.nodeContext = undefined;
|
|
1298
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1026
1299
|
for (const plugin of this.plugins) {
|
|
1027
1300
|
if (plugin.nodeContext) {
|
|
1028
1301
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1039,12 +1312,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1039
1312
|
}
|
|
1040
1313
|
this.plugins.clear();
|
|
1041
1314
|
this.devices.clear();
|
|
1315
|
+
// Deregisters the process handlers
|
|
1042
1316
|
this.deregisterProcesslHandlers();
|
|
1043
1317
|
if (restart) {
|
|
1044
1318
|
if (message === 'updating...') {
|
|
1045
1319
|
this.log.info('Cleanup completed. Updating...');
|
|
1046
1320
|
Matterbridge.instance = undefined;
|
|
1047
|
-
this.emit('update');
|
|
1321
|
+
this.emit('update'); // Restart the process but the update has been done before
|
|
1048
1322
|
}
|
|
1049
1323
|
else if (message === 'restarting...') {
|
|
1050
1324
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1061,6 +1335,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1061
1335
|
this.initialized = false;
|
|
1062
1336
|
}
|
|
1063
1337
|
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1340
|
+
*
|
|
1341
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1342
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1343
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1344
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1345
|
+
*/
|
|
1064
1346
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1065
1347
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1066
1348
|
plugin.locked = true;
|
|
@@ -1072,6 +1354,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1072
1354
|
await this.startServerNode(plugin.serverNode);
|
|
1073
1355
|
}
|
|
1074
1356
|
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1359
|
+
*
|
|
1360
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1361
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1362
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1363
|
+
*/
|
|
1075
1364
|
async createDynamicPlugin(plugin, start = false) {
|
|
1076
1365
|
if (!plugin.locked) {
|
|
1077
1366
|
plugin.locked = true;
|
|
@@ -1083,7 +1372,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1083
1372
|
await this.startServerNode(plugin.serverNode);
|
|
1084
1373
|
}
|
|
1085
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Starts the Matterbridge in bridge mode.
|
|
1377
|
+
* @private
|
|
1378
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1379
|
+
*/
|
|
1086
1380
|
async startBridge() {
|
|
1381
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1087
1382
|
if (!this.matterStorageManager)
|
|
1088
1383
|
throw new Error('No storage manager initialized');
|
|
1089
1384
|
if (!this.matterbridgeContext)
|
|
@@ -1120,7 +1415,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1120
1415
|
clearInterval(this.startMatterInterval);
|
|
1121
1416
|
this.startMatterInterval = undefined;
|
|
1122
1417
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1418
|
+
// Start the Matter server node
|
|
1123
1419
|
this.startServerNode(this.serverNode);
|
|
1420
|
+
// Configure the plugins
|
|
1124
1421
|
this.configureTimeout = setTimeout(async () => {
|
|
1125
1422
|
for (const plugin of this.plugins) {
|
|
1126
1423
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1135,6 +1432,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1135
1432
|
}
|
|
1136
1433
|
this.frontend.wssSendRefreshRequired();
|
|
1137
1434
|
}, 30 * 1000);
|
|
1435
|
+
// Setting reachability to true
|
|
1138
1436
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1139
1437
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1140
1438
|
if (this.serverNode)
|
|
@@ -1145,7 +1443,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1145
1443
|
}, 60 * 1000);
|
|
1146
1444
|
}, 1000);
|
|
1147
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1448
|
+
* @private
|
|
1449
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1450
|
+
*/
|
|
1148
1451
|
async startChildbridge() {
|
|
1452
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1453
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1149
1454
|
if (!this.matterStorageManager)
|
|
1150
1455
|
throw new Error('No storage manager initialized');
|
|
1151
1456
|
for (const plugin of this.plugins) {
|
|
@@ -1192,12 +1497,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1192
1497
|
clearInterval(this.startMatterInterval);
|
|
1193
1498
|
this.startMatterInterval = undefined;
|
|
1194
1499
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1500
|
+
// Configure the plugins
|
|
1195
1501
|
this.configureTimeout = setTimeout(async () => {
|
|
1196
1502
|
for (const plugin of this.plugins) {
|
|
1197
1503
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1198
1504
|
continue;
|
|
1199
1505
|
try {
|
|
1200
|
-
await this.plugins.configure(plugin);
|
|
1506
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1201
1507
|
}
|
|
1202
1508
|
catch (error) {
|
|
1203
1509
|
plugin.error = true;
|
|
@@ -1225,7 +1531,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1225
1531
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1226
1532
|
continue;
|
|
1227
1533
|
}
|
|
1534
|
+
// Start the Matter server node
|
|
1228
1535
|
this.startServerNode(plugin.serverNode);
|
|
1536
|
+
// Setting reachability to true
|
|
1229
1537
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1230
1538
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1231
1539
|
if (plugin.serverNode)
|
|
@@ -1239,9 +1547,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1239
1547
|
}
|
|
1240
1548
|
}, 1000);
|
|
1241
1549
|
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Starts the Matterbridge controller.
|
|
1552
|
+
* @private
|
|
1553
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1554
|
+
*/
|
|
1242
1555
|
async startController() {
|
|
1556
|
+
/*
|
|
1557
|
+
if (!this.storageManager) {
|
|
1558
|
+
this.log.error('No storage manager initialized');
|
|
1559
|
+
await this.cleanup('No storage manager initialized');
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1563
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1564
|
+
if (!this.mattercontrollerContext) {
|
|
1565
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1566
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1571
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1572
|
+
this.log.info('Creating matter commissioning controller');
|
|
1573
|
+
this.commissioningController = new CommissioningController({
|
|
1574
|
+
autoConnect: false,
|
|
1575
|
+
});
|
|
1576
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1577
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1578
|
+
|
|
1579
|
+
this.log.info('Starting matter server');
|
|
1580
|
+
await this.matterServer.start();
|
|
1581
|
+
this.log.info('Matter server started');
|
|
1582
|
+
|
|
1583
|
+
if (hasParameter('pairingcode')) {
|
|
1584
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1585
|
+
const pairingCode = getParameter('pairingcode');
|
|
1586
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1587
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1588
|
+
|
|
1589
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1590
|
+
if (pairingCode !== undefined) {
|
|
1591
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1592
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1593
|
+
longDiscriminator = undefined;
|
|
1594
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1595
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1596
|
+
} else {
|
|
1597
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1598
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1599
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1600
|
+
}
|
|
1601
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1602
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1606
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1607
|
+
regulatoryCountryCode: 'XX',
|
|
1608
|
+
};
|
|
1609
|
+
const options = {
|
|
1610
|
+
commissioning: commissioningOptions,
|
|
1611
|
+
discovery: {
|
|
1612
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1613
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1614
|
+
},
|
|
1615
|
+
passcode: setupPin,
|
|
1616
|
+
} as NodeCommissioningOptions;
|
|
1617
|
+
this.log.info('Commissioning with options:', options);
|
|
1618
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1619
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1620
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1621
|
+
} // (hasParameter('pairingcode'))
|
|
1622
|
+
|
|
1623
|
+
if (hasParameter('unpairall')) {
|
|
1624
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1625
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1626
|
+
for (const nodeId of nodeIds) {
|
|
1627
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1628
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1629
|
+
}
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
if (hasParameter('discover')) {
|
|
1634
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1635
|
+
// console.log(discover);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1639
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1644
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1645
|
+
for (const nodeId of nodeIds) {
|
|
1646
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1647
|
+
|
|
1648
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1649
|
+
autoSubscribe: false,
|
|
1650
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1651
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1652
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1653
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1654
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1655
|
+
switch (info) {
|
|
1656
|
+
case NodeStateInformation.Connected:
|
|
1657
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1658
|
+
break;
|
|
1659
|
+
case NodeStateInformation.Disconnected:
|
|
1660
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1661
|
+
break;
|
|
1662
|
+
case NodeStateInformation.Reconnecting:
|
|
1663
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1664
|
+
break;
|
|
1665
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1666
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1667
|
+
break;
|
|
1668
|
+
case NodeStateInformation.StructureChanged:
|
|
1669
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1670
|
+
break;
|
|
1671
|
+
case NodeStateInformation.Decommissioned:
|
|
1672
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1673
|
+
break;
|
|
1674
|
+
default:
|
|
1675
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
node.logStructure();
|
|
1682
|
+
|
|
1683
|
+
// Get the interaction client
|
|
1684
|
+
this.log.info('Getting the interaction client');
|
|
1685
|
+
const interactionClient = await node.getInteractionClient();
|
|
1686
|
+
let cluster;
|
|
1687
|
+
let attributes;
|
|
1688
|
+
|
|
1689
|
+
// Log BasicInformationCluster
|
|
1690
|
+
cluster = BasicInformationCluster;
|
|
1691
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1692
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1693
|
+
});
|
|
1694
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1695
|
+
attributes.forEach((attribute) => {
|
|
1696
|
+
this.log.info(
|
|
1697
|
+
`- 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}`,
|
|
1698
|
+
);
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
// Log PowerSourceCluster
|
|
1702
|
+
cluster = PowerSourceCluster;
|
|
1703
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1704
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1705
|
+
});
|
|
1706
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1707
|
+
attributes.forEach((attribute) => {
|
|
1708
|
+
this.log.info(
|
|
1709
|
+
`- 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}`,
|
|
1710
|
+
);
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
// Log ThreadNetworkDiagnostics
|
|
1714
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1715
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1716
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1717
|
+
});
|
|
1718
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1719
|
+
attributes.forEach((attribute) => {
|
|
1720
|
+
this.log.info(
|
|
1721
|
+
`- 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}`,
|
|
1722
|
+
);
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
// Log SwitchCluster
|
|
1726
|
+
cluster = SwitchCluster;
|
|
1727
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1728
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1729
|
+
});
|
|
1730
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1731
|
+
attributes.forEach((attribute) => {
|
|
1732
|
+
this.log.info(
|
|
1733
|
+
`- 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}`,
|
|
1734
|
+
);
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1738
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1739
|
+
ignoreInitialTriggers: false,
|
|
1740
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1741
|
+
this.log.info(
|
|
1742
|
+
`***${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}`,
|
|
1743
|
+
),
|
|
1744
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1745
|
+
this.log.info(
|
|
1746
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1747
|
+
);
|
|
1748
|
+
},
|
|
1749
|
+
});
|
|
1750
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1751
|
+
}
|
|
1752
|
+
*/
|
|
1243
1753
|
}
|
|
1754
|
+
/** ***********************************************************************************************************************************/
|
|
1755
|
+
/** Matter.js methods */
|
|
1756
|
+
/** ***********************************************************************************************************************************/
|
|
1757
|
+
/**
|
|
1758
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1759
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1760
|
+
*/
|
|
1244
1761
|
async startMatterStorage() {
|
|
1762
|
+
// Setup Matter storage
|
|
1245
1763
|
this.log.info(`Starting matter node storage...`);
|
|
1246
1764
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1247
1765
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1249,13 +1767,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1249
1767
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1250
1768
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1251
1769
|
this.log.info('Matter node storage started');
|
|
1770
|
+
// Backup matter storage since it is created/opened correctly
|
|
1252
1771
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1253
1772
|
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1775
|
+
*
|
|
1776
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1777
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1778
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1779
|
+
*/
|
|
1254
1780
|
async backupMatterStorage(storageName, backupName) {
|
|
1255
1781
|
this.log.info('Creating matter node storage backup...');
|
|
1256
1782
|
await copyDirectory(storageName, backupName);
|
|
1257
1783
|
this.log.info('Created matter node storage backup');
|
|
1258
1784
|
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Stops the matter storage.
|
|
1787
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1788
|
+
*/
|
|
1259
1789
|
async stopMatterStorage() {
|
|
1260
1790
|
this.log.info('Closing matter node storage...');
|
|
1261
1791
|
this.matterStorageManager?.close();
|
|
@@ -1264,6 +1794,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1264
1794
|
this.matterbridgeContext = undefined;
|
|
1265
1795
|
this.log.info('Matter node storage closed');
|
|
1266
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Creates a server node storage context.
|
|
1799
|
+
*
|
|
1800
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1801
|
+
* @param {string} deviceName - The name of the device.
|
|
1802
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1803
|
+
* @param {number} vendorId - The vendor ID.
|
|
1804
|
+
* @param {string} vendorName - The vendor name.
|
|
1805
|
+
* @param {number} productId - The product ID.
|
|
1806
|
+
* @param {string} productName - The product name.
|
|
1807
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1808
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1809
|
+
*/
|
|
1267
1810
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1268
1811
|
if (!this.matterStorageService)
|
|
1269
1812
|
throw new Error('No storage service initialized');
|
|
@@ -1296,6 +1839,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1296
1839
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1297
1840
|
return storageContext;
|
|
1298
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Creates a server node.
|
|
1844
|
+
*
|
|
1845
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1846
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1847
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1848
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1849
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1850
|
+
*/
|
|
1299
1851
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1300
1852
|
const storeId = await storageContext.get('storeId');
|
|
1301
1853
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1305,21 +1857,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1305
1857
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1306
1858
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1307
1859
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1860
|
+
/**
|
|
1861
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1862
|
+
*/
|
|
1308
1863
|
const serverNode = await ServerNode.create({
|
|
1864
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1309
1865
|
id: storeId,
|
|
1866
|
+
// Provide Network relevant configuration like the port
|
|
1867
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1310
1868
|
network: {
|
|
1311
1869
|
listeningAddressIpv4: this.ipv4address,
|
|
1312
1870
|
listeningAddressIpv6: this.ipv6address,
|
|
1313
1871
|
port,
|
|
1314
1872
|
},
|
|
1873
|
+
// Provide Commissioning relevant settings
|
|
1874
|
+
// Optional for development/testing purposes
|
|
1315
1875
|
commissioning: {
|
|
1316
1876
|
passcode,
|
|
1317
1877
|
discriminator,
|
|
1318
1878
|
},
|
|
1879
|
+
// Provide Node announcement settings
|
|
1880
|
+
// Optional: If Ommitted some development defaults are used
|
|
1319
1881
|
productDescription: {
|
|
1320
1882
|
name: await storageContext.get('deviceName'),
|
|
1321
1883
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1322
1884
|
},
|
|
1885
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1886
|
+
// Optional: If Omitted some development defaults are used
|
|
1323
1887
|
basicInformation: {
|
|
1324
1888
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1325
1889
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1336,12 +1900,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1336
1900
|
},
|
|
1337
1901
|
});
|
|
1338
1902
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1903
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1339
1904
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1340
1905
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1341
1906
|
if (this.bridgeMode === 'bridge') {
|
|
1342
1907
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1343
1908
|
if (resetSessions)
|
|
1344
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1909
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1345
1910
|
this.matterbridgePaired = true;
|
|
1346
1911
|
}
|
|
1347
1912
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1349,13 +1914,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1349
1914
|
if (plugin) {
|
|
1350
1915
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1351
1916
|
if (resetSessions)
|
|
1352
|
-
plugin.sessionInformations = undefined;
|
|
1917
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1353
1918
|
plugin.paired = true;
|
|
1354
1919
|
}
|
|
1355
1920
|
}
|
|
1356
1921
|
};
|
|
1922
|
+
/**
|
|
1923
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1924
|
+
* This means: It is added to the first fabric.
|
|
1925
|
+
*/
|
|
1357
1926
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1927
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1358
1928
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1929
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1359
1930
|
serverNode.lifecycle.online.on(async () => {
|
|
1360
1931
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1361
1932
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1401,6 +1972,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1401
1972
|
}
|
|
1402
1973
|
this.frontend.wssSendRefreshRequired();
|
|
1403
1974
|
});
|
|
1975
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1404
1976
|
serverNode.lifecycle.offline.on(() => {
|
|
1405
1977
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1406
1978
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1422,6 +1994,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1422
1994
|
}
|
|
1423
1995
|
this.frontend.wssSendRefreshRequired();
|
|
1424
1996
|
});
|
|
1997
|
+
/**
|
|
1998
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
1999
|
+
* information is needed.
|
|
2000
|
+
*/
|
|
1425
2001
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1426
2002
|
let action = '';
|
|
1427
2003
|
switch (fabricAction) {
|
|
@@ -1455,16 +2031,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1455
2031
|
}
|
|
1456
2032
|
}
|
|
1457
2033
|
};
|
|
2034
|
+
/**
|
|
2035
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2036
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2037
|
+
*/
|
|
1458
2038
|
serverNode.events.sessions.opened.on((session) => {
|
|
1459
2039
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1460
2040
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1461
2041
|
this.frontend.wssSendRefreshRequired();
|
|
1462
2042
|
});
|
|
2043
|
+
/**
|
|
2044
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2045
|
+
*/
|
|
1463
2046
|
serverNode.events.sessions.closed.on((session) => {
|
|
1464
2047
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1465
2048
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1466
2049
|
this.frontend.wssSendRefreshRequired();
|
|
1467
2050
|
});
|
|
2051
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1468
2052
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1469
2053
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1470
2054
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1473,21 +2057,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1473
2057
|
this.log.info(`Created server node for ${storeId}`);
|
|
1474
2058
|
return serverNode;
|
|
1475
2059
|
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Starts the specified server node.
|
|
2062
|
+
*
|
|
2063
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2064
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2065
|
+
*/
|
|
1476
2066
|
async startServerNode(matterServerNode) {
|
|
1477
2067
|
if (!matterServerNode)
|
|
1478
2068
|
return;
|
|
1479
2069
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1480
2070
|
await matterServerNode.start();
|
|
1481
2071
|
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Stops the specified server node.
|
|
2074
|
+
*
|
|
2075
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2076
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2077
|
+
*/
|
|
1482
2078
|
async stopServerNode(matterServerNode) {
|
|
1483
2079
|
if (!matterServerNode)
|
|
1484
2080
|
return;
|
|
1485
2081
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1486
2082
|
await matterServerNode.close();
|
|
1487
2083
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1488
|
-
await matterServerNode.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
1489
|
-
this.log.info(`Closed ${matterServerNode.id} MdnsService`);
|
|
2084
|
+
// await matterServerNode.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
2085
|
+
// this.log.info(`Closed ${matterServerNode.id} MdnsService`);
|
|
1490
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Advertises the specified server node if it is commissioned.
|
|
2089
|
+
*
|
|
2090
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2091
|
+
* @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.
|
|
2092
|
+
*/
|
|
1491
2093
|
async advertiseServerNode(matterServerNode) {
|
|
1492
2094
|
if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
|
|
1493
2095
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1497,17 +2099,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1497
2099
|
}
|
|
1498
2100
|
return undefined;
|
|
1499
2101
|
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Creates an aggregator node with the specified storage context.
|
|
2104
|
+
*
|
|
2105
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2106
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2107
|
+
*/
|
|
1500
2108
|
async createAggregatorNode(storageContext) {
|
|
1501
2109
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1502
2110
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1503
2111
|
return aggregatorNode;
|
|
1504
2112
|
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2115
|
+
*
|
|
2116
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2117
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2118
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2119
|
+
*/
|
|
1505
2120
|
async addBridgedEndpoint(pluginName, device) {
|
|
2121
|
+
// Check if the plugin is registered
|
|
1506
2122
|
const plugin = this.plugins.get(pluginName);
|
|
1507
2123
|
if (!plugin) {
|
|
1508
2124
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1509
2125
|
return;
|
|
1510
2126
|
}
|
|
2127
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1511
2128
|
if (this.bridgeMode === 'bridge') {
|
|
1512
2129
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1513
2130
|
if (!this.aggregatorNode)
|
|
@@ -1530,16 +2147,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1530
2147
|
plugin.registeredDevices++;
|
|
1531
2148
|
if (plugin.addedDevices !== undefined)
|
|
1532
2149
|
plugin.addedDevices++;
|
|
2150
|
+
// Add the device to the DeviceManager
|
|
1533
2151
|
this.devices.set(device);
|
|
1534
2152
|
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}`);
|
|
1535
2153
|
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2156
|
+
*
|
|
2157
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2158
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2159
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2160
|
+
*/
|
|
1536
2161
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1537
2162
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2163
|
+
// Check if the plugin is registered
|
|
1538
2164
|
const plugin = this.plugins.get(pluginName);
|
|
1539
2165
|
if (!plugin) {
|
|
1540
2166
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1541
2167
|
return;
|
|
1542
2168
|
}
|
|
2169
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1543
2170
|
if (this.bridgeMode === 'bridge') {
|
|
1544
2171
|
if (!this.aggregatorNode) {
|
|
1545
2172
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1554,6 +2181,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1554
2181
|
}
|
|
1555
2182
|
else if (this.bridgeMode === 'childbridge') {
|
|
1556
2183
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2184
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1557
2185
|
}
|
|
1558
2186
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1559
2187
|
if (!plugin.aggregatorNode) {
|
|
@@ -1567,6 +2195,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1567
2195
|
plugin.registeredDevices--;
|
|
1568
2196
|
if (plugin.addedDevices !== undefined)
|
|
1569
2197
|
plugin.addedDevices--;
|
|
2198
|
+
// Close the server node TODO check if this is correct
|
|
1570
2199
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1571
2200
|
if (plugin.serverNode) {
|
|
1572
2201
|
await this.stopServerNode(plugin.serverNode);
|
|
@@ -1577,14 +2206,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1577
2206
|
}
|
|
1578
2207
|
}
|
|
1579
2208
|
}
|
|
2209
|
+
// Remove the device from the DeviceManager
|
|
1580
2210
|
this.devices.remove(device);
|
|
1581
2211
|
}
|
|
2212
|
+
/**
|
|
2213
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2214
|
+
*
|
|
2215
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2216
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2217
|
+
*/
|
|
1582
2218
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1583
2219
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1584
2220
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1585
2221
|
await this.removeBridgedEndpoint(pluginName, device);
|
|
1586
2222
|
}
|
|
1587
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2226
|
+
*
|
|
2227
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2228
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2229
|
+
*/
|
|
1588
2230
|
sanitizeFabricInformations(fabricInfo) {
|
|
1589
2231
|
return fabricInfo.map((info) => {
|
|
1590
2232
|
return {
|
|
@@ -1598,6 +2240,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1598
2240
|
};
|
|
1599
2241
|
});
|
|
1600
2242
|
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2245
|
+
*
|
|
2246
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2247
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2248
|
+
*/
|
|
1601
2249
|
sanitizeSessionInformation(sessionInfo) {
|
|
1602
2250
|
return sessionInfo
|
|
1603
2251
|
.filter((session) => session.isPeerActive)
|
|
@@ -1625,11 +2273,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1625
2273
|
};
|
|
1626
2274
|
});
|
|
1627
2275
|
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Sets the reachability of a matter server node and trigger ReachableChanged event.
|
|
2278
|
+
*
|
|
2279
|
+
* @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
|
|
2280
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2281
|
+
*/
|
|
2282
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1628
2283
|
setServerNodeReachability(serverNode, reachable) {
|
|
2284
|
+
/*
|
|
2285
|
+
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2286
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2287
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2288
|
+
*/
|
|
1629
2289
|
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2292
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
|
|
2293
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2294
|
+
*/
|
|
2295
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1630
2296
|
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2297
|
+
/*
|
|
2298
|
+
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2299
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2300
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2301
|
+
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2302
|
+
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2303
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2304
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2305
|
+
});
|
|
2306
|
+
*/
|
|
1631
2307
|
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Sets the reachability of a device and trigger.
|
|
2310
|
+
*
|
|
2311
|
+
* @param {MatterbridgeEndpoint} device - The device to set the reachability for.
|
|
2312
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2313
|
+
*/
|
|
2314
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1632
2315
|
setDeviceReachability(device, reachable) {
|
|
2316
|
+
/*
|
|
2317
|
+
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2318
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2319
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2320
|
+
*/
|
|
1633
2321
|
}
|
|
1634
2322
|
getVendorIdName = (vendorId) => {
|
|
1635
2323
|
if (!vendorId)
|
|
@@ -1672,13 +2360,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1672
2360
|
}
|
|
1673
2361
|
return vendorName;
|
|
1674
2362
|
};
|
|
2363
|
+
/**
|
|
2364
|
+
* Spawns a child process with the given command and arguments.
|
|
2365
|
+
* @param {string} command - The command to execute.
|
|
2366
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2367
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2368
|
+
*/
|
|
1675
2369
|
async spawnCommand(command, args = []) {
|
|
2370
|
+
/*
|
|
2371
|
+
npm > npm.cmd on windows
|
|
2372
|
+
cmd.exe ['dir'] on windows
|
|
2373
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2374
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2375
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2379
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2380
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2381
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2382
|
+
*/
|
|
1676
2383
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1677
2384
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2385
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1678
2386
|
const argstring = 'npm ' + args.join(' ');
|
|
1679
2387
|
args.splice(0, args.length, '/c', argstring);
|
|
1680
2388
|
command = 'cmd.exe';
|
|
1681
2389
|
}
|
|
2390
|
+
// Decide when using sudo on linux
|
|
2391
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2392
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1682
2393
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1683
2394
|
args.unshift(command);
|
|
1684
2395
|
command = 'sudo';
|
|
@@ -1737,3 +2448,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1737
2448
|
});
|
|
1738
2449
|
}
|
|
1739
2450
|
}
|
|
2451
|
+
//# sourceMappingURL=matterbridge.js.map
|