matterbridge 3.0.1 → 3.0.2-dev-20250509-ae61aa7
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 +26 -3
- package/README.md +2 -2
- package/dist/cli.js +2 -37
- package/dist/cluster/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -23
- package/dist/deviceManager.js +1 -94
- package/dist/frontend.js +37 -341
- package/dist/index.js +2 -28
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -2
- package/dist/matter/types.js +0 -2
- package/dist/matterbridge.js +107 -748
- package/dist/matterbridgeAccessoryPlatform.js +0 -34
- package/dist/matterbridgeBehaviors.js +109 -48
- package/dist/matterbridgeDeviceTypes.js +12 -431
- package/dist/matterbridgeDynamicPlatform.js +0 -34
- package/dist/matterbridgeEndpoint.js +16 -814
- package/dist/matterbridgeEndpointHelpers.js +44 -148
- package/dist/matterbridgePlatform.js +7 -225
- package/dist/matterbridgeTypes.js +0 -24
- package/dist/pluginManager.js +3 -264
- package/dist/roboticVacuumCleaner.js +87 -0
- package/dist/shelly.js +6 -146
- package/dist/storage/export.js +0 -1
- package/dist/update.js +1 -53
- package/dist/utils/colorUtils.js +2 -205
- package/dist/utils/{parameter.js → commandLine.js} +1 -54
- package/dist/utils/copyDirectory.js +1 -37
- package/dist/utils/createZip.js +2 -42
- package/dist/utils/deepCopy.js +8 -43
- package/dist/utils/deepEqual.js +7 -69
- package/dist/utils/export.js +2 -2
- package/dist/utils/hex.js +27 -0
- package/dist/utils/isvalid.js +3 -93
- package/dist/utils/network.js +7 -78
- package/dist/utils/wait.js +5 -48
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -2
- package/dist/cli.d.ts +0 -29
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cluster/export.d.ts +0 -2
- package/dist/cluster/export.d.ts.map +0 -1
- package/dist/cluster/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -27
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -114
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/frontend.d.ts +0 -240
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/index.d.ts +0 -35
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -433
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -40
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -1166
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -494
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -40
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -956
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -2706
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -294
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -187
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -273
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts +0 -92
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -32
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -61
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -32
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -38
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -31
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -53
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/export.d.ts +0 -10
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -95
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/network.d.ts +0 -69
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/parameter.d.ts +0 -58
- package/dist/utils/parameter.d.ts.map +0 -1
- package/dist/utils/parameter.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -43
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,36 +1,10 @@
|
|
|
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
|
|
24
1
|
import os from 'node:os';
|
|
25
2
|
import path from 'node:path';
|
|
26
3
|
import { promises as fs } from 'node:fs';
|
|
27
4
|
import EventEmitter from 'node:events';
|
|
28
5
|
import { inspect } from 'node:util';
|
|
29
|
-
// AnsiLogger module
|
|
30
6
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
31
|
-
// NodeStorage module
|
|
32
7
|
import { NodeStorageManager } from './storage/export.js';
|
|
33
|
-
// Matterbridge
|
|
34
8
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout, waiter, isValidString, parseVersionString, isValidNumber } from './utils/export.js';
|
|
35
9
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
36
10
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -38,19 +12,16 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
38
12
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
39
13
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
40
14
|
import { Frontend } from './frontend.js';
|
|
41
|
-
|
|
42
|
-
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, } from '@matter/main';
|
|
15
|
+
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Endpoint, } from '@matter/main';
|
|
43
16
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
17
|
+
import { OnOffPlugInUnitDevice } from '@matter/main/devices/on-off-plug-in-unit';
|
|
44
18
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
45
19
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
20
|
+
import { OnOffBaseServer } from '@matter/main/behaviors/on-off';
|
|
46
21
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
47
|
-
// Default colors
|
|
48
22
|
const plg = '\u001B[38;5;33m';
|
|
49
23
|
const dev = '\u001B[38;5;79m';
|
|
50
24
|
const typ = '\u001B[38;5;207m';
|
|
51
|
-
/**
|
|
52
|
-
* Represents the Matterbridge application.
|
|
53
|
-
*/
|
|
54
25
|
export class Matterbridge extends EventEmitter {
|
|
55
26
|
systemInformation = {
|
|
56
27
|
interfaceName: '',
|
|
@@ -97,7 +68,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
97
68
|
shellySysUpdate: false,
|
|
98
69
|
shellyMainUpdate: false,
|
|
99
70
|
profile: getParameter('profile'),
|
|
100
|
-
loggerLevel: "info"
|
|
71
|
+
loggerLevel: "info",
|
|
101
72
|
fileLogger: false,
|
|
102
73
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
103
74
|
matterFileLogger: false,
|
|
@@ -136,11 +107,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
136
107
|
plugins;
|
|
137
108
|
devices;
|
|
138
109
|
frontend = new Frontend(this);
|
|
139
|
-
// Matterbridge storage
|
|
140
110
|
nodeStorage;
|
|
141
111
|
nodeContext;
|
|
142
112
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
143
|
-
// Cleanup
|
|
144
113
|
hasCleanupStarted = false;
|
|
145
114
|
initialized = false;
|
|
146
115
|
execRunningCount = 0;
|
|
@@ -153,21 +122,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
153
122
|
sigtermHandler;
|
|
154
123
|
exceptionHandler;
|
|
155
124
|
rejectionHandler;
|
|
156
|
-
// Matter environment
|
|
157
125
|
environment = Environment.default;
|
|
158
|
-
// Matter storage
|
|
159
126
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
160
127
|
matterStorageService;
|
|
161
128
|
matterStorageManager;
|
|
162
129
|
matterbridgeContext;
|
|
163
130
|
controllerContext;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
131
|
+
mdnsInterface;
|
|
132
|
+
ipv4address;
|
|
133
|
+
ipv6address;
|
|
134
|
+
port;
|
|
135
|
+
passcode;
|
|
136
|
+
discriminator;
|
|
137
|
+
certification;
|
|
171
138
|
serverNode;
|
|
172
139
|
aggregatorNode;
|
|
173
140
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -175,50 +142,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
175
142
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
176
143
|
aggregatorProductName = getParameter('productName') ?? 'Matterbridge aggregator';
|
|
177
144
|
static instance;
|
|
178
|
-
// We load asyncronously so is private
|
|
179
145
|
constructor() {
|
|
180
146
|
super();
|
|
181
147
|
}
|
|
182
|
-
/**
|
|
183
|
-
* Emits an event of the specified type with the provided arguments.
|
|
184
|
-
*
|
|
185
|
-
* @template K - The type of the event.
|
|
186
|
-
* @param {K} eventName - The name of the event to emit.
|
|
187
|
-
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
188
|
-
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
189
|
-
*/
|
|
190
148
|
emit(eventName, ...args) {
|
|
191
149
|
return super.emit(eventName, ...args);
|
|
192
150
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Registers an event listener for the specified event type.
|
|
195
|
-
*
|
|
196
|
-
* @template K - The type of the event.
|
|
197
|
-
* @param {K} eventName - The name of the event to listen for.
|
|
198
|
-
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
199
|
-
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
200
|
-
*/
|
|
201
151
|
on(eventName, listener) {
|
|
202
152
|
return super.on(eventName, listener);
|
|
203
153
|
}
|
|
204
|
-
/**
|
|
205
|
-
* Retrieves the list of Matterbridge devices.
|
|
206
|
-
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
207
|
-
*/
|
|
208
154
|
getDevices() {
|
|
209
155
|
return this.devices.array();
|
|
210
156
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Retrieves the list of registered plugins.
|
|
213
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
214
|
-
*/
|
|
215
157
|
getPlugins() {
|
|
216
158
|
return this.plugins.array();
|
|
217
159
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Set the logger logLevel for the Matterbridge classes.
|
|
220
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
221
|
-
*/
|
|
222
160
|
async setLogLevel(logLevel) {
|
|
223
161
|
if (this.log)
|
|
224
162
|
this.log.logLevel = logLevel;
|
|
@@ -232,31 +170,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
232
170
|
for (const plugin of this.plugins) {
|
|
233
171
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
234
172
|
continue;
|
|
235
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug"
|
|
236
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug"
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
173
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
174
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
175
|
+
}
|
|
176
|
+
let callbackLogLevel = "notice";
|
|
177
|
+
if (this.matterbridgeInformation.loggerLevel === "info" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
178
|
+
callbackLogLevel = "info";
|
|
179
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
180
|
+
callbackLogLevel = "debug";
|
|
244
181
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
245
182
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
246
183
|
}
|
|
247
|
-
/** ***********************************************************************************************************************************/
|
|
248
|
-
/** loadInstance() and cleanup() methods */
|
|
249
|
-
/** ***********************************************************************************************************************************/
|
|
250
|
-
/**
|
|
251
|
-
* Loads an instance of the Matterbridge class.
|
|
252
|
-
* If an instance already exists, return that instance.
|
|
253
|
-
*
|
|
254
|
-
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
255
|
-
* @returns The loaded Matterbridge instance.
|
|
256
|
-
*/
|
|
257
184
|
static async loadInstance(initialize = false) {
|
|
258
185
|
if (!Matterbridge.instance) {
|
|
259
|
-
// eslint-disable-next-line no-console
|
|
260
186
|
if (hasParameter('debug'))
|
|
261
187
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
262
188
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -265,14 +191,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
265
191
|
}
|
|
266
192
|
return Matterbridge.instance;
|
|
267
193
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Call cleanup().
|
|
270
|
-
* @deprecated This method is deprecated and is only used for jest tests.
|
|
271
|
-
*
|
|
272
|
-
*/
|
|
273
194
|
async destroyInstance() {
|
|
274
195
|
this.log.info(`Destroy instance...`);
|
|
275
|
-
// Save server nodes to close
|
|
276
196
|
const servers = [];
|
|
277
197
|
if (this.bridgeMode === 'bridge') {
|
|
278
198
|
if (this.serverNode)
|
|
@@ -284,81 +204,55 @@ export class Matterbridge extends EventEmitter {
|
|
|
284
204
|
servers.push(plugin.serverNode);
|
|
285
205
|
}
|
|
286
206
|
}
|
|
287
|
-
// Cleanup
|
|
288
207
|
await this.cleanup('destroying instance...', false);
|
|
289
|
-
// Close servers mdns service
|
|
290
208
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
291
209
|
for (const server of servers) {
|
|
292
210
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
293
211
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
294
212
|
}
|
|
295
|
-
// Wait for the cleanup to finish
|
|
296
213
|
await new Promise((resolve) => {
|
|
297
214
|
setTimeout(resolve, 1000);
|
|
298
215
|
});
|
|
299
216
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Initializes the Matterbridge application.
|
|
302
|
-
*
|
|
303
|
-
* @remarks
|
|
304
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
305
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
306
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
307
|
-
*
|
|
308
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
309
|
-
*/
|
|
310
217
|
async initialize() {
|
|
311
|
-
// Set the restart mode
|
|
312
218
|
if (hasParameter('service'))
|
|
313
219
|
this.restartMode = 'service';
|
|
314
220
|
if (hasParameter('docker'))
|
|
315
221
|
this.restartMode = 'docker';
|
|
316
|
-
// Set the matterbridge directory
|
|
317
222
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
318
223
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
319
|
-
// Setup the matter environment
|
|
320
224
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
321
225
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
322
226
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
323
227
|
this.environment.vars.set('runtime.signals', false);
|
|
324
228
|
this.environment.vars.set('runtime.exitcode', false);
|
|
325
|
-
|
|
326
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
327
|
-
// Register process handlers
|
|
229
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
328
230
|
this.registerProcessHandlers();
|
|
329
|
-
// Initialize nodeStorage and nodeContext
|
|
330
231
|
try {
|
|
331
232
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
332
233
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
333
234
|
this.log.debug('Creating node storage context for matterbridge');
|
|
334
235
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
335
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
336
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
337
236
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
338
237
|
for (const key of keys) {
|
|
339
238
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
340
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
341
239
|
await this.nodeStorage?.storage.get(key);
|
|
342
240
|
}
|
|
343
241
|
const storages = await this.nodeStorage.getStorageNames();
|
|
344
242
|
for (const storage of storages) {
|
|
345
243
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
346
244
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
347
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
348
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
349
245
|
const keys = (await nodeContext?.storage.keys());
|
|
350
246
|
keys.forEach(async (key) => {
|
|
351
247
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
352
248
|
await nodeContext?.get(key);
|
|
353
249
|
});
|
|
354
250
|
}
|
|
355
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
356
251
|
this.log.debug('Creating node storage backup...');
|
|
357
252
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
358
253
|
this.log.debug('Created node storage backup');
|
|
359
254
|
}
|
|
360
255
|
catch (error) {
|
|
361
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
362
256
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
363
257
|
if (hasParameter('norestore')) {
|
|
364
258
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -373,46 +267,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
373
267
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
374
268
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
375
269
|
}
|
|
376
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
377
270
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
378
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
379
271
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
380
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
381
272
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
273
|
+
const pairingFilePath = path.join(this.homeDirectory, '.mattercert', 'pairing.json');
|
|
274
|
+
try {
|
|
275
|
+
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
276
|
+
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
277
|
+
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
278
|
+
if (pairingFileJson.passcode && pairingFileJson.discriminator) {
|
|
279
|
+
this.passcode = pairingFileJson.passcode;
|
|
280
|
+
this.discriminator = pairingFileJson.discriminator;
|
|
281
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf}`);
|
|
282
|
+
}
|
|
283
|
+
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
284
|
+
const hexStringToUint8Array = (hexString) => {
|
|
285
|
+
const matches = hexString.match(/.{1,2}/g);
|
|
286
|
+
return matches ? new Uint8Array(matches.map((byte) => parseInt(byte, 16))) : new Uint8Array();
|
|
287
|
+
};
|
|
288
|
+
this.certification = {
|
|
289
|
+
privateKey: hexStringToUint8Array(pairingFileJson.privateKey),
|
|
290
|
+
certificate: hexStringToUint8Array(pairingFileJson.certificate),
|
|
291
|
+
intermediateCertificate: hexStringToUint8Array(pairingFileJson.intermediateCertificate),
|
|
292
|
+
declaration: hexStringToUint8Array(pairingFileJson.declaration),
|
|
293
|
+
};
|
|
294
|
+
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using privateKey, certificate, intermediateCertificate and declaration from pairing file.`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
299
|
+
}
|
|
300
|
+
await this.nodeContext.set('matterport', this.port);
|
|
301
|
+
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
302
|
+
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
382
303
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
383
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
384
304
|
if (hasParameter('logger')) {
|
|
385
305
|
const level = getParameter('logger');
|
|
386
306
|
if (level === 'debug') {
|
|
387
|
-
this.log.logLevel = "debug"
|
|
307
|
+
this.log.logLevel = "debug";
|
|
388
308
|
}
|
|
389
309
|
else if (level === 'info') {
|
|
390
|
-
this.log.logLevel = "info"
|
|
310
|
+
this.log.logLevel = "info";
|
|
391
311
|
}
|
|
392
312
|
else if (level === 'notice') {
|
|
393
|
-
this.log.logLevel = "notice"
|
|
313
|
+
this.log.logLevel = "notice";
|
|
394
314
|
}
|
|
395
315
|
else if (level === 'warn') {
|
|
396
|
-
this.log.logLevel = "warn"
|
|
316
|
+
this.log.logLevel = "warn";
|
|
397
317
|
}
|
|
398
318
|
else if (level === 'error') {
|
|
399
|
-
this.log.logLevel = "error"
|
|
319
|
+
this.log.logLevel = "error";
|
|
400
320
|
}
|
|
401
321
|
else if (level === 'fatal') {
|
|
402
|
-
this.log.logLevel = "fatal"
|
|
322
|
+
this.log.logLevel = "fatal";
|
|
403
323
|
}
|
|
404
324
|
else {
|
|
405
325
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
406
|
-
this.log.logLevel = "info"
|
|
326
|
+
this.log.logLevel = "info";
|
|
407
327
|
}
|
|
408
328
|
}
|
|
409
329
|
else {
|
|
410
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice"
|
|
330
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
411
331
|
}
|
|
412
332
|
this.frontend.logLevel = this.log.logLevel;
|
|
413
333
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
414
334
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
415
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
416
335
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
417
336
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
418
337
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -421,7 +340,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
421
340
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
422
341
|
if (this.profile !== undefined)
|
|
423
342
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
424
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
425
343
|
if (hasParameter('matterlogger')) {
|
|
426
344
|
const level = getParameter('matterlogger');
|
|
427
345
|
if (level === 'debug') {
|
|
@@ -453,7 +371,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
453
371
|
Logger.format = MatterLogFormat.ANSI;
|
|
454
372
|
Logger.setLogger('default', this.createMatterLogger());
|
|
455
373
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
456
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
457
374
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
458
375
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
459
376
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -462,7 +379,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
462
379
|
});
|
|
463
380
|
}
|
|
464
381
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
465
|
-
// Log network interfaces
|
|
466
382
|
const networkInterfaces = os.networkInterfaces();
|
|
467
383
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
468
384
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -474,7 +390,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
474
390
|
});
|
|
475
391
|
}
|
|
476
392
|
}
|
|
477
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
478
393
|
if (hasParameter('mdnsinterface')) {
|
|
479
394
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
480
395
|
}
|
|
@@ -483,7 +398,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
483
398
|
if (this.mdnsInterface === '')
|
|
484
399
|
this.mdnsInterface = undefined;
|
|
485
400
|
}
|
|
486
|
-
// Validate mdnsInterface
|
|
487
401
|
if (this.mdnsInterface) {
|
|
488
402
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
489
403
|
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -496,7 +410,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
496
410
|
}
|
|
497
411
|
if (this.mdnsInterface)
|
|
498
412
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
499
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
500
413
|
if (hasParameter('ipv4address')) {
|
|
501
414
|
this.ipv4address = getParameter('ipv4address');
|
|
502
415
|
}
|
|
@@ -505,7 +418,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
505
418
|
if (this.ipv4address === '')
|
|
506
419
|
this.ipv4address = undefined;
|
|
507
420
|
}
|
|
508
|
-
// Validate ipv4address
|
|
509
421
|
if (this.ipv4address) {
|
|
510
422
|
let isValid = false;
|
|
511
423
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -521,7 +433,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
521
433
|
await this.nodeContext.remove('matteripv4address');
|
|
522
434
|
}
|
|
523
435
|
}
|
|
524
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
525
436
|
if (hasParameter('ipv6address')) {
|
|
526
437
|
this.ipv6address = getParameter('ipv6address');
|
|
527
438
|
}
|
|
@@ -530,7 +441,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
530
441
|
if (this.ipv6address === '')
|
|
531
442
|
this.ipv6address = undefined;
|
|
532
443
|
}
|
|
533
|
-
// Validate ipv6address
|
|
534
444
|
if (this.ipv6address) {
|
|
535
445
|
let isValid = false;
|
|
536
446
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -551,19 +461,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
551
461
|
await this.nodeContext.remove('matteripv6address');
|
|
552
462
|
}
|
|
553
463
|
}
|
|
554
|
-
// Initialize PluginManager
|
|
555
464
|
this.plugins = new PluginManager(this);
|
|
556
465
|
await this.plugins.loadFromStorage();
|
|
557
466
|
this.plugins.logLevel = this.log.logLevel;
|
|
558
|
-
// Initialize DeviceManager
|
|
559
467
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
560
468
|
this.devices.logLevel = this.log.logLevel;
|
|
561
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
562
469
|
for (const plugin of this.plugins) {
|
|
563
470
|
const packageJson = await this.plugins.parse(plugin);
|
|
564
471
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
565
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
566
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
567
472
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
568
473
|
try {
|
|
569
474
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -585,7 +490,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
585
490
|
await plugin.nodeContext.set('description', plugin.description);
|
|
586
491
|
await plugin.nodeContext.set('author', plugin.author);
|
|
587
492
|
}
|
|
588
|
-
// Log system info and create .matterbridge directory
|
|
589
493
|
await this.logNodeAndSystemInfo();
|
|
590
494
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
591
495
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -593,7 +497,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
593
497
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
594
498
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
595
499
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
596
|
-
// Check node version and throw error
|
|
597
500
|
const minNodeVersion = 18;
|
|
598
501
|
const nodeVersion = process.versions.node;
|
|
599
502
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -601,15 +504,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
601
504
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
602
505
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
603
506
|
}
|
|
604
|
-
// Parse command line
|
|
605
507
|
await this.parseCommandLine();
|
|
606
508
|
this.initialized = true;
|
|
607
509
|
}
|
|
608
|
-
/**
|
|
609
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
610
|
-
* @private
|
|
611
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
612
|
-
*/
|
|
613
510
|
async parseCommandLine() {
|
|
614
511
|
if (hasParameter('help')) {
|
|
615
512
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -728,7 +625,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
728
625
|
this.shutdown = true;
|
|
729
626
|
return;
|
|
730
627
|
}
|
|
731
|
-
// Start the matter storage and create the matterbridge context
|
|
732
628
|
try {
|
|
733
629
|
await this.startMatterStorage();
|
|
734
630
|
}
|
|
@@ -736,14 +632,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
736
632
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
737
633
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
738
634
|
}
|
|
739
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
740
635
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
741
636
|
this.initialized = true;
|
|
742
637
|
await this.shutdownProcessAndReset();
|
|
743
638
|
this.shutdown = true;
|
|
744
639
|
return;
|
|
745
640
|
}
|
|
746
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
747
641
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
748
642
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
749
643
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -768,37 +662,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
768
662
|
this.shutdown = true;
|
|
769
663
|
return;
|
|
770
664
|
}
|
|
771
|
-
// Initialize frontend
|
|
772
665
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
773
666
|
await this.frontend.start(getIntParameter('frontend'));
|
|
774
|
-
// Check in 30 seconds the latest versions
|
|
775
667
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
776
668
|
const { checkUpdates } = await import('./update.js');
|
|
777
669
|
checkUpdates(this);
|
|
778
670
|
}, 30 * 1000).unref();
|
|
779
|
-
// Check each 24 hours the latest versions
|
|
780
671
|
this.checkUpdateInterval = setInterval(async () => {
|
|
781
672
|
const { checkUpdates } = await import('./update.js');
|
|
782
673
|
checkUpdates(this);
|
|
783
674
|
}, 12 * 60 * 60 * 1000).unref();
|
|
784
|
-
// Start the matterbridge in mode test
|
|
785
675
|
if (hasParameter('test')) {
|
|
786
676
|
this.bridgeMode = 'bridge';
|
|
787
677
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
788
678
|
return;
|
|
789
679
|
}
|
|
790
|
-
// Start the matterbridge in mode controller
|
|
791
680
|
if (hasParameter('controller')) {
|
|
792
681
|
this.bridgeMode = 'controller';
|
|
793
682
|
await this.startController();
|
|
794
683
|
return;
|
|
795
684
|
}
|
|
796
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
797
685
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
798
686
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
799
687
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
800
688
|
}
|
|
801
|
-
// Start matterbridge in bridge mode
|
|
802
689
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
803
690
|
this.bridgeMode = 'bridge';
|
|
804
691
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -806,7 +693,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
806
693
|
await this.startBridge();
|
|
807
694
|
return;
|
|
808
695
|
}
|
|
809
|
-
// Start matterbridge in childbridge mode
|
|
810
696
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
811
697
|
this.bridgeMode = 'childbridge';
|
|
812
698
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -815,20 +701,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
815
701
|
return;
|
|
816
702
|
}
|
|
817
703
|
}
|
|
818
|
-
/**
|
|
819
|
-
* Asynchronously loads and starts the registered plugins.
|
|
820
|
-
*
|
|
821
|
-
* This method is responsible for initializing and staarting all enabled plugins.
|
|
822
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
823
|
-
*
|
|
824
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
825
|
-
*/
|
|
826
704
|
async startPlugins() {
|
|
827
|
-
// Check, load and start the plugins
|
|
828
705
|
for (const plugin of this.plugins) {
|
|
829
706
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
830
707
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
831
|
-
// Check if the plugin is available
|
|
832
708
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
833
709
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
834
710
|
plugin.enabled = false;
|
|
@@ -848,14 +724,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
848
724
|
plugin.addedDevices = undefined;
|
|
849
725
|
plugin.qrPairingCode = undefined;
|
|
850
726
|
plugin.manualPairingCode = undefined;
|
|
851
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
727
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
852
728
|
}
|
|
853
729
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
854
730
|
}
|
|
855
|
-
/**
|
|
856
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
857
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
858
|
-
*/
|
|
859
731
|
registerProcessHandlers() {
|
|
860
732
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
861
733
|
process.removeAllListeners('uncaughtException');
|
|
@@ -882,9 +754,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
882
754
|
};
|
|
883
755
|
process.on('SIGTERM', this.sigtermHandler);
|
|
884
756
|
}
|
|
885
|
-
/**
|
|
886
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
887
|
-
*/
|
|
888
757
|
deregisterProcesslHandlers() {
|
|
889
758
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
890
759
|
if (this.exceptionHandler)
|
|
@@ -901,17 +770,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
901
770
|
process.off('SIGTERM', this.sigtermHandler);
|
|
902
771
|
this.sigtermHandler = undefined;
|
|
903
772
|
}
|
|
904
|
-
/**
|
|
905
|
-
* Logs the node and system information.
|
|
906
|
-
*/
|
|
907
773
|
async logNodeAndSystemInfo() {
|
|
908
|
-
// IP address information
|
|
909
774
|
const networkInterfaces = os.networkInterfaces();
|
|
910
775
|
this.systemInformation.interfaceName = '';
|
|
911
776
|
this.systemInformation.ipv4Address = '';
|
|
912
777
|
this.systemInformation.ipv6Address = '';
|
|
913
778
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
914
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
915
779
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
916
780
|
continue;
|
|
917
781
|
if (!interfaceDetails) {
|
|
@@ -937,22 +801,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
937
801
|
break;
|
|
938
802
|
}
|
|
939
803
|
}
|
|
940
|
-
// Node information
|
|
941
804
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
942
805
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
943
806
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
944
807
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
945
|
-
// Host system information
|
|
946
808
|
this.systemInformation.hostname = os.hostname();
|
|
947
809
|
this.systemInformation.user = os.userInfo().username;
|
|
948
|
-
this.systemInformation.osType = os.type();
|
|
949
|
-
this.systemInformation.osRelease = os.release();
|
|
950
|
-
this.systemInformation.osPlatform = os.platform();
|
|
951
|
-
this.systemInformation.osArch = os.arch();
|
|
952
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
953
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
954
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
955
|
-
// Log the system information
|
|
810
|
+
this.systemInformation.osType = os.type();
|
|
811
|
+
this.systemInformation.osRelease = os.release();
|
|
812
|
+
this.systemInformation.osPlatform = os.platform();
|
|
813
|
+
this.systemInformation.osArch = os.arch();
|
|
814
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
815
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
816
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
956
817
|
this.log.debug('Host System Information:');
|
|
957
818
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
958
819
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -968,20 +829,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
968
829
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
969
830
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
970
831
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
971
|
-
// Home directory
|
|
972
832
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
973
833
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
974
834
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
975
|
-
// Package root directory
|
|
976
835
|
const { fileURLToPath } = await import('node:url');
|
|
977
836
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
978
837
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
979
838
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
980
839
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
981
|
-
// Global node_modules directory
|
|
982
840
|
if (this.nodeContext)
|
|
983
841
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
984
|
-
// First run of Matterbridge so the node storage is empty
|
|
985
842
|
if (this.globalModulesDirectory === '') {
|
|
986
843
|
try {
|
|
987
844
|
this.execRunningCount++;
|
|
@@ -997,20 +854,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
997
854
|
}
|
|
998
855
|
else
|
|
999
856
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1000
|
-
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
1001
|
-
else {
|
|
1002
|
-
this.getGlobalNodeModules()
|
|
1003
|
-
.then(async (globalModulesDirectory) => {
|
|
1004
|
-
this.globalModulesDirectory = globalModulesDirectory;
|
|
1005
|
-
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1006
|
-
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1007
|
-
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1008
|
-
})
|
|
1009
|
-
.catch((error) => {
|
|
1010
|
-
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1011
|
-
});
|
|
1012
|
-
}*/
|
|
1013
|
-
// Create the data directory .matterbridge in the home directory
|
|
1014
857
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
1015
858
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
1016
859
|
try {
|
|
@@ -1034,7 +877,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1034
877
|
}
|
|
1035
878
|
}
|
|
1036
879
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1037
|
-
// Create the plugin directory Matterbridge in the home directory
|
|
1038
880
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
1039
881
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
1040
882
|
try {
|
|
@@ -1058,7 +900,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1058
900
|
}
|
|
1059
901
|
}
|
|
1060
902
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1061
|
-
// Create the matter cert directory in the home directory
|
|
1062
903
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
1063
904
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
1064
905
|
try {
|
|
@@ -1082,68 +923,50 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
923
|
}
|
|
1083
924
|
}
|
|
1084
925
|
this.log.debug(`Matterbridge Matter Cert Directory: ${this.matterbridgeCertDirectory}`);
|
|
1085
|
-
// Matterbridge version
|
|
1086
926
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1087
927
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
1088
928
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
1089
929
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
-
// Matterbridge latest version
|
|
1091
930
|
if (this.nodeContext)
|
|
1092
931
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1093
932
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
-
// this.getMatterbridgeLatestVersion();
|
|
1095
|
-
// Current working directory
|
|
1096
933
|
const currentDir = process.cwd();
|
|
1097
934
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1098
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1099
935
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1100
936
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1101
937
|
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1104
|
-
*
|
|
1105
|
-
* @returns {Function} The MatterLogger function.
|
|
1106
|
-
*/
|
|
1107
938
|
createMatterLogger() {
|
|
1108
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
939
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1109
940
|
return (_level, formattedLog) => {
|
|
1110
941
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1111
942
|
const message = formattedLog.slice(65);
|
|
1112
943
|
matterLogger.logName = logger;
|
|
1113
944
|
switch (_level) {
|
|
1114
945
|
case MatterLogLevel.DEBUG:
|
|
1115
|
-
matterLogger.log("debug"
|
|
946
|
+
matterLogger.log("debug", message);
|
|
1116
947
|
break;
|
|
1117
948
|
case MatterLogLevel.INFO:
|
|
1118
|
-
matterLogger.log("info"
|
|
949
|
+
matterLogger.log("info", message);
|
|
1119
950
|
break;
|
|
1120
951
|
case MatterLogLevel.NOTICE:
|
|
1121
|
-
matterLogger.log("notice"
|
|
952
|
+
matterLogger.log("notice", message);
|
|
1122
953
|
break;
|
|
1123
954
|
case MatterLogLevel.WARN:
|
|
1124
|
-
matterLogger.log("warn"
|
|
955
|
+
matterLogger.log("warn", message);
|
|
1125
956
|
break;
|
|
1126
957
|
case MatterLogLevel.ERROR:
|
|
1127
|
-
matterLogger.log("error"
|
|
958
|
+
matterLogger.log("error", message);
|
|
1128
959
|
break;
|
|
1129
960
|
case MatterLogLevel.FATAL:
|
|
1130
|
-
matterLogger.log("fatal"
|
|
961
|
+
matterLogger.log("fatal", message);
|
|
1131
962
|
break;
|
|
1132
963
|
default:
|
|
1133
|
-
matterLogger.log("debug"
|
|
964
|
+
matterLogger.log("debug", message);
|
|
1134
965
|
break;
|
|
1135
966
|
}
|
|
1136
967
|
};
|
|
1137
968
|
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Creates a Matter File Logger.
|
|
1140
|
-
*
|
|
1141
|
-
* @param {string} filePath - The path to the log file.
|
|
1142
|
-
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1143
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1144
|
-
*/
|
|
1145
969
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1146
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1147
970
|
let fileSize = 0;
|
|
1148
971
|
if (unlink) {
|
|
1149
972
|
try {
|
|
@@ -1192,21 +1015,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1192
1015
|
}
|
|
1193
1016
|
};
|
|
1194
1017
|
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1197
|
-
*/
|
|
1198
1018
|
async restartProcess() {
|
|
1199
1019
|
await this.cleanup('restarting...', true);
|
|
1200
1020
|
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Shut down the process by exiting the current process.
|
|
1203
|
-
*/
|
|
1204
1021
|
async shutdownProcess() {
|
|
1205
1022
|
await this.cleanup('shutting down...', false);
|
|
1206
1023
|
}
|
|
1207
|
-
/**
|
|
1208
|
-
* Update matterbridge and and shut down the process.
|
|
1209
|
-
*/
|
|
1210
1024
|
async updateProcess() {
|
|
1211
1025
|
this.log.info('Updating matterbridge...');
|
|
1212
1026
|
try {
|
|
@@ -1219,73 +1033,52 @@ export class Matterbridge extends EventEmitter {
|
|
|
1219
1033
|
this.frontend.wssSendRestartRequired();
|
|
1220
1034
|
await this.cleanup('updating...', false);
|
|
1221
1035
|
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Unregister all devices and shut down the process.
|
|
1224
|
-
*/
|
|
1225
1036
|
async unregisterAndShutdownProcess() {
|
|
1226
1037
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1227
1038
|
for (const plugin of this.plugins) {
|
|
1228
1039
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
1229
1040
|
}
|
|
1230
1041
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1231
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1042
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1232
1043
|
this.log.debug('Cleaning up and shutting down...');
|
|
1233
1044
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1234
1045
|
}
|
|
1235
|
-
/**
|
|
1236
|
-
* Reset commissioning and shut down the process.
|
|
1237
|
-
*/
|
|
1238
1046
|
async shutdownProcessAndReset() {
|
|
1239
1047
|
await this.cleanup('shutting down with reset...', false);
|
|
1240
1048
|
}
|
|
1241
|
-
/**
|
|
1242
|
-
* Factory reset and shut down the process.
|
|
1243
|
-
*/
|
|
1244
1049
|
async shutdownProcessAndFactoryReset() {
|
|
1245
1050
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1246
1051
|
}
|
|
1247
|
-
/**
|
|
1248
|
-
* Cleans up the Matterbridge instance.
|
|
1249
|
-
* @param message - The cleanup message.
|
|
1250
|
-
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1251
|
-
* @returns A promise that resolves when the cleanup is completed.
|
|
1252
|
-
*/
|
|
1253
1052
|
async cleanup(message, restart = false) {
|
|
1254
1053
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1255
1054
|
this.emit('cleanup_started');
|
|
1256
1055
|
this.hasCleanupStarted = true;
|
|
1257
1056
|
this.log.info(message);
|
|
1258
|
-
// Clear the start matter interval
|
|
1259
1057
|
if (this.startMatterInterval) {
|
|
1260
1058
|
clearInterval(this.startMatterInterval);
|
|
1261
1059
|
this.startMatterInterval = undefined;
|
|
1262
1060
|
this.log.debug('Start matter interval cleared');
|
|
1263
1061
|
}
|
|
1264
|
-
// Clear the check update timeout
|
|
1265
1062
|
if (this.checkUpdateTimeout) {
|
|
1266
1063
|
clearInterval(this.checkUpdateTimeout);
|
|
1267
1064
|
this.checkUpdateTimeout = undefined;
|
|
1268
1065
|
this.log.debug('Check update timeout cleared');
|
|
1269
1066
|
}
|
|
1270
|
-
// Clear the check update interval
|
|
1271
1067
|
if (this.checkUpdateInterval) {
|
|
1272
1068
|
clearInterval(this.checkUpdateInterval);
|
|
1273
1069
|
this.checkUpdateInterval = undefined;
|
|
1274
1070
|
this.log.debug('Check update interval cleared');
|
|
1275
1071
|
}
|
|
1276
|
-
// Clear the configure timeout
|
|
1277
1072
|
if (this.configureTimeout) {
|
|
1278
1073
|
clearTimeout(this.configureTimeout);
|
|
1279
1074
|
this.configureTimeout = undefined;
|
|
1280
1075
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1281
1076
|
}
|
|
1282
|
-
// Clear the reachability timeout
|
|
1283
1077
|
if (this.reachabilityTimeout) {
|
|
1284
1078
|
clearTimeout(this.reachabilityTimeout);
|
|
1285
1079
|
this.reachabilityTimeout = undefined;
|
|
1286
1080
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1287
1081
|
}
|
|
1288
|
-
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1289
1082
|
for (const plugin of this.plugins) {
|
|
1290
1083
|
if (!plugin.enabled || plugin.error)
|
|
1291
1084
|
continue;
|
|
@@ -1296,10 +1089,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1296
1089
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1297
1090
|
}
|
|
1298
1091
|
}
|
|
1299
|
-
// Stop matter server nodes
|
|
1300
1092
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1301
1093
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1302
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1094
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1303
1095
|
if (this.bridgeMode === 'bridge') {
|
|
1304
1096
|
if (this.serverNode) {
|
|
1305
1097
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1315,7 +1107,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1315
1107
|
}
|
|
1316
1108
|
}
|
|
1317
1109
|
this.log.notice('Stopped matter server nodes');
|
|
1318
|
-
// Matter commisioning reset
|
|
1319
1110
|
if (message === 'shutting down with reset...') {
|
|
1320
1111
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1321
1112
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1325,37 +1116,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1325
1116
|
await this.matterbridgeContext?.clearAll();
|
|
1326
1117
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1327
1118
|
}
|
|
1328
|
-
// Stop matter storage
|
|
1329
1119
|
await this.stopMatterStorage();
|
|
1330
|
-
// Stop the frontend
|
|
1331
1120
|
await this.frontend.stop();
|
|
1332
|
-
// Remove the matterfilelogger
|
|
1333
1121
|
try {
|
|
1334
1122
|
Logger.removeLogger('matterfilelogger');
|
|
1335
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1336
1123
|
}
|
|
1337
1124
|
catch (error) {
|
|
1338
|
-
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1339
1125
|
}
|
|
1340
|
-
// Serialize registeredDevices
|
|
1341
1126
|
if (this.nodeStorage && this.nodeContext) {
|
|
1342
|
-
/*
|
|
1343
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1344
|
-
this.log.info('Saving registered devices...');
|
|
1345
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1346
|
-
this.devices.forEach(async (device) => {
|
|
1347
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1348
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1349
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1350
|
-
});
|
|
1351
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1352
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1353
|
-
*/
|
|
1354
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1355
1127
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1356
1128
|
await this.nodeContext.close();
|
|
1357
1129
|
this.nodeContext = undefined;
|
|
1358
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1359
1130
|
for (const plugin of this.plugins) {
|
|
1360
1131
|
if (plugin.nodeContext) {
|
|
1361
1132
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1372,10 +1143,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1372
1143
|
}
|
|
1373
1144
|
this.plugins.clear();
|
|
1374
1145
|
this.devices.clear();
|
|
1375
|
-
// Factory reset
|
|
1376
1146
|
if (message === 'shutting down with factory reset...') {
|
|
1377
1147
|
try {
|
|
1378
|
-
// Delete old matter storage file and backup
|
|
1379
1148
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1380
1149
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1381
1150
|
await fs.unlink(file);
|
|
@@ -1389,7 +1158,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1158
|
}
|
|
1390
1159
|
}
|
|
1391
1160
|
try {
|
|
1392
|
-
// Delete matter node storage directory with its subdirectories and backup
|
|
1393
1161
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1394
1162
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1395
1163
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1403,7 +1171,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1403
1171
|
}
|
|
1404
1172
|
}
|
|
1405
1173
|
try {
|
|
1406
|
-
// Delete node storage directory with its subdirectories and backup
|
|
1407
1174
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1408
1175
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1409
1176
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1418,13 +1185,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1418
1185
|
}
|
|
1419
1186
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1420
1187
|
}
|
|
1421
|
-
// Deregisters the process handlers
|
|
1422
1188
|
this.deregisterProcesslHandlers();
|
|
1423
1189
|
if (restart) {
|
|
1424
1190
|
if (message === 'updating...') {
|
|
1425
1191
|
this.log.info('Cleanup completed. Updating...');
|
|
1426
1192
|
Matterbridge.instance = undefined;
|
|
1427
|
-
this.emit('update');
|
|
1193
|
+
this.emit('update');
|
|
1428
1194
|
}
|
|
1429
1195
|
else if (message === 'restarting...') {
|
|
1430
1196
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1445,14 +1211,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1445
1211
|
this.log.debug('Cleanup already started...');
|
|
1446
1212
|
}
|
|
1447
1213
|
}
|
|
1448
|
-
/**
|
|
1449
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1450
|
-
*
|
|
1451
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1452
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1453
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1454
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1455
|
-
*/
|
|
1456
1214
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1457
1215
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1458
1216
|
plugin.locked = true;
|
|
@@ -1466,13 +1224,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1466
1224
|
await this.startServerNode(plugin.serverNode);
|
|
1467
1225
|
}
|
|
1468
1226
|
}
|
|
1469
|
-
/**
|
|
1470
|
-
* Creates and configures the server node for a dynamic plugin.
|
|
1471
|
-
*
|
|
1472
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1473
|
-
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1474
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1475
|
-
*/
|
|
1476
1227
|
async createDynamicPlugin(plugin, start = false) {
|
|
1477
1228
|
if (!plugin.locked) {
|
|
1478
1229
|
plugin.locked = true;
|
|
@@ -1485,13 +1236,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1485
1236
|
await this.startServerNode(plugin.serverNode);
|
|
1486
1237
|
}
|
|
1487
1238
|
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Starts the Matterbridge in bridge mode.
|
|
1490
|
-
* @private
|
|
1491
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1492
|
-
*/
|
|
1493
1239
|
async startBridge() {
|
|
1494
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1495
1240
|
if (!this.matterStorageManager)
|
|
1496
1241
|
throw new Error('No storage manager initialized');
|
|
1497
1242
|
if (!this.matterbridgeContext)
|
|
@@ -1529,9 +1274,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1529
1274
|
clearInterval(this.startMatterInterval);
|
|
1530
1275
|
this.startMatterInterval = undefined;
|
|
1531
1276
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1532
|
-
// Start the Matter server node
|
|
1533
1277
|
this.startServerNode(this.serverNode);
|
|
1534
|
-
// Configure the plugins
|
|
1535
1278
|
this.configureTimeout = setTimeout(async () => {
|
|
1536
1279
|
for (const plugin of this.plugins) {
|
|
1537
1280
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1549,7 +1292,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1549
1292
|
}
|
|
1550
1293
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1551
1294
|
}, 30 * 1000);
|
|
1552
|
-
// Setting reachability to true
|
|
1553
1295
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1554
1296
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1555
1297
|
if (this.aggregatorNode)
|
|
@@ -1558,11 +1300,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1558
1300
|
}, 60 * 1000);
|
|
1559
1301
|
}, 1000);
|
|
1560
1302
|
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1563
|
-
* @private
|
|
1564
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1565
|
-
*/
|
|
1566
1303
|
async startChildbridge() {
|
|
1567
1304
|
if (!this.matterStorageManager)
|
|
1568
1305
|
throw new Error('No storage manager initialized');
|
|
@@ -1600,7 +1337,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1600
1337
|
clearInterval(this.startMatterInterval);
|
|
1601
1338
|
this.startMatterInterval = undefined;
|
|
1602
1339
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1603
|
-
// Configure the plugins
|
|
1604
1340
|
this.configureTimeout = setTimeout(async () => {
|
|
1605
1341
|
for (const plugin of this.plugins) {
|
|
1606
1342
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1637,9 +1373,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1637
1373
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1638
1374
|
continue;
|
|
1639
1375
|
}
|
|
1640
|
-
// Start the Matter server node
|
|
1641
1376
|
this.startServerNode(plugin.serverNode);
|
|
1642
|
-
// Setting reachability to true
|
|
1643
1377
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1644
1378
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1645
1379
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1649,11 +1383,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1649
1383
|
}
|
|
1650
1384
|
}, 1000);
|
|
1651
1385
|
}
|
|
1652
|
-
/**
|
|
1653
|
-
* Starts the Matterbridge controller.
|
|
1654
|
-
* @private
|
|
1655
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1656
|
-
*/
|
|
1657
1386
|
async startController() {
|
|
1658
1387
|
if (!this.matterStorageManager) {
|
|
1659
1388
|
this.log.error('No storage manager initialized');
|
|
@@ -1668,207 +1397,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1668
1397
|
return;
|
|
1669
1398
|
}
|
|
1670
1399
|
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1671
|
-
/*
|
|
1672
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1673
|
-
this.log.info('Creating matter commissioning controller');
|
|
1674
|
-
this.commissioningController = new CommissioningController({
|
|
1675
|
-
autoConnect: false,
|
|
1676
|
-
});
|
|
1677
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1678
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1679
|
-
|
|
1680
|
-
this.log.info('Starting matter server');
|
|
1681
|
-
await this.matterServer.start();
|
|
1682
|
-
this.log.info('Matter server started');
|
|
1683
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1684
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1685
|
-
regulatoryCountryCode: 'XX',
|
|
1686
|
-
};
|
|
1687
|
-
const commissioningController = new CommissioningController({
|
|
1688
|
-
environment: {
|
|
1689
|
-
environment,
|
|
1690
|
-
id: uniqueId,
|
|
1691
|
-
},
|
|
1692
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1693
|
-
adminFabricLabel,
|
|
1694
|
-
});
|
|
1695
|
-
|
|
1696
|
-
if (hasParameter('pairingcode')) {
|
|
1697
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1698
|
-
const pairingCode = getParameter('pairingcode');
|
|
1699
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1700
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1701
|
-
|
|
1702
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1703
|
-
if (pairingCode !== undefined) {
|
|
1704
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1705
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1706
|
-
longDiscriminator = undefined;
|
|
1707
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1708
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1709
|
-
} else {
|
|
1710
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1711
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1712
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1713
|
-
}
|
|
1714
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1715
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
const options = {
|
|
1719
|
-
commissioning: commissioningOptions,
|
|
1720
|
-
discovery: {
|
|
1721
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1722
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1723
|
-
},
|
|
1724
|
-
passcode: setupPin,
|
|
1725
|
-
} as NodeCommissioningOptions;
|
|
1726
|
-
this.log.info('Commissioning with options:', options);
|
|
1727
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1728
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1729
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1730
|
-
} // (hasParameter('pairingcode'))
|
|
1731
|
-
|
|
1732
|
-
if (hasParameter('unpairall')) {
|
|
1733
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1734
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1735
|
-
for (const nodeId of nodeIds) {
|
|
1736
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1737
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1738
|
-
}
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
if (hasParameter('discover')) {
|
|
1743
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1744
|
-
// console.log(discover);
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1748
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1749
|
-
return;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1753
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1754
|
-
for (const nodeId of nodeIds) {
|
|
1755
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1756
|
-
|
|
1757
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1758
|
-
autoSubscribe: false,
|
|
1759
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1760
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1761
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1762
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1763
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1764
|
-
switch (info) {
|
|
1765
|
-
case NodeStateInformation.Connected:
|
|
1766
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1767
|
-
break;
|
|
1768
|
-
case NodeStateInformation.Disconnected:
|
|
1769
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1770
|
-
break;
|
|
1771
|
-
case NodeStateInformation.Reconnecting:
|
|
1772
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1773
|
-
break;
|
|
1774
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1775
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1776
|
-
break;
|
|
1777
|
-
case NodeStateInformation.StructureChanged:
|
|
1778
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1779
|
-
break;
|
|
1780
|
-
case NodeStateInformation.Decommissioned:
|
|
1781
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1782
|
-
break;
|
|
1783
|
-
default:
|
|
1784
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1785
|
-
break;
|
|
1786
|
-
}
|
|
1787
|
-
},
|
|
1788
|
-
});
|
|
1789
|
-
|
|
1790
|
-
node.logStructure();
|
|
1791
|
-
|
|
1792
|
-
// Get the interaction client
|
|
1793
|
-
this.log.info('Getting the interaction client');
|
|
1794
|
-
const interactionClient = await node.getInteractionClient();
|
|
1795
|
-
let cluster;
|
|
1796
|
-
let attributes;
|
|
1797
|
-
|
|
1798
|
-
// Log BasicInformationCluster
|
|
1799
|
-
cluster = BasicInformationCluster;
|
|
1800
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1801
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1802
|
-
});
|
|
1803
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1804
|
-
attributes.forEach((attribute) => {
|
|
1805
|
-
this.log.info(
|
|
1806
|
-
`- 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}`,
|
|
1807
|
-
);
|
|
1808
|
-
});
|
|
1809
|
-
|
|
1810
|
-
// Log PowerSourceCluster
|
|
1811
|
-
cluster = PowerSourceCluster;
|
|
1812
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1813
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1814
|
-
});
|
|
1815
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1816
|
-
attributes.forEach((attribute) => {
|
|
1817
|
-
this.log.info(
|
|
1818
|
-
`- 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}`,
|
|
1819
|
-
);
|
|
1820
|
-
});
|
|
1821
|
-
|
|
1822
|
-
// Log ThreadNetworkDiagnostics
|
|
1823
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1824
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1825
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1826
|
-
});
|
|
1827
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1828
|
-
attributes.forEach((attribute) => {
|
|
1829
|
-
this.log.info(
|
|
1830
|
-
`- 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}`,
|
|
1831
|
-
);
|
|
1832
|
-
});
|
|
1833
|
-
|
|
1834
|
-
// Log SwitchCluster
|
|
1835
|
-
cluster = SwitchCluster;
|
|
1836
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1837
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1838
|
-
});
|
|
1839
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1840
|
-
attributes.forEach((attribute) => {
|
|
1841
|
-
this.log.info(
|
|
1842
|
-
`- 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}`,
|
|
1843
|
-
);
|
|
1844
|
-
});
|
|
1845
|
-
|
|
1846
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1847
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1848
|
-
ignoreInitialTriggers: false,
|
|
1849
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1850
|
-
this.log.info(
|
|
1851
|
-
`***${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}`,
|
|
1852
|
-
),
|
|
1853
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1854
|
-
this.log.info(
|
|
1855
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1856
|
-
);
|
|
1857
|
-
},
|
|
1858
|
-
});
|
|
1859
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1860
|
-
}
|
|
1861
|
-
*/
|
|
1862
1400
|
}
|
|
1863
|
-
/** ***********************************************************************************************************************************/
|
|
1864
|
-
/** Matter.js methods */
|
|
1865
|
-
/** ***********************************************************************************************************************************/
|
|
1866
|
-
/**
|
|
1867
|
-
* Starts the matter storage process with name Matterbridge.
|
|
1868
|
-
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1869
|
-
*/
|
|
1870
1401
|
async startMatterStorage() {
|
|
1871
|
-
// Setup Matter storage
|
|
1872
1402
|
this.log.info(`Starting matter node storage...`);
|
|
1873
1403
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1874
1404
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1877,25 +1407,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1877
1407
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName);
|
|
1878
1408
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1879
1409
|
this.log.info('Matter node storage started');
|
|
1880
|
-
// Backup matter storage since it is created/opened correctly
|
|
1881
1410
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1882
1411
|
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1885
|
-
*
|
|
1886
|
-
* @param storageName - The name of the storage directory to be backed up.
|
|
1887
|
-
* @param backupName - The name of the backup directory to be created.
|
|
1888
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1889
|
-
*/
|
|
1890
1412
|
async backupMatterStorage(storageName, backupName) {
|
|
1891
1413
|
this.log.info('Creating matter node storage backup...');
|
|
1892
1414
|
await copyDirectory(storageName, backupName);
|
|
1893
1415
|
this.log.info('Created matter node storage backup');
|
|
1894
1416
|
}
|
|
1895
|
-
/**
|
|
1896
|
-
* Stops the matter storage.
|
|
1897
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1898
|
-
*/
|
|
1899
1417
|
async stopMatterStorage() {
|
|
1900
1418
|
this.log.info('Closing matter node storage...');
|
|
1901
1419
|
await this.matterStorageManager?.close();
|
|
@@ -1904,19 +1422,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1904
1422
|
this.matterbridgeContext = undefined;
|
|
1905
1423
|
this.log.info('Matter node storage closed');
|
|
1906
1424
|
}
|
|
1907
|
-
/**
|
|
1908
|
-
* Creates a server node storage context.
|
|
1909
|
-
*
|
|
1910
|
-
* @param {string} pluginName - The name of the plugin.
|
|
1911
|
-
* @param {string} deviceName - The name of the device.
|
|
1912
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1913
|
-
* @param {number} vendorId - The vendor ID.
|
|
1914
|
-
* @param {string} vendorName - The vendor name.
|
|
1915
|
-
* @param {number} productId - The product ID.
|
|
1916
|
-
* @param {string} productName - The product name.
|
|
1917
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1918
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1919
|
-
*/
|
|
1920
1425
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1921
1426
|
const { randomBytes } = await import('node:crypto');
|
|
1922
1427
|
if (!this.matterStorageService)
|
|
@@ -1950,15 +1455,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1950
1455
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1951
1456
|
return storageContext;
|
|
1952
1457
|
}
|
|
1953
|
-
/**
|
|
1954
|
-
* Creates a server node.
|
|
1955
|
-
*
|
|
1956
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1957
|
-
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1958
|
-
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1959
|
-
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1960
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1961
|
-
*/
|
|
1962
1458
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1963
1459
|
const storeId = await storageContext.get('storeId');
|
|
1964
1460
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1968,33 +1464,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1968
1464
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1969
1465
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1970
1466
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1971
|
-
/**
|
|
1972
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1973
|
-
*/
|
|
1974
1467
|
const serverNode = await ServerNode.create({
|
|
1975
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1976
1468
|
id: storeId,
|
|
1977
|
-
// Provide Network relevant configuration like the port
|
|
1978
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
1979
1469
|
network: {
|
|
1980
1470
|
listeningAddressIpv4: this.ipv4address,
|
|
1981
1471
|
listeningAddressIpv6: this.ipv6address,
|
|
1982
1472
|
port,
|
|
1983
1473
|
},
|
|
1984
|
-
|
|
1985
|
-
|
|
1474
|
+
operationalCredentials: {
|
|
1475
|
+
certification: this.certification,
|
|
1476
|
+
},
|
|
1986
1477
|
commissioning: {
|
|
1987
1478
|
passcode,
|
|
1988
1479
|
discriminator,
|
|
1989
1480
|
},
|
|
1990
|
-
// Provide Node announcement settings
|
|
1991
|
-
// Optional: If Ommitted some development defaults are used
|
|
1992
1481
|
productDescription: {
|
|
1993
1482
|
name: await storageContext.get('deviceName'),
|
|
1994
1483
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1995
1484
|
},
|
|
1996
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
-
// Optional: If Omitted some development defaults are used
|
|
1998
1485
|
basicInformation: {
|
|
1999
1486
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2000
1487
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2012,13 +1499,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2012
1499
|
},
|
|
2013
1500
|
});
|
|
2014
1501
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
2015
|
-
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
2016
1502
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
2017
1503
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
2018
1504
|
if (this.bridgeMode === 'bridge') {
|
|
2019
1505
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
2020
1506
|
if (resetSessions)
|
|
2021
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1507
|
+
this.matterbridgeSessionInformations = undefined;
|
|
2022
1508
|
this.matterbridgePaired = true;
|
|
2023
1509
|
}
|
|
2024
1510
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -2026,21 +1512,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
2026
1512
|
if (plugin) {
|
|
2027
1513
|
plugin.fabricInformations = sanitizedFabrics;
|
|
2028
1514
|
if (resetSessions)
|
|
2029
|
-
plugin.sessionInformations = undefined;
|
|
1515
|
+
plugin.sessionInformations = undefined;
|
|
2030
1516
|
plugin.paired = true;
|
|
2031
1517
|
}
|
|
2032
1518
|
}
|
|
2033
1519
|
};
|
|
2034
|
-
/**
|
|
2035
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2036
|
-
* This means: It is added to the first fabric.
|
|
2037
|
-
*/
|
|
2038
1520
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
2039
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2040
1521
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
2041
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2042
1522
|
serverNode.lifecycle.online.on(async () => {
|
|
2043
1523
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1524
|
+
if (!hasParameter('novirtual') && this.bridgeMode === 'bridge') {
|
|
1525
|
+
this.log.notice(`Creating virtual devices for server node ${storeId}`);
|
|
1526
|
+
const virtualRestart = new Endpoint(OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer), { id: 'Restart Matterbridge', bridgedDeviceBasicInformation: { nodeLabel: 'Restart' } });
|
|
1527
|
+
virtualRestart.events.onOff.onOff$Changed.on(async (value) => {
|
|
1528
|
+
if (value) {
|
|
1529
|
+
await virtualRestart.setStateOf(OnOffBaseServer, { onOff: false });
|
|
1530
|
+
if (this.restartMode === '')
|
|
1531
|
+
this.restartProcess();
|
|
1532
|
+
else
|
|
1533
|
+
this.shutdownProcess();
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
await this.aggregatorNode?.add(virtualRestart);
|
|
1537
|
+
await virtualRestart.setStateOf(OnOffBaseServer, { onOff: false });
|
|
1538
|
+
const virtualUpdate = new Endpoint(OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer), { id: 'Update Matterbridge', bridgedDeviceBasicInformation: { nodeLabel: 'Update' } });
|
|
1539
|
+
virtualUpdate.events.onOff.onOff$Changed.on(async (value) => {
|
|
1540
|
+
if (value) {
|
|
1541
|
+
await virtualUpdate.setStateOf(OnOffBaseServer, { onOff: false });
|
|
1542
|
+
this.updateProcess();
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
await this.aggregatorNode?.add(virtualUpdate);
|
|
1546
|
+
await virtualUpdate.setStateOf(OnOffBaseServer, { onOff: false });
|
|
1547
|
+
}
|
|
2044
1548
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
2045
1549
|
this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
|
|
2046
1550
|
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
@@ -2107,7 +1611,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2107
1611
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2108
1612
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2109
1613
|
});
|
|
2110
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2111
1614
|
serverNode.lifecycle.offline.on(() => {
|
|
2112
1615
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2113
1616
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -2131,10 +1634,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2131
1634
|
this.frontend.wssSendRefreshRequired('settings');
|
|
2132
1635
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2133
1636
|
});
|
|
2134
|
-
/**
|
|
2135
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2136
|
-
* information is needed.
|
|
2137
|
-
*/
|
|
2138
1637
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2139
1638
|
let action = '';
|
|
2140
1639
|
switch (fabricAction) {
|
|
@@ -2168,24 +1667,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2168
1667
|
}
|
|
2169
1668
|
}
|
|
2170
1669
|
};
|
|
2171
|
-
/**
|
|
2172
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2173
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2174
|
-
*/
|
|
2175
1670
|
serverNode.events.sessions.opened.on((session) => {
|
|
2176
1671
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2177
1672
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2178
1673
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2179
1674
|
});
|
|
2180
|
-
/**
|
|
2181
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2182
|
-
*/
|
|
2183
1675
|
serverNode.events.sessions.closed.on((session) => {
|
|
2184
1676
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2185
1677
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
2186
1678
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
2187
1679
|
});
|
|
2188
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2189
1680
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2190
1681
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2191
1682
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -2194,42 +1685,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2194
1685
|
this.log.info(`Created server node for ${storeId}`);
|
|
2195
1686
|
return serverNode;
|
|
2196
1687
|
}
|
|
2197
|
-
/**
|
|
2198
|
-
* Starts the specified server node.
|
|
2199
|
-
*
|
|
2200
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2201
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2202
|
-
*/
|
|
2203
1688
|
async startServerNode(matterServerNode) {
|
|
2204
1689
|
if (!matterServerNode)
|
|
2205
1690
|
return;
|
|
2206
1691
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2207
1692
|
await matterServerNode.start();
|
|
2208
1693
|
}
|
|
2209
|
-
/**
|
|
2210
|
-
* Stops the specified server node.
|
|
2211
|
-
*
|
|
2212
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2213
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2214
|
-
*/
|
|
2215
1694
|
async stopServerNode(matterServerNode) {
|
|
2216
1695
|
if (!matterServerNode)
|
|
2217
1696
|
return;
|
|
2218
1697
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
2219
1698
|
try {
|
|
2220
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
1699
|
+
await withTimeout(matterServerNode.close(), 30000);
|
|
2221
1700
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
2222
1701
|
}
|
|
2223
1702
|
catch (error) {
|
|
2224
1703
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2225
1704
|
}
|
|
2226
1705
|
}
|
|
2227
|
-
/**
|
|
2228
|
-
* Advertises the specified server node.
|
|
2229
|
-
*
|
|
2230
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2231
|
-
* @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.
|
|
2232
|
-
*/
|
|
2233
1706
|
async advertiseServerNode(matterServerNode) {
|
|
2234
1707
|
if (matterServerNode) {
|
|
2235
1708
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -2238,45 +1711,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
2238
1711
|
return { qrPairingCode, manualPairingCode };
|
|
2239
1712
|
}
|
|
2240
1713
|
}
|
|
2241
|
-
/**
|
|
2242
|
-
* Stop advertise the specified server node.
|
|
2243
|
-
*
|
|
2244
|
-
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2245
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2246
|
-
*/
|
|
2247
1714
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
2248
1715
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
2249
1716
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
2250
1717
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
2251
1718
|
}
|
|
2252
1719
|
}
|
|
2253
|
-
/**
|
|
2254
|
-
* Creates an aggregator node with the specified storage context.
|
|
2255
|
-
*
|
|
2256
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2257
|
-
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2258
|
-
*/
|
|
2259
1720
|
async createAggregatorNode(storageContext) {
|
|
2260
1721
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
2261
1722
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2262
1723
|
return aggregatorNode;
|
|
2263
1724
|
}
|
|
2264
|
-
/**
|
|
2265
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2266
|
-
*
|
|
2267
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2268
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2269
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2270
|
-
*/
|
|
2271
1725
|
async addBridgedEndpoint(pluginName, device) {
|
|
2272
|
-
// Check if the plugin is registered
|
|
2273
1726
|
const plugin = this.plugins.get(pluginName);
|
|
2274
1727
|
if (!plugin) {
|
|
2275
1728
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
2276
1729
|
return;
|
|
2277
1730
|
}
|
|
2278
1731
|
if (this.bridgeMode === 'bridge') {
|
|
2279
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2280
1732
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2281
1733
|
if (!this.aggregatorNode) {
|
|
2282
1734
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2293,7 +1745,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2293
1745
|
}
|
|
2294
1746
|
}
|
|
2295
1747
|
else if (this.bridgeMode === 'childbridge') {
|
|
2296
|
-
// Register and add the device to the plugin server node
|
|
2297
1748
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2298
1749
|
try {
|
|
2299
1750
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2310,12 +1761,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2310
1761
|
return;
|
|
2311
1762
|
}
|
|
2312
1763
|
}
|
|
2313
|
-
// Register and add the device to the plugin aggregator node
|
|
2314
1764
|
if (plugin.type === 'DynamicPlatform') {
|
|
2315
1765
|
try {
|
|
2316
1766
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2317
1767
|
await this.createDynamicPlugin(plugin);
|
|
2318
|
-
// Fast plugins can add another device before the server node is created
|
|
2319
1768
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2320
1769
|
if (!plugin.aggregatorNode) {
|
|
2321
1770
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2335,28 +1784,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2335
1784
|
plugin.registeredDevices++;
|
|
2336
1785
|
if (plugin.addedDevices !== undefined)
|
|
2337
1786
|
plugin.addedDevices++;
|
|
2338
|
-
// Add the device to the DeviceManager
|
|
2339
1787
|
this.devices.set(device);
|
|
2340
|
-
// Subscribe to the reachable$Changed event
|
|
2341
1788
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2342
1789
|
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}`);
|
|
2343
1790
|
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2346
|
-
*
|
|
2347
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2348
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2349
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2350
|
-
*/
|
|
2351
1791
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2352
1792
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2353
|
-
// Check if the plugin is registered
|
|
2354
1793
|
const plugin = this.plugins.get(pluginName);
|
|
2355
1794
|
if (!plugin) {
|
|
2356
1795
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2357
1796
|
return;
|
|
2358
1797
|
}
|
|
2359
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2360
1798
|
if (this.bridgeMode === 'bridge') {
|
|
2361
1799
|
if (!this.aggregatorNode) {
|
|
2362
1800
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2371,7 +1809,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2371
1809
|
}
|
|
2372
1810
|
else if (this.bridgeMode === 'childbridge') {
|
|
2373
1811
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2374
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2375
1812
|
}
|
|
2376
1813
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2377
1814
|
if (!plugin.aggregatorNode) {
|
|
@@ -2386,21 +1823,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2386
1823
|
if (plugin.addedDevices !== undefined)
|
|
2387
1824
|
plugin.addedDevices--;
|
|
2388
1825
|
}
|
|
2389
|
-
// Remove the device from the DeviceManager
|
|
2390
1826
|
this.devices.remove(device);
|
|
2391
1827
|
}
|
|
2392
|
-
/**
|
|
2393
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2394
|
-
*
|
|
2395
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2396
|
-
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2397
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2398
|
-
*
|
|
2399
|
-
* @remarks
|
|
2400
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2401
|
-
* It also applies a delay between each removal if specified.
|
|
2402
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2403
|
-
*/
|
|
2404
1828
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2405
1829
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2406
1830
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2411,25 +1835,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
2411
1835
|
if (delay > 0)
|
|
2412
1836
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2413
1837
|
}
|
|
2414
|
-
/**
|
|
2415
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2416
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2417
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2418
|
-
*
|
|
2419
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2420
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2421
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2422
|
-
*/
|
|
2423
1838
|
async subscribeAttributeChanged(plugin, device) {
|
|
2424
1839
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2425
1840
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2426
|
-
/*
|
|
2427
|
-
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
|
|
2428
|
-
setTimeout(async () => {
|
|
2429
|
-
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
|
|
2430
|
-
await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
|
|
2431
|
-
}, 60000).unref();
|
|
2432
|
-
*/
|
|
2433
1841
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
2434
1842
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2435
1843
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
@@ -2442,12 +1850,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2442
1850
|
});
|
|
2443
1851
|
}
|
|
2444
1852
|
}
|
|
2445
|
-
/**
|
|
2446
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2447
|
-
*
|
|
2448
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2449
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2450
|
-
*/
|
|
2451
1853
|
sanitizeFabricInformations(fabricInfo) {
|
|
2452
1854
|
return fabricInfo.map((info) => {
|
|
2453
1855
|
return {
|
|
@@ -2461,12 +1863,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2461
1863
|
};
|
|
2462
1864
|
});
|
|
2463
1865
|
}
|
|
2464
|
-
/**
|
|
2465
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2466
|
-
*
|
|
2467
|
-
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2468
|
-
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2469
|
-
*/
|
|
2470
1866
|
sanitizeSessionInformation(sessionInfo) {
|
|
2471
1867
|
return sessionInfo
|
|
2472
1868
|
.filter((session) => session.isPeerActive)
|
|
@@ -2494,20 +1890,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2494
1890
|
};
|
|
2495
1891
|
});
|
|
2496
1892
|
}
|
|
2497
|
-
/**
|
|
2498
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2499
|
-
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2500
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2501
|
-
*/
|
|
2502
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2503
1893
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2504
|
-
/*
|
|
2505
|
-
for (const child of aggregatorNode.parts) {
|
|
2506
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2507
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2508
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2509
|
-
}
|
|
2510
|
-
*/
|
|
2511
1894
|
}
|
|
2512
1895
|
getVendorIdName = (vendorId) => {
|
|
2513
1896
|
if (!vendorId)
|
|
@@ -2550,37 +1933,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2550
1933
|
}
|
|
2551
1934
|
return vendorName;
|
|
2552
1935
|
};
|
|
2553
|
-
/**
|
|
2554
|
-
* Spawns a child process with the given command and arguments.
|
|
2555
|
-
* @param {string} command - The command to execute.
|
|
2556
|
-
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2557
|
-
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2558
|
-
*/
|
|
2559
1936
|
async spawnCommand(command, args = []) {
|
|
2560
1937
|
const { spawn } = await import('node:child_process');
|
|
2561
|
-
/*
|
|
2562
|
-
npm > npm.cmd on windows
|
|
2563
|
-
cmd.exe ['dir'] on windows
|
|
2564
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2565
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
2566
|
-
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2567
|
-
});
|
|
2568
|
-
|
|
2569
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2570
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2571
|
-
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2572
|
-
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2573
|
-
*/
|
|
2574
1938
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2575
1939
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2576
|
-
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2577
1940
|
const argstring = 'npm ' + args.join(' ');
|
|
2578
1941
|
args.splice(0, args.length, '/c', argstring);
|
|
2579
1942
|
command = 'cmd.exe';
|
|
2580
1943
|
}
|
|
2581
|
-
// Decide when using sudo on linux
|
|
2582
|
-
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2583
|
-
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2584
1944
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2585
1945
|
args.unshift(command);
|
|
2586
1946
|
command = 'sudo';
|
|
@@ -2639,4 +1999,3 @@ export class Matterbridge extends EventEmitter {
|
|
|
2639
1999
|
});
|
|
2640
2000
|
}
|
|
2641
2001
|
}
|
|
2642
|
-
//# sourceMappingURL=matterbridge.js.map
|