matterbridge 2.1.5-dev.8 → 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 +268 -28
- 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 +748 -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 +230 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/export.d.ts +3 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/utils.d.ts +231 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +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,46 +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;
|
|
252
343
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
344
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
253
345
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
254
346
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
255
347
|
this.matterbridgeInformation.fileLogger = true;
|
|
256
348
|
}
|
|
257
349
|
this.log.notice('Matterbridge is starting...');
|
|
258
350
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
351
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
259
352
|
if (hasParameter('matterlogger')) {
|
|
260
353
|
const level = getParameter('matterlogger');
|
|
261
354
|
if (level === 'debug') {
|
|
@@ -287,6 +380,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
287
380
|
Logger.format = MatterLogFormat.ANSI;
|
|
288
381
|
Logger.setLogger('default', this.createMatterLogger());
|
|
289
382
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
383
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
290
384
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
291
385
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
292
386
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -295,6 +389,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
295
389
|
});
|
|
296
390
|
}
|
|
297
391
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
392
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
298
393
|
if (hasParameter('mdnsinterface')) {
|
|
299
394
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
300
395
|
}
|
|
@@ -303,6 +398,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
303
398
|
if (this.mdnsInterface === '')
|
|
304
399
|
this.mdnsInterface = undefined;
|
|
305
400
|
}
|
|
401
|
+
// Validate mdnsInterface
|
|
306
402
|
if (this.mdnsInterface) {
|
|
307
403
|
const networkInterfaces = os.networkInterfaces();
|
|
308
404
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -316,6 +412,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
316
412
|
}
|
|
317
413
|
if (this.mdnsInterface)
|
|
318
414
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
415
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
319
416
|
if (hasParameter('ipv4address')) {
|
|
320
417
|
this.ipv4address = getParameter('ipv4address');
|
|
321
418
|
}
|
|
@@ -324,6 +421,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
324
421
|
if (this.ipv4address === '')
|
|
325
422
|
this.ipv4address = undefined;
|
|
326
423
|
}
|
|
424
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
327
425
|
if (hasParameter('ipv6address')) {
|
|
328
426
|
this.ipv6address = getParameter('ipv6address');
|
|
329
427
|
}
|
|
@@ -332,14 +430,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
332
430
|
if (this.ipv6address === '')
|
|
333
431
|
this.ipv6address = undefined;
|
|
334
432
|
}
|
|
433
|
+
// Initialize PluginManager
|
|
335
434
|
this.plugins = new PluginManager(this);
|
|
336
435
|
await this.plugins.loadFromStorage();
|
|
337
436
|
this.plugins.logLevel = this.log.logLevel;
|
|
437
|
+
// Initialize DeviceManager
|
|
338
438
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
339
439
|
this.devices.logLevel = this.log.logLevel;
|
|
440
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
340
441
|
for (const plugin of this.plugins) {
|
|
341
442
|
const packageJson = await this.plugins.parse(plugin);
|
|
342
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
|
|
343
446
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
344
447
|
try {
|
|
345
448
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -361,6 +464,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
361
464
|
await plugin.nodeContext.set('description', plugin.description);
|
|
362
465
|
await plugin.nodeContext.set('author', plugin.author);
|
|
363
466
|
}
|
|
467
|
+
// Log system info and create .matterbridge directory
|
|
364
468
|
await this.logNodeAndSystemInfo();
|
|
365
469
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
366
470
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -368,6 +472,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
368
472
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
369
473
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
370
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
|
|
371
476
|
const minNodeVersion = 18;
|
|
372
477
|
const nodeVersion = process.versions.node;
|
|
373
478
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -375,9 +480,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
375
480
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
376
481
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
377
482
|
}
|
|
483
|
+
// Parse command line
|
|
378
484
|
await this.parseCommandLine();
|
|
379
485
|
this.initialized = true;
|
|
380
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
|
+
*/
|
|
381
492
|
async parseCommandLine() {
|
|
382
493
|
if (hasParameter('help')) {
|
|
383
494
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -487,6 +598,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
487
598
|
await this.shutdownProcessAndFactoryReset();
|
|
488
599
|
return;
|
|
489
600
|
}
|
|
601
|
+
// Start the matter storage and create the matterbridge context
|
|
490
602
|
try {
|
|
491
603
|
await this.startMatterStorage();
|
|
492
604
|
}
|
|
@@ -494,10 +606,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
494
606
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
495
607
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
496
608
|
}
|
|
609
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
497
610
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
498
611
|
await this.shutdownProcessAndReset();
|
|
499
612
|
return;
|
|
500
613
|
}
|
|
614
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
501
615
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
502
616
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
503
617
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -519,13 +633,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
519
633
|
this.emit('shutdown');
|
|
520
634
|
return;
|
|
521
635
|
}
|
|
636
|
+
// Initialize frontend
|
|
522
637
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
523
638
|
await this.frontend.start(getIntParameter('frontend'));
|
|
524
639
|
this.frontend.logLevel = this.log.logLevel;
|
|
640
|
+
// Check now the latest versions of matterbridge and plugins
|
|
525
641
|
this.getMatterbridgeLatestVersion();
|
|
526
642
|
for (const plugin of this.plugins) {
|
|
527
643
|
this.getPluginLatestVersion(plugin);
|
|
528
644
|
}
|
|
645
|
+
// Check each 60 minutes the latest versions
|
|
529
646
|
this.checkUpdateInterval = setInterval(() => {
|
|
530
647
|
this.getMatterbridgeLatestVersion();
|
|
531
648
|
for (const plugin of this.plugins) {
|
|
@@ -533,20 +650,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
533
650
|
}
|
|
534
651
|
this.frontend.wssSendRefreshRequired();
|
|
535
652
|
}, 60 * 60 * 1000);
|
|
653
|
+
// Start the matterbridge in mode test
|
|
536
654
|
if (hasParameter('test')) {
|
|
537
655
|
this.bridgeMode = 'bridge';
|
|
538
656
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
539
657
|
return;
|
|
540
658
|
}
|
|
659
|
+
// Start the matterbridge in mode controller
|
|
541
660
|
if (hasParameter('controller')) {
|
|
542
661
|
this.bridgeMode = 'controller';
|
|
543
662
|
await this.startController();
|
|
544
663
|
return;
|
|
545
664
|
}
|
|
665
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
546
666
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
547
667
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
548
668
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
549
669
|
}
|
|
670
|
+
// Start matterbridge in bridge mode
|
|
550
671
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
551
672
|
this.bridgeMode = 'bridge';
|
|
552
673
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -554,6 +675,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
554
675
|
await this.startBridge();
|
|
555
676
|
return;
|
|
556
677
|
}
|
|
678
|
+
// Start matterbridge in childbridge mode
|
|
557
679
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
558
680
|
this.bridgeMode = 'childbridge';
|
|
559
681
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -562,16 +684,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
562
684
|
return;
|
|
563
685
|
}
|
|
564
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
|
+
*/
|
|
565
695
|
async startPlugins() {
|
|
696
|
+
// Check, load and start the plugins
|
|
566
697
|
for (const plugin of this.plugins) {
|
|
567
698
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
568
699
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
700
|
+
// Check if the plugin is available
|
|
569
701
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
570
702
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
571
703
|
plugin.enabled = false;
|
|
572
704
|
plugin.error = true;
|
|
573
705
|
continue;
|
|
574
706
|
}
|
|
707
|
+
// Check if the plugin has a new version
|
|
708
|
+
// this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
575
709
|
if (!plugin.enabled) {
|
|
576
710
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
577
711
|
continue;
|
|
@@ -585,20 +719,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
585
719
|
plugin.addedDevices = undefined;
|
|
586
720
|
plugin.qrPairingCode = undefined;
|
|
587
721
|
plugin.manualPairingCode = undefined;
|
|
588
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
722
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
589
723
|
}
|
|
590
724
|
this.frontend.wssSendRefreshRequired();
|
|
591
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
|
+
*/
|
|
592
730
|
registerProcessHandlers() {
|
|
593
731
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
594
732
|
process.removeAllListeners('uncaughtException');
|
|
595
733
|
process.removeAllListeners('unhandledRejection');
|
|
596
734
|
this.exceptionHandler = async (error) => {
|
|
597
735
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
736
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
598
737
|
};
|
|
599
738
|
process.on('uncaughtException', this.exceptionHandler);
|
|
600
739
|
this.rejectionHandler = async (reason, promise) => {
|
|
601
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...');
|
|
602
742
|
};
|
|
603
743
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
604
744
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -611,6 +751,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
611
751
|
};
|
|
612
752
|
process.on('SIGTERM', this.sigtermHandler);
|
|
613
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
756
|
+
*/
|
|
614
757
|
deregisterProcesslHandlers() {
|
|
615
758
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
616
759
|
if (this.exceptionHandler)
|
|
@@ -627,12 +770,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
627
770
|
process.off('SIGTERM', this.sigtermHandler);
|
|
628
771
|
this.sigtermHandler = undefined;
|
|
629
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* Logs the node and system information.
|
|
775
|
+
*/
|
|
630
776
|
async logNodeAndSystemInfo() {
|
|
777
|
+
// IP address information
|
|
631
778
|
const networkInterfaces = os.networkInterfaces();
|
|
632
779
|
this.systemInformation.interfaceName = '';
|
|
633
780
|
this.systemInformation.ipv4Address = '';
|
|
634
781
|
this.systemInformation.ipv6Address = '';
|
|
635
782
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
783
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
636
784
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
637
785
|
continue;
|
|
638
786
|
if (!interfaceDetails) {
|
|
@@ -658,19 +806,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
658
806
|
break;
|
|
659
807
|
}
|
|
660
808
|
}
|
|
809
|
+
// Node information
|
|
661
810
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
662
811
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
663
812
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
664
813
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
814
|
+
// Host system information
|
|
665
815
|
this.systemInformation.hostname = os.hostname();
|
|
666
816
|
this.systemInformation.user = os.userInfo().username;
|
|
667
|
-
this.systemInformation.osType = os.type();
|
|
668
|
-
this.systemInformation.osRelease = os.release();
|
|
669
|
-
this.systemInformation.osPlatform = os.platform();
|
|
670
|
-
this.systemInformation.osArch = os.arch();
|
|
671
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
672
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
673
|
-
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
|
|
674
825
|
this.log.debug('Host System Information:');
|
|
675
826
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
676
827
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -686,15 +837,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
686
837
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
687
838
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
688
839
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
840
|
+
// Home directory
|
|
689
841
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
690
842
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
691
843
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
844
|
+
// Package root directory
|
|
692
845
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
693
846
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
694
847
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
695
848
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
849
|
+
// Global node_modules directory
|
|
696
850
|
if (this.nodeContext)
|
|
697
851
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
852
|
+
// First run of Matterbridge so the node storage is empty
|
|
698
853
|
if (this.globalModulesDirectory === '') {
|
|
699
854
|
try {
|
|
700
855
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -708,6 +863,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
708
863
|
}
|
|
709
864
|
else
|
|
710
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
|
|
711
880
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
712
881
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
713
882
|
try {
|
|
@@ -731,6 +900,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
731
900
|
}
|
|
732
901
|
}
|
|
733
902
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
903
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
734
904
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
735
905
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
736
906
|
try {
|
|
@@ -754,18 +924,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
754
924
|
}
|
|
755
925
|
}
|
|
756
926
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
927
|
+
// Matterbridge version
|
|
757
928
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
758
929
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
759
930
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
760
931
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
932
|
+
// Matterbridge latest version
|
|
761
933
|
if (this.nodeContext)
|
|
762
934
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
763
935
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
936
|
+
// this.getMatterbridgeLatestVersion();
|
|
937
|
+
// Current working directory
|
|
764
938
|
const currentDir = process.cwd();
|
|
765
939
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
940
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
766
941
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
767
942
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
768
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
|
+
*/
|
|
769
949
|
async getLatestVersion(packageName) {
|
|
770
950
|
return new Promise((resolve, reject) => {
|
|
771
951
|
this.execRunningCount++;
|
|
@@ -780,6 +960,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
780
960
|
});
|
|
781
961
|
});
|
|
782
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
|
+
*/
|
|
783
967
|
async getGlobalNodeModules() {
|
|
784
968
|
return new Promise((resolve, reject) => {
|
|
785
969
|
this.execRunningCount++;
|
|
@@ -794,6 +978,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
794
978
|
});
|
|
795
979
|
});
|
|
796
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
|
+
*/
|
|
797
988
|
async getMatterbridgeLatestVersion() {
|
|
798
989
|
getNpmPackageVersion('matterbridge')
|
|
799
990
|
.then(async (version) => {
|
|
@@ -812,6 +1003,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
812
1003
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
813
1004
|
});
|
|
814
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
|
+
*/
|
|
815
1014
|
async getPluginLatestVersion(plugin) {
|
|
816
1015
|
getNpmPackageVersion(plugin.name)
|
|
817
1016
|
.then((version) => {
|
|
@@ -825,38 +1024,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
825
1024
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
826
1025
|
});
|
|
827
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
|
+
*/
|
|
828
1032
|
createMatterLogger() {
|
|
829
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1033
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
830
1034
|
return (_level, formattedLog) => {
|
|
831
1035
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
832
1036
|
const message = formattedLog.slice(65);
|
|
833
1037
|
matterLogger.logName = logger;
|
|
834
1038
|
switch (_level) {
|
|
835
1039
|
case MatterLogLevel.DEBUG:
|
|
836
|
-
matterLogger.log("debug"
|
|
1040
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
837
1041
|
break;
|
|
838
1042
|
case MatterLogLevel.INFO:
|
|
839
|
-
matterLogger.log("info"
|
|
1043
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
840
1044
|
break;
|
|
841
1045
|
case MatterLogLevel.NOTICE:
|
|
842
|
-
matterLogger.log("notice"
|
|
1046
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
843
1047
|
break;
|
|
844
1048
|
case MatterLogLevel.WARN:
|
|
845
|
-
matterLogger.log("warn"
|
|
1049
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
846
1050
|
break;
|
|
847
1051
|
case MatterLogLevel.ERROR:
|
|
848
|
-
matterLogger.log("error"
|
|
1052
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
849
1053
|
break;
|
|
850
1054
|
case MatterLogLevel.FATAL:
|
|
851
|
-
matterLogger.log("fatal"
|
|
1055
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
852
1056
|
break;
|
|
853
1057
|
default:
|
|
854
|
-
matterLogger.log("debug"
|
|
1058
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
855
1059
|
break;
|
|
856
1060
|
}
|
|
857
1061
|
};
|
|
858
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
|
+
*/
|
|
859
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
|
|
860
1072
|
let fileSize = 0;
|
|
861
1073
|
if (unlink) {
|
|
862
1074
|
try {
|
|
@@ -905,12 +1117,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
905
1117
|
}
|
|
906
1118
|
};
|
|
907
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1122
|
+
*/
|
|
908
1123
|
async restartProcess() {
|
|
909
1124
|
await this.cleanup('restarting...', true);
|
|
910
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Shut down the process by exiting the current process.
|
|
1128
|
+
*/
|
|
911
1129
|
async shutdownProcess() {
|
|
912
1130
|
await this.cleanup('shutting down...', false);
|
|
913
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Update matterbridge and and shut down the process.
|
|
1134
|
+
*/
|
|
914
1135
|
async updateProcess() {
|
|
915
1136
|
this.log.info('Updating matterbridge...');
|
|
916
1137
|
try {
|
|
@@ -923,46 +1144,66 @@ export class Matterbridge extends EventEmitter {
|
|
|
923
1144
|
this.frontend.wssSendRestartRequired();
|
|
924
1145
|
await this.cleanup('updating...', false);
|
|
925
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Unregister all devices and shut down the process.
|
|
1149
|
+
*/
|
|
926
1150
|
async unregisterAndShutdownProcess() {
|
|
927
1151
|
this.log.info('Unregistering all devices and shutting down...');
|
|
928
1152
|
for (const plugin of this.plugins) {
|
|
929
1153
|
await this.removeAllBridgedEndpoints(plugin.name);
|
|
930
1154
|
}
|
|
931
1155
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
932
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1156
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
933
1157
|
this.log.debug('Cleaning up and shutting down...');
|
|
934
1158
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
935
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Reset commissioning and shut down the process.
|
|
1162
|
+
*/
|
|
936
1163
|
async shutdownProcessAndReset() {
|
|
937
1164
|
await this.cleanup('shutting down with reset...', false);
|
|
938
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Factory reset and shut down the process.
|
|
1168
|
+
*/
|
|
939
1169
|
async shutdownProcessAndFactoryReset() {
|
|
940
1170
|
await this.cleanup('shutting down with factory reset...', false);
|
|
941
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
|
+
*/
|
|
942
1178
|
async cleanup(message, restart = false) {
|
|
943
1179
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
944
1180
|
this.hasCleanupStarted = true;
|
|
945
1181
|
this.log.info(message);
|
|
1182
|
+
// Clear the start matter interval
|
|
946
1183
|
if (this.startMatterInterval) {
|
|
947
1184
|
clearInterval(this.startMatterInterval);
|
|
948
1185
|
this.startMatterInterval = undefined;
|
|
949
1186
|
this.log.debug('Start matter interval cleared');
|
|
950
1187
|
}
|
|
1188
|
+
// Clear the check update interval
|
|
951
1189
|
if (this.checkUpdateInterval) {
|
|
952
1190
|
clearInterval(this.checkUpdateInterval);
|
|
953
1191
|
this.checkUpdateInterval = undefined;
|
|
954
1192
|
this.log.debug('Check update interval cleared');
|
|
955
1193
|
}
|
|
1194
|
+
// Clear the configure timeout
|
|
956
1195
|
if (this.configureTimeout) {
|
|
957
1196
|
clearTimeout(this.configureTimeout);
|
|
958
1197
|
this.configureTimeout = undefined;
|
|
959
1198
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
960
1199
|
}
|
|
1200
|
+
// Clear the reachability timeout
|
|
961
1201
|
if (this.reachabilityTimeout) {
|
|
962
1202
|
clearTimeout(this.reachabilityTimeout);
|
|
963
1203
|
this.reachabilityTimeout = undefined;
|
|
964
1204
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
965
1205
|
}
|
|
1206
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
966
1207
|
for (const plugin of this.plugins) {
|
|
967
1208
|
if (!plugin.enabled || plugin.error)
|
|
968
1209
|
continue;
|
|
@@ -973,9 +1214,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
973
1214
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
974
1215
|
}
|
|
975
1216
|
}
|
|
1217
|
+
// Stopping matter server nodes
|
|
976
1218
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
977
1219
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
978
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1220
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
979
1221
|
if (this.bridgeMode === 'bridge') {
|
|
980
1222
|
if (this.serverNode) {
|
|
981
1223
|
await this.stopServerNode(this.serverNode);
|
|
@@ -991,6 +1233,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
991
1233
|
}
|
|
992
1234
|
}
|
|
993
1235
|
this.log.notice('Stopped matter server nodes');
|
|
1236
|
+
// Matter commisioning reset
|
|
994
1237
|
if (message === 'shutting down with reset...') {
|
|
995
1238
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
996
1239
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1000,17 +1243,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1000
1243
|
await this.matterbridgeContext?.clearAll();
|
|
1001
1244
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1002
1245
|
}
|
|
1246
|
+
// Stop matter storage
|
|
1003
1247
|
await this.stopMatterStorage();
|
|
1248
|
+
// Stop the frontend
|
|
1004
1249
|
await this.frontend.stop();
|
|
1250
|
+
// Remove the matterfilelogger
|
|
1005
1251
|
try {
|
|
1006
1252
|
Logger.removeLogger('matterfilelogger');
|
|
1253
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1007
1254
|
}
|
|
1008
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}`);
|
|
1009
1257
|
}
|
|
1258
|
+
// Serialize registeredDevices
|
|
1010
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)
|
|
1011
1273
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1012
1274
|
await this.nodeContext.close();
|
|
1013
1275
|
this.nodeContext = undefined;
|
|
1276
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1014
1277
|
for (const plugin of this.plugins) {
|
|
1015
1278
|
if (plugin.nodeContext) {
|
|
1016
1279
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1027,8 +1290,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1027
1290
|
}
|
|
1028
1291
|
this.plugins.clear();
|
|
1029
1292
|
this.devices.clear();
|
|
1293
|
+
// Factory reset
|
|
1030
1294
|
if (message === 'shutting down with factory reset...') {
|
|
1031
1295
|
try {
|
|
1296
|
+
// Delete old matter storage file and backup
|
|
1032
1297
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1033
1298
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1034
1299
|
await fs.unlink(file);
|
|
@@ -1042,6 +1307,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1042
1307
|
}
|
|
1043
1308
|
}
|
|
1044
1309
|
try {
|
|
1310
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1045
1311
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1046
1312
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1047
1313
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1055,6 +1321,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1055
1321
|
}
|
|
1056
1322
|
}
|
|
1057
1323
|
try {
|
|
1324
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1058
1325
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1059
1326
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1060
1327
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1069,12 +1336,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1069
1336
|
}
|
|
1070
1337
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1071
1338
|
}
|
|
1339
|
+
// Deregisters the process handlers
|
|
1072
1340
|
this.deregisterProcesslHandlers();
|
|
1073
1341
|
if (restart) {
|
|
1074
1342
|
if (message === 'updating...') {
|
|
1075
1343
|
this.log.info('Cleanup completed. Updating...');
|
|
1076
1344
|
Matterbridge.instance = undefined;
|
|
1077
|
-
this.emit('update');
|
|
1345
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1078
1346
|
}
|
|
1079
1347
|
else if (message === 'restarting...') {
|
|
1080
1348
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1094,6 +1362,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1094
1362
|
this.log.debug('Cleanup already started...');
|
|
1095
1363
|
}
|
|
1096
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
|
+
*/
|
|
1097
1373
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1098
1374
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1099
1375
|
plugin.locked = true;
|
|
@@ -1105,6 +1381,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1105
1381
|
await this.startServerNode(plugin.serverNode);
|
|
1106
1382
|
}
|
|
1107
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
|
+
*/
|
|
1108
1391
|
async createDynamicPlugin(plugin, start = false) {
|
|
1109
1392
|
if (!plugin.locked) {
|
|
1110
1393
|
plugin.locked = true;
|
|
@@ -1116,7 +1399,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1116
1399
|
await this.startServerNode(plugin.serverNode);
|
|
1117
1400
|
}
|
|
1118
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
|
+
*/
|
|
1119
1407
|
async startBridge() {
|
|
1408
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1120
1409
|
if (!this.matterStorageManager)
|
|
1121
1410
|
throw new Error('No storage manager initialized');
|
|
1122
1411
|
if (!this.matterbridgeContext)
|
|
@@ -1153,7 +1442,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1153
1442
|
clearInterval(this.startMatterInterval);
|
|
1154
1443
|
this.startMatterInterval = undefined;
|
|
1155
1444
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1445
|
+
// Start the Matter server node
|
|
1156
1446
|
this.startServerNode(this.serverNode);
|
|
1447
|
+
// Configure the plugins
|
|
1157
1448
|
this.configureTimeout = setTimeout(async () => {
|
|
1158
1449
|
for (const plugin of this.plugins) {
|
|
1159
1450
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1168,6 +1459,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1168
1459
|
}
|
|
1169
1460
|
this.frontend.wssSendRefreshRequired();
|
|
1170
1461
|
}, 30 * 1000);
|
|
1462
|
+
// Setting reachability to true
|
|
1171
1463
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1172
1464
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1173
1465
|
if (this.serverNode)
|
|
@@ -1178,7 +1470,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1178
1470
|
}, 60 * 1000);
|
|
1179
1471
|
}, 1000);
|
|
1180
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
|
+
*/
|
|
1181
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
|
|
1182
1481
|
if (!this.matterStorageManager)
|
|
1183
1482
|
throw new Error('No storage manager initialized');
|
|
1184
1483
|
for (const plugin of this.plugins) {
|
|
@@ -1225,12 +1524,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1225
1524
|
clearInterval(this.startMatterInterval);
|
|
1226
1525
|
this.startMatterInterval = undefined;
|
|
1227
1526
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1527
|
+
// Configure the plugins
|
|
1228
1528
|
this.configureTimeout = setTimeout(async () => {
|
|
1229
1529
|
for (const plugin of this.plugins) {
|
|
1230
1530
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1231
1531
|
continue;
|
|
1232
1532
|
try {
|
|
1233
|
-
await this.plugins.configure(plugin);
|
|
1533
|
+
await this.plugins.configure(plugin); // TODO No await do it in parallel
|
|
1234
1534
|
}
|
|
1235
1535
|
catch (error) {
|
|
1236
1536
|
plugin.error = true;
|
|
@@ -1258,7 +1558,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1258
1558
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1259
1559
|
continue;
|
|
1260
1560
|
}
|
|
1561
|
+
// Start the Matter server node
|
|
1261
1562
|
this.startServerNode(plugin.serverNode);
|
|
1563
|
+
// Setting reachability to true
|
|
1262
1564
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1263
1565
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1264
1566
|
if (plugin.serverNode)
|
|
@@ -1272,9 +1574,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1272
1574
|
}
|
|
1273
1575
|
}, 1000);
|
|
1274
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Starts the Matterbridge controller.
|
|
1579
|
+
* @private
|
|
1580
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1581
|
+
*/
|
|
1275
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
|
+
*/
|
|
1276
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
|
+
*/
|
|
1277
1788
|
async startMatterStorage() {
|
|
1789
|
+
// Setup Matter storage
|
|
1278
1790
|
this.log.info(`Starting matter node storage...`);
|
|
1279
1791
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1280
1792
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1282,13 +1794,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1282
1794
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1283
1795
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1284
1796
|
this.log.info('Matter node storage started');
|
|
1797
|
+
// Backup matter storage since it is created/opened correctly
|
|
1285
1798
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1286
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
|
+
*/
|
|
1287
1807
|
async backupMatterStorage(storageName, backupName) {
|
|
1288
1808
|
this.log.info('Creating matter node storage backup...');
|
|
1289
1809
|
await copyDirectory(storageName, backupName);
|
|
1290
1810
|
this.log.info('Created matter node storage backup');
|
|
1291
1811
|
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Stops the matter storage.
|
|
1814
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1815
|
+
*/
|
|
1292
1816
|
async stopMatterStorage() {
|
|
1293
1817
|
this.log.info('Closing matter node storage...');
|
|
1294
1818
|
this.matterStorageManager?.close();
|
|
@@ -1297,6 +1821,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1297
1821
|
this.matterbridgeContext = undefined;
|
|
1298
1822
|
this.log.info('Matter node storage closed');
|
|
1299
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
|
+
*/
|
|
1300
1837
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1301
1838
|
if (!this.matterStorageService)
|
|
1302
1839
|
throw new Error('No storage service initialized');
|
|
@@ -1329,6 +1866,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1329
1866
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1330
1867
|
return storageContext;
|
|
1331
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
|
+
*/
|
|
1332
1878
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1333
1879
|
const storeId = await storageContext.get('storeId');
|
|
1334
1880
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1338,21 +1884,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1338
1884
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1339
1885
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1340
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
|
+
*/
|
|
1341
1890
|
const serverNode = await ServerNode.create({
|
|
1891
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1342
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
|
|
1343
1895
|
network: {
|
|
1344
1896
|
listeningAddressIpv4: this.ipv4address,
|
|
1345
1897
|
listeningAddressIpv6: this.ipv6address,
|
|
1346
1898
|
port,
|
|
1347
1899
|
},
|
|
1900
|
+
// Provide Commissioning relevant settings
|
|
1901
|
+
// Optional for development/testing purposes
|
|
1348
1902
|
commissioning: {
|
|
1349
1903
|
passcode,
|
|
1350
1904
|
discriminator,
|
|
1351
1905
|
},
|
|
1906
|
+
// Provide Node announcement settings
|
|
1907
|
+
// Optional: If Ommitted some development defaults are used
|
|
1352
1908
|
productDescription: {
|
|
1353
1909
|
name: await storageContext.get('deviceName'),
|
|
1354
1910
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1355
1911
|
},
|
|
1912
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1913
|
+
// Optional: If Omitted some development defaults are used
|
|
1356
1914
|
basicInformation: {
|
|
1357
1915
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1358
1916
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1369,12 +1927,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1369
1927
|
},
|
|
1370
1928
|
});
|
|
1371
1929
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1930
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1372
1931
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1373
1932
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1374
1933
|
if (this.bridgeMode === 'bridge') {
|
|
1375
1934
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1376
1935
|
if (resetSessions)
|
|
1377
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1936
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1378
1937
|
this.matterbridgePaired = true;
|
|
1379
1938
|
}
|
|
1380
1939
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1382,13 +1941,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1382
1941
|
if (plugin) {
|
|
1383
1942
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1384
1943
|
if (resetSessions)
|
|
1385
|
-
plugin.sessionInformations = undefined;
|
|
1944
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1386
1945
|
plugin.paired = true;
|
|
1387
1946
|
}
|
|
1388
1947
|
}
|
|
1389
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
|
+
*/
|
|
1390
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. */
|
|
1391
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. */
|
|
1392
1957
|
serverNode.lifecycle.online.on(async () => {
|
|
1393
1958
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1394
1959
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1434,6 +1999,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1434
1999
|
}
|
|
1435
2000
|
this.frontend.wssSendRefreshRequired();
|
|
1436
2001
|
});
|
|
2002
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1437
2003
|
serverNode.lifecycle.offline.on(() => {
|
|
1438
2004
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1439
2005
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1455,6 +2021,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1455
2021
|
}
|
|
1456
2022
|
this.frontend.wssSendRefreshRequired();
|
|
1457
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
|
+
*/
|
|
1458
2028
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1459
2029
|
let action = '';
|
|
1460
2030
|
switch (fabricAction) {
|
|
@@ -1488,16 +2058,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1488
2058
|
}
|
|
1489
2059
|
}
|
|
1490
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
|
+
*/
|
|
1491
2065
|
serverNode.events.sessions.opened.on((session) => {
|
|
1492
2066
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1493
2067
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1494
2068
|
this.frontend.wssSendRefreshRequired();
|
|
1495
2069
|
});
|
|
2070
|
+
/**
|
|
2071
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2072
|
+
*/
|
|
1496
2073
|
serverNode.events.sessions.closed.on((session) => {
|
|
1497
2074
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1498
2075
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1499
2076
|
this.frontend.wssSendRefreshRequired();
|
|
1500
2077
|
});
|
|
2078
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1501
2079
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1502
2080
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1503
2081
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1506,38 +2084,57 @@ export class Matterbridge extends EventEmitter {
|
|
|
1506
2084
|
this.log.info(`Created server node for ${storeId}`);
|
|
1507
2085
|
return serverNode;
|
|
1508
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
|
+
*/
|
|
1509
2093
|
async startServerNode(matterServerNode) {
|
|
1510
2094
|
if (!matterServerNode)
|
|
1511
2095
|
return;
|
|
1512
2096
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1513
2097
|
await matterServerNode.start();
|
|
1514
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
|
+
*/
|
|
1515
2105
|
async stopServerNode(matterServerNode) {
|
|
1516
2106
|
if (!matterServerNode)
|
|
1517
2107
|
return;
|
|
1518
2108
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2109
|
+
// Helper function to add a timeout to a promise
|
|
1519
2110
|
const withTimeout = (promise, ms) => {
|
|
1520
2111
|
return new Promise((resolve, reject) => {
|
|
1521
2112
|
const timer = setTimeout(() => reject(new Error('Operation timed out')), ms);
|
|
1522
2113
|
promise
|
|
1523
2114
|
.then((result) => {
|
|
1524
|
-
clearTimeout(timer);
|
|
2115
|
+
clearTimeout(timer); // Prevent memory leak
|
|
1525
2116
|
resolve(result);
|
|
1526
2117
|
})
|
|
1527
2118
|
.catch((error) => {
|
|
1528
|
-
clearTimeout(timer);
|
|
2119
|
+
clearTimeout(timer); // Ensure timeout does not fire if promise rejects first
|
|
1529
2120
|
reject(error);
|
|
1530
2121
|
});
|
|
1531
2122
|
});
|
|
1532
2123
|
};
|
|
1533
2124
|
try {
|
|
1534
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2125
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1535
2126
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1536
2127
|
}
|
|
1537
2128
|
catch (error) {
|
|
1538
2129
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1539
2130
|
}
|
|
1540
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
|
+
*/
|
|
1541
2138
|
async advertiseServerNode(matterServerNode) {
|
|
1542
2139
|
if (matterServerNode && matterServerNode.lifecycle.isCommissioned) {
|
|
1543
2140
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1547,17 +2144,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
1547
2144
|
}
|
|
1548
2145
|
return undefined;
|
|
1549
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
|
+
*/
|
|
1550
2153
|
async createAggregatorNode(storageContext) {
|
|
1551
2154
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1552
2155
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1553
2156
|
return aggregatorNode;
|
|
1554
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
|
+
*/
|
|
1555
2165
|
async addBridgedEndpoint(pluginName, device) {
|
|
2166
|
+
// Check if the plugin is registered
|
|
1556
2167
|
const plugin = this.plugins.get(pluginName);
|
|
1557
2168
|
if (!plugin) {
|
|
1558
2169
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1559
2170
|
return;
|
|
1560
2171
|
}
|
|
2172
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1561
2173
|
if (this.bridgeMode === 'bridge') {
|
|
1562
2174
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1563
2175
|
if (!this.aggregatorNode)
|
|
@@ -1580,16 +2192,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1580
2192
|
plugin.registeredDevices++;
|
|
1581
2193
|
if (plugin.addedDevices !== undefined)
|
|
1582
2194
|
plugin.addedDevices++;
|
|
2195
|
+
// Add the device to the DeviceManager
|
|
1583
2196
|
this.devices.set(device);
|
|
1584
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}`);
|
|
1585
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
|
+
*/
|
|
1586
2206
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1587
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
|
|
1588
2209
|
const plugin = this.plugins.get(pluginName);
|
|
1589
2210
|
if (!plugin) {
|
|
1590
2211
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1591
2212
|
return;
|
|
1592
2213
|
}
|
|
2214
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1593
2215
|
if (this.bridgeMode === 'bridge') {
|
|
1594
2216
|
if (!this.aggregatorNode) {
|
|
1595
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`);
|
|
@@ -1604,6 +2226,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1604
2226
|
}
|
|
1605
2227
|
else if (this.bridgeMode === 'childbridge') {
|
|
1606
2228
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2229
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1607
2230
|
}
|
|
1608
2231
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1609
2232
|
if (!plugin.aggregatorNode) {
|
|
@@ -1617,6 +2240,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1617
2240
|
plugin.registeredDevices--;
|
|
1618
2241
|
if (plugin.addedDevices !== undefined)
|
|
1619
2242
|
plugin.addedDevices--;
|
|
2243
|
+
// Close the server node TODO check if this is correct
|
|
1620
2244
|
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1621
2245
|
if (plugin.serverNode) {
|
|
1622
2246
|
await this.stopServerNode(plugin.serverNode);
|
|
@@ -1627,14 +2251,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1627
2251
|
}
|
|
1628
2252
|
}
|
|
1629
2253
|
}
|
|
2254
|
+
// Remove the device from the DeviceManager
|
|
1630
2255
|
this.devices.remove(device);
|
|
1631
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
|
+
*/
|
|
1632
2263
|
async removeAllBridgedEndpoints(pluginName) {
|
|
1633
2264
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1634
2265
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1635
2266
|
await this.removeBridgedEndpoint(pluginName, device);
|
|
1636
2267
|
}
|
|
1637
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
|
+
*/
|
|
1638
2275
|
sanitizeFabricInformations(fabricInfo) {
|
|
1639
2276
|
return fabricInfo.map((info) => {
|
|
1640
2277
|
return {
|
|
@@ -1648,6 +2285,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1648
2285
|
};
|
|
1649
2286
|
});
|
|
1650
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
|
+
*/
|
|
1651
2294
|
sanitizeSessionInformation(sessionInfo) {
|
|
1652
2295
|
return sessionInfo
|
|
1653
2296
|
.filter((session) => session.isPeerActive)
|
|
@@ -1675,11 +2318,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1675
2318
|
};
|
|
1676
2319
|
});
|
|
1677
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
|
|
1678
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
|
+
*/
|
|
1679
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
|
|
1680
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
|
+
*/
|
|
1681
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
|
|
1682
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
|
+
*/
|
|
1683
2366
|
}
|
|
1684
2367
|
getVendorIdName = (vendorId) => {
|
|
1685
2368
|
if (!vendorId)
|
|
@@ -1722,13 +2405,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1722
2405
|
}
|
|
1723
2406
|
return vendorName;
|
|
1724
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
|
+
*/
|
|
1725
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
|
+
*/
|
|
1726
2428
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1727
2429
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2430
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1728
2431
|
const argstring = 'npm ' + args.join(' ');
|
|
1729
2432
|
args.splice(0, args.length, '/c', argstring);
|
|
1730
2433
|
command = 'cmd.exe';
|
|
1731
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
|
|
1732
2438
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1733
2439
|
args.unshift(command);
|
|
1734
2440
|
command = 'sudo';
|
|
@@ -1787,3 +2493,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1787
2493
|
});
|
|
1788
2494
|
}
|
|
1789
2495
|
}
|
|
2496
|
+
//# sourceMappingURL=matterbridge.js.map
|