matterbridge 2.1.5-dev.7 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -2
- package/README-DOCKER.md +6 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +143 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +270 -24
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +409 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +750 -41
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +117 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +159 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +121 -5
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +169 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +231 -4
- package/dist/pluginManager.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/export.d.ts +3 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/utils.d.ts +231 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +264 -10
- package/dist/utils/utils.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 1.5.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// Node.js modules
|
|
1
24
|
import { fileURLToPath } from 'url';
|
|
2
25
|
import { promises as fs } from 'fs';
|
|
3
26
|
import { exec, spawn } from 'child_process';
|
|
@@ -5,20 +28,27 @@ import EventEmitter from 'events';
|
|
|
5
28
|
import os from 'os';
|
|
6
29
|
import path from 'path';
|
|
7
30
|
import { randomBytes } from 'crypto';
|
|
31
|
+
// NodeStorage and AnsiLogger modules
|
|
8
32
|
import { NodeStorageManager } from './storage/export.js';
|
|
9
33
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from './logger/export.js';
|
|
34
|
+
// Matterbridge
|
|
10
35
|
import { logInterfaces, copyDirectory, getParameter, getIntParameter, hasParameter, getNpmPackageVersion } from './utils/utils.js';
|
|
11
36
|
import { PluginManager } from './pluginManager.js';
|
|
12
37
|
import { DeviceManager } from './deviceManager.js';
|
|
13
38
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
14
39
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
15
40
|
import { Frontend } from './frontend.js';
|
|
41
|
+
// @matter
|
|
16
42
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
17
43
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
18
44
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
45
|
+
// Default colors
|
|
19
46
|
const plg = '\u001B[38;5;33m';
|
|
20
47
|
const dev = '\u001B[38;5;79m';
|
|
21
48
|
const typ = '\u001B[38;5;207m';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
22
52
|
export class Matterbridge extends EventEmitter {
|
|
23
53
|
systemInformation = {
|
|
24
54
|
interfaceName: '',
|
|
@@ -57,7 +87,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
57
87
|
restartMode: '',
|
|
58
88
|
readOnly: hasParameter('readonly'),
|
|
59
89
|
profile: getParameter('profile'),
|
|
60
|
-
loggerLevel: "info"
|
|
90
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
61
91
|
fileLogger: false,
|
|
62
92
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
63
93
|
matterFileLogger: false,
|
|
@@ -92,9 +122,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
92
122
|
plugins;
|
|
93
123
|
devices;
|
|
94
124
|
frontend = new Frontend(this);
|
|
125
|
+
// Matterbridge storage
|
|
95
126
|
nodeStorage;
|
|
96
127
|
nodeContext;
|
|
97
128
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
129
|
+
// Cleanup
|
|
98
130
|
hasCleanupStarted = false;
|
|
99
131
|
initialized = false;
|
|
100
132
|
execRunningCount = 0;
|
|
@@ -106,34 +138,57 @@ export class Matterbridge extends EventEmitter {
|
|
|
106
138
|
sigtermHandler;
|
|
107
139
|
exceptionHandler;
|
|
108
140
|
rejectionHandler;
|
|
141
|
+
// Matter environment
|
|
109
142
|
environment = Environment.default;
|
|
143
|
+
// Matter storage
|
|
110
144
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
111
145
|
matterStorageService;
|
|
112
146
|
matterStorageManager;
|
|
113
147
|
matterbridgeContext;
|
|
114
148
|
mattercontrollerContext;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
149
|
+
// Matter parameters
|
|
150
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
151
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
152
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
153
|
+
port; // first server node port
|
|
154
|
+
passcode; // first server node passcode
|
|
155
|
+
discriminator; // first server node discriminator
|
|
121
156
|
serverNode;
|
|
122
157
|
aggregatorNode;
|
|
123
158
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
124
159
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
125
160
|
static instance;
|
|
161
|
+
// We load asyncronously so is private
|
|
126
162
|
constructor() {
|
|
127
163
|
super();
|
|
128
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Retrieves the list of Matterbridge devices.
|
|
167
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
168
|
+
*/
|
|
129
169
|
getDevices() {
|
|
130
170
|
return this.devices.array();
|
|
131
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Retrieves the list of registered plugins.
|
|
174
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
175
|
+
*/
|
|
132
176
|
getPlugins() {
|
|
133
177
|
return this.plugins.array();
|
|
134
178
|
}
|
|
179
|
+
/** ***********************************************************************************************************************************/
|
|
180
|
+
/** loadInstance() and cleanup() methods */
|
|
181
|
+
/** ***********************************************************************************************************************************/
|
|
182
|
+
/**
|
|
183
|
+
* Loads an instance of the Matterbridge class.
|
|
184
|
+
* If an instance already exists, return that instance.
|
|
185
|
+
*
|
|
186
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
187
|
+
* @returns The loaded Matterbridge instance.
|
|
188
|
+
*/
|
|
135
189
|
static async loadInstance(initialize = false) {
|
|
136
190
|
if (!Matterbridge.instance) {
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
137
192
|
if (hasParameter('debug'))
|
|
138
193
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
139
194
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -142,7 +197,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
142
197
|
}
|
|
143
198
|
return Matterbridge.instance;
|
|
144
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Call cleanup().
|
|
202
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
203
|
+
*
|
|
204
|
+
*/
|
|
145
205
|
async destroyInstance() {
|
|
206
|
+
// Save server nodes to close
|
|
146
207
|
const servers = [];
|
|
147
208
|
if (this.bridgeMode === 'bridge') {
|
|
148
209
|
if (this.serverNode)
|
|
@@ -154,54 +215,80 @@ export class Matterbridge extends EventEmitter {
|
|
|
154
215
|
servers.push(plugin.serverNode);
|
|
155
216
|
}
|
|
156
217
|
}
|
|
218
|
+
// Cleanup
|
|
157
219
|
await this.cleanup('destroying instance...', false);
|
|
220
|
+
// Close servers mdns service
|
|
158
221
|
for (const server of servers) {
|
|
159
222
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
160
223
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
161
224
|
}
|
|
225
|
+
// Wait for the cleanup to finish
|
|
162
226
|
await new Promise((resolve) => {
|
|
163
227
|
setTimeout(resolve, 1000);
|
|
164
228
|
});
|
|
165
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Initializes the Matterbridge application.
|
|
232
|
+
*
|
|
233
|
+
* @remarks
|
|
234
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
235
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
236
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
237
|
+
*
|
|
238
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
239
|
+
*/
|
|
166
240
|
async initialize() {
|
|
241
|
+
// Set the restart mode
|
|
167
242
|
if (hasParameter('service'))
|
|
168
243
|
this.restartMode = 'service';
|
|
169
244
|
if (hasParameter('docker'))
|
|
170
245
|
this.restartMode = 'docker';
|
|
246
|
+
// Set the matterbridge directory
|
|
171
247
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
172
248
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
249
|
+
// Setup the matter environment
|
|
173
250
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
174
251
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
175
252
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
176
253
|
this.environment.vars.set('runtime.signals', false);
|
|
177
254
|
this.environment.vars.set('runtime.exitcode', false);
|
|
178
|
-
|
|
255
|
+
// Create the matterbridge logger
|
|
256
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
257
|
+
// Register process handlers
|
|
179
258
|
this.registerProcessHandlers();
|
|
259
|
+
// Initialize nodeStorage and nodeContext
|
|
180
260
|
try {
|
|
181
261
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
182
262
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
183
263
|
this.log.debug('Creating node storage context for matterbridge');
|
|
184
264
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
265
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
266
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
267
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
186
268
|
for (const key of keys) {
|
|
187
269
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
188
271
|
await this.nodeStorage?.storage.get(key);
|
|
189
272
|
}
|
|
190
273
|
const storages = await this.nodeStorage.getStorageNames();
|
|
191
274
|
for (const storage of storages) {
|
|
192
275
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
193
276
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
277
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
279
|
const keys = (await nodeContext?.storage.keys());
|
|
195
280
|
keys.forEach(async (key) => {
|
|
196
281
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
197
282
|
await nodeContext?.get(key);
|
|
198
283
|
});
|
|
199
284
|
}
|
|
285
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
200
286
|
this.log.debug('Creating node storage backup...');
|
|
201
287
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
202
288
|
this.log.debug('Created node storage backup');
|
|
203
289
|
}
|
|
204
290
|
catch (error) {
|
|
291
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
205
292
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
206
293
|
if (hasParameter('norestore')) {
|
|
207
294
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -216,45 +303,52 @@ export class Matterbridge extends EventEmitter {
|
|
|
216
303
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
217
304
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
218
305
|
}
|
|
306
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
219
307
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
308
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
220
309
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
310
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
221
311
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
222
312
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
313
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
223
314
|
if (hasParameter('logger')) {
|
|
224
315
|
const level = getParameter('logger');
|
|
225
316
|
if (level === 'debug') {
|
|
226
|
-
this.log.logLevel = "debug"
|
|
317
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
227
318
|
}
|
|
228
319
|
else if (level === 'info') {
|
|
229
|
-
this.log.logLevel = "info"
|
|
320
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
230
321
|
}
|
|
231
322
|
else if (level === 'notice') {
|
|
232
|
-
this.log.logLevel = "notice"
|
|
323
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
233
324
|
}
|
|
234
325
|
else if (level === 'warn') {
|
|
235
|
-
this.log.logLevel = "warn"
|
|
326
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
236
327
|
}
|
|
237
328
|
else if (level === 'error') {
|
|
238
|
-
this.log.logLevel = "error"
|
|
329
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
239
330
|
}
|
|
240
331
|
else if (level === 'fatal') {
|
|
241
|
-
this.log.logLevel = "fatal"
|
|
332
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
242
333
|
}
|
|
243
334
|
else {
|
|
244
335
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
245
|
-
this.log.logLevel = "info"
|
|
336
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
246
337
|
}
|
|
247
338
|
}
|
|
248
339
|
else {
|
|
249
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
340
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info" /* LogLevel.INFO */);
|
|
250
341
|
}
|
|
251
342
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
343
|
+
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
344
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
252
345
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
253
346
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
254
347
|
this.matterbridgeInformation.fileLogger = true;
|
|
255
348
|
}
|
|
256
349
|
this.log.notice('Matterbridge is starting...');
|
|
257
350
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
351
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
258
352
|
if (hasParameter('matterlogger')) {
|
|
259
353
|
const level = getParameter('matterlogger');
|
|
260
354
|
if (level === 'debug') {
|
|
@@ -285,6 +379,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
285
379
|
}
|
|
286
380
|
Logger.format = MatterLogFormat.ANSI;
|
|
287
381
|
Logger.setLogger('default', this.createMatterLogger());
|
|
382
|
+
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
383
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
288
384
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
289
385
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
290
386
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -293,6 +389,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
293
389
|
});
|
|
294
390
|
}
|
|
295
391
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
392
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
296
393
|
if (hasParameter('mdnsinterface')) {
|
|
297
394
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
298
395
|
}
|
|
@@ -301,6 +398,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
301
398
|
if (this.mdnsInterface === '')
|
|
302
399
|
this.mdnsInterface = undefined;
|
|
303
400
|
}
|
|
401
|
+
// Validate mdnsInterface
|
|
304
402
|
if (this.mdnsInterface) {
|
|
305
403
|
const networkInterfaces = os.networkInterfaces();
|
|
306
404
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -314,6 +412,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
314
412
|
}
|
|
315
413
|
if (this.mdnsInterface)
|
|
316
414
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
415
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
317
416
|
if (hasParameter('ipv4address')) {
|
|
318
417
|
this.ipv4address = getParameter('ipv4address');
|
|
319
418
|
}
|
|
@@ -322,6 +421,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
322
421
|
if (this.ipv4address === '')
|
|
323
422
|
this.ipv4address = undefined;
|
|
324
423
|
}
|
|
424
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
325
425
|
if (hasParameter('ipv6address')) {
|
|
326
426
|
this.ipv6address = getParameter('ipv6address');
|
|
327
427
|
}
|
|
@@ -330,14 +430,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
330
430
|
if (this.ipv6address === '')
|
|
331
431
|
this.ipv6address = undefined;
|
|
332
432
|
}
|
|
433
|
+
// Initialize PluginManager
|
|
333
434
|
this.plugins = new PluginManager(this);
|
|
334
435
|
await this.plugins.loadFromStorage();
|
|
335
436
|
this.plugins.logLevel = this.log.logLevel;
|
|
437
|
+
// Initialize DeviceManager
|
|
336
438
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
337
439
|
this.devices.logLevel = this.log.logLevel;
|
|
440
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
338
441
|
for (const plugin of this.plugins) {
|
|
339
442
|
const packageJson = await this.plugins.parse(plugin);
|
|
340
443
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
444
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
445
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
341
446
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
342
447
|
try {
|
|
343
448
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -359,6 +464,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
359
464
|
await plugin.nodeContext.set('description', plugin.description);
|
|
360
465
|
await plugin.nodeContext.set('author', plugin.author);
|
|
361
466
|
}
|
|
467
|
+
// Log system info and create .matterbridge directory
|
|
362
468
|
await this.logNodeAndSystemInfo();
|
|
363
469
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
364
470
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -366,6 +472,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
366
472
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
367
473
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
368
474
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
475
|
+
// Check node version and throw error
|
|
369
476
|
const minNodeVersion = 18;
|
|
370
477
|
const nodeVersion = process.versions.node;
|
|
371
478
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -373,9 +480,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
373
480
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
374
481
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
375
482
|
}
|
|
483
|
+
// Parse command line
|
|
376
484
|
await this.parseCommandLine();
|
|
377
485
|
this.initialized = true;
|
|
378
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
489
|
+
* @private
|
|
490
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
491
|
+
*/
|
|
379
492
|
async parseCommandLine() {
|
|
380
493
|
if (hasParameter('help')) {
|
|
381
494
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -485,6 +598,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
485
598
|
await this.shutdownProcessAndFactoryReset();
|
|
486
599
|
return;
|
|
487
600
|
}
|
|
601
|
+
// Start the matter storage and create the matterbridge context
|
|
488
602
|
try {
|
|
489
603
|
await this.startMatterStorage();
|
|
490
604
|
}
|
|
@@ -492,10 +606,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
492
606
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
493
607
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
494
608
|
}
|
|
609
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
495
610
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
496
611
|
await this.shutdownProcessAndReset();
|
|
497
612
|
return;
|
|
498
613
|
}
|
|
614
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
499
615
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
500
616
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
501
617
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -517,13 +633,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
517
633
|
this.emit('shutdown');
|
|
518
634
|
return;
|
|
519
635
|
}
|
|
636
|
+
// Initialize frontend
|
|
520
637
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
521
638
|
await this.frontend.start(getIntParameter('frontend'));
|
|
522
639
|
this.frontend.logLevel = this.log.logLevel;
|
|
640
|
+
// Check now the latest versions of matterbridge and plugins
|
|
523
641
|
this.getMatterbridgeLatestVersion();
|
|
524
642
|
for (const plugin of this.plugins) {
|
|
525
643
|
this.getPluginLatestVersion(plugin);
|
|
526
644
|
}
|
|
645
|
+
// Check each 60 minutes the latest versions
|
|
527
646
|
this.checkUpdateInterval = setInterval(() => {
|
|
528
647
|
this.getMatterbridgeLatestVersion();
|
|
529
648
|
for (const plugin of this.plugins) {
|
|
@@ -531,20 +650,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
531
650
|
}
|
|
532
651
|
this.frontend.wssSendRefreshRequired();
|
|
533
652
|
}, 60 * 60 * 1000);
|
|
653
|
+
// Start the matterbridge in mode test
|
|
534
654
|
if (hasParameter('test')) {
|
|
535
655
|
this.bridgeMode = 'bridge';
|
|
536
656
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
537
657
|
return;
|
|
538
658
|
}
|
|
659
|
+
// Start the matterbridge in mode controller
|
|
539
660
|
if (hasParameter('controller')) {
|
|
540
661
|
this.bridgeMode = 'controller';
|
|
541
662
|
await this.startController();
|
|
542
663
|
return;
|
|
543
664
|
}
|
|
665
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
544
666
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
545
667
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
546
668
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
547
669
|
}
|
|
670
|
+
// Start matterbridge in bridge mode
|
|
548
671
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
549
672
|
this.bridgeMode = 'bridge';
|
|
550
673
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -552,6 +675,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
552
675
|
await this.startBridge();
|
|
553
676
|
return;
|
|
554
677
|
}
|
|
678
|
+
// Start matterbridge in childbridge mode
|
|
555
679
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
556
680
|
this.bridgeMode = 'childbridge';
|
|
557
681
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -560,16 +684,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
560
684
|
return;
|
|
561
685
|
}
|
|
562
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Asynchronously loads and starts the registered plugins.
|
|
689
|
+
*
|
|
690
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
691
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
692
|
+
*
|
|
693
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
694
|
+
*/
|
|
563
695
|
async startPlugins() {
|
|
696
|
+
// Check, load and start the plugins
|
|
564
697
|
for (const plugin of this.plugins) {
|
|
565
698
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
566
699
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
700
|
+
// Check if the plugin is available
|
|
567
701
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
568
702
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
569
703
|
plugin.enabled = false;
|
|
570
704
|
plugin.error = true;
|
|
571
705
|
continue;
|
|
572
706
|
}
|
|
707
|
+
// Check if the plugin has a new version
|
|
708
|
+
// this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
573
709
|
if (!plugin.enabled) {
|
|
574
710
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
575
711
|
continue;
|
|
@@ -583,20 +719,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
583
719
|
plugin.addedDevices = undefined;
|
|
584
720
|
plugin.qrPairingCode = undefined;
|
|
585
721
|
plugin.manualPairingCode = undefined;
|
|
586
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
722
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
587
723
|
}
|
|
588
724
|
this.frontend.wssSendRefreshRequired();
|
|
589
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
728
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
729
|
+
*/
|
|
590
730
|
registerProcessHandlers() {
|
|
591
731
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
592
732
|
process.removeAllListeners('uncaughtException');
|
|
593
733
|
process.removeAllListeners('unhandledRejection');
|
|
594
734
|
this.exceptionHandler = async (error) => {
|
|
595
735
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
736
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
596
737
|
};
|
|
597
738
|
process.on('uncaughtException', this.exceptionHandler);
|
|
598
739
|
this.rejectionHandler = async (reason, promise) => {
|
|
599
740
|
this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
741
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
600
742
|
};
|
|
601
743
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
602
744
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -609,6 +751,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
609
751
|
};
|
|
610
752
|
process.on('SIGTERM', this.sigtermHandler);
|
|
611
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
756
|
+
*/
|
|
612
757
|
deregisterProcesslHandlers() {
|
|
613
758
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
614
759
|
if (this.exceptionHandler)
|
|
@@ -625,12 +770,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
625
770
|
process.off('SIGTERM', this.sigtermHandler);
|
|
626
771
|
this.sigtermHandler = undefined;
|
|
627
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Logs the node and system information.
|
|
775
|
+
*/
|
|
628
776
|
async logNodeAndSystemInfo() {
|
|
777
|
+
// IP address information
|
|
629
778
|
const networkInterfaces = os.networkInterfaces();
|
|
630
779
|
this.systemInformation.interfaceName = '';
|
|
631
780
|
this.systemInformation.ipv4Address = '';
|
|
632
781
|
this.systemInformation.ipv6Address = '';
|
|
633
782
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
783
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
634
784
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
635
785
|
continue;
|
|
636
786
|
if (!interfaceDetails) {
|
|
@@ -656,19 +806,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
656
806
|
break;
|
|
657
807
|
}
|
|
658
808
|
}
|
|
809
|
+
// Node information
|
|
659
810
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
660
811
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
661
812
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
662
813
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
814
|
+
// Host system information
|
|
663
815
|
this.systemInformation.hostname = os.hostname();
|
|
664
816
|
this.systemInformation.user = os.userInfo().username;
|
|
665
|
-
this.systemInformation.osType = os.type();
|
|
666
|
-
this.systemInformation.osRelease = os.release();
|
|
667
|
-
this.systemInformation.osPlatform = os.platform();
|
|
668
|
-
this.systemInformation.osArch = os.arch();
|
|
669
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
670
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
671
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
817
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
818
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
819
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
820
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
821
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
822
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
823
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
824
|
+
// Log the system information
|
|
672
825
|
this.log.debug('Host System Information:');
|
|
673
826
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
674
827
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -684,15 +837,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
684
837
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
685
838
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
686
839
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
840
|
+
// Home directory
|
|
687
841
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
688
842
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
689
843
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
844
|
+
// Package root directory
|
|
690
845
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
691
846
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
692
847
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
693
848
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
849
|
+
// Global node_modules directory
|
|
694
850
|
if (this.nodeContext)
|
|
695
851
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
852
|
+
// First run of Matterbridge so the node storage is empty
|
|
696
853
|
if (this.globalModulesDirectory === '') {
|
|
697
854
|
try {
|
|
698
855
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -706,6 +863,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
706
863
|
}
|
|
707
864
|
else
|
|
708
865
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
866
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
867
|
+
else {
|
|
868
|
+
this.getGlobalNodeModules()
|
|
869
|
+
.then(async (globalModulesDirectory) => {
|
|
870
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
871
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
872
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
873
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
874
|
+
})
|
|
875
|
+
.catch((error) => {
|
|
876
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
877
|
+
});
|
|
878
|
+
}*/
|
|
879
|
+
// Create the data directory .matterbridge in the home directory
|
|
709
880
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
710
881
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
711
882
|
try {
|
|
@@ -729,6 +900,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
729
900
|
}
|
|
730
901
|
}
|
|
731
902
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
903
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
732
904
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
733
905
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
734
906
|
try {
|
|
@@ -752,18 +924,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
752
924
|
}
|
|
753
925
|
}
|
|
754
926
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
927
|
+
// Matterbridge version
|
|
755
928
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
756
929
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
757
930
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
758
931
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
932
|
+
// Matterbridge latest version
|
|
759
933
|
if (this.nodeContext)
|
|
760
934
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
761
935
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
936
|
+
// this.getMatterbridgeLatestVersion();
|
|
937
|
+
// Current working directory
|
|
762
938
|
const currentDir = process.cwd();
|
|
763
939
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
940
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
764
941
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
765
942
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
766
943
|
}
|
|
944
|
+
/**
|
|
945
|
+
* Retrieves the latest version of a package from the npm registry.
|
|
946
|
+
* @param packageName - The name of the package.
|
|
947
|
+
* @returns A Promise that resolves to the latest version of the package.
|
|
948
|
+
*/
|
|
767
949
|
async getLatestVersion(packageName) {
|
|
768
950
|
return new Promise((resolve, reject) => {
|
|
769
951
|
this.execRunningCount++;
|
|
@@ -778,6 +960,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
778
960
|
});
|
|
779
961
|
});
|
|
780
962
|
}
|
|
963
|
+
/**
|
|
964
|
+
* Retrieves the path to the global Node.js modules directory.
|
|
965
|
+
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
966
|
+
*/
|
|
781
967
|
async getGlobalNodeModules() {
|
|
782
968
|
return new Promise((resolve, reject) => {
|
|
783
969
|
this.execRunningCount++;
|
|
@@ -792,6 +978,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
792
978
|
});
|
|
793
979
|
});
|
|
794
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Retrieves the latest version of Matterbridge and updates the matterbridgeLatestVersion property.
|
|
983
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
984
|
+
*
|
|
985
|
+
* @private
|
|
986
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved.
|
|
987
|
+
*/
|
|
795
988
|
async getMatterbridgeLatestVersion() {
|
|
796
989
|
getNpmPackageVersion('matterbridge')
|
|
797
990
|
.then(async (version) => {
|
|
@@ -810,6 +1003,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
810
1003
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
811
1004
|
});
|
|
812
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
1008
|
+
* If there is an error retrieving the latest version, logs an error message.
|
|
1009
|
+
*
|
|
1010
|
+
* @private
|
|
1011
|
+
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
1012
|
+
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved.
|
|
1013
|
+
*/
|
|
813
1014
|
async getPluginLatestVersion(plugin) {
|
|
814
1015
|
getNpmPackageVersion(plugin.name)
|
|
815
1016
|
.then((version) => {
|
|
@@ -823,38 +1024,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
823
1024
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
824
1025
|
});
|
|
825
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1029
|
+
*
|
|
1030
|
+
* @returns {Function} The MatterLogger function.
|
|
1031
|
+
*/
|
|
826
1032
|
createMatterLogger() {
|
|
827
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1033
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
828
1034
|
return (_level, formattedLog) => {
|
|
829
1035
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
830
1036
|
const message = formattedLog.slice(65);
|
|
831
1037
|
matterLogger.logName = logger;
|
|
832
1038
|
switch (_level) {
|
|
833
1039
|
case MatterLogLevel.DEBUG:
|
|
834
|
-
matterLogger.log("debug"
|
|
1040
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
835
1041
|
break;
|
|
836
1042
|
case MatterLogLevel.INFO:
|
|
837
|
-
matterLogger.log("info"
|
|
1043
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
838
1044
|
break;
|
|
839
1045
|
case MatterLogLevel.NOTICE:
|
|
840
|
-
matterLogger.log("notice"
|
|
1046
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
841
1047
|
break;
|
|
842
1048
|
case MatterLogLevel.WARN:
|
|
843
|
-
matterLogger.log("warn"
|
|
1049
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
844
1050
|
break;
|
|
845
1051
|
case MatterLogLevel.ERROR:
|
|
846
|
-
matterLogger.log("error"
|
|
1052
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
847
1053
|
break;
|
|
848
1054
|
case MatterLogLevel.FATAL:
|
|
849
|
-
matterLogger.log("fatal"
|
|
1055
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
850
1056
|
break;
|
|
851
1057
|
default:
|
|
852
|
-
matterLogger.log("debug"
|
|
1058
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
853
1059
|
break;
|
|
854
1060
|
}
|
|
855
1061
|
};
|
|
856
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Creates a Matter File Logger.
|
|
1065
|
+
*
|
|
1066
|
+
* @param {string} filePath - The path to the log file.
|
|
1067
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1068
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1069
|
+
*/
|
|
857
1070
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1071
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
858
1072
|
let fileSize = 0;
|
|
859
1073
|
if (unlink) {
|
|
860
1074
|
try {
|
|
@@ -903,12 +1117,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
903
1117
|
}
|
|
904
1118
|
};
|
|
905
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1122
|
+
*/
|
|
906
1123
|
async restartProcess() {
|
|
907
1124
|
await this.cleanup('restarting...', true);
|
|
908
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Shut down the process by exiting the current process.
|
|
1128
|
+
*/
|
|
909
1129
|
async shutdownProcess() {
|
|
910
1130
|
await this.cleanup('shutting down...', false);
|
|
911
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Update matterbridge and and shut down the process.
|
|
1134
|
+
*/
|
|
912
1135
|
async updateProcess() {
|
|
913
1136
|
this.log.info('Updating matterbridge...');
|
|
914
1137
|
try {
|
|
@@ -921,46 +1144,66 @@ export class Matterbridge extends EventEmitter {
|
|
|
921
1144
|
this.frontend.wssSendRestartRequired();
|
|
922
1145
|
await this.cleanup('updating...', false);
|
|
923
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Unregister all devices and shut down the process.
|
|
1149
|
+
*/
|
|
924
1150
|
async unregisterAndShutdownProcess() {
|
|
925
1151
|
this.log.info('Unregistering all devices and shutting down...');
|
|
926
1152
|
for (const plugin of this.plugins) {
|
|
927
1153
|
await this.removeAllBridgedEndpoints(plugin.name);
|
|
928
1154
|
}
|
|
929
1155
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
930
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1156
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
931
1157
|
this.log.debug('Cleaning up and shutting down...');
|
|
932
1158
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
933
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Reset commissioning and shut down the process.
|
|
1162
|
+
*/
|
|
934
1163
|
async shutdownProcessAndReset() {
|
|
935
1164
|
await this.cleanup('shutting down with reset...', false);
|
|
936
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Factory reset and shut down the process.
|
|
1168
|
+
*/
|
|
937
1169
|
async shutdownProcessAndFactoryReset() {
|
|
938
1170
|
await this.cleanup('shutting down with factory reset...', false);
|
|
939
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Cleans up the Matterbridge instance.
|
|
1174
|
+
* @param message - The cleanup message.
|
|
1175
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1176
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1177
|
+
*/
|
|
940
1178
|
async cleanup(message, restart = false) {
|
|
941
1179
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
942
1180
|
this.hasCleanupStarted = true;
|
|
943
1181
|
this.log.info(message);
|
|
1182
|
+
// Clear the start matter interval
|
|
944
1183
|
if (this.startMatterInterval) {
|
|
945
1184
|
clearInterval(this.startMatterInterval);
|
|
946
1185
|
this.startMatterInterval = undefined;
|
|
947
1186
|
this.log.debug('Start matter interval cleared');
|
|
948
1187
|
}
|
|
1188
|
+
// Clear the check update interval
|
|
949
1189
|
if (this.checkUpdateInterval) {
|
|
950
1190
|
clearInterval(this.checkUpdateInterval);
|
|
951
1191
|
this.checkUpdateInterval = undefined;
|
|
952
1192
|
this.log.debug('Check update interval cleared');
|
|
953
1193
|
}
|
|
1194
|
+
// Clear the configure timeout
|
|
954
1195
|
if (this.configureTimeout) {
|
|
955
1196
|
clearTimeout(this.configureTimeout);
|
|
956
1197
|
this.configureTimeout = undefined;
|
|
957
1198
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
958
1199
|
}
|
|
1200
|
+
// Clear the reachability timeout
|
|
959
1201
|
if (this.reachabilityTimeout) {
|
|
960
1202
|
clearTimeout(this.reachabilityTimeout);
|
|
961
1203
|
this.reachabilityTimeout = undefined;
|
|
962
1204
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
963
1205
|
}
|
|
1206
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
964
1207
|
for (const plugin of this.plugins) {
|
|
965
1208
|
if (!plugin.enabled || plugin.error)
|
|
966
1209
|
continue;
|
|
@@ -971,9 +1214,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
971
1214
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
972
1215
|
}
|
|
973
1216
|
}
|
|
1217
|
+
// Stopping matter server nodes
|
|
974
1218
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
975
1219
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
976
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1220
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
977
1221
|
if (this.bridgeMode === 'bridge') {
|
|
978
1222
|
if (this.serverNode) {
|
|
979
1223
|
await this.stopServerNode(this.serverNode);
|
|
@@ -989,6 +1233,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
989
1233
|
}
|
|
990
1234
|
}
|
|
991
1235
|
this.log.notice('Stopped matter server nodes');
|
|
1236
|
+
// Matter commisioning reset
|
|
992
1237
|
if (message === 'shutting down with reset...') {
|
|
993
1238
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
994
1239
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -998,17 +1243,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
998
1243
|
await this.matterbridgeContext?.clearAll();
|
|
999
1244
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1000
1245
|
}
|
|
1246
|
+
// Stop matter storage
|
|
1001
1247
|
await this.stopMatterStorage();
|
|
1248
|
+
// Stop the frontend
|
|
1002
1249
|
await this.frontend.stop();
|
|
1250
|
+
// Remove the matterfilelogger
|
|
1003
1251
|
try {
|
|
1004
1252
|
Logger.removeLogger('matterfilelogger');
|
|
1253
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1005
1254
|
}
|
|
1006
1255
|
catch (error) {
|
|
1256
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1007
1257
|
}
|
|
1258
|
+
// Serialize registeredDevices
|
|
1008
1259
|
if (this.nodeStorage && this.nodeContext) {
|
|
1260
|
+
/*
|
|
1261
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1262
|
+
this.log.info('Saving registered devices...');
|
|
1263
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1264
|
+
this.devices.forEach(async (device) => {
|
|
1265
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1266
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1267
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1268
|
+
});
|
|
1269
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1270
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1271
|
+
*/
|
|
1272
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1009
1273
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1010
1274
|
await this.nodeContext.close();
|
|
1011
1275
|
this.nodeContext = undefined;
|
|
1276
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1012
1277
|
for (const plugin of this.plugins) {
|
|
1013
1278
|
if (plugin.nodeContext) {
|
|
1014
1279
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1025,8 +1290,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1025
1290
|
}
|
|
1026
1291
|
this.plugins.clear();
|
|
1027
1292
|
this.devices.clear();
|
|
1293
|
+
// Factory reset
|
|
1028
1294
|
if (message === 'shutting down with factory reset...') {
|
|
1029
1295
|
try {
|
|
1296
|
+
// Delete old matter storage file and backup
|
|
1030
1297
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1031
1298
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1032
1299
|
await fs.unlink(file);
|
|
@@ -1040,6 +1307,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1040
1307
|
}
|
|
1041
1308
|
}
|
|
1042
1309
|
try {
|
|
1310
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1043
1311
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1044
1312
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1045
1313
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1053,6 +1321,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1053
1321
|
}
|
|
1054
1322
|
}
|
|
1055
1323
|
try {
|
|
1324
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1056
1325
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1057
1326
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1058
1327
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1067,12 +1336,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1067
1336
|
}
|
|
1068
1337
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1069
1338
|
}
|
|
1339
|
+
// Deregisters the process handlers
|
|
1070
1340
|
this.deregisterProcesslHandlers();
|
|
1071
1341
|
if (restart) {
|
|
1072
1342
|
if (message === 'updating...') {
|
|
1073
1343
|
this.log.info('Cleanup completed. Updating...');
|
|
1074
1344
|
Matterbridge.instance = undefined;
|
|
1075
|
-
this.emit('update');
|
|
1345
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1076
1346
|
}
|
|
1077
1347
|
else if (message === 'restarting...') {
|
|
1078
1348
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1092,6 +1362,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1092
1362
|
this.log.debug('Cleanup already started...');
|
|
1093
1363
|
}
|
|
1094
1364
|
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1367
|
+
*
|
|
1368
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1369
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1370
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1371
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1372
|
+
*/
|
|
1095
1373
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1096
1374
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1097
1375
|
plugin.locked = true;
|
|
@@ -1103,6 +1381,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1103
1381
|
await this.startServerNode(plugin.serverNode);
|
|
1104
1382
|
}
|
|
1105
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1386
|
+
*
|
|
1387
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1388
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1389
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1390
|
+
*/
|
|
1106
1391
|
async createDynamicPlugin(plugin, start = false) {
|
|
1107
1392
|
if (!plugin.locked) {
|
|
1108
1393
|
plugin.locked = true;
|
|
@@ -1114,7 +1399,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1114
1399
|
await this.startServerNode(plugin.serverNode);
|
|
1115
1400
|
}
|
|
1116
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Starts the Matterbridge in bridge mode.
|
|
1404
|
+
* @private
|
|
1405
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1406
|
+
*/
|
|
1117
1407
|
async startBridge() {
|
|
1408
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1118
1409
|
if (!this.matterStorageManager)
|
|
1119
1410
|
throw new Error('No storage manager initialized');
|
|
1120
1411
|
if (!this.matterbridgeContext)
|
|
@@ -1151,7 +1442,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1151
1442
|
clearInterval(this.startMatterInterval);
|
|
1152
1443
|
this.startMatterInterval = undefined;
|
|
1153
1444
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1445
|
+
// Start the Matter server node
|
|
1154
1446
|
this.startServerNode(this.serverNode);
|
|
1447
|
+
// Configure the plugins
|
|
1155
1448
|
this.configureTimeout = setTimeout(async () => {
|
|
1156
1449
|
for (const plugin of this.plugins) {
|
|
1157
1450
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1166,6 +1459,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1166
1459
|
}
|
|
1167
1460
|
this.frontend.wssSendRefreshRequired();
|
|
1168
1461
|
}, 30 * 1000);
|
|
1462
|
+
// Setting reachability to true
|
|
1169
1463
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1170
1464
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1171
1465
|
if (this.serverNode)
|
|
@@ -1176,7 +1470,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1176
1470
|
}, 60 * 1000);
|
|
1177
1471
|
}, 1000);
|
|
1178
1472
|
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1475
|
+
* @private
|
|
1476
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1477
|
+
*/
|
|
1179
1478
|
async startChildbridge() {
|
|
1479
|
+
// Matterbridge.addBridgedDevice creates the commissionig servers and add the devices to the the commissioning server or to the aggregator
|
|
1480
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1180
1481
|
if (!this.matterStorageManager)
|
|
1181
1482
|
throw new Error('No storage manager initialized');
|
|
1182
1483
|
for (const plugin of this.plugins) {
|
|
@@ -1223,12 +1524,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1223
1524
|
clearInterval(this.startMatterInterval);
|
|
1224
1525
|
this.startMatterInterval = undefined;
|
|
1225
1526
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1527
|
+
// Configure the plugins
|
|
1226
1528
|
this.configureTimeout = setTimeout(async () => {
|
|
1227
1529
|
for (const plugin of this.plugins) {
|
|
1228
1530
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1229
1531
|
continue;
|
|
1230
1532
|
try {
|
|
1231
|
-
await this.plugins.configure(plugin);
|
|
1533
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1232
1534
|
}
|
|
1233
1535
|
catch (error) {
|
|
1234
1536
|
plugin.error = true;
|
|
@@ -1256,7 +1558,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1256
1558
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1257
1559
|
continue;
|
|
1258
1560
|
}
|
|
1561
|
+
// Start the Matter server node
|
|
1259
1562
|
this.startServerNode(plugin.serverNode);
|
|
1563
|
+
// Setting reachability to true
|
|
1260
1564
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1261
1565
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1262
1566
|
if (plugin.serverNode)
|
|
@@ -1270,9 +1574,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1270
1574
|
}
|
|
1271
1575
|
}, 1000);
|
|
1272
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Starts the Matterbridge controller.
|
|
1579
|
+
* @private
|
|
1580
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1581
|
+
*/
|
|
1273
1582
|
async startController() {
|
|
1583
|
+
/*
|
|
1584
|
+
if (!this.storageManager) {
|
|
1585
|
+
this.log.error('No storage manager initialized');
|
|
1586
|
+
await this.cleanup('No storage manager initialized');
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1590
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1591
|
+
if (!this.mattercontrollerContext) {
|
|
1592
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1593
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1598
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1599
|
+
this.log.info('Creating matter commissioning controller');
|
|
1600
|
+
this.commissioningController = new CommissioningController({
|
|
1601
|
+
autoConnect: false,
|
|
1602
|
+
});
|
|
1603
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1604
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1605
|
+
|
|
1606
|
+
this.log.info('Starting matter server');
|
|
1607
|
+
await this.matterServer.start();
|
|
1608
|
+
this.log.info('Matter server started');
|
|
1609
|
+
|
|
1610
|
+
if (hasParameter('pairingcode')) {
|
|
1611
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1612
|
+
const pairingCode = getParameter('pairingcode');
|
|
1613
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1614
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1615
|
+
|
|
1616
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1617
|
+
if (pairingCode !== undefined) {
|
|
1618
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1619
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1620
|
+
longDiscriminator = undefined;
|
|
1621
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1622
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1623
|
+
} else {
|
|
1624
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1625
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1626
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1627
|
+
}
|
|
1628
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1629
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1633
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1634
|
+
regulatoryCountryCode: 'XX',
|
|
1635
|
+
};
|
|
1636
|
+
const options = {
|
|
1637
|
+
commissioning: commissioningOptions,
|
|
1638
|
+
discovery: {
|
|
1639
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1640
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1641
|
+
},
|
|
1642
|
+
passcode: setupPin,
|
|
1643
|
+
} as NodeCommissioningOptions;
|
|
1644
|
+
this.log.info('Commissioning with options:', options);
|
|
1645
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1646
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1647
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1648
|
+
} // (hasParameter('pairingcode'))
|
|
1649
|
+
|
|
1650
|
+
if (hasParameter('unpairall')) {
|
|
1651
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1652
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1653
|
+
for (const nodeId of nodeIds) {
|
|
1654
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1655
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1656
|
+
}
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
if (hasParameter('discover')) {
|
|
1661
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1662
|
+
// console.log(discover);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1666
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1671
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1672
|
+
for (const nodeId of nodeIds) {
|
|
1673
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1674
|
+
|
|
1675
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1676
|
+
autoSubscribe: false,
|
|
1677
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1678
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1679
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1680
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1681
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1682
|
+
switch (info) {
|
|
1683
|
+
case NodeStateInformation.Connected:
|
|
1684
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1685
|
+
break;
|
|
1686
|
+
case NodeStateInformation.Disconnected:
|
|
1687
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1688
|
+
break;
|
|
1689
|
+
case NodeStateInformation.Reconnecting:
|
|
1690
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1691
|
+
break;
|
|
1692
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1693
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1694
|
+
break;
|
|
1695
|
+
case NodeStateInformation.StructureChanged:
|
|
1696
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1697
|
+
break;
|
|
1698
|
+
case NodeStateInformation.Decommissioned:
|
|
1699
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1700
|
+
break;
|
|
1701
|
+
default:
|
|
1702
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
},
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
node.logStructure();
|
|
1709
|
+
|
|
1710
|
+
// Get the interaction client
|
|
1711
|
+
this.log.info('Getting the interaction client');
|
|
1712
|
+
const interactionClient = await node.getInteractionClient();
|
|
1713
|
+
let cluster;
|
|
1714
|
+
let attributes;
|
|
1715
|
+
|
|
1716
|
+
// Log BasicInformationCluster
|
|
1717
|
+
cluster = BasicInformationCluster;
|
|
1718
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1719
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1720
|
+
});
|
|
1721
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1722
|
+
attributes.forEach((attribute) => {
|
|
1723
|
+
this.log.info(
|
|
1724
|
+
`- 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}`,
|
|
1725
|
+
);
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
// Log PowerSourceCluster
|
|
1729
|
+
cluster = PowerSourceCluster;
|
|
1730
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1731
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1732
|
+
});
|
|
1733
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1734
|
+
attributes.forEach((attribute) => {
|
|
1735
|
+
this.log.info(
|
|
1736
|
+
`- 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}`,
|
|
1737
|
+
);
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
// Log ThreadNetworkDiagnostics
|
|
1741
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1742
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1743
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1744
|
+
});
|
|
1745
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1746
|
+
attributes.forEach((attribute) => {
|
|
1747
|
+
this.log.info(
|
|
1748
|
+
`- 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}`,
|
|
1749
|
+
);
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
// Log SwitchCluster
|
|
1753
|
+
cluster = SwitchCluster;
|
|
1754
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1755
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1756
|
+
});
|
|
1757
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1758
|
+
attributes.forEach((attribute) => {
|
|
1759
|
+
this.log.info(
|
|
1760
|
+
`- 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}`,
|
|
1761
|
+
);
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1765
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1766
|
+
ignoreInitialTriggers: false,
|
|
1767
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1768
|
+
this.log.info(
|
|
1769
|
+
`***${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}`,
|
|
1770
|
+
),
|
|
1771
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1772
|
+
this.log.info(
|
|
1773
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1774
|
+
);
|
|
1775
|
+
},
|
|
1776
|
+
});
|
|
1777
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1778
|
+
}
|
|
1779
|
+
*/
|
|
1274
1780
|
}
|
|
1781
|
+
/** ***********************************************************************************************************************************/
|
|
1782
|
+
/** Matter.js methods */
|
|
1783
|
+
/** ***********************************************************************************************************************************/
|
|
1784
|
+
/**
|
|
1785
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1786
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1787
|
+
*/
|
|
1275
1788
|
async startMatterStorage() {
|
|
1789
|
+
// Setup Matter storage
|
|
1276
1790
|
this.log.info(`Starting matter node storage...`);
|
|
1277
1791
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1278
1792
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1280,13 +1794,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1280
1794
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1281
1795
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1282
1796
|
this.log.info('Matter node storage started');
|
|
1797
|
+
// Backup matter storage since it is created/opened correctly
|
|
1283
1798
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1284
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1802
|
+
*
|
|
1803
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1804
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1805
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1806
|
+
*/
|
|
1285
1807
|
async backupMatterStorage(storageName, backupName) {
|
|
1286
1808
|
this.log.info('Creating matter node storage backup...');
|
|
1287
1809
|
await copyDirectory(storageName, backupName);
|
|
1288
1810
|
this.log.info('Created matter node storage backup');
|
|
1289
1811
|
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Stops the matter storage.
|
|
1814
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1815
|
+
*/
|
|
1290
1816
|
async stopMatterStorage() {
|
|
1291
1817
|
this.log.info('Closing matter node storage...');
|
|
1292
1818
|
this.matterStorageManager?.close();
|
|
@@ -1295,6 +1821,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1295
1821
|
this.matterbridgeContext = undefined;
|
|
1296
1822
|
this.log.info('Matter node storage closed');
|
|
1297
1823
|
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Creates a server node storage context.
|
|
1826
|
+
*
|
|
1827
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1828
|
+
* @param {string} deviceName - The name of the device.
|
|
1829
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1830
|
+
* @param {number} vendorId - The vendor ID.
|
|
1831
|
+
* @param {string} vendorName - The vendor name.
|
|
1832
|
+
* @param {number} productId - The product ID.
|
|
1833
|
+
* @param {string} productName - The product name.
|
|
1834
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1835
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1836
|
+
*/
|
|
1298
1837
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1299
1838
|
if (!this.matterStorageService)
|
|
1300
1839
|
throw new Error('No storage service initialized');
|
|
@@ -1327,6 +1866,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1327
1866
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1328
1867
|
return storageContext;
|
|
1329
1868
|
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Creates a server node.
|
|
1871
|
+
*
|
|
1872
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1873
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1874
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1875
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1876
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1877
|
+
*/
|
|
1330
1878
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1331
1879
|
const storeId = await storageContext.get('storeId');
|
|
1332
1880
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1336,21 +1884,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1336
1884
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1337
1885
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1338
1886
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1887
|
+
/**
|
|
1888
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1889
|
+
*/
|
|
1339
1890
|
const serverNode = await ServerNode.create({
|
|
1891
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1340
1892
|
id: storeId,
|
|
1893
|
+
// Provide Network relevant configuration like the port
|
|
1894
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1341
1895
|
network: {
|
|
1342
1896
|
listeningAddressIpv4: this.ipv4address,
|
|
1343
1897
|
listeningAddressIpv6: this.ipv6address,
|
|
1344
1898
|
port,
|
|
1345
1899
|
},
|
|
1900
|
+
// Provide Commissioning relevant settings
|
|
1901
|
+
// Optional for development/testing purposes
|
|
1346
1902
|
commissioning: {
|
|
1347
1903
|
passcode,
|
|
1348
1904
|
discriminator,
|
|
1349
1905
|
},
|
|
1906
|
+
// Provide Node announcement settings
|
|
1907
|
+
// Optional: If Ommitted some development defaults are used
|
|
1350
1908
|
productDescription: {
|
|
1351
1909
|
name: await storageContext.get('deviceName'),
|
|
1352
1910
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1353
1911
|
},
|
|
1912
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1913
|
+
// Optional: If Omitted some development defaults are used
|
|
1354
1914
|
basicInformation: {
|
|
1355
1915
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1356
1916
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1367,12 +1927,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1367
1927
|
},
|
|
1368
1928
|
});
|
|
1369
1929
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1930
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1370
1931
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1371
1932
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1372
1933
|
if (this.bridgeMode === 'bridge') {
|
|
1373
1934
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1374
1935
|
if (resetSessions)
|
|
1375
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1936
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1376
1937
|
this.matterbridgePaired = true;
|
|
1377
1938
|
}
|
|
1378
1939
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1380,13 +1941,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1380
1941
|
if (plugin) {
|
|
1381
1942
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1382
1943
|
if (resetSessions)
|
|
1383
|
-
plugin.sessionInformations = undefined;
|
|
1944
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1384
1945
|
plugin.paired = true;
|
|
1385
1946
|
}
|
|
1386
1947
|
}
|
|
1387
1948
|
};
|
|
1949
|
+
/**
|
|
1950
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1951
|
+
* This means: It is added to the first fabric.
|
|
1952
|
+
*/
|
|
1388
1953
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1954
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1389
1955
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1956
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1390
1957
|
serverNode.lifecycle.online.on(async () => {
|
|
1391
1958
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1392
1959
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1432,6 +1999,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1432
1999
|
}
|
|
1433
2000
|
this.frontend.wssSendRefreshRequired();
|
|
1434
2001
|
});
|
|
2002
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1435
2003
|
serverNode.lifecycle.offline.on(() => {
|
|
1436
2004
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1437
2005
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1453,6 +2021,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1453
2021
|
}
|
|
1454
2022
|
this.frontend.wssSendRefreshRequired();
|
|
1455
2023
|
});
|
|
2024
|
+
/**
|
|
2025
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2026
|
+
* information is needed.
|
|
2027
|
+
*/
|
|
1456
2028
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1457
2029
|
let action = '';
|
|
1458
2030
|
switch (fabricAction) {
|
|
@@ -1486,16 +2058,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1486
2058
|
}
|
|
1487
2059
|
}
|
|
1488
2060
|
};
|
|
2061
|
+
/**
|
|
2062
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2063
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2064
|
+
*/
|
|
1489
2065
|
serverNode.events.sessions.opened.on((session) => {
|
|
1490
2066
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1491
2067
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1492
2068
|
this.frontend.wssSendRefreshRequired();
|
|
1493
2069
|
});
|
|
2070
|
+
/**
|
|
2071
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2072
|
+
*/
|
|
1494
2073
|
serverNode.events.sessions.closed.on((session) => {
|
|
1495
2074
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1496
2075
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1497
2076
|
this.frontend.wssSendRefreshRequired();
|
|
1498
2077
|
});
|
|
2078
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1499
2079
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1500
2080
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1501
2081
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1504,38 +2084,57 @@ export class Matterbridge extends EventEmitter {
|
|
|
1504
2084
|
this.log.info(`Created server node for ${storeId}`);
|
|
1505
2085
|
return serverNode;
|
|
1506
2086
|
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Starts the specified server node.
|
|
2089
|
+
*
|
|
2090
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2091
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2092
|
+
*/
|
|
1507
2093
|
async startServerNode(matterServerNode) {
|
|
1508
2094
|
if (!matterServerNode)
|
|
1509
2095
|
return;
|
|
1510
2096
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1511
2097
|
await matterServerNode.start();
|
|
1512
2098
|
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Stops the specified server node.
|
|
2101
|
+
*
|
|
2102
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2103
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2104
|
+
*/
|
|
1513
2105
|
async stopServerNode(matterServerNode) {
|
|
1514
2106
|
if (!matterServerNode)
|
|
1515
2107
|
return;
|
|
1516
2108
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2109
|
+
// Helper function to add a timeout to a promise
|
|
1517
2110
|
const withTimeout = (promise, ms) => {
|
|
1518
2111
|
return new Promise((resolve, reject) => {
|
|
1519
2112
|
const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
|
|
1520
2113
|
promise
|
|
1521
2114
|
.then((result) => {
|
|
1522
|
-
clearTimeout(timer);
|
|
2115
|
+
clearTimeout(timer); // Prevent memory leak
|
|
1523
2116
|
resolve(result);
|
|
1524
2117
|
})
|
|
1525
2118
|
.catch((error) => {
|
|
1526
|
-
clearTimeout(timer);
|
|
2119
|
+
clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
|
|
1527
2120
|
reject(error);
|
|
1528
2121
|
});
|
|
1529
2122
|
});
|
|
1530
2123
|
};
|
|
1531
2124
|
try {
|
|
1532
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2125
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1533
2126
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1534
2127
|
}
|
|
1535
2128
|
catch (error) {
|
|
1536
2129
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1537
2130
|
}
|
|
1538
2131
|
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Advertises the specified server node if it is commissioned.
|
|
2134
|
+
*
|
|
2135
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2136
|
+
* @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
|
|
2137
|
+
*/
|
|
1539
2138
|
async advertiseServerNode(matterServerNode) {
|
|
1540
2139
|
if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
|
|
1541
2140
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1545,17 +2144,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1545
2144
|
}
|
|
1546
2145
|
return undefined;
|
|
1547
2146
|
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Creates an aggregator node with the specified storage context.
|
|
2149
|
+
*
|
|
2150
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2151
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2152
|
+
*/
|
|
1548
2153
|
async createAggregatorNode(storageContext) {
|
|
1549
2154
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1550
2155
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1551
2156
|
return aggregatorNode;
|
|
1552
2157
|
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2160
|
+
*
|
|
2161
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2162
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2163
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2164
|
+
*/
|
|
1553
2165
|
async addBridgedEndpoint(pluginName, device) {
|
|
2166
|
+
// Check if the plugin is registered
|
|
1554
2167
|
const plugin = this.plugins.get(pluginName);
|
|
1555
2168
|
if (!plugin) {
|
|
1556
2169
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1557
2170
|
return;
|
|
1558
2171
|
}
|
|
2172
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1559
2173
|
if (this.bridgeMode === 'bridge') {
|
|
1560
2174
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1561
2175
|
if (!this.aggregatorNode)
|
|
@@ -1578,16 +2192,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1578
2192
|
plugin.registeredDevices++;
|
|
1579
2193
|
if (plugin.addedDevices !== undefined)
|
|
1580
2194
|
plugin.addedDevices++;
|
|
2195
|
+
// Add the device to the DeviceManager
|
|
1581
2196
|
this.devices.set(device);
|
|
1582
2197
|
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}`);
|
|
1583
2198
|
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2201
|
+
*
|
|
2202
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2203
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2204
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2205
|
+
*/
|
|
1584
2206
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1585
2207
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2208
|
+
// Check if the plugin is registered
|
|
1586
2209
|
const plugin = this.plugins.get(pluginName);
|
|
1587
2210
|
if (!plugin) {
|
|
1588
2211
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1589
2212
|
return;
|
|
1590
2213
|
}
|
|
2214
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1591
2215
|
if (this.bridgeMode === 'bridge') {
|
|
1592
2216
|
if (!this.aggregatorNode) {
|
|
1593
2217
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1602,6 +2226,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1602
2226
|
}
|
|
1603
2227
|
else if (this.bridgeMode === 'childbridge') {
|
|
1604
2228
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2229
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1605
2230
|
}
|
|
1606
2231
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1607
2232
|
if (!plugin.aggregatorNode) {
|
|
@@ -1615,6 +2240,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
2240
|
plugin.registeredDevices--;
|
|
1616
2241
|
if (plugin.addedDevices !== undefined)
|
|
1617
2242
|
plugin.addedDevices--;
|
|
2243
|
+
// Close the server node TODO check if this is correct
|
|
1618
2244
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1619
2245
|
if (plugin.serverNode) {
|
|
1620
2246
|
await this.stopServerNode(plugin.serverNode);
|
|
@@ -1625,14 +2251,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1625
2251
|
}
|
|
1626
2252
|
}
|
|
1627
2253
|
}
|
|
2254
|
+
// Remove the device from the DeviceManager
|
|
1628
2255
|
this.devices.remove(device);
|
|
1629
2256
|
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2259
|
+
*
|
|
2260
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2261
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2262
|
+
*/
|
|
1630
2263
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1631
2264
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1632
2265
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1633
2266
|
await this.removeBridgedEndpoint(pluginName, device);
|
|
1634
2267
|
}
|
|
1635
2268
|
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2271
|
+
*
|
|
2272
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2273
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2274
|
+
*/
|
|
1636
2275
|
sanitizeFabricInformations(fabricInfo) {
|
|
1637
2276
|
return fabricInfo.map((info) => {
|
|
1638
2277
|
return {
|
|
@@ -1646,6 +2285,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1646
2285
|
};
|
|
1647
2286
|
});
|
|
1648
2287
|
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2290
|
+
*
|
|
2291
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2292
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2293
|
+
*/
|
|
1649
2294
|
sanitizeSessionInformation(sessionInfo) {
|
|
1650
2295
|
return sessionInfo
|
|
1651
2296
|
.filter((session) => session.isPeerActive)
|
|
@@ -1673,11 +2318,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1673
2318
|
};
|
|
1674
2319
|
});
|
|
1675
2320
|
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Sets the reachability of a matter server node and trigger ReachableChanged event.
|
|
2323
|
+
*
|
|
2324
|
+
* @param {ServerNode<ServerNode.RootEndpoint>} serverNode - The commissioning server to set the reachability for.
|
|
2325
|
+
* @param {boolean} reachable - The new reachability status.
|
|
2326
|
+
*/
|
|
2327
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1676
2328
|
setServerNodeReachability(serverNode, reachable) {
|
|
2329
|
+
/*
|
|
2330
|
+
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2331
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2332
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2333
|
+
*/
|
|
1677
2334
|
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2337
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The matter aggregator to set the reachability for.
|
|
2338
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2339
|
+
*/
|
|
2340
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1678
2341
|
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2342
|
+
/*
|
|
2343
|
+
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2344
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2345
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2346
|
+
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2347
|
+
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2348
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2349
|
+
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2350
|
+
});
|
|
2351
|
+
*/
|
|
1679
2352
|
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Sets the reachability of a device and trigger.
|
|
2355
|
+
*
|
|
2356
|
+
* @param {MatterbridgeEndpoint} device - The device to set the reachability for.
|
|
2357
|
+
* @param {boolean} reachable - The new reachability status of the device.
|
|
2358
|
+
*/
|
|
2359
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1680
2360
|
setDeviceReachability(device, reachable) {
|
|
2361
|
+
/*
|
|
2362
|
+
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2363
|
+
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined) basicInformationCluster.setReachableAttribute(reachable);
|
|
2364
|
+
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent) basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2365
|
+
*/
|
|
1681
2366
|
}
|
|
1682
2367
|
getVendorIdName = (vendorId) => {
|
|
1683
2368
|
if (!vendorId)
|
|
@@ -1720,13 +2405,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1720
2405
|
}
|
|
1721
2406
|
return vendorName;
|
|
1722
2407
|
};
|
|
2408
|
+
/**
|
|
2409
|
+
* Spawns a child process with the given command and arguments.
|
|
2410
|
+
* @param {string} command - The command to execute.
|
|
2411
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2412
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2413
|
+
*/
|
|
1723
2414
|
async spawnCommand(command, args = []) {
|
|
2415
|
+
/*
|
|
2416
|
+
npm > npm.cmd on windows
|
|
2417
|
+
cmd.exe ['dir'] on windows
|
|
2418
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2419
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2420
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2423
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2424
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2425
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2426
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2427
|
+
*/
|
|
1724
2428
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1725
2429
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2430
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1726
2431
|
const argstring = 'npm ' + args.join(' ');
|
|
1727
2432
|
args.splice(0, args.length, '/c', argstring);
|
|
1728
2433
|
command = 'cmd.exe';
|
|
1729
2434
|
}
|
|
2435
|
+
// Decide when using sudo on linux
|
|
2436
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2437
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1730
2438
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1731
2439
|
args.unshift(command);
|
|
1732
2440
|
command = 'sudo';
|
|
@@ -1785,3 +2493,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1785
2493
|
});
|
|
1786
2494
|
}
|
|
1787
2495
|
}
|
|
2496
|
+
//# sourceMappingURL=matterbridge.js.map
|