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