matterbridge 2.1.4-dev.2 → 2.1.4
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 +4 -1
- package/README-DOCKER.md +11 -7
- 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 +232 -23
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +409 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +749 -39
- 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 +691 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2264 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +105 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +159 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +122 -7
- 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 +231 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +307 -9
- 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, MdnsService, 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,7 +195,13 @@ 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() {
|
|
204
|
+
// Save server nodes to close
|
|
144
205
|
const servers = [];
|
|
145
206
|
if (this.bridgeMode === 'bridge') {
|
|
146
207
|
if (this.serverNode)
|
|
@@ -152,54 +213,80 @@ export class Matterbridge extends EventEmitter {
|
|
|
152
213
|
servers.push(plugin.serverNode);
|
|
153
214
|
}
|
|
154
215
|
}
|
|
216
|
+
// Cleanup
|
|
155
217
|
await this.cleanup('destroying instance...', false);
|
|
218
|
+
// Close servers mdns service
|
|
156
219
|
for (const server of servers) {
|
|
157
220
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
158
221
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
159
222
|
}
|
|
223
|
+
// Wait for the cleanup to finish
|
|
160
224
|
await new Promise((resolve) => {
|
|
161
225
|
setTimeout(resolve, 1000);
|
|
162
226
|
});
|
|
163
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Initializes the Matterbridge application.
|
|
230
|
+
*
|
|
231
|
+
* @remarks
|
|
232
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
233
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
234
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
235
|
+
*
|
|
236
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
237
|
+
*/
|
|
164
238
|
async initialize() {
|
|
239
|
+
// Set the restart mode
|
|
165
240
|
if (hasParameter('service'))
|
|
166
241
|
this.restartMode = 'service';
|
|
167
242
|
if (hasParameter('docker'))
|
|
168
243
|
this.restartMode = 'docker';
|
|
244
|
+
// Set the matterbridge directory
|
|
169
245
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
170
246
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
247
|
+
// Setup the matter environment
|
|
171
248
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
172
249
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
173
250
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
174
251
|
this.environment.vars.set('runtime.signals', false);
|
|
175
252
|
this.environment.vars.set('runtime.exitcode', false);
|
|
176
|
-
|
|
253
|
+
// Create the matterbridge logger
|
|
254
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
255
|
+
// Register process handlers
|
|
177
256
|
this.registerProcessHandlers();
|
|
257
|
+
// Initialize nodeStorage and nodeContext
|
|
178
258
|
try {
|
|
179
259
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
180
260
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
181
261
|
this.log.debug('Creating node storage context for matterbridge');
|
|
182
262
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
263
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
265
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
184
266
|
for (const key of keys) {
|
|
185
267
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
268
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
269
|
await this.nodeStorage?.storage.get(key);
|
|
187
270
|
}
|
|
188
271
|
const storages = await this.nodeStorage.getStorageNames();
|
|
189
272
|
for (const storage of storages) {
|
|
190
273
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
191
274
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
275
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
276
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
277
|
const keys = (await nodeContext?.storage.keys());
|
|
193
278
|
keys.forEach(async (key) => {
|
|
194
279
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
195
280
|
await nodeContext?.get(key);
|
|
196
281
|
});
|
|
197
282
|
}
|
|
283
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
198
284
|
this.log.debug('Creating node storage backup...');
|
|
199
285
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
200
286
|
this.log.debug('Created node storage backup');
|
|
201
287
|
}
|
|
202
288
|
catch (error) {
|
|
289
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
203
290
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
204
291
|
if (hasParameter('norestore')) {
|
|
205
292
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -214,45 +301,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
214
301
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
215
302
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
216
303
|
}
|
|
304
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
217
305
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
306
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
218
307
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
308
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
219
309
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
220
310
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
311
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
221
312
|
if (hasParameter('logger')) {
|
|
222
313
|
const level = getParameter('logger');
|
|
223
314
|
if (level === 'debug') {
|
|
224
|
-
this.log.logLevel = "debug"
|
|
315
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
225
316
|
}
|
|
226
317
|
else if (level === 'info') {
|
|
227
|
-
this.log.logLevel = "info"
|
|
318
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
228
319
|
}
|
|
229
320
|
else if (level === 'notice') {
|
|
230
|
-
this.log.logLevel = "notice"
|
|
321
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
231
322
|
}
|
|
232
323
|
else if (level === 'warn') {
|
|
233
|
-
this.log.logLevel = "warn"
|
|
324
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
234
325
|
}
|
|
235
326
|
else if (level === 'error') {
|
|
236
|
-
this.log.logLevel = "error"
|
|
327
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
237
328
|
}
|
|
238
329
|
else if (level === 'fatal') {
|
|
239
|
-
this.log.logLevel = "fatal"
|
|
330
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
240
331
|
}
|
|
241
332
|
else {
|
|
242
333
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
243
|
-
this.log.logLevel = "info"
|
|
334
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
244
335
|
}
|
|
245
336
|
}
|
|
246
337
|
else {
|
|
247
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
338
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
248
339
|
}
|
|
249
340
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
341
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
250
342
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
251
343
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
252
344
|
this.matterbridgeInformation.fileLogger = true;
|
|
253
345
|
}
|
|
254
346
|
this.log.notice('Matterbridge is starting...');
|
|
255
347
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
348
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
256
349
|
if (hasParameter('matterlogger')) {
|
|
257
350
|
const level = getParameter('matterlogger');
|
|
258
351
|
if (level === 'debug') {
|
|
@@ -283,6 +376,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
283
376
|
}
|
|
284
377
|
Logger.format = MatterLogFormat.ANSI;
|
|
285
378
|
Logger.setLogger('default', this.createMatterLogger());
|
|
379
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
286
380
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
287
381
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
288
382
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -291,6 +385,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
291
385
|
});
|
|
292
386
|
}
|
|
293
387
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
388
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
294
389
|
if (hasParameter('mdnsinterface')) {
|
|
295
390
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
296
391
|
}
|
|
@@ -299,6 +394,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
299
394
|
if (this.mdnsInterface === '')
|
|
300
395
|
this.mdnsInterface = undefined;
|
|
301
396
|
}
|
|
397
|
+
// Validate mdnsInterface
|
|
302
398
|
if (this.mdnsInterface) {
|
|
303
399
|
const networkInterfaces = os.networkInterfaces();
|
|
304
400
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -312,6 +408,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
312
408
|
}
|
|
313
409
|
if (this.mdnsInterface)
|
|
314
410
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
411
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
315
412
|
if (hasParameter('ipv4address')) {
|
|
316
413
|
this.ipv4address = getParameter('ipv4address');
|
|
317
414
|
}
|
|
@@ -320,6 +417,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
320
417
|
if (this.ipv4address === '')
|
|
321
418
|
this.ipv4address = undefined;
|
|
322
419
|
}
|
|
420
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
323
421
|
if (hasParameter('ipv6address')) {
|
|
324
422
|
this.ipv6address = getParameter('ipv6address');
|
|
325
423
|
}
|
|
@@ -328,14 +426,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
328
426
|
if (this.ipv6address === '')
|
|
329
427
|
this.ipv6address = undefined;
|
|
330
428
|
}
|
|
429
|
+
// Initialize PluginManager
|
|
331
430
|
this.plugins = new PluginManager(this);
|
|
332
431
|
await this.plugins.loadFromStorage();
|
|
333
432
|
this.plugins.logLevel = this.log.logLevel;
|
|
433
|
+
// Initialize DeviceManager
|
|
334
434
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
335
435
|
this.devices.logLevel = this.log.logLevel;
|
|
436
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
336
437
|
for (const plugin of this.plugins) {
|
|
337
438
|
const packageJson = await this.plugins.parse(plugin);
|
|
338
439
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
440
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
441
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
339
442
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
340
443
|
try {
|
|
341
444
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -357,6 +460,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
357
460
|
await plugin.nodeContext.set('description', plugin.description);
|
|
358
461
|
await plugin.nodeContext.set('author', plugin.author);
|
|
359
462
|
}
|
|
463
|
+
// Log system info and create .matterbridge directory
|
|
360
464
|
await this.logNodeAndSystemInfo();
|
|
361
465
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
362
466
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -364,6 +468,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
364
468
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
365
469
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
366
470
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
471
|
+
// Check node version and throw error
|
|
367
472
|
const minNodeVersion = 18;
|
|
368
473
|
const nodeVersion = process.versions.node;
|
|
369
474
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -371,9 +476,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
371
476
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
372
477
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
373
478
|
}
|
|
479
|
+
// Parse command line
|
|
374
480
|
await this.parseCommandLine();
|
|
375
481
|
this.initialized = true;
|
|
376
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
485
|
+
* @private
|
|
486
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
487
|
+
*/
|
|
377
488
|
async parseCommandLine() {
|
|
378
489
|
if (hasParameter('help')) {
|
|
379
490
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -483,6 +594,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
483
594
|
await this.shutdownProcessAndFactoryReset();
|
|
484
595
|
return;
|
|
485
596
|
}
|
|
597
|
+
// Start the matter storage and create the matterbridge context
|
|
486
598
|
try {
|
|
487
599
|
await this.startMatterStorage();
|
|
488
600
|
}
|
|
@@ -490,10 +602,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
490
602
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
491
603
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
492
604
|
}
|
|
605
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
493
606
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
494
607
|
await this.shutdownProcessAndReset();
|
|
495
608
|
return;
|
|
496
609
|
}
|
|
610
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
497
611
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
498
612
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
499
613
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -515,9 +629,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
515
629
|
this.emit('shutdown');
|
|
516
630
|
return;
|
|
517
631
|
}
|
|
632
|
+
// Initialize frontend
|
|
518
633
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
519
634
|
await this.frontend.start(getIntParameter('frontend'));
|
|
520
635
|
this.frontend.logLevel = this.log.logLevel;
|
|
636
|
+
// Check each 60 minutes the latest versions
|
|
521
637
|
this.checkUpdateInterval = setInterval(() => {
|
|
522
638
|
this.getMatterbridgeLatestVersion();
|
|
523
639
|
for (const plugin of this.plugins) {
|
|
@@ -525,20 +641,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
525
641
|
}
|
|
526
642
|
this.frontend.wssSendRefreshRequired();
|
|
527
643
|
}, 60 * 60 * 1000);
|
|
644
|
+
// Start the matterbridge in mode test
|
|
528
645
|
if (hasParameter('test')) {
|
|
529
646
|
this.bridgeMode = 'bridge';
|
|
530
647
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
531
648
|
return;
|
|
532
649
|
}
|
|
650
|
+
// Start the matterbridge in mode controller
|
|
533
651
|
if (hasParameter('controller')) {
|
|
534
652
|
this.bridgeMode = 'controller';
|
|
535
653
|
await this.startController();
|
|
536
654
|
return;
|
|
537
655
|
}
|
|
656
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
538
657
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
539
658
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
540
659
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
541
660
|
}
|
|
661
|
+
// Start matterbridge in bridge mode
|
|
542
662
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
543
663
|
this.bridgeMode = 'bridge';
|
|
544
664
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -546,6 +666,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
546
666
|
await this.startBridge();
|
|
547
667
|
return;
|
|
548
668
|
}
|
|
669
|
+
// Start matterbridge in childbridge mode
|
|
549
670
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
550
671
|
this.bridgeMode = 'childbridge';
|
|
551
672
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -554,16 +675,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
554
675
|
return;
|
|
555
676
|
}
|
|
556
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* Asynchronously loads and starts the registered plugins.
|
|
680
|
+
*
|
|
681
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
682
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
683
|
+
*
|
|
684
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
685
|
+
*/
|
|
557
686
|
async startPlugins() {
|
|
687
|
+
// Check, load and start the plugins
|
|
558
688
|
for (const plugin of this.plugins) {
|
|
559
689
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
560
690
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
691
|
+
// Check if the plugin is available
|
|
561
692
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
562
693
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
563
694
|
plugin.enabled = false;
|
|
564
695
|
plugin.error = true;
|
|
565
696
|
continue;
|
|
566
697
|
}
|
|
698
|
+
// Check if the plugin has a new version
|
|
699
|
+
// this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
567
700
|
if (!plugin.enabled) {
|
|
568
701
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
569
702
|
continue;
|
|
@@ -577,20 +710,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
577
710
|
plugin.addedDevices = undefined;
|
|
578
711
|
plugin.qrPairingCode = undefined;
|
|
579
712
|
plugin.manualPairingCode = undefined;
|
|
580
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
713
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
581
714
|
}
|
|
582
715
|
this.frontend.wssSendRefreshRequired();
|
|
583
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
719
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
720
|
+
*/
|
|
584
721
|
registerProcessHandlers() {
|
|
585
722
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
586
723
|
process.removeAllListeners('uncaughtException');
|
|
587
724
|
process.removeAllListeners('unhandledRejection');
|
|
588
725
|
this.exceptionHandler = async (error) => {
|
|
589
726
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
727
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
590
728
|
};
|
|
591
729
|
process.on('uncaughtException', this.exceptionHandler);
|
|
592
730
|
this.rejectionHandler = async (reason, promise) => {
|
|
593
731
|
this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
732
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
594
733
|
};
|
|
595
734
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
596
735
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -603,6 +742,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
603
742
|
};
|
|
604
743
|
process.on('SIGTERM', this.sigtermHandler);
|
|
605
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
747
|
+
*/
|
|
606
748
|
deregisterProcesslHandlers() {
|
|
607
749
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
608
750
|
if (this.exceptionHandler)
|
|
@@ -619,12 +761,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
619
761
|
process.off('SIGTERM', this.sigtermHandler);
|
|
620
762
|
this.sigtermHandler = undefined;
|
|
621
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Logs the node and system information.
|
|
766
|
+
*/
|
|
622
767
|
async logNodeAndSystemInfo() {
|
|
768
|
+
// IP address information
|
|
623
769
|
const networkInterfaces = os.networkInterfaces();
|
|
624
770
|
this.systemInformation.interfaceName = '';
|
|
625
771
|
this.systemInformation.ipv4Address = '';
|
|
626
772
|
this.systemInformation.ipv6Address = '';
|
|
627
773
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
774
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
628
775
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
629
776
|
continue;
|
|
630
777
|
if (!interfaceDetails) {
|
|
@@ -650,19 +797,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
650
797
|
break;
|
|
651
798
|
}
|
|
652
799
|
}
|
|
800
|
+
// Node information
|
|
653
801
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
654
802
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
655
803
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
656
804
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
805
|
+
// Host system information
|
|
657
806
|
this.systemInformation.hostname = os.hostname();
|
|
658
807
|
this.systemInformation.user = os.userInfo().username;
|
|
659
|
-
this.systemInformation.osType = os.type();
|
|
660
|
-
this.systemInformation.osRelease = os.release();
|
|
661
|
-
this.systemInformation.osPlatform = os.platform();
|
|
662
|
-
this.systemInformation.osArch = os.arch();
|
|
663
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
664
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
665
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
808
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
809
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
810
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
811
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
812
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
813
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
814
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
815
|
+
// Log the system information
|
|
666
816
|
this.log.debug('Host System Information:');
|
|
667
817
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
668
818
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -678,15 +828,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
678
828
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
679
829
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
680
830
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
831
|
+
// Home directory
|
|
681
832
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
682
833
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
683
834
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
835
|
+
// Package root directory
|
|
684
836
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
685
837
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
686
838
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
687
839
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
840
|
+
// Global node_modules directory
|
|
688
841
|
if (this.nodeContext)
|
|
689
842
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
843
|
+
// First run of Matterbridge so the node storage is empty
|
|
690
844
|
if (this.globalModulesDirectory === '') {
|
|
691
845
|
try {
|
|
692
846
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -700,6 +854,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
700
854
|
}
|
|
701
855
|
else
|
|
702
856
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
857
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
858
|
+
else {
|
|
859
|
+
this.getGlobalNodeModules()
|
|
860
|
+
.then(async (globalModulesDirectory) => {
|
|
861
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
862
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
863
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
864
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
865
|
+
})
|
|
866
|
+
.catch((error) => {
|
|
867
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
868
|
+
});
|
|
869
|
+
}*/
|
|
870
|
+
// Create the data directory .matterbridge in the home directory
|
|
703
871
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
704
872
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
705
873
|
try {
|
|
@@ -723,6 +891,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
723
891
|
}
|
|
724
892
|
}
|
|
725
893
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
894
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
726
895
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
727
896
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
728
897
|
try {
|
|
@@ -746,18 +915,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
746
915
|
}
|
|
747
916
|
}
|
|
748
917
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
918
|
+
// Matterbridge version
|
|
749
919
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
750
920
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
751
921
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
752
922
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
923
|
+
// Matterbridge latest version
|
|
753
924
|
if (this.nodeContext)
|
|
754
925
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
755
926
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
927
|
+
// this.getMatterbridgeLatestVersion();
|
|
928
|
+
// Current working directory
|
|
756
929
|
const currentDir = process.cwd();
|
|
757
930
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
931
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
758
932
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
759
933
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
760
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
937
|
+
* @param packageName - The name of the package.
|
|
938
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
939
|
+
*/
|
|
761
940
|
async getLatestVersion(packageName) {
|
|
762
941
|
return new Promise((resolve, reject) => {
|
|
763
942
|
this.execRunningCount++;
|
|
@@ -772,6 +951,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
772
951
|
});
|
|
773
952
|
});
|
|
774
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
956
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
957
|
+
*/
|
|
775
958
|
async getGlobalNodeModules() {
|
|
776
959
|
return new Promise((resolve, reject) => {
|
|
777
960
|
this.execRunningCount++;
|
|
@@ -786,6 +969,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
786
969
|
});
|
|
787
970
|
});
|
|
788
971
|
}
|
|
972
|
+
/**
|
|
973
|
+
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
974
|
+
* @private
|
|
975
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
976
|
+
*/
|
|
789
977
|
async getMatterbridgeLatestVersion() {
|
|
790
978
|
this.getLatestVersion('matterbridge')
|
|
791
979
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -802,8 +990,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
802
990
|
})
|
|
803
991
|
.catch((error) => {
|
|
804
992
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
993
|
+
// error.stack && this.log.debug(error.stack);
|
|
805
994
|
});
|
|
806
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
998
|
+
* If the plugin's version is different from the latest version, logs a warning message.
|
|
999
|
+
* If the plugin's version is the same as the latest version, logs an info message.
|
|
1000
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
1001
|
+
*
|
|
1002
|
+
* @private
|
|
1003
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
1004
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
1005
|
+
*/
|
|
807
1006
|
async getPluginLatestVersion(plugin) {
|
|
808
1007
|
this.getLatestVersion(plugin.name)
|
|
809
1008
|
.then(async (latestVersion) => {
|
|
@@ -815,40 +1014,54 @@ export class Matterbridge extends EventEmitter {
|
|
|
815
1014
|
})
|
|
816
1015
|
.catch((error) => {
|
|
817
1016
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1017
|
+
// error.stack && this.log.debug(error.stack);
|
|
818
1018
|
});
|
|
819
1019
|
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1022
|
+
*
|
|
1023
|
+
* @returns {Function} The MatterLogger function.
|
|
1024
|
+
*/
|
|
820
1025
|
createMatterLogger() {
|
|
821
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1026
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
822
1027
|
return (_level, formattedLog) => {
|
|
823
1028
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
824
1029
|
const message = formattedLog.slice(65);
|
|
825
1030
|
matterLogger.logName = logger;
|
|
826
1031
|
switch (_level) {
|
|
827
1032
|
case MatterLogLevel.DEBUG:
|
|
828
|
-
matterLogger.log("debug"
|
|
1033
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
829
1034
|
break;
|
|
830
1035
|
case MatterLogLevel.INFO:
|
|
831
|
-
matterLogger.log("info"
|
|
1036
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
832
1037
|
break;
|
|
833
1038
|
case MatterLogLevel.NOTICE:
|
|
834
|
-
matterLogger.log("notice"
|
|
1039
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
835
1040
|
break;
|
|
836
1041
|
case MatterLogLevel.WARN:
|
|
837
|
-
matterLogger.log("warn"
|
|
1042
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
838
1043
|
break;
|
|
839
1044
|
case MatterLogLevel.ERROR:
|
|
840
|
-
matterLogger.log("error"
|
|
1045
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
841
1046
|
break;
|
|
842
1047
|
case MatterLogLevel.FATAL:
|
|
843
|
-
matterLogger.log("fatal"
|
|
1048
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
844
1049
|
break;
|
|
845
1050
|
default:
|
|
846
|
-
matterLogger.log("debug"
|
|
1051
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
847
1052
|
break;
|
|
848
1053
|
}
|
|
849
1054
|
};
|
|
850
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Creates a Matter File Logger.
|
|
1058
|
+
*
|
|
1059
|
+
* @param {string} filePath - The path to the log file.
|
|
1060
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1061
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1062
|
+
*/
|
|
851
1063
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1064
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
852
1065
|
let fileSize = 0;
|
|
853
1066
|
if (unlink) {
|
|
854
1067
|
try {
|
|
@@ -897,12 +1110,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
897
1110
|
}
|
|
898
1111
|
};
|
|
899
1112
|
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1115
|
+
*/
|
|
900
1116
|
async restartProcess() {
|
|
901
1117
|
await this.cleanup('restarting...', true);
|
|
902
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Shut down the process by exiting the current process.
|
|
1121
|
+
*/
|
|
903
1122
|
async shutdownProcess() {
|
|
904
1123
|
await this.cleanup('shutting down...', false);
|
|
905
1124
|
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Update matterbridge and and shut down the process.
|
|
1127
|
+
*/
|
|
906
1128
|
async updateProcess() {
|
|
907
1129
|
this.log.info('Updating matterbridge...');
|
|
908
1130
|
try {
|
|
@@ -915,6 +1137,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
915
1137
|
this.frontend.wssSendRestartRequired();
|
|
916
1138
|
await this.cleanup('updating...', false);
|
|
917
1139
|
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Unregister all devices and shut down the process.
|
|
1142
|
+
*/
|
|
918
1143
|
async unregisterAndShutdownProcess() {
|
|
919
1144
|
this.log.info('Unregistering all devices and shutting down...');
|
|
920
1145
|
for (const plugin of this.plugins) {
|
|
@@ -922,6 +1147,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
922
1147
|
}
|
|
923
1148
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
924
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Reset commissioning and shut down the process.
|
|
1152
|
+
*/
|
|
925
1153
|
async shutdownProcessAndReset() {
|
|
926
1154
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
927
1155
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -933,8 +1161,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
933
1161
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
934
1162
|
await this.cleanup('shutting down with reset...', false);
|
|
935
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Factory reset and shut down the process.
|
|
1166
|
+
*/
|
|
936
1167
|
async shutdownProcessAndFactoryReset() {
|
|
937
1168
|
try {
|
|
1169
|
+
// Delete old matter storage file and backup
|
|
938
1170
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
939
1171
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
940
1172
|
await fs.unlink(file);
|
|
@@ -948,6 +1180,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
948
1180
|
}
|
|
949
1181
|
}
|
|
950
1182
|
try {
|
|
1183
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
951
1184
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
952
1185
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
953
1186
|
await fs.rm(dir, { recursive: true });
|
|
@@ -961,6 +1194,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
961
1194
|
}
|
|
962
1195
|
}
|
|
963
1196
|
try {
|
|
1197
|
+
// Delete node storage directory with its subdirectories and backup
|
|
964
1198
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
965
1199
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
966
1200
|
await fs.rm(dir, { recursive: true });
|
|
@@ -980,30 +1214,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
980
1214
|
this.devices.clear();
|
|
981
1215
|
await this.cleanup('shutting down with factory reset...', false);
|
|
982
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Cleans up the Matterbridge instance.
|
|
1219
|
+
* @param message - The cleanup message.
|
|
1220
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1221
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1222
|
+
*/
|
|
983
1223
|
async cleanup(message, restart = false) {
|
|
984
1224
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
985
1225
|
this.hasCleanupStarted = true;
|
|
986
1226
|
this.log.info(message);
|
|
1227
|
+
// Clear the start matter interval
|
|
987
1228
|
if (this.startMatterInterval) {
|
|
988
1229
|
clearInterval(this.startMatterInterval);
|
|
989
1230
|
this.startMatterInterval = undefined;
|
|
990
1231
|
this.log.debug('Start matter interval cleared');
|
|
991
1232
|
}
|
|
1233
|
+
// Clear the check update interval
|
|
992
1234
|
if (this.checkUpdateInterval) {
|
|
993
1235
|
clearInterval(this.checkUpdateInterval);
|
|
994
1236
|
this.checkUpdateInterval = undefined;
|
|
995
1237
|
this.log.debug('Check update interval cleared');
|
|
996
1238
|
}
|
|
1239
|
+
// Clear the configure timeout
|
|
997
1240
|
if (this.configureTimeout) {
|
|
998
1241
|
clearTimeout(this.configureTimeout);
|
|
999
1242
|
this.configureTimeout = undefined;
|
|
1000
1243
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1001
1244
|
}
|
|
1245
|
+
// Clear the reachability timeout
|
|
1002
1246
|
if (this.reachabilityTimeout) {
|
|
1003
1247
|
clearTimeout(this.reachabilityTimeout);
|
|
1004
1248
|
this.reachabilityTimeout = undefined;
|
|
1005
1249
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1006
1250
|
}
|
|
1251
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1007
1252
|
for (const plugin of this.plugins) {
|
|
1008
1253
|
if (!plugin.enabled || plugin.error)
|
|
1009
1254
|
continue;
|
|
@@ -1014,6 +1259,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1014
1259
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1015
1260
|
}
|
|
1016
1261
|
}
|
|
1262
|
+
// Stopping matter server nodes
|
|
1017
1263
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1018
1264
|
if (this.bridgeMode === 'bridge') {
|
|
1019
1265
|
if (this.serverNode) {
|
|
@@ -1030,17 +1276,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1030
1276
|
}
|
|
1031
1277
|
}
|
|
1032
1278
|
this.log.notice('Stopped matter server nodes');
|
|
1279
|
+
// Stop matter storage
|
|
1033
1280
|
await this.stopMatterStorage();
|
|
1281
|
+
// Stop the frontend
|
|
1034
1282
|
await this.frontend.stop();
|
|
1283
|
+
// Remove the matterfilelogger
|
|
1035
1284
|
try {
|
|
1036
1285
|
Logger.removeLogger('matterfilelogger');
|
|
1286
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1037
1287
|
}
|
|
1038
1288
|
catch (error) {
|
|
1289
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1039
1290
|
}
|
|
1291
|
+
// Serialize registeredDevices
|
|
1040
1292
|
if (this.nodeStorage && this.nodeContext) {
|
|
1293
|
+
/*
|
|
1294
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1295
|
+
this.log.info('Saving registered devices...');
|
|
1296
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1297
|
+
this.devices.forEach(async (device) => {
|
|
1298
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1299
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1300
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1301
|
+
});
|
|
1302
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1303
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1304
|
+
*/
|
|
1305
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1041
1306
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1042
1307
|
await this.nodeContext.close();
|
|
1043
1308
|
this.nodeContext = undefined;
|
|
1309
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1044
1310
|
for (const plugin of this.plugins) {
|
|
1045
1311
|
if (plugin.nodeContext) {
|
|
1046
1312
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1057,12 +1323,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1057
1323
|
}
|
|
1058
1324
|
this.plugins.clear();
|
|
1059
1325
|
this.devices.clear();
|
|
1326
|
+
// Deregisters the process handlers
|
|
1060
1327
|
this.deregisterProcesslHandlers();
|
|
1061
1328
|
if (restart) {
|
|
1062
1329
|
if (message === 'updating...') {
|
|
1063
1330
|
this.log.info('Cleanup completed. Updating...');
|
|
1064
1331
|
Matterbridge.instance = undefined;
|
|
1065
|
-
this.emit('update');
|
|
1332
|
+
this.emit('update'); // Restart the process but the update has been done before
|
|
1066
1333
|
}
|
|
1067
1334
|
else if (message === 'restarting...') {
|
|
1068
1335
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1082,6 +1349,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
1349
|
this.log.debug('Cleanup already started...');
|
|
1083
1350
|
}
|
|
1084
1351
|
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1354
|
+
*
|
|
1355
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1356
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1357
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1358
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1359
|
+
*/
|
|
1085
1360
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1086
1361
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1087
1362
|
plugin.locked = true;
|
|
@@ -1093,6 +1368,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1093
1368
|
await this.startServerNode(plugin.serverNode);
|
|
1094
1369
|
}
|
|
1095
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1373
|
+
*
|
|
1374
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1375
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1376
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1377
|
+
*/
|
|
1096
1378
|
async createDynamicPlugin(plugin, start = false) {
|
|
1097
1379
|
if (!plugin.locked) {
|
|
1098
1380
|
plugin.locked = true;
|
|
@@ -1104,7 +1386,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1104
1386
|
await this.startServerNode(plugin.serverNode);
|
|
1105
1387
|
}
|
|
1106
1388
|
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Starts the Matterbridge in bridge mode.
|
|
1391
|
+
* @private
|
|
1392
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1393
|
+
*/
|
|
1107
1394
|
async startBridge() {
|
|
1395
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1108
1396
|
if (!this.matterStorageManager)
|
|
1109
1397
|
throw new Error('No storage manager initialized');
|
|
1110
1398
|
if (!this.matterbridgeContext)
|
|
@@ -1141,7 +1429,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1141
1429
|
clearInterval(this.startMatterInterval);
|
|
1142
1430
|
this.startMatterInterval = undefined;
|
|
1143
1431
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1432
|
+
// Start the Matter server node
|
|
1144
1433
|
this.startServerNode(this.serverNode);
|
|
1434
|
+
// Configure the plugins
|
|
1145
1435
|
this.configureTimeout = setTimeout(async () => {
|
|
1146
1436
|
for (const plugin of this.plugins) {
|
|
1147
1437
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1156,6 +1446,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1156
1446
|
}
|
|
1157
1447
|
this.frontend.wssSendRefreshRequired();
|
|
1158
1448
|
}, 30 * 1000);
|
|
1449
|
+
// Setting reachability to true
|
|
1159
1450
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1160
1451
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1161
1452
|
if (this.serverNode)
|
|
@@ -1166,7 +1457,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1166
1457
|
}, 60 * 1000);
|
|
1167
1458
|
}, 1000);
|
|
1168
1459
|
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1462
|
+
* @private
|
|
1463
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1464
|
+
*/
|
|
1169
1465
|
async startChildbridge() {
|
|
1466
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1467
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1170
1468
|
if (!this.matterStorageManager)
|
|
1171
1469
|
throw new Error('No storage manager initialized');
|
|
1172
1470
|
for (const plugin of this.plugins) {
|
|
@@ -1213,12 +1511,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1213
1511
|
clearInterval(this.startMatterInterval);
|
|
1214
1512
|
this.startMatterInterval = undefined;
|
|
1215
1513
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1514
|
+
// Configure the plugins
|
|
1216
1515
|
this.configureTimeout = setTimeout(async () => {
|
|
1217
1516
|
for (const plugin of this.plugins) {
|
|
1218
1517
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1219
1518
|
continue;
|
|
1220
1519
|
try {
|
|
1221
|
-
await this.plugins.configure(plugin);
|
|
1520
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1222
1521
|
}
|
|
1223
1522
|
catch (error) {
|
|
1224
1523
|
plugin.error = true;
|
|
@@ -1246,7 +1545,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1246
1545
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1247
1546
|
continue;
|
|
1248
1547
|
}
|
|
1548
|
+
// Start the Matter server node
|
|
1249
1549
|
this.startServerNode(plugin.serverNode);
|
|
1550
|
+
// Setting reachability to true
|
|
1250
1551
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1251
1552
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1252
1553
|
if (plugin.serverNode)
|
|
@@ -1260,9 +1561,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1260
1561
|
}
|
|
1261
1562
|
}, 1000);
|
|
1262
1563
|
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Starts the Matterbridge controller.
|
|
1566
|
+
* @private
|
|
1567
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1568
|
+
*/
|
|
1263
1569
|
async startController() {
|
|
1570
|
+
/*
|
|
1571
|
+
if (!this.storageManager) {
|
|
1572
|
+
this.log.error('No storage manager initialized');
|
|
1573
|
+
await this.cleanup('No storage manager initialized');
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1577
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1578
|
+
if (!this.mattercontrollerContext) {
|
|
1579
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1580
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1585
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1586
|
+
this.log.info('Creating matter commissioning controller');
|
|
1587
|
+
this.commissioningController = new CommissioningController({
|
|
1588
|
+
autoConnect: false,
|
|
1589
|
+
});
|
|
1590
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1591
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1592
|
+
|
|
1593
|
+
this.log.info('Starting matter server');
|
|
1594
|
+
await this.matterServer.start();
|
|
1595
|
+
this.log.info('Matter server started');
|
|
1596
|
+
|
|
1597
|
+
if (hasParameter('pairingcode')) {
|
|
1598
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1599
|
+
const pairingCode = getParameter('pairingcode');
|
|
1600
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1601
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1602
|
+
|
|
1603
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1604
|
+
if (pairingCode !== undefined) {
|
|
1605
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1606
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1607
|
+
longDiscriminator = undefined;
|
|
1608
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1609
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1610
|
+
} else {
|
|
1611
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1612
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1613
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1614
|
+
}
|
|
1615
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1616
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1620
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1621
|
+
regulatoryCountryCode: 'XX',
|
|
1622
|
+
};
|
|
1623
|
+
const options = {
|
|
1624
|
+
commissioning: commissioningOptions,
|
|
1625
|
+
discovery: {
|
|
1626
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1627
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1628
|
+
},
|
|
1629
|
+
passcode: setupPin,
|
|
1630
|
+
} as NodeCommissioningOptions;
|
|
1631
|
+
this.log.info('Commissioning with options:', options);
|
|
1632
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1633
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1634
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1635
|
+
} // (hasParameter('pairingcode'))
|
|
1636
|
+
|
|
1637
|
+
if (hasParameter('unpairall')) {
|
|
1638
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1639
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1640
|
+
for (const nodeId of nodeIds) {
|
|
1641
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1642
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1643
|
+
}
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
if (hasParameter('discover')) {
|
|
1648
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1649
|
+
// console.log(discover);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1653
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1658
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1659
|
+
for (const nodeId of nodeIds) {
|
|
1660
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1661
|
+
|
|
1662
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1663
|
+
autoSubscribe: false,
|
|
1664
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1665
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1666
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1667
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1668
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1669
|
+
switch (info) {
|
|
1670
|
+
case NodeStateInformation.Connected:
|
|
1671
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1672
|
+
break;
|
|
1673
|
+
case NodeStateInformation.Disconnected:
|
|
1674
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1675
|
+
break;
|
|
1676
|
+
case NodeStateInformation.Reconnecting:
|
|
1677
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1678
|
+
break;
|
|
1679
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1680
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1681
|
+
break;
|
|
1682
|
+
case NodeStateInformation.StructureChanged:
|
|
1683
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1684
|
+
break;
|
|
1685
|
+
case NodeStateInformation.Decommissioned:
|
|
1686
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1687
|
+
break;
|
|
1688
|
+
default:
|
|
1689
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1690
|
+
break;
|
|
1691
|
+
}
|
|
1692
|
+
},
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
node.logStructure();
|
|
1696
|
+
|
|
1697
|
+
// Get the interaction client
|
|
1698
|
+
this.log.info('Getting the interaction client');
|
|
1699
|
+
const interactionClient = await node.getInteractionClient();
|
|
1700
|
+
let cluster;
|
|
1701
|
+
let attributes;
|
|
1702
|
+
|
|
1703
|
+
// Log BasicInformationCluster
|
|
1704
|
+
cluster = BasicInformationCluster;
|
|
1705
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1706
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1707
|
+
});
|
|
1708
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1709
|
+
attributes.forEach((attribute) => {
|
|
1710
|
+
this.log.info(
|
|
1711
|
+
`- 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}`,
|
|
1712
|
+
);
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
// Log PowerSourceCluster
|
|
1716
|
+
cluster = PowerSourceCluster;
|
|
1717
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1718
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1719
|
+
});
|
|
1720
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1721
|
+
attributes.forEach((attribute) => {
|
|
1722
|
+
this.log.info(
|
|
1723
|
+
`- 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}`,
|
|
1724
|
+
);
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
// Log ThreadNetworkDiagnostics
|
|
1728
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1729
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1730
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1731
|
+
});
|
|
1732
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1733
|
+
attributes.forEach((attribute) => {
|
|
1734
|
+
this.log.info(
|
|
1735
|
+
`- 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}`,
|
|
1736
|
+
);
|
|
1737
|
+
});
|
|
1738
|
+
|
|
1739
|
+
// Log SwitchCluster
|
|
1740
|
+
cluster = SwitchCluster;
|
|
1741
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1742
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1743
|
+
});
|
|
1744
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1745
|
+
attributes.forEach((attribute) => {
|
|
1746
|
+
this.log.info(
|
|
1747
|
+
`- 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}`,
|
|
1748
|
+
);
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1752
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1753
|
+
ignoreInitialTriggers: false,
|
|
1754
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1755
|
+
this.log.info(
|
|
1756
|
+
`***${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}`,
|
|
1757
|
+
),
|
|
1758
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1759
|
+
this.log.info(
|
|
1760
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1761
|
+
);
|
|
1762
|
+
},
|
|
1763
|
+
});
|
|
1764
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1765
|
+
}
|
|
1766
|
+
*/
|
|
1264
1767
|
}
|
|
1768
|
+
/** ***********************************************************************************************************************************/
|
|
1769
|
+
/** Matter.js methods */
|
|
1770
|
+
/** ***********************************************************************************************************************************/
|
|
1771
|
+
/**
|
|
1772
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1773
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1774
|
+
*/
|
|
1265
1775
|
async startMatterStorage() {
|
|
1776
|
+
// Setup Matter storage
|
|
1266
1777
|
this.log.info(`Starting matter node storage...`);
|
|
1267
1778
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1268
1779
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1270,13 +1781,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1270
1781
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1271
1782
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1272
1783
|
this.log.info('Matter node storage started');
|
|
1784
|
+
// Backup matter storage since it is created/opened correctly
|
|
1273
1785
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1274
1786
|
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1789
|
+
*
|
|
1790
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1791
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1792
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1793
|
+
*/
|
|
1275
1794
|
async backupMatterStorage(storageName, backupName) {
|
|
1276
1795
|
this.log.info('Creating matter node storage backup...');
|
|
1277
1796
|
await copyDirectory(storageName, backupName);
|
|
1278
1797
|
this.log.info('Created matter node storage backup');
|
|
1279
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Stops the matter storage.
|
|
1801
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1802
|
+
*/
|
|
1280
1803
|
async stopMatterStorage() {
|
|
1281
1804
|
this.log.info('Closing matter node storage...');
|
|
1282
1805
|
this.matterStorageManager?.close();
|
|
@@ -1285,6 +1808,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1285
1808
|
this.matterbridgeContext = undefined;
|
|
1286
1809
|
this.log.info('Matter node storage closed');
|
|
1287
1810
|
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Creates a server node storage context.
|
|
1813
|
+
*
|
|
1814
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1815
|
+
* @param {string} deviceName - The name of the device.
|
|
1816
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1817
|
+
* @param {number} vendorId - The vendor ID.
|
|
1818
|
+
* @param {string} vendorName - The vendor name.
|
|
1819
|
+
* @param {number} productId - The product ID.
|
|
1820
|
+
* @param {string} productName - The product name.
|
|
1821
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1822
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1823
|
+
*/
|
|
1288
1824
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1289
1825
|
if (!this.matterStorageService)
|
|
1290
1826
|
throw new Error('No storage service initialized');
|
|
@@ -1317,6 +1853,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1317
1853
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1318
1854
|
return storageContext;
|
|
1319
1855
|
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Creates a server node.
|
|
1858
|
+
*
|
|
1859
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1860
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1861
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1862
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1863
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1864
|
+
*/
|
|
1320
1865
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1321
1866
|
const storeId = await storageContext.get('storeId');
|
|
1322
1867
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1326,21 +1871,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1326
1871
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1327
1872
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1328
1873
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1874
|
+
/**
|
|
1875
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1876
|
+
*/
|
|
1329
1877
|
const serverNode = await ServerNode.create({
|
|
1878
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1330
1879
|
id: storeId,
|
|
1880
|
+
// Provide Network relevant configuration like the port
|
|
1881
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1331
1882
|
network: {
|
|
1332
1883
|
listeningAddressIpv4: this.ipv4address,
|
|
1333
1884
|
listeningAddressIpv6: this.ipv6address,
|
|
1334
1885
|
port,
|
|
1335
1886
|
},
|
|
1887
|
+
// Provide Commissioning relevant settings
|
|
1888
|
+
// Optional for development/testing purposes
|
|
1336
1889
|
commissioning: {
|
|
1337
1890
|
passcode,
|
|
1338
1891
|
discriminator,
|
|
1339
1892
|
},
|
|
1893
|
+
// Provide Node announcement settings
|
|
1894
|
+
// Optional: If Ommitted some development defaults are used
|
|
1340
1895
|
productDescription: {
|
|
1341
1896
|
name: await storageContext.get('deviceName'),
|
|
1342
1897
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1343
1898
|
},
|
|
1899
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1900
|
+
// Optional: If Omitted some development defaults are used
|
|
1344
1901
|
basicInformation: {
|
|
1345
1902
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1346
1903
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1357,12 +1914,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1357
1914
|
},
|
|
1358
1915
|
});
|
|
1359
1916
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1917
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1360
1918
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1361
1919
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1362
1920
|
if (this.bridgeMode === 'bridge') {
|
|
1363
1921
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1364
1922
|
if (resetSessions)
|
|
1365
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1923
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1366
1924
|
this.matterbridgePaired = true;
|
|
1367
1925
|
}
|
|
1368
1926
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1370,13 +1928,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1370
1928
|
if (plugin) {
|
|
1371
1929
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1372
1930
|
if (resetSessions)
|
|
1373
|
-
plugin.sessionInformations = undefined;
|
|
1931
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1374
1932
|
plugin.paired = true;
|
|
1375
1933
|
}
|
|
1376
1934
|
}
|
|
1377
1935
|
};
|
|
1936
|
+
/**
|
|
1937
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1938
|
+
* This means: It is added to the first fabric.
|
|
1939
|
+
*/
|
|
1378
1940
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1941
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1379
1942
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1943
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1380
1944
|
serverNode.lifecycle.online.on(async () => {
|
|
1381
1945
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1382
1946
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1422,6 +1986,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1422
1986
|
}
|
|
1423
1987
|
this.frontend.wssSendRefreshRequired();
|
|
1424
1988
|
});
|
|
1989
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1425
1990
|
serverNode.lifecycle.offline.on(() => {
|
|
1426
1991
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1427
1992
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1443,6 +2008,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1443
2008
|
}
|
|
1444
2009
|
this.frontend.wssSendRefreshRequired();
|
|
1445
2010
|
});
|
|
2011
|
+
/**
|
|
2012
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2013
|
+
* information is needed.
|
|
2014
|
+
*/
|
|
1446
2015
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1447
2016
|
let action = '';
|
|
1448
2017
|
switch (fabricAction) {
|
|
@@ -1476,16 +2045,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
2045
|
}
|
|
1477
2046
|
}
|
|
1478
2047
|
};
|
|
2048
|
+
/**
|
|
2049
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2050
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2051
|
+
*/
|
|
1479
2052
|
serverNode.events.sessions.opened.on((session) => {
|
|
1480
2053
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1481
2054
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1482
2055
|
this.frontend.wssSendRefreshRequired();
|
|
1483
2056
|
});
|
|
2057
|
+
/**
|
|
2058
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2059
|
+
*/
|
|
1484
2060
|
serverNode.events.sessions.closed.on((session) => {
|
|
1485
2061
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1486
2062
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1487
2063
|
this.frontend.wssSendRefreshRequired();
|
|
1488
2064
|
});
|
|
2065
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1489
2066
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1490
2067
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1491
2068
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1494,38 +2071,61 @@ export class Matterbridge extends EventEmitter {
|
|
|
1494
2071
|
this.log.info(`Created server node for ${storeId}`);
|
|
1495
2072
|
return serverNode;
|
|
1496
2073
|
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Starts the specified server node.
|
|
2076
|
+
*
|
|
2077
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2078
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2079
|
+
*/
|
|
1497
2080
|
async startServerNode(matterServerNode) {
|
|
1498
2081
|
if (!matterServerNode)
|
|
1499
2082
|
return;
|
|
1500
2083
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1501
2084
|
await matterServerNode.start();
|
|
1502
2085
|
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Stops the specified server node.
|
|
2088
|
+
*
|
|
2089
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2090
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2091
|
+
*/
|
|
1503
2092
|
async stopServerNode(matterServerNode) {
|
|
1504
2093
|
if (!matterServerNode)
|
|
1505
2094
|
return;
|
|
1506
2095
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2096
|
+
/*
|
|
2097
|
+
await matterServerNode.close();
|
|
2098
|
+
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
2099
|
+
*/
|
|
2100
|
+
// Helper function to add a timeout to a promise
|
|
1507
2101
|
const withTimeout = (promise, ms) => {
|
|
1508
2102
|
return new Promise((resolve, reject) => {
|
|
1509
2103
|
const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
|
|
1510
2104
|
promise
|
|
1511
2105
|
.then((result) => {
|
|
1512
|
-
clearTimeout(timer);
|
|
2106
|
+
clearTimeout(timer); // Prevent memory leak
|
|
1513
2107
|
resolve(result);
|
|
1514
2108
|
})
|
|
1515
2109
|
.catch((error) => {
|
|
1516
|
-
clearTimeout(timer);
|
|
2110
|
+
clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
|
|
1517
2111
|
reject(error);
|
|
1518
2112
|
});
|
|
1519
2113
|
});
|
|
1520
2114
|
};
|
|
1521
2115
|
try {
|
|
1522
|
-
await withTimeout(matterServerNode.close(), 5000);
|
|
2116
|
+
await withTimeout(matterServerNode.close(), 5000); // 5 seconds timeout
|
|
1523
2117
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1524
2118
|
}
|
|
1525
2119
|
catch (error) {
|
|
1526
2120
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1527
2121
|
}
|
|
1528
2122
|
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Advertises the specified server node if it is commissioned.
|
|
2125
|
+
*
|
|
2126
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2127
|
+
* @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.
|
|
2128
|
+
*/
|
|
1529
2129
|
async advertiseServerNode(matterServerNode) {
|
|
1530
2130
|
if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
|
|
1531
2131
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1535,17 +2135,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1535
2135
|
}
|
|
1536
2136
|
return undefined;
|
|
1537
2137
|
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Creates an aggregator node with the specified storage context.
|
|
2140
|
+
*
|
|
2141
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2142
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2143
|
+
*/
|
|
1538
2144
|
async createAggregatorNode(storageContext) {
|
|
1539
2145
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1540
2146
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1541
2147
|
return aggregatorNode;
|
|
1542
2148
|
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2151
|
+
*
|
|
2152
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2153
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2154
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2155
|
+
*/
|
|
1543
2156
|
async addBridgedEndpoint(pluginName, device) {
|
|
2157
|
+
// Check if the plugin is registered
|
|
1544
2158
|
const plugin = this.plugins.get(pluginName);
|
|
1545
2159
|
if (!plugin) {
|
|
1546
2160
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1547
2161
|
return;
|
|
1548
2162
|
}
|
|
2163
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1549
2164
|
if (this.bridgeMode === 'bridge') {
|
|
1550
2165
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1551
2166
|
if (!this.aggregatorNode)
|
|
@@ -1568,16 +2183,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1568
2183
|
plugin.registeredDevices++;
|
|
1569
2184
|
if (plugin.addedDevices !== undefined)
|
|
1570
2185
|
plugin.addedDevices++;
|
|
2186
|
+
// Add the device to the DeviceManager
|
|
1571
2187
|
this.devices.set(device);
|
|
1572
2188
|
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}`);
|
|
1573
2189
|
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2192
|
+
*
|
|
2193
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2194
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2195
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2196
|
+
*/
|
|
1574
2197
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1575
2198
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2199
|
+
// Check if the plugin is registered
|
|
1576
2200
|
const plugin = this.plugins.get(pluginName);
|
|
1577
2201
|
if (!plugin) {
|
|
1578
2202
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1579
2203
|
return;
|
|
1580
2204
|
}
|
|
2205
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1581
2206
|
if (this.bridgeMode === 'bridge') {
|
|
1582
2207
|
if (!this.aggregatorNode) {
|
|
1583
2208
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1592,6 +2217,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1592
2217
|
}
|
|
1593
2218
|
else if (this.bridgeMode === 'childbridge') {
|
|
1594
2219
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2220
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1595
2221
|
}
|
|
1596
2222
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1597
2223
|
if (!plugin.aggregatorNode) {
|
|
@@ -1605,6 +2231,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1605
2231
|
plugin.registeredDevices--;
|
|
1606
2232
|
if (plugin.addedDevices !== undefined)
|
|
1607
2233
|
plugin.addedDevices--;
|
|
2234
|
+
// Close the server node TODO check if this is correct
|
|
1608
2235
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1609
2236
|
if (plugin.serverNode) {
|
|
1610
2237
|
await this.stopServerNode(plugin.serverNode);
|
|
@@ -1615,14 +2242,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
2242
|
}
|
|
1616
2243
|
}
|
|
1617
2244
|
}
|
|
2245
|
+
// Remove the device from the DeviceManager
|
|
1618
2246
|
this.devices.remove(device);
|
|
1619
2247
|
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2250
|
+
*
|
|
2251
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2252
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2253
|
+
*/
|
|
1620
2254
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1621
2255
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1622
2256
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1623
2257
|
await this.removeBridgedEndpoint(pluginName, device);
|
|
1624
2258
|
}
|
|
1625
2259
|
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2262
|
+
*
|
|
2263
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2264
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2265
|
+
*/
|
|
1626
2266
|
sanitizeFabricInformations(fabricInfo) {
|
|
1627
2267
|
return fabricInfo.map((info) => {
|
|
1628
2268
|
return {
|
|
@@ -1636,6 +2276,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1636
2276
|
};
|
|
1637
2277
|
});
|
|
1638
2278
|
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2281
|
+
*
|
|
2282
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2283
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2284
|
+
*/
|
|
1639
2285
|
sanitizeSessionInformation(sessionInfo) {
|
|
1640
2286
|
return sessionInfo
|
|
1641
2287
|
.filter((session) => session.isPeerActive)
|
|
@@ -1663,11 +2309,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1663
2309
|
};
|
|
1664
2310
|
});
|
|
1665
2311
|
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Sets the reachability of a matter server node and trigger ReachableChanged event.
|
|
2314
|
+
*
|
|
2315
|
+
* @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
|
|
2316
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2317
|
+
*/
|
|
2318
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1666
2319
|
setServerNodeReachability(serverNode, reachable) {
|
|
2320
|
+
/*
|
|
2321
|
+
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2322
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2323
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2324
|
+
*/
|
|
1667
2325
|
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2328
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
|
|
2329
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2330
|
+
*/
|
|
2331
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1668
2332
|
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2333
|
+
/*
|
|
2334
|
+
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2335
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2336
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2337
|
+
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2338
|
+
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2339
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2340
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2341
|
+
});
|
|
2342
|
+
*/
|
|
1669
2343
|
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Sets the reachability of a device and trigger.
|
|
2346
|
+
*
|
|
2347
|
+
* @param {MatterbridgeEndpoint} device - The device to set the reachability for.
|
|
2348
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2349
|
+
*/
|
|
2350
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1670
2351
|
setDeviceReachability(device, reachable) {
|
|
2352
|
+
/*
|
|
2353
|
+
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2354
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2355
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2356
|
+
*/
|
|
1671
2357
|
}
|
|
1672
2358
|
getVendorIdName = (vendorId) => {
|
|
1673
2359
|
if (!vendorId)
|
|
@@ -1710,13 +2396,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1710
2396
|
}
|
|
1711
2397
|
return vendorName;
|
|
1712
2398
|
};
|
|
2399
|
+
/**
|
|
2400
|
+
* Spawns a child process with the given command and arguments.
|
|
2401
|
+
* @param {string} command - The command to execute.
|
|
2402
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2403
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2404
|
+
*/
|
|
1713
2405
|
async spawnCommand(command, args = []) {
|
|
2406
|
+
/*
|
|
2407
|
+
npm > npm.cmd on windows
|
|
2408
|
+
cmd.exe ['dir'] on windows
|
|
2409
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2410
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2411
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2415
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2416
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2417
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2418
|
+
*/
|
|
1714
2419
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1715
2420
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2421
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1716
2422
|
const argstring = 'npm ' + args.join(' ');
|
|
1717
2423
|
args.splice(0, args.length, '/c', argstring);
|
|
1718
2424
|
command = 'cmd.exe';
|
|
1719
2425
|
}
|
|
2426
|
+
// Decide when using sudo on linux
|
|
2427
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2428
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1720
2429
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1721
2430
|
args.unshift(command);
|
|
1722
2431
|
command = 'sudo';
|
|
@@ -1775,3 +2484,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1775
2484
|
});
|
|
1776
2485
|
}
|
|
1777
2486
|
}
|
|
2487
|
+
//# sourceMappingURL=matterbridge.js.map
|