matterbridge 2.2.4-dev.2 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -1
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +37 -2
- package/dist/cli.js.map +1 -0
- package/dist/cluster/export.d.ts +2 -0
- package/dist/cluster/export.d.ts.map +1 -0
- package/dist/cluster/export.js +2 -0
- package/dist/cluster/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +27 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +23 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +114 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/frontend.d.ts +221 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +330 -26
- package/dist/frontend.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +422 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +743 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +39 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +33 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1056 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +32 -1
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +177 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +112 -11
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +39 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +33 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +835 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +690 -6
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +2275 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +118 -9
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +251 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +185 -7
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +178 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +24 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +236 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +229 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +77 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +121 -6
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +32 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +45 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +61 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +205 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +32 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +42 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +40 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +65 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/export.d.ts +10 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/isvalid.d.ts +87 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +86 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +70 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +77 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/parameter.d.ts +44 -0
- package/dist/utils/parameter.d.ts.map +1 -0
- package/dist/utils/parameter.js +41 -0
- package/dist/utils/parameter.js.map +1 -0
- package/dist/utils/wait.d.ts +43 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +48 -5
- package/dist/utils/wait.js.map +1 -0
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.819c0908.js → main.4a12038d.js} +4 -4
- package/frontend/build/static/js/{main.819c0908.js.map → main.4a12038d.js.map} +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- /package/frontend/build/static/js/{main.819c0908.js.LICENSE.txt → main.4a12038d.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 1.5.2
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
23
|
+
// Node.js modules
|
|
1
24
|
import os from 'node:os';
|
|
2
25
|
import path from 'node:path';
|
|
3
26
|
import { promises as fs } from 'node:fs';
|
|
4
27
|
import EventEmitter from 'node:events';
|
|
28
|
+
// AnsiLogger module
|
|
5
29
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN } from './logger/export.js';
|
|
30
|
+
// NodeStorage module
|
|
6
31
|
import { NodeStorageManager } from './storage/export.js';
|
|
32
|
+
// Matterbridge
|
|
7
33
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, withTimeout } from './utils/export.js';
|
|
8
34
|
import { logInterfaces, getGlobalNodeModules } from './utils/network.js';
|
|
9
35
|
import { PluginManager } from './pluginManager.js';
|
|
@@ -11,14 +37,19 @@ import { DeviceManager } from './deviceManager.js';
|
|
|
11
37
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
12
38
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
13
39
|
import { Frontend } from './frontend.js';
|
|
40
|
+
// @matter
|
|
14
41
|
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
15
42
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
16
43
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
17
44
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
18
45
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
46
|
+
// Default colors
|
|
19
47
|
const plg = '\u001B[38;5;33m';
|
|
20
48
|
const dev = '\u001B[38;5;79m';
|
|
21
49
|
const typ = '\u001B[38;5;207m';
|
|
50
|
+
/**
|
|
51
|
+
* Represents the Matterbridge application.
|
|
52
|
+
*/
|
|
22
53
|
export class Matterbridge extends EventEmitter {
|
|
23
54
|
systemInformation = {
|
|
24
55
|
interfaceName: '',
|
|
@@ -62,7 +93,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
62
93
|
shellySysUpdate: false,
|
|
63
94
|
shellyMainUpdate: false,
|
|
64
95
|
profile: getParameter('profile'),
|
|
65
|
-
loggerLevel: "info"
|
|
96
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
66
97
|
fileLogger: false,
|
|
67
98
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
68
99
|
matterFileLogger: false,
|
|
@@ -99,9 +130,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
99
130
|
plugins;
|
|
100
131
|
devices;
|
|
101
132
|
frontend = new Frontend(this);
|
|
133
|
+
// Matterbridge storage
|
|
102
134
|
nodeStorage;
|
|
103
135
|
nodeContext;
|
|
104
136
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
|
+
// Cleanup
|
|
105
138
|
hasCleanupStarted = false;
|
|
106
139
|
initialized = false;
|
|
107
140
|
execRunningCount = 0;
|
|
@@ -114,38 +147,70 @@ export class Matterbridge extends EventEmitter {
|
|
|
114
147
|
sigtermHandler;
|
|
115
148
|
exceptionHandler;
|
|
116
149
|
rejectionHandler;
|
|
150
|
+
// Matter environment
|
|
117
151
|
environment = Environment.default;
|
|
152
|
+
// Matter storage
|
|
118
153
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
119
154
|
matterStorageService;
|
|
120
155
|
matterStorageManager;
|
|
121
156
|
matterbridgeContext;
|
|
122
157
|
mattercontrollerContext;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
// Matter parameters
|
|
159
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
160
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
161
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
162
|
+
port; // first server node port
|
|
163
|
+
passcode; // first server node passcode
|
|
164
|
+
discriminator; // first server node discriminator
|
|
129
165
|
serverNode;
|
|
130
166
|
aggregatorNode;
|
|
131
167
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
132
168
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
133
169
|
static instance;
|
|
170
|
+
// We load asyncronously so is private
|
|
134
171
|
constructor() {
|
|
135
172
|
super();
|
|
136
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Emits an event of the specified type with the provided arguments.
|
|
176
|
+
*
|
|
177
|
+
* @template K - The type of the event.
|
|
178
|
+
* @param {K} eventName - The name of the event to emit.
|
|
179
|
+
* @param {...MatterbridgeEvent[K]} args - The arguments to pass to the event listeners.
|
|
180
|
+
* @returns {boolean} - Returns true if the event had listeners, false otherwise.
|
|
181
|
+
*/
|
|
137
182
|
emit(eventName, ...args) {
|
|
138
183
|
return super.emit(eventName, ...args);
|
|
139
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Registers an event listener for the specified event type.
|
|
187
|
+
*
|
|
188
|
+
* @template K - The type of the event.
|
|
189
|
+
* @param {K} eventName - The name of the event to listen for.
|
|
190
|
+
* @param {(...args: MatterbridgeEvent[K]) => void} listener - The callback function to invoke when the event is emitted.
|
|
191
|
+
* @returns {this} - Returns the instance of the Matterbridge class.
|
|
192
|
+
*/
|
|
140
193
|
on(eventName, listener) {
|
|
141
194
|
return super.on(eventName, listener);
|
|
142
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Retrieves the list of Matterbridge devices.
|
|
198
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
199
|
+
*/
|
|
143
200
|
getDevices() {
|
|
144
201
|
return this.devices.array();
|
|
145
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Retrieves the list of registered plugins.
|
|
205
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
206
|
+
*/
|
|
146
207
|
getPlugins() {
|
|
147
208
|
return this.plugins.array();
|
|
148
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Set the logger logLevel for the Matterbridge classes.
|
|
212
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
213
|
+
*/
|
|
149
214
|
async setLogLevel(logLevel) {
|
|
150
215
|
if (this.log)
|
|
151
216
|
this.log.logLevel = logLevel;
|
|
@@ -159,19 +224,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
159
224
|
for (const plugin of this.plugins) {
|
|
160
225
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
161
226
|
continue;
|
|
162
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
163
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
227
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
228
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
229
|
+
}
|
|
230
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
231
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
232
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
233
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
234
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
235
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
170
236
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
171
237
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
172
238
|
}
|
|
239
|
+
/** ***********************************************************************************************************************************/
|
|
240
|
+
/** loadInstance() and cleanup() methods */
|
|
241
|
+
/** ***********************************************************************************************************************************/
|
|
242
|
+
/**
|
|
243
|
+
* Loads an instance of the Matterbridge class.
|
|
244
|
+
* If an instance already exists, return that instance.
|
|
245
|
+
*
|
|
246
|
+
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
247
|
+
* @returns The loaded Matterbridge instance.
|
|
248
|
+
*/
|
|
173
249
|
static async loadInstance(initialize = false) {
|
|
174
250
|
if (!Matterbridge.instance) {
|
|
251
|
+
// eslint-disable-next-line no-console
|
|
175
252
|
if (hasParameter('debug'))
|
|
176
253
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
177
254
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -180,8 +257,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
180
257
|
}
|
|
181
258
|
return Matterbridge.instance;
|
|
182
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Call cleanup().
|
|
262
|
+
* @deprecated This method is deprecated and is only used for jest tests.
|
|
263
|
+
*
|
|
264
|
+
*/
|
|
183
265
|
async destroyInstance() {
|
|
184
266
|
this.log.info(`Destroy instance...`);
|
|
267
|
+
// Save server nodes to close
|
|
185
268
|
const servers = [];
|
|
186
269
|
if (this.bridgeMode === 'bridge') {
|
|
187
270
|
if (this.serverNode)
|
|
@@ -193,55 +276,81 @@ export class Matterbridge extends EventEmitter {
|
|
|
193
276
|
servers.push(plugin.serverNode);
|
|
194
277
|
}
|
|
195
278
|
}
|
|
279
|
+
// Cleanup
|
|
196
280
|
await this.cleanup('destroying instance...', false);
|
|
281
|
+
// Close servers mdns service
|
|
197
282
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
198
283
|
for (const server of servers) {
|
|
199
284
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
200
285
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
201
286
|
}
|
|
287
|
+
// Wait for the cleanup to finish
|
|
202
288
|
await new Promise((resolve) => {
|
|
203
289
|
setTimeout(resolve, 1000);
|
|
204
290
|
});
|
|
205
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Initializes the Matterbridge application.
|
|
294
|
+
*
|
|
295
|
+
* @remarks
|
|
296
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
297
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
298
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
299
|
+
*
|
|
300
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
301
|
+
*/
|
|
206
302
|
async initialize() {
|
|
303
|
+
// Set the restart mode
|
|
207
304
|
if (hasParameter('service'))
|
|
208
305
|
this.restartMode = 'service';
|
|
209
306
|
if (hasParameter('docker'))
|
|
210
307
|
this.restartMode = 'docker';
|
|
308
|
+
// Set the matterbridge directory
|
|
211
309
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
212
310
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
311
|
+
// Setup the matter environment
|
|
213
312
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
214
313
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
215
314
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
216
315
|
this.environment.vars.set('runtime.signals', false);
|
|
217
316
|
this.environment.vars.set('runtime.exitcode', false);
|
|
218
|
-
|
|
317
|
+
// Create the matterbridge logger
|
|
318
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
319
|
+
// Register process handlers
|
|
219
320
|
this.registerProcessHandlers();
|
|
321
|
+
// Initialize nodeStorage and nodeContext
|
|
220
322
|
try {
|
|
221
323
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
222
324
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
223
325
|
this.log.debug('Creating node storage context for matterbridge');
|
|
224
326
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
327
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
225
329
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
226
330
|
for (const key of keys) {
|
|
227
331
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
332
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
228
333
|
await this.nodeStorage?.storage.get(key);
|
|
229
334
|
}
|
|
230
335
|
const storages = await this.nodeStorage.getStorageNames();
|
|
231
336
|
for (const storage of storages) {
|
|
232
337
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
233
338
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
339
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
340
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
341
|
const keys = (await nodeContext?.storage.keys());
|
|
235
342
|
keys.forEach(async (key) => {
|
|
236
343
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
237
344
|
await nodeContext?.get(key);
|
|
238
345
|
});
|
|
239
346
|
}
|
|
347
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
240
348
|
this.log.debug('Creating node storage backup...');
|
|
241
349
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
242
350
|
this.log.debug('Created node storage backup');
|
|
243
351
|
}
|
|
244
352
|
catch (error) {
|
|
353
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
245
354
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
246
355
|
if (hasParameter('norestore')) {
|
|
247
356
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -256,41 +365,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
256
365
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
257
366
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
258
367
|
}
|
|
368
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
259
369
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
370
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
260
371
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
372
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
261
373
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
262
374
|
this.log.debug(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
375
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
263
376
|
if (hasParameter('logger')) {
|
|
264
377
|
const level = getParameter('logger');
|
|
265
378
|
if (level === 'debug') {
|
|
266
|
-
this.log.logLevel = "debug"
|
|
379
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
267
380
|
}
|
|
268
381
|
else if (level === 'info') {
|
|
269
|
-
this.log.logLevel = "info"
|
|
382
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
270
383
|
}
|
|
271
384
|
else if (level === 'notice') {
|
|
272
|
-
this.log.logLevel = "notice"
|
|
385
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
273
386
|
}
|
|
274
387
|
else if (level === 'warn') {
|
|
275
|
-
this.log.logLevel = "warn"
|
|
388
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
276
389
|
}
|
|
277
390
|
else if (level === 'error') {
|
|
278
|
-
this.log.logLevel = "error"
|
|
391
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
279
392
|
}
|
|
280
393
|
else if (level === 'fatal') {
|
|
281
|
-
this.log.logLevel = "fatal"
|
|
394
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
282
395
|
}
|
|
283
396
|
else {
|
|
284
397
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
285
|
-
this.log.logLevel = "info"
|
|
398
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
286
399
|
}
|
|
287
400
|
}
|
|
288
401
|
else {
|
|
289
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
402
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
290
403
|
}
|
|
291
404
|
this.frontend.logLevel = this.log.logLevel;
|
|
292
405
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
293
406
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
407
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
294
408
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
295
409
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
296
410
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -299,6 +413,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
299
413
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
300
414
|
if (this.profile !== undefined)
|
|
301
415
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
416
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
302
417
|
if (hasParameter('matterlogger')) {
|
|
303
418
|
const level = getParameter('matterlogger');
|
|
304
419
|
if (level === 'debug') {
|
|
@@ -330,6 +445,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
330
445
|
Logger.format = MatterLogFormat.ANSI;
|
|
331
446
|
Logger.setLogger('default', this.createMatterLogger());
|
|
332
447
|
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
448
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
333
449
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
334
450
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
335
451
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -338,6 +454,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
338
454
|
});
|
|
339
455
|
}
|
|
340
456
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
457
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
341
458
|
if (hasParameter('mdnsinterface')) {
|
|
342
459
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
343
460
|
}
|
|
@@ -346,6 +463,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
346
463
|
if (this.mdnsInterface === '')
|
|
347
464
|
this.mdnsInterface = undefined;
|
|
348
465
|
}
|
|
466
|
+
// Validate mdnsInterface
|
|
349
467
|
if (this.mdnsInterface) {
|
|
350
468
|
const networkInterfaces = os.networkInterfaces();
|
|
351
469
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -359,6 +477,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
359
477
|
}
|
|
360
478
|
if (this.mdnsInterface)
|
|
361
479
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
480
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
362
481
|
if (hasParameter('ipv4address')) {
|
|
363
482
|
this.ipv4address = getParameter('ipv4address');
|
|
364
483
|
}
|
|
@@ -367,6 +486,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
367
486
|
if (this.ipv4address === '')
|
|
368
487
|
this.ipv4address = undefined;
|
|
369
488
|
}
|
|
489
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
370
490
|
if (hasParameter('ipv6address')) {
|
|
371
491
|
this.ipv6address = getParameter('ipv6address');
|
|
372
492
|
}
|
|
@@ -375,14 +495,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
375
495
|
if (this.ipv6address === '')
|
|
376
496
|
this.ipv6address = undefined;
|
|
377
497
|
}
|
|
498
|
+
// Initialize PluginManager
|
|
378
499
|
this.plugins = new PluginManager(this);
|
|
379
500
|
await this.plugins.loadFromStorage();
|
|
380
501
|
this.plugins.logLevel = this.log.logLevel;
|
|
502
|
+
// Initialize DeviceManager
|
|
381
503
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
382
504
|
this.devices.logLevel = this.log.logLevel;
|
|
505
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
383
506
|
for (const plugin of this.plugins) {
|
|
384
507
|
const packageJson = await this.plugins.parse(plugin);
|
|
385
508
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
509
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
510
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
386
511
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
387
512
|
try {
|
|
388
513
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
@@ -404,6 +529,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
404
529
|
await plugin.nodeContext.set('description', plugin.description);
|
|
405
530
|
await plugin.nodeContext.set('author', plugin.author);
|
|
406
531
|
}
|
|
532
|
+
// Log system info and create .matterbridge directory
|
|
407
533
|
await this.logNodeAndSystemInfo();
|
|
408
534
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
409
535
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -411,6 +537,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
411
537
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
412
538
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
413
539
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
540
|
+
// Check node version and throw error
|
|
414
541
|
const minNodeVersion = 18;
|
|
415
542
|
const nodeVersion = process.versions.node;
|
|
416
543
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -418,9 +545,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
418
545
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
419
546
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
420
547
|
}
|
|
548
|
+
// Parse command line
|
|
421
549
|
await this.parseCommandLine();
|
|
422
550
|
this.initialized = true;
|
|
423
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
554
|
+
* @private
|
|
555
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
556
|
+
*/
|
|
424
557
|
async parseCommandLine() {
|
|
425
558
|
if (hasParameter('help')) {
|
|
426
559
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -532,6 +665,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
532
665
|
this.shutdown = true;
|
|
533
666
|
return;
|
|
534
667
|
}
|
|
668
|
+
// Start the matter storage and create the matterbridge context
|
|
535
669
|
try {
|
|
536
670
|
await this.startMatterStorage();
|
|
537
671
|
}
|
|
@@ -539,12 +673,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
539
673
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
540
674
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
541
675
|
}
|
|
676
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
542
677
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
543
678
|
this.initialized = true;
|
|
544
679
|
await this.shutdownProcessAndReset();
|
|
545
680
|
this.shutdown = true;
|
|
546
681
|
return;
|
|
547
682
|
}
|
|
683
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
548
684
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
549
685
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
550
686
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
@@ -569,30 +705,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
569
705
|
this.shutdown = true;
|
|
570
706
|
return;
|
|
571
707
|
}
|
|
708
|
+
// Initialize frontend
|
|
572
709
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
573
710
|
await this.frontend.start(getIntParameter('frontend'));
|
|
711
|
+
// Check in 30 seconds the latest versions
|
|
574
712
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
575
713
|
const { checkUpdates } = await import('./update.js');
|
|
576
714
|
checkUpdates(this);
|
|
577
715
|
}, 30 * 1000).unref();
|
|
716
|
+
// Check each 24 hours the latest versions
|
|
578
717
|
this.checkUpdateInterval = setInterval(async () => {
|
|
579
718
|
const { checkUpdates } = await import('./update.js');
|
|
580
719
|
checkUpdates(this);
|
|
581
720
|
}, 24 * 60 * 60 * 1000).unref();
|
|
721
|
+
// Start the matterbridge in mode test
|
|
582
722
|
if (hasParameter('test')) {
|
|
583
723
|
this.bridgeMode = 'bridge';
|
|
584
724
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
585
725
|
return;
|
|
586
726
|
}
|
|
727
|
+
// Start the matterbridge in mode controller
|
|
587
728
|
if (hasParameter('controller')) {
|
|
588
729
|
this.bridgeMode = 'controller';
|
|
589
730
|
await this.startController();
|
|
590
731
|
return;
|
|
591
732
|
}
|
|
733
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
592
734
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
593
735
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
594
736
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
595
737
|
}
|
|
738
|
+
// Start matterbridge in bridge mode
|
|
596
739
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
597
740
|
this.bridgeMode = 'bridge';
|
|
598
741
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -600,6 +743,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
600
743
|
await this.startBridge();
|
|
601
744
|
return;
|
|
602
745
|
}
|
|
746
|
+
// Start matterbridge in childbridge mode
|
|
603
747
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
604
748
|
this.bridgeMode = 'childbridge';
|
|
605
749
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -608,10 +752,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
608
752
|
return;
|
|
609
753
|
}
|
|
610
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Asynchronously loads and starts the registered plugins.
|
|
757
|
+
*
|
|
758
|
+
* This method is responsible for initializing and staarting all enabled plugins.
|
|
759
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
760
|
+
*
|
|
761
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
762
|
+
*/
|
|
611
763
|
async startPlugins() {
|
|
764
|
+
// Check, load and start the plugins
|
|
612
765
|
for (const plugin of this.plugins) {
|
|
613
766
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
614
767
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
768
|
+
// Check if the plugin is available
|
|
615
769
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
616
770
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
617
771
|
plugin.enabled = false;
|
|
@@ -631,20 +785,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
631
785
|
plugin.addedDevices = undefined;
|
|
632
786
|
plugin.qrPairingCode = undefined;
|
|
633
787
|
plugin.manualPairingCode = undefined;
|
|
634
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
788
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
635
789
|
}
|
|
636
790
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
637
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
794
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
795
|
+
*/
|
|
638
796
|
registerProcessHandlers() {
|
|
639
797
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
640
798
|
process.removeAllListeners('uncaughtException');
|
|
641
799
|
process.removeAllListeners('unhandledRejection');
|
|
642
800
|
this.exceptionHandler = async (error) => {
|
|
643
801
|
this.log.error('Unhandled Exception detected at:', error.stack || error, rs);
|
|
802
|
+
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
644
803
|
};
|
|
645
804
|
process.on('uncaughtException', this.exceptionHandler);
|
|
646
805
|
this.rejectionHandler = async (reason, promise) => {
|
|
647
806
|
this.log.error('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
807
|
+
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
648
808
|
};
|
|
649
809
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
650
810
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -657,6 +817,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
657
817
|
};
|
|
658
818
|
process.on('SIGTERM', this.sigtermHandler);
|
|
659
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
822
|
+
*/
|
|
660
823
|
deregisterProcesslHandlers() {
|
|
661
824
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
662
825
|
if (this.exceptionHandler)
|
|
@@ -673,12 +836,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
673
836
|
process.off('SIGTERM', this.sigtermHandler);
|
|
674
837
|
this.sigtermHandler = undefined;
|
|
675
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Logs the node and system information.
|
|
841
|
+
*/
|
|
676
842
|
async logNodeAndSystemInfo() {
|
|
843
|
+
// IP address information
|
|
677
844
|
const networkInterfaces = os.networkInterfaces();
|
|
678
845
|
this.systemInformation.interfaceName = '';
|
|
679
846
|
this.systemInformation.ipv4Address = '';
|
|
680
847
|
this.systemInformation.ipv6Address = '';
|
|
681
848
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
849
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
682
850
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
683
851
|
continue;
|
|
684
852
|
if (!interfaceDetails) {
|
|
@@ -704,19 +872,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
704
872
|
break;
|
|
705
873
|
}
|
|
706
874
|
}
|
|
875
|
+
// Node information
|
|
707
876
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
708
877
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
709
878
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
710
879
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
880
|
+
// Host system information
|
|
711
881
|
this.systemInformation.hostname = os.hostname();
|
|
712
882
|
this.systemInformation.user = os.userInfo().username;
|
|
713
|
-
this.systemInformation.osType = os.type();
|
|
714
|
-
this.systemInformation.osRelease = os.release();
|
|
715
|
-
this.systemInformation.osPlatform = os.platform();
|
|
716
|
-
this.systemInformation.osArch = os.arch();
|
|
717
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
718
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
719
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
883
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
884
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
885
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
886
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
887
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
888
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
889
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
890
|
+
// Log the system information
|
|
720
891
|
this.log.debug('Host System Information:');
|
|
721
892
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
722
893
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -732,16 +903,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
732
903
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
733
904
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
734
905
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
906
|
+
// Home directory
|
|
735
907
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
736
908
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
737
909
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
910
|
+
// Package root directory
|
|
738
911
|
const { fileURLToPath } = await import('node:url');
|
|
739
912
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
740
913
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
741
914
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
742
915
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
916
|
+
// Global node_modules directory
|
|
743
917
|
if (this.nodeContext)
|
|
744
918
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
919
|
+
// First run of Matterbridge so the node storage is empty
|
|
745
920
|
if (this.globalModulesDirectory === '') {
|
|
746
921
|
try {
|
|
747
922
|
this.execRunningCount++;
|
|
@@ -757,6 +932,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
757
932
|
}
|
|
758
933
|
else
|
|
759
934
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
935
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should it change the globalModulesDirectory?
|
|
936
|
+
else {
|
|
937
|
+
this.getGlobalNodeModules()
|
|
938
|
+
.then(async (globalModulesDirectory) => {
|
|
939
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
940
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
941
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
942
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
943
|
+
})
|
|
944
|
+
.catch((error) => {
|
|
945
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
946
|
+
});
|
|
947
|
+
}*/
|
|
948
|
+
// Create the data directory .matterbridge in the home directory
|
|
760
949
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
761
950
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
762
951
|
try {
|
|
@@ -780,6 +969,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
780
969
|
}
|
|
781
970
|
}
|
|
782
971
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
972
|
+
// Create the plugin directory Matterbridge in the home directory
|
|
783
973
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
784
974
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
785
975
|
try {
|
|
@@ -803,50 +993,68 @@ export class Matterbridge extends EventEmitter {
|
|
|
803
993
|
}
|
|
804
994
|
}
|
|
805
995
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
996
|
+
// Matterbridge version
|
|
806
997
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
807
998
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = packageJson.version;
|
|
808
999
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeVersion;
|
|
809
1000
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1001
|
+
// Matterbridge latest version
|
|
810
1002
|
if (this.nodeContext)
|
|
811
1003
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
812
1004
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1005
|
+
// this.getMatterbridgeLatestVersion();
|
|
1006
|
+
// Current working directory
|
|
813
1007
|
const currentDir = process.cwd();
|
|
814
1008
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1009
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
815
1010
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
816
1011
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
817
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1015
|
+
*
|
|
1016
|
+
* @returns {Function} The MatterLogger function.
|
|
1017
|
+
*/
|
|
818
1018
|
createMatterLogger() {
|
|
819
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1019
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
820
1020
|
return (_level, formattedLog) => {
|
|
821
1021
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
822
1022
|
const message = formattedLog.slice(65);
|
|
823
1023
|
matterLogger.logName = logger;
|
|
824
1024
|
switch (_level) {
|
|
825
1025
|
case MatterLogLevel.DEBUG:
|
|
826
|
-
matterLogger.log("debug"
|
|
1026
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
827
1027
|
break;
|
|
828
1028
|
case MatterLogLevel.INFO:
|
|
829
|
-
matterLogger.log("info"
|
|
1029
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
830
1030
|
break;
|
|
831
1031
|
case MatterLogLevel.NOTICE:
|
|
832
|
-
matterLogger.log("notice"
|
|
1032
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
833
1033
|
break;
|
|
834
1034
|
case MatterLogLevel.WARN:
|
|
835
|
-
matterLogger.log("warn"
|
|
1035
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
836
1036
|
break;
|
|
837
1037
|
case MatterLogLevel.ERROR:
|
|
838
|
-
matterLogger.log("error"
|
|
1038
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
839
1039
|
break;
|
|
840
1040
|
case MatterLogLevel.FATAL:
|
|
841
|
-
matterLogger.log("fatal"
|
|
1041
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
842
1042
|
break;
|
|
843
1043
|
default:
|
|
844
|
-
matterLogger.log("debug"
|
|
1044
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
845
1045
|
break;
|
|
846
1046
|
}
|
|
847
1047
|
};
|
|
848
1048
|
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Creates a Matter File Logger.
|
|
1051
|
+
*
|
|
1052
|
+
* @param {string} filePath - The path to the log file.
|
|
1053
|
+
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1054
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1055
|
+
*/
|
|
849
1056
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1057
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
850
1058
|
let fileSize = 0;
|
|
851
1059
|
if (unlink) {
|
|
852
1060
|
try {
|
|
@@ -895,12 +1103,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
895
1103
|
}
|
|
896
1104
|
};
|
|
897
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Restarts the process by exiting the current instance and loading a new instance.
|
|
1108
|
+
*/
|
|
898
1109
|
async restartProcess() {
|
|
899
1110
|
await this.cleanup('restarting...', true);
|
|
900
1111
|
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Shut down the process by exiting the current process.
|
|
1114
|
+
*/
|
|
901
1115
|
async shutdownProcess() {
|
|
902
1116
|
await this.cleanup('shutting down...', false);
|
|
903
1117
|
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Update matterbridge and and shut down the process.
|
|
1120
|
+
*/
|
|
904
1121
|
async updateProcess() {
|
|
905
1122
|
this.log.info('Updating matterbridge...');
|
|
906
1123
|
try {
|
|
@@ -913,51 +1130,72 @@ export class Matterbridge extends EventEmitter {
|
|
|
913
1130
|
this.frontend.wssSendRestartRequired();
|
|
914
1131
|
await this.cleanup('updating...', false);
|
|
915
1132
|
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Unregister all devices and shut down the process.
|
|
1135
|
+
*/
|
|
916
1136
|
async unregisterAndShutdownProcess() {
|
|
917
1137
|
this.log.info('Unregistering all devices and shutting down...');
|
|
918
1138
|
for (const plugin of this.plugins) {
|
|
919
1139
|
await this.removeAllBridgedEndpoints(plugin.name, 250);
|
|
920
1140
|
}
|
|
921
1141
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
922
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1142
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
923
1143
|
this.log.debug('Cleaning up and shutting down...');
|
|
924
1144
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
925
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Reset commissioning and shut down the process.
|
|
1148
|
+
*/
|
|
926
1149
|
async shutdownProcessAndReset() {
|
|
927
1150
|
await this.cleanup('shutting down with reset...', false);
|
|
928
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Factory reset and shut down the process.
|
|
1154
|
+
*/
|
|
929
1155
|
async shutdownProcessAndFactoryReset() {
|
|
930
1156
|
await this.cleanup('shutting down with factory reset...', false);
|
|
931
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Cleans up the Matterbridge instance.
|
|
1160
|
+
* @param message - The cleanup message.
|
|
1161
|
+
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1162
|
+
* @returns A promise that resolves when the cleanup is completed.
|
|
1163
|
+
*/
|
|
932
1164
|
async cleanup(message, restart = false) {
|
|
933
1165
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
934
1166
|
this.hasCleanupStarted = true;
|
|
935
1167
|
this.log.info(message);
|
|
1168
|
+
// Clear the start matter interval
|
|
936
1169
|
if (this.startMatterInterval) {
|
|
937
1170
|
clearInterval(this.startMatterInterval);
|
|
938
1171
|
this.startMatterInterval = undefined;
|
|
939
1172
|
this.log.debug('Start matter interval cleared');
|
|
940
1173
|
}
|
|
1174
|
+
// Clear the check update timeout
|
|
941
1175
|
if (this.checkUpdateTimeout) {
|
|
942
1176
|
clearInterval(this.checkUpdateTimeout);
|
|
943
1177
|
this.checkUpdateTimeout = undefined;
|
|
944
1178
|
this.log.debug('Check update timeout cleared');
|
|
945
1179
|
}
|
|
1180
|
+
// Clear the check update interval
|
|
946
1181
|
if (this.checkUpdateInterval) {
|
|
947
1182
|
clearInterval(this.checkUpdateInterval);
|
|
948
1183
|
this.checkUpdateInterval = undefined;
|
|
949
1184
|
this.log.debug('Check update interval cleared');
|
|
950
1185
|
}
|
|
1186
|
+
// Clear the configure timeout
|
|
951
1187
|
if (this.configureTimeout) {
|
|
952
1188
|
clearTimeout(this.configureTimeout);
|
|
953
1189
|
this.configureTimeout = undefined;
|
|
954
1190
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
955
1191
|
}
|
|
1192
|
+
// Clear the reachability timeout
|
|
956
1193
|
if (this.reachabilityTimeout) {
|
|
957
1194
|
clearTimeout(this.reachabilityTimeout);
|
|
958
1195
|
this.reachabilityTimeout = undefined;
|
|
959
1196
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
960
1197
|
}
|
|
1198
|
+
// Calling the shutdown method of each plugin and clear the plugins reachability timeout
|
|
961
1199
|
for (const plugin of this.plugins) {
|
|
962
1200
|
if (!plugin.enabled || plugin.error)
|
|
963
1201
|
continue;
|
|
@@ -968,9 +1206,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
968
1206
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
969
1207
|
}
|
|
970
1208
|
}
|
|
1209
|
+
// Stopping matter server nodes
|
|
971
1210
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
972
1211
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
973
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1212
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second for MessageExchange to finish
|
|
974
1213
|
if (this.bridgeMode === 'bridge') {
|
|
975
1214
|
if (this.serverNode) {
|
|
976
1215
|
await this.stopServerNode(this.serverNode);
|
|
@@ -986,6 +1225,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
986
1225
|
}
|
|
987
1226
|
}
|
|
988
1227
|
this.log.notice('Stopped matter server nodes');
|
|
1228
|
+
// Matter commisioning reset
|
|
989
1229
|
if (message === 'shutting down with reset...') {
|
|
990
1230
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
991
1231
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -995,17 +1235,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
995
1235
|
await this.matterbridgeContext?.clearAll();
|
|
996
1236
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
997
1237
|
}
|
|
1238
|
+
// Stop matter storage
|
|
998
1239
|
await this.stopMatterStorage();
|
|
1240
|
+
// Stop the frontend
|
|
999
1241
|
await this.frontend.stop();
|
|
1242
|
+
// Remove the matterfilelogger
|
|
1000
1243
|
try {
|
|
1001
1244
|
Logger.removeLogger('matterfilelogger');
|
|
1245
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1002
1246
|
}
|
|
1003
1247
|
catch (error) {
|
|
1248
|
+
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1004
1249
|
}
|
|
1250
|
+
// Serialize registeredDevices
|
|
1005
1251
|
if (this.nodeStorage && this.nodeContext) {
|
|
1252
|
+
/*
|
|
1253
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1254
|
+
this.log.info('Saving registered devices...');
|
|
1255
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1256
|
+
this.devices.forEach(async (device) => {
|
|
1257
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1258
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1259
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1260
|
+
});
|
|
1261
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1262
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1263
|
+
*/
|
|
1264
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1006
1265
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1007
1266
|
await this.nodeContext.close();
|
|
1008
1267
|
this.nodeContext = undefined;
|
|
1268
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1009
1269
|
for (const plugin of this.plugins) {
|
|
1010
1270
|
if (plugin.nodeContext) {
|
|
1011
1271
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1022,8 +1282,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1022
1282
|
}
|
|
1023
1283
|
this.plugins.clear();
|
|
1024
1284
|
this.devices.clear();
|
|
1285
|
+
// Factory reset
|
|
1025
1286
|
if (message === 'shutting down with factory reset...') {
|
|
1026
1287
|
try {
|
|
1288
|
+
// Delete old matter storage file and backup
|
|
1027
1289
|
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1028
1290
|
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1029
1291
|
await fs.unlink(file);
|
|
@@ -1037,6 +1299,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1037
1299
|
}
|
|
1038
1300
|
}
|
|
1039
1301
|
try {
|
|
1302
|
+
// Delete matter node storage directory with its subdirectories and backup
|
|
1040
1303
|
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1041
1304
|
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1042
1305
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1050,6 +1313,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1050
1313
|
}
|
|
1051
1314
|
}
|
|
1052
1315
|
try {
|
|
1316
|
+
// Delete node storage directory with its subdirectories and backup
|
|
1053
1317
|
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1054
1318
|
this.log.info(`Removing storage directory: ${dir}`);
|
|
1055
1319
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1064,12 +1328,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1064
1328
|
}
|
|
1065
1329
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1066
1330
|
}
|
|
1331
|
+
// Deregisters the process handlers
|
|
1067
1332
|
this.deregisterProcesslHandlers();
|
|
1068
1333
|
if (restart) {
|
|
1069
1334
|
if (message === 'updating...') {
|
|
1070
1335
|
this.log.info('Cleanup completed. Updating...');
|
|
1071
1336
|
Matterbridge.instance = undefined;
|
|
1072
|
-
this.emit('update');
|
|
1337
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1073
1338
|
}
|
|
1074
1339
|
else if (message === 'restarting...') {
|
|
1075
1340
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1089,6 +1354,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1089
1354
|
this.log.debug('Cleanup already started...');
|
|
1090
1355
|
}
|
|
1091
1356
|
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1359
|
+
*
|
|
1360
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1361
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1362
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the device.
|
|
1363
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1364
|
+
*/
|
|
1092
1365
|
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1093
1366
|
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1094
1367
|
plugin.locked = true;
|
|
@@ -1101,6 +1374,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1101
1374
|
await this.startServerNode(plugin.serverNode);
|
|
1102
1375
|
}
|
|
1103
1376
|
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Creates and configures the server node for a dynamic plugin.
|
|
1379
|
+
*
|
|
1380
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1381
|
+
* @param {boolean} [start=false] - Whether to start the server node after adding the aggregator node.
|
|
1382
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the dynamic plugin is created and configured.
|
|
1383
|
+
*/
|
|
1104
1384
|
async createDynamicPlugin(plugin, start = false) {
|
|
1105
1385
|
if (!plugin.locked) {
|
|
1106
1386
|
plugin.locked = true;
|
|
@@ -1112,7 +1392,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1112
1392
|
await this.startServerNode(plugin.serverNode);
|
|
1113
1393
|
}
|
|
1114
1394
|
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Starts the Matterbridge in bridge mode.
|
|
1397
|
+
* @private
|
|
1398
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1399
|
+
*/
|
|
1115
1400
|
async startBridge() {
|
|
1401
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1116
1402
|
if (!this.matterStorageManager)
|
|
1117
1403
|
throw new Error('No storage manager initialized');
|
|
1118
1404
|
if (!this.matterbridgeContext)
|
|
@@ -1150,7 +1436,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1150
1436
|
clearInterval(this.startMatterInterval);
|
|
1151
1437
|
this.startMatterInterval = undefined;
|
|
1152
1438
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1439
|
+
// Start the Matter server node
|
|
1153
1440
|
this.startServerNode(this.serverNode);
|
|
1441
|
+
// Configure the plugins
|
|
1154
1442
|
this.configureTimeout = setTimeout(async () => {
|
|
1155
1443
|
for (const plugin of this.plugins) {
|
|
1156
1444
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1168,6 +1456,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1168
1456
|
}
|
|
1169
1457
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1170
1458
|
}, 30 * 1000);
|
|
1459
|
+
// Setting reachability to true
|
|
1171
1460
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1172
1461
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1173
1462
|
if (this.aggregatorNode)
|
|
@@ -1176,6 +1465,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1176
1465
|
}, 60 * 1000);
|
|
1177
1466
|
}, 1000);
|
|
1178
1467
|
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1470
|
+
* @private
|
|
1471
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1472
|
+
*/
|
|
1179
1473
|
async startChildbridge() {
|
|
1180
1474
|
if (!this.matterStorageManager)
|
|
1181
1475
|
throw new Error('No storage manager initialized');
|
|
@@ -1220,6 +1514,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1220
1514
|
clearInterval(this.startMatterInterval);
|
|
1221
1515
|
this.startMatterInterval = undefined;
|
|
1222
1516
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1517
|
+
// Configure the plugins
|
|
1223
1518
|
this.configureTimeout = setTimeout(async () => {
|
|
1224
1519
|
for (const plugin of this.plugins) {
|
|
1225
1520
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1256,7 +1551,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1256
1551
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1257
1552
|
continue;
|
|
1258
1553
|
}
|
|
1554
|
+
// Start the Matter server node
|
|
1259
1555
|
this.startServerNode(plugin.serverNode);
|
|
1556
|
+
// Setting reachability to true
|
|
1260
1557
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1261
1558
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggragator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1262
1559
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1266,9 +1563,219 @@ export class Matterbridge extends EventEmitter {
|
|
|
1266
1563
|
}
|
|
1267
1564
|
}, 1000);
|
|
1268
1565
|
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Starts the Matterbridge controller.
|
|
1568
|
+
* @private
|
|
1569
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1570
|
+
*/
|
|
1269
1571
|
async startController() {
|
|
1572
|
+
/*
|
|
1573
|
+
if (!this.storageManager) {
|
|
1574
|
+
this.log.error('No storage manager initialized');
|
|
1575
|
+
await this.cleanup('No storage manager initialized');
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1579
|
+
this.mattercontrollerContext = this.storageManager.createContext('mattercontrollerContext');
|
|
1580
|
+
if (!this.mattercontrollerContext) {
|
|
1581
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1582
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1587
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1588
|
+
this.log.info('Creating matter commissioning controller');
|
|
1589
|
+
this.commissioningController = new CommissioningController({
|
|
1590
|
+
autoConnect: false,
|
|
1591
|
+
});
|
|
1592
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1593
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1594
|
+
|
|
1595
|
+
this.log.info('Starting matter server');
|
|
1596
|
+
await this.matterServer.start();
|
|
1597
|
+
this.log.info('Matter server started');
|
|
1598
|
+
|
|
1599
|
+
if (hasParameter('pairingcode')) {
|
|
1600
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1601
|
+
const pairingCode = getParameter('pairingcode');
|
|
1602
|
+
const ip = this.mattercontrollerContext.has('ip') ? this.mattercontrollerContext.get<string>('ip') : undefined;
|
|
1603
|
+
const port = this.mattercontrollerContext.has('port') ? this.mattercontrollerContext.get<number>('port') : undefined;
|
|
1604
|
+
|
|
1605
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1606
|
+
if (pairingCode !== undefined) {
|
|
1607
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1608
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1609
|
+
longDiscriminator = undefined;
|
|
1610
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1611
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1612
|
+
} else {
|
|
1613
|
+
longDiscriminator = await this.mattercontrollerContext.get('longDiscriminator', 3840);
|
|
1614
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1615
|
+
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1616
|
+
}
|
|
1617
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1618
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1622
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1623
|
+
regulatoryCountryCode: 'XX',
|
|
1624
|
+
};
|
|
1625
|
+
const options = {
|
|
1626
|
+
commissioning: commissioningOptions,
|
|
1627
|
+
discovery: {
|
|
1628
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1629
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1630
|
+
},
|
|
1631
|
+
passcode: setupPin,
|
|
1632
|
+
} as NodeCommissioningOptions;
|
|
1633
|
+
this.log.info('Commissioning with options:', options);
|
|
1634
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1635
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1636
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1637
|
+
} // (hasParameter('pairingcode'))
|
|
1638
|
+
|
|
1639
|
+
if (hasParameter('unpairall')) {
|
|
1640
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1641
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1642
|
+
for (const nodeId of nodeIds) {
|
|
1643
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1644
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1645
|
+
}
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
if (hasParameter('discover')) {
|
|
1650
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1651
|
+
// console.log(discover);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1655
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1660
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1661
|
+
for (const nodeId of nodeIds) {
|
|
1662
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1663
|
+
|
|
1664
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1665
|
+
autoSubscribe: false,
|
|
1666
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1667
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1668
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1669
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1670
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1671
|
+
switch (info) {
|
|
1672
|
+
case NodeStateInformation.Connected:
|
|
1673
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1674
|
+
break;
|
|
1675
|
+
case NodeStateInformation.Disconnected:
|
|
1676
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1677
|
+
break;
|
|
1678
|
+
case NodeStateInformation.Reconnecting:
|
|
1679
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1680
|
+
break;
|
|
1681
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1682
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1683
|
+
break;
|
|
1684
|
+
case NodeStateInformation.StructureChanged:
|
|
1685
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1686
|
+
break;
|
|
1687
|
+
case NodeStateInformation.Decommissioned:
|
|
1688
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1689
|
+
break;
|
|
1690
|
+
default:
|
|
1691
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1692
|
+
break;
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
node.logStructure();
|
|
1698
|
+
|
|
1699
|
+
// Get the interaction client
|
|
1700
|
+
this.log.info('Getting the interaction client');
|
|
1701
|
+
const interactionClient = await node.getInteractionClient();
|
|
1702
|
+
let cluster;
|
|
1703
|
+
let attributes;
|
|
1704
|
+
|
|
1705
|
+
// Log BasicInformationCluster
|
|
1706
|
+
cluster = BasicInformationCluster;
|
|
1707
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1708
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1709
|
+
});
|
|
1710
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1711
|
+
attributes.forEach((attribute) => {
|
|
1712
|
+
this.log.info(
|
|
1713
|
+
`- 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}`,
|
|
1714
|
+
);
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
// Log PowerSourceCluster
|
|
1718
|
+
cluster = PowerSourceCluster;
|
|
1719
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1720
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1721
|
+
});
|
|
1722
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1723
|
+
attributes.forEach((attribute) => {
|
|
1724
|
+
this.log.info(
|
|
1725
|
+
`- 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}`,
|
|
1726
|
+
);
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
// Log ThreadNetworkDiagnostics
|
|
1730
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1731
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1732
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1733
|
+
});
|
|
1734
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1735
|
+
attributes.forEach((attribute) => {
|
|
1736
|
+
this.log.info(
|
|
1737
|
+
`- 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}`,
|
|
1738
|
+
);
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
// Log SwitchCluster
|
|
1742
|
+
cluster = SwitchCluster;
|
|
1743
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1744
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1745
|
+
});
|
|
1746
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1747
|
+
attributes.forEach((attribute) => {
|
|
1748
|
+
this.log.info(
|
|
1749
|
+
`- 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}`,
|
|
1750
|
+
);
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1754
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1755
|
+
ignoreInitialTriggers: false,
|
|
1756
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1757
|
+
this.log.info(
|
|
1758
|
+
`***${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}`,
|
|
1759
|
+
),
|
|
1760
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1761
|
+
this.log.info(
|
|
1762
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1763
|
+
);
|
|
1764
|
+
},
|
|
1765
|
+
});
|
|
1766
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1767
|
+
}
|
|
1768
|
+
*/
|
|
1270
1769
|
}
|
|
1770
|
+
/** ***********************************************************************************************************************************/
|
|
1771
|
+
/** Matter.js methods */
|
|
1772
|
+
/** ***********************************************************************************************************************************/
|
|
1773
|
+
/**
|
|
1774
|
+
* Starts the matter storage process with name Matterbridge.
|
|
1775
|
+
* @returns {Promise<void>} - A promise that resolves when the storage process is started.
|
|
1776
|
+
*/
|
|
1271
1777
|
async startMatterStorage() {
|
|
1778
|
+
// Setup Matter storage
|
|
1272
1779
|
this.log.info(`Starting matter node storage...`);
|
|
1273
1780
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1274
1781
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1276,13 +1783,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1276
1783
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1277
1784
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1278
1785
|
this.log.info('Matter node storage started');
|
|
1786
|
+
// Backup matter storage since it is created/opened correctly
|
|
1279
1787
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1280
1788
|
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1791
|
+
*
|
|
1792
|
+
* @param storageName - The name of the storage directory to be backed up.
|
|
1793
|
+
* @param backupName - The name of the backup directory to be created.
|
|
1794
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1795
|
+
*/
|
|
1281
1796
|
async backupMatterStorage(storageName, backupName) {
|
|
1282
1797
|
this.log.info('Creating matter node storage backup...');
|
|
1283
1798
|
await copyDirectory(storageName, backupName);
|
|
1284
1799
|
this.log.info('Created matter node storage backup');
|
|
1285
1800
|
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Stops the matter storage.
|
|
1803
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1804
|
+
*/
|
|
1286
1805
|
async stopMatterStorage() {
|
|
1287
1806
|
this.log.info('Closing matter node storage...');
|
|
1288
1807
|
this.matterStorageManager?.close();
|
|
@@ -1291,6 +1810,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1291
1810
|
this.matterbridgeContext = undefined;
|
|
1292
1811
|
this.log.info('Matter node storage closed');
|
|
1293
1812
|
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Creates a server node storage context.
|
|
1815
|
+
*
|
|
1816
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1817
|
+
* @param {string} deviceName - The name of the device.
|
|
1818
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1819
|
+
* @param {number} vendorId - The vendor ID.
|
|
1820
|
+
* @param {string} vendorName - The vendor name.
|
|
1821
|
+
* @param {number} productId - The product ID.
|
|
1822
|
+
* @param {string} productName - The product name.
|
|
1823
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1824
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1825
|
+
*/
|
|
1294
1826
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1295
1827
|
const { randomBytes } = await import('node:crypto');
|
|
1296
1828
|
if (!this.matterStorageService)
|
|
@@ -1324,6 +1856,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1324
1856
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1325
1857
|
return storageContext;
|
|
1326
1858
|
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Creates a server node.
|
|
1861
|
+
*
|
|
1862
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1863
|
+
* @param {number} [port=5540] - The port number for the server node. Defaults to 5540.
|
|
1864
|
+
* @param {number} [passcode=20242025] - The passcode for the server node. Defaults to 20242025.
|
|
1865
|
+
* @param {number} [discriminator=3850] - The discriminator for the server node. Defaults to 3850.
|
|
1866
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1867
|
+
*/
|
|
1327
1868
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1328
1869
|
const storeId = await storageContext.get('storeId');
|
|
1329
1870
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1333,21 +1874,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1333
1874
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1334
1875
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1335
1876
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1877
|
+
/**
|
|
1878
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1879
|
+
*/
|
|
1336
1880
|
const serverNode = await ServerNode.create({
|
|
1881
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1337
1882
|
id: storeId,
|
|
1883
|
+
// Provide Network relevant configuration like the port
|
|
1884
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1338
1885
|
network: {
|
|
1339
1886
|
listeningAddressIpv4: this.ipv4address,
|
|
1340
1887
|
listeningAddressIpv6: this.ipv6address,
|
|
1341
1888
|
port,
|
|
1342
1889
|
},
|
|
1890
|
+
// Provide Commissioning relevant settings
|
|
1891
|
+
// Optional for development/testing purposes
|
|
1343
1892
|
commissioning: {
|
|
1344
1893
|
passcode,
|
|
1345
1894
|
discriminator,
|
|
1346
1895
|
},
|
|
1896
|
+
// Provide Node announcement settings
|
|
1897
|
+
// Optional: If Ommitted some development defaults are used
|
|
1347
1898
|
productDescription: {
|
|
1348
1899
|
name: await storageContext.get('deviceName'),
|
|
1349
1900
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1350
1901
|
},
|
|
1902
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1903
|
+
// Optional: If Omitted some development defaults are used
|
|
1351
1904
|
basicInformation: {
|
|
1352
1905
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1353
1906
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1364,12 +1917,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1364
1917
|
},
|
|
1365
1918
|
});
|
|
1366
1919
|
const sanitizeFabrics = (fabrics, resetSessions = false) => {
|
|
1920
|
+
// New type of fabric information: Record<FabricIndex, ExposedFabricInformation>
|
|
1367
1921
|
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1368
1922
|
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1369
1923
|
if (this.bridgeMode === 'bridge') {
|
|
1370
1924
|
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1371
1925
|
if (resetSessions)
|
|
1372
|
-
this.matterbridgeSessionInformations = undefined;
|
|
1926
|
+
this.matterbridgeSessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1373
1927
|
this.matterbridgePaired = true;
|
|
1374
1928
|
}
|
|
1375
1929
|
if (this.bridgeMode === 'childbridge') {
|
|
@@ -1377,13 +1931,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1377
1931
|
if (plugin) {
|
|
1378
1932
|
plugin.fabricInformations = sanitizedFabrics;
|
|
1379
1933
|
if (resetSessions)
|
|
1380
|
-
plugin.sessionInformations = undefined;
|
|
1934
|
+
plugin.sessionInformations = undefined; // Changed cause Invoke Matterbridge.operationalCredentials.updateFabricLabel is sent after the session is created
|
|
1381
1935
|
plugin.paired = true;
|
|
1382
1936
|
}
|
|
1383
1937
|
}
|
|
1384
1938
|
};
|
|
1939
|
+
/**
|
|
1940
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
1941
|
+
* This means: It is added to the first fabric.
|
|
1942
|
+
*/
|
|
1385
1943
|
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1944
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1386
1945
|
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1946
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1387
1947
|
serverNode.lifecycle.online.on(async () => {
|
|
1388
1948
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1389
1949
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1431,6 +1991,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1431
1991
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1432
1992
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1433
1993
|
});
|
|
1994
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1434
1995
|
serverNode.lifecycle.offline.on(() => {
|
|
1435
1996
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1436
1997
|
if (this.bridgeMode === 'bridge') {
|
|
@@ -1454,6 +2015,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1454
2015
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1455
2016
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1456
2017
|
});
|
|
2018
|
+
/**
|
|
2019
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2020
|
+
* information is needed.
|
|
2021
|
+
*/
|
|
1457
2022
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1458
2023
|
let action = '';
|
|
1459
2024
|
switch (fabricAction) {
|
|
@@ -1487,16 +2052,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1487
2052
|
}
|
|
1488
2053
|
}
|
|
1489
2054
|
};
|
|
2055
|
+
/**
|
|
2056
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2057
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2058
|
+
*/
|
|
1490
2059
|
serverNode.events.sessions.opened.on((session) => {
|
|
1491
2060
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1492
2061
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1493
2062
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1494
2063
|
});
|
|
2064
|
+
/**
|
|
2065
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2066
|
+
*/
|
|
1495
2067
|
serverNode.events.sessions.closed.on((session) => {
|
|
1496
2068
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1497
2069
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1498
2070
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1499
2071
|
});
|
|
2072
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1500
2073
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1501
2074
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1502
2075
|
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
@@ -1505,24 +2078,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1505
2078
|
this.log.info(`Created server node for ${storeId}`);
|
|
1506
2079
|
return serverNode;
|
|
1507
2080
|
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Starts the specified server node.
|
|
2083
|
+
*
|
|
2084
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2085
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2086
|
+
*/
|
|
1508
2087
|
async startServerNode(matterServerNode) {
|
|
1509
2088
|
if (!matterServerNode)
|
|
1510
2089
|
return;
|
|
1511
2090
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1512
2091
|
await matterServerNode.start();
|
|
1513
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Stops the specified server node.
|
|
2095
|
+
*
|
|
2096
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2097
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2098
|
+
*/
|
|
1514
2099
|
async stopServerNode(matterServerNode) {
|
|
1515
2100
|
if (!matterServerNode)
|
|
1516
2101
|
return;
|
|
1517
2102
|
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1518
2103
|
try {
|
|
1519
|
-
await withTimeout(matterServerNode.close(), 30000);
|
|
2104
|
+
await withTimeout(matterServerNode.close(), 30000); // 30 seconds timeout to allow slow devices to close gracefully
|
|
1520
2105
|
this.log.info(`Closed ${matterServerNode.id} server node`);
|
|
1521
2106
|
}
|
|
1522
2107
|
catch (error) {
|
|
1523
2108
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1524
2109
|
}
|
|
1525
2110
|
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Advertises the specified server node.
|
|
2113
|
+
*
|
|
2114
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2115
|
+
* @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.
|
|
2116
|
+
*/
|
|
1526
2117
|
async advertiseServerNode(matterServerNode) {
|
|
1527
2118
|
if (matterServerNode) {
|
|
1528
2119
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1531,23 +2122,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
1531
2122
|
return { qrPairingCode, manualPairingCode };
|
|
1532
2123
|
}
|
|
1533
2124
|
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Stop advertise the specified server node.
|
|
2127
|
+
*
|
|
2128
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2129
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2130
|
+
*/
|
|
1534
2131
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1535
2132
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1536
2133
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1537
2134
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1538
2135
|
}
|
|
1539
2136
|
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Creates an aggregator node with the specified storage context.
|
|
2139
|
+
*
|
|
2140
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2141
|
+
* @returns {Promise<EndpointNode<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2142
|
+
*/
|
|
1540
2143
|
async createAggregatorNode(storageContext) {
|
|
1541
2144
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1542
2145
|
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1543
2146
|
return aggregatorNode;
|
|
1544
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2150
|
+
*
|
|
2151
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2152
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2153
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2154
|
+
*/
|
|
1545
2155
|
async addBridgedEndpoint(pluginName, device) {
|
|
2156
|
+
// Check if the plugin is registered
|
|
1546
2157
|
const plugin = this.plugins.get(pluginName);
|
|
1547
2158
|
if (!plugin) {
|
|
1548
2159
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1549
2160
|
return;
|
|
1550
2161
|
}
|
|
2162
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1551
2163
|
if (this.bridgeMode === 'bridge') {
|
|
1552
2164
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1553
2165
|
if (!this.aggregatorNode)
|
|
@@ -1570,17 +2182,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1570
2182
|
plugin.registeredDevices++;
|
|
1571
2183
|
if (plugin.addedDevices !== undefined)
|
|
1572
2184
|
plugin.addedDevices++;
|
|
2185
|
+
// Add the device to the DeviceManager
|
|
1573
2186
|
this.devices.set(device);
|
|
2187
|
+
// Subscribe to the reachable$Changed event
|
|
1574
2188
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1575
2189
|
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}`);
|
|
1576
2190
|
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2193
|
+
*
|
|
2194
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2195
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2196
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2197
|
+
*/
|
|
1577
2198
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1578
2199
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2200
|
+
// Check if the plugin is registered
|
|
1579
2201
|
const plugin = this.plugins.get(pluginName);
|
|
1580
2202
|
if (!plugin) {
|
|
1581
2203
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1582
2204
|
return;
|
|
1583
2205
|
}
|
|
2206
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1584
2207
|
if (this.bridgeMode === 'bridge') {
|
|
1585
2208
|
if (!this.aggregatorNode) {
|
|
1586
2209
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1595,6 +2218,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1595
2218
|
}
|
|
1596
2219
|
else if (this.bridgeMode === 'childbridge') {
|
|
1597
2220
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2221
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1598
2222
|
}
|
|
1599
2223
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1600
2224
|
if (!plugin.aggregatorNode) {
|
|
@@ -1609,8 +2233,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1609
2233
|
if (plugin.addedDevices !== undefined)
|
|
1610
2234
|
plugin.addedDevices--;
|
|
1611
2235
|
}
|
|
2236
|
+
// Remove the device from the DeviceManager
|
|
1612
2237
|
this.devices.remove(device);
|
|
1613
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2241
|
+
*
|
|
2242
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2243
|
+
* @param {number} [delay=0] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2244
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2245
|
+
*/
|
|
1614
2246
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1615
2247
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1616
2248
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1619,9 +2251,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1619
2251
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1620
2252
|
}
|
|
1621
2253
|
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2256
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2257
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2258
|
+
*
|
|
2259
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2260
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2261
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2262
|
+
*/
|
|
1622
2263
|
async subscribeAttributeChanged(plugin, device) {
|
|
1623
2264
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1624
2265
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2266
|
+
/*
|
|
2267
|
+
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) subscribed to reachable$Changed`);
|
|
2268
|
+
setTimeout(async () => {
|
|
2269
|
+
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) changed to reachable false`);
|
|
2270
|
+
await plugin.serverNode?.setStateOf(BasicInformationServer, { reachable: false });
|
|
2271
|
+
}, 60000).unref();
|
|
2272
|
+
*/
|
|
1625
2273
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1626
2274
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
1627
2275
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
@@ -1634,6 +2282,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1634
2282
|
});
|
|
1635
2283
|
}
|
|
1636
2284
|
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2287
|
+
*
|
|
2288
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2289
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2290
|
+
*/
|
|
1637
2291
|
sanitizeFabricInformations(fabricInfo) {
|
|
1638
2292
|
return fabricInfo.map((info) => {
|
|
1639
2293
|
return {
|
|
@@ -1647,6 +2301,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1647
2301
|
};
|
|
1648
2302
|
});
|
|
1649
2303
|
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2306
|
+
*
|
|
2307
|
+
* @param {SessionInformation[]} sessionInfo - The array of session information objects.
|
|
2308
|
+
* @returns {SanitizedSessionInformation[]} An array of sanitized session information objects.
|
|
2309
|
+
*/
|
|
1650
2310
|
sanitizeSessionInformation(sessionInfo) {
|
|
1651
2311
|
return sessionInfo
|
|
1652
2312
|
.filter((session) => session.isPeerActive)
|
|
@@ -1674,7 +2334,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1674
2334
|
};
|
|
1675
2335
|
});
|
|
1676
2336
|
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2339
|
+
* @param {EndpointNode<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2340
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2341
|
+
*/
|
|
2342
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1677
2343
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2344
|
+
/*
|
|
2345
|
+
for (const child of aggregatorNode.parts) {
|
|
2346
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2347
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2348
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2349
|
+
}
|
|
2350
|
+
*/
|
|
1678
2351
|
}
|
|
1679
2352
|
getVendorIdName = (vendorId) => {
|
|
1680
2353
|
if (!vendorId)
|
|
@@ -1714,14 +2387,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1714
2387
|
}
|
|
1715
2388
|
return vendorName;
|
|
1716
2389
|
};
|
|
2390
|
+
/**
|
|
2391
|
+
* Spawns a child process with the given command and arguments.
|
|
2392
|
+
* @param {string} command - The command to execute.
|
|
2393
|
+
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2394
|
+
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2395
|
+
*/
|
|
1717
2396
|
async spawnCommand(command, args = []) {
|
|
1718
2397
|
const { spawn } = await import('node:child_process');
|
|
2398
|
+
/*
|
|
2399
|
+
npm > npm.cmd on windows
|
|
2400
|
+
cmd.exe ['dir'] on windows
|
|
2401
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2402
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
2403
|
+
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2404
|
+
});
|
|
2405
|
+
|
|
2406
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2407
|
+
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2408
|
+
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2409
|
+
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2410
|
+
*/
|
|
1719
2411
|
const cmdLine = command + ' ' + args.join(' ');
|
|
1720
2412
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2413
|
+
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
1721
2414
|
const argstring = 'npm ' + args.join(' ');
|
|
1722
2415
|
args.splice(0, args.length, '/c', argstring);
|
|
1723
2416
|
command = 'cmd.exe';
|
|
1724
2417
|
}
|
|
2418
|
+
// Decide when using sudo on linux
|
|
2419
|
+
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2420
|
+
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
1725
2421
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
1726
2422
|
args.unshift(command);
|
|
1727
2423
|
command = 'sudo';
|
|
@@ -1780,3 +2476,4 @@ export class Matterbridge extends EventEmitter {
|
|
|
1780
2476
|
});
|
|
1781
2477
|
}
|
|
1782
2478
|
}
|
|
2479
|
+
//# sourceMappingURL=matterbridge.js.map
|