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