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