matterbridge 1.7.3 → 2.0.0-edge.3
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 +30 -1
- package/dist/cli.js +3 -39
- package/dist/cluster/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -23
- package/dist/deviceManager.js +1 -26
- package/dist/frontend.js +1199 -0
- package/dist/index.js +1 -36
- package/dist/logger/export.js +0 -1
- package/dist/matter/export.js +0 -5
- package/dist/matterbridge.js +521 -2681
- package/dist/matterbridgeAccessoryPlatform.js +0 -33
- package/dist/matterbridgeBehaviors.js +23 -31
- package/dist/matterbridgeDeviceTypes.js +11 -82
- package/dist/matterbridgeDynamicPlatform.js +0 -33
- package/dist/matterbridgeEndpoint.js +45 -1145
- package/dist/matterbridgePlatform.js +14 -145
- package/dist/matterbridgeTypes.js +3 -24
- package/dist/pluginManager.js +5 -247
- package/dist/storage/export.js +0 -1
- package/dist/utils/colorUtils.js +2 -205
- package/dist/utils/export.js +0 -1
- package/dist/utils/utils.js +7 -252
- package/frontend/build/asset-manifest.json +3 -3
- package/frontend/build/index.html +1 -1
- package/frontend/build/static/js/{main.6bbd1772.js → main.6df4ebe4.js} +3 -3
- package/frontend/build/static/js/main.6df4ebe4.js.map +1 -0
- package/npm-shrinkwrap.json +9 -9
- package/package.json +2 -3
- package/dist/cli.d.ts +0 -25
- 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 -46
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/index.d.ts +0 -40
- 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/export.d.ts +0 -11
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -483
- 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 -942
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDevice.d.ts +0 -7077
- package/dist/matterbridgeDevice.d.ts.map +0 -1
- package/dist/matterbridgeDevice.js +0 -2736
- package/dist/matterbridgeDevice.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -109
- 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/matterbridgeEdge.d.ts +0 -91
- package/dist/matterbridgeEdge.d.ts.map +0 -1
- package/dist/matterbridgeEdge.js +0 -1077
- package/dist/matterbridgeEdge.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -10156
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -168
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -172
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/matterbridgeWebsocket.d.ts +0 -49
- package/dist/matterbridgeWebsocket.d.ts.map +0 -1
- package/dist/matterbridgeWebsocket.js +0 -325
- package/dist/matterbridgeWebsocket.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -238
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.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/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/export.d.ts +0 -3
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/utils.d.ts +0 -221
- package/dist/utils/utils.d.ts.map +0 -1
- package/dist/utils/utils.js.map +0 -1
- package/frontend/build/static/js/main.6bbd1772.js.map +0 -1
- /package/frontend/build/static/js/{main.6bbd1772.js.LICENSE.txt → main.6df4ebe4.js.LICENSE.txt} +0 -0
package/dist/matterbridge.js
CHANGED
|
@@ -1,64 +1,24 @@
|
|
|
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 { fileURLToPath } from 'url';
|
|
25
2
|
import { promises as fs } from 'fs';
|
|
26
3
|
import { exec, spawn } from 'child_process';
|
|
27
|
-
import { createServer } from 'http';
|
|
28
4
|
import EventEmitter from 'events';
|
|
29
5
|
import os from 'os';
|
|
30
6
|
import path from 'path';
|
|
31
7
|
import { randomBytes } from 'crypto';
|
|
32
|
-
// Package modules
|
|
33
|
-
import https from 'https';
|
|
34
|
-
import express from 'express';
|
|
35
|
-
import WebSocket, { WebSocketServer } from 'ws';
|
|
36
|
-
// NodeStorage and AnsiLogger modules
|
|
37
8
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
38
|
-
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify,
|
|
39
|
-
|
|
40
|
-
import { MatterbridgeDevice } from './matterbridgeDevice.js';
|
|
41
|
-
import { WS_ID_LOG, WS_ID_REFRESH_NEEDED, WS_ID_RESTART_NEEDED, wsMessageHandler } from './matterbridgeWebsocket.js';
|
|
42
|
-
import { logInterfaces, wait, waiter, createZip, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
9
|
+
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt } from 'node-ansi-logger';
|
|
10
|
+
import { logInterfaces, wait, waiter, copyDirectory, getParameter, getIntParameter, hasParameter } from './utils/utils.js';
|
|
43
11
|
import { PluginManager } from './pluginManager.js';
|
|
44
12
|
import { DeviceManager } from './deviceManager.js';
|
|
45
13
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
46
|
-
|
|
47
|
-
import { DeviceTypeId, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId,
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
// @project-chip
|
|
52
|
-
import { CommissioningController, CommissioningServer, MatterServer } from '@project-chip/matter.js';
|
|
53
|
-
import { Aggregator, DeviceTypes, NodeStateInformation } from '@project-chip/matter.js/device';
|
|
54
|
-
import { aggregator } from './matterbridgeDeviceTypes.js';
|
|
55
|
-
// Default colors
|
|
14
|
+
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
15
|
+
import { DeviceTypeId, Endpoint as EndpointNode, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode } from '@matter/main';
|
|
16
|
+
import { FabricAction, PaseClient } from '@matter/main/protocol';
|
|
17
|
+
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
18
|
+
import { Frontend } from './frontend.js';
|
|
56
19
|
const plg = '\u001B[38;5;33m';
|
|
57
20
|
const dev = '\u001B[38;5;79m';
|
|
58
21
|
const typ = '\u001B[38;5;207m';
|
|
59
|
-
/**
|
|
60
|
-
* Represents the Matterbridge application.
|
|
61
|
-
*/
|
|
62
22
|
export class Matterbridge extends EventEmitter {
|
|
63
23
|
systemInformation = {
|
|
64
24
|
interfaceName: '',
|
|
@@ -89,13 +49,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
89
49
|
matterbridgeFabricInformations: [],
|
|
90
50
|
matterbridgeSessionInformations: [],
|
|
91
51
|
matterbridgePaired: false,
|
|
92
|
-
matterbridgeConnected: false,
|
|
93
52
|
bridgeMode: '',
|
|
94
53
|
restartMode: '',
|
|
95
|
-
edge: hasParameter('edge'),
|
|
96
54
|
readOnly: hasParameter('readonly'),
|
|
97
55
|
profile: getParameter('profile'),
|
|
98
|
-
loggerLevel: "info"
|
|
56
|
+
loggerLevel: "info",
|
|
99
57
|
fileLogger: false,
|
|
100
58
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
101
59
|
matterFileLogger: false,
|
|
@@ -120,21 +78,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
120
78
|
matterbridgeFabricInformations = [];
|
|
121
79
|
matterbridgeSessionInformations = [];
|
|
122
80
|
matterbridgePaired = false;
|
|
123
|
-
matterbridgeConnected = false;
|
|
124
81
|
bridgeMode = '';
|
|
125
82
|
restartMode = '';
|
|
126
83
|
profile = getParameter('profile');
|
|
127
|
-
edge =
|
|
84
|
+
edge = true;
|
|
128
85
|
log;
|
|
129
86
|
matterbrideLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
130
87
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
131
88
|
plugins;
|
|
132
89
|
devices;
|
|
90
|
+
frontend = new Frontend(this);
|
|
133
91
|
nodeStorage;
|
|
134
92
|
nodeContext;
|
|
135
|
-
matterStorageName = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json';
|
|
136
93
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
137
|
-
// Cleanup
|
|
138
94
|
hasCleanupStarted = false;
|
|
139
95
|
initialized = false;
|
|
140
96
|
execRunningCount = 0;
|
|
@@ -146,62 +102,34 @@ export class Matterbridge extends EventEmitter {
|
|
|
146
102
|
sigtermHandler;
|
|
147
103
|
exceptionHandler;
|
|
148
104
|
rejectionHandler;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
webSocketServer;
|
|
154
|
-
// Matter
|
|
155
|
-
mdnsInterface; // matter server mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
156
|
-
ipv4address; // matter commissioning server listeningAddressIpv4
|
|
157
|
-
ipv6address; // matter commissioning server listeningAddressIpv6
|
|
158
|
-
port = 5540; // first commissioning server port
|
|
159
|
-
passcode; // first commissioning server passcode
|
|
160
|
-
discriminator; // first commissioning server discriminator
|
|
161
|
-
storageManager;
|
|
105
|
+
environment = Environment.default;
|
|
106
|
+
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
107
|
+
matterStorageService;
|
|
108
|
+
matterStorageManager;
|
|
162
109
|
matterbridgeContext;
|
|
163
110
|
mattercontrollerContext;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
111
|
+
mdnsInterface;
|
|
112
|
+
ipv4address;
|
|
113
|
+
ipv6address;
|
|
114
|
+
port;
|
|
115
|
+
passcode;
|
|
116
|
+
discriminator;
|
|
117
|
+
serverNode;
|
|
118
|
+
aggregatorNode;
|
|
168
119
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
169
120
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
170
121
|
static instance;
|
|
171
|
-
// We load asyncronously so is private
|
|
172
122
|
constructor() {
|
|
173
123
|
super();
|
|
174
|
-
// Bind the handler to the instance
|
|
175
|
-
this.matterbridgeMessageHandler = wsMessageHandler.bind(this);
|
|
176
124
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Retrieves the list of Matterbridge devices.
|
|
179
|
-
* @returns {MatterbridgeDevice[]} An array of MatterbridgeDevice objects.
|
|
180
|
-
*/
|
|
181
125
|
getDevices() {
|
|
182
126
|
return this.devices.array();
|
|
183
127
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Retrieves the list of registered plugins.
|
|
186
|
-
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
187
|
-
*/
|
|
188
128
|
getPlugins() {
|
|
189
129
|
return this.plugins.array();
|
|
190
130
|
}
|
|
191
|
-
matterbridgeMessageHandler;
|
|
192
|
-
/** ***********************************************************************************************************************************/
|
|
193
|
-
/** loadInstance() and cleanup() methods */
|
|
194
|
-
/** ***********************************************************************************************************************************/
|
|
195
|
-
/**
|
|
196
|
-
* Loads an instance of the Matterbridge class.
|
|
197
|
-
* If an instance already exists, return that instance.
|
|
198
|
-
*
|
|
199
|
-
* @param initialize - Whether to initialize the Matterbridge instance after loading.
|
|
200
|
-
* @returns The loaded Matterbridge instance.
|
|
201
|
-
*/
|
|
202
131
|
static async loadInstance(initialize = false) {
|
|
203
132
|
if (!Matterbridge.instance) {
|
|
204
|
-
// eslint-disable-next-line no-console
|
|
205
133
|
if (hasParameter('debug'))
|
|
206
134
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
207
135
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -210,11 +138,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
210
138
|
}
|
|
211
139
|
return Matterbridge.instance;
|
|
212
140
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Call cleanup().
|
|
215
|
-
* @deprecated This method is deprecated and is only used for jest tests.
|
|
216
|
-
*
|
|
217
|
-
*/
|
|
218
141
|
async destroyInstance() {
|
|
219
142
|
await this.cleanup('destroying instance...', false);
|
|
220
143
|
await waiter('destroying instance...', () => {
|
|
@@ -222,60 +145,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
222
145
|
}, false, 60000, 100, false);
|
|
223
146
|
await wait(1000, 'Wait for the global node_modules and matterbridge version', false);
|
|
224
147
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Initializes the Matterbridge application.
|
|
227
|
-
*
|
|
228
|
-
* @remarks
|
|
229
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
230
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
231
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
232
|
-
*
|
|
233
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
234
|
-
*/
|
|
235
148
|
async initialize() {
|
|
236
|
-
// Set the restart mode
|
|
237
149
|
if (hasParameter('service'))
|
|
238
150
|
this.restartMode = 'service';
|
|
239
151
|
if (hasParameter('docker'))
|
|
240
152
|
this.restartMode = 'docker';
|
|
241
|
-
// Set the matterbridge directory
|
|
242
153
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
243
154
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
244
|
-
|
|
245
|
-
this.
|
|
246
|
-
|
|
155
|
+
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
156
|
+
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
157
|
+
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
158
|
+
this.environment.vars.set('runtime.signals', false);
|
|
159
|
+
this.environment.vars.set('runtime.exitcode', false);
|
|
160
|
+
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
161
|
+
this.registerProcessHandlers();
|
|
247
162
|
try {
|
|
248
163
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
249
164
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
250
165
|
this.log.debug('Creating node storage context for matterbridge');
|
|
251
166
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
252
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
253
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
167
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
255
168
|
for (const key of keys) {
|
|
256
169
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
257
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
170
|
await this.nodeStorage?.storage.get(key);
|
|
259
171
|
}
|
|
260
172
|
const storages = await this.nodeStorage.getStorageNames();
|
|
261
173
|
for (const storage of storages) {
|
|
262
174
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
263
175
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
264
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
265
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
266
176
|
const keys = (await nodeContext?.storage.keys());
|
|
267
177
|
keys.forEach(async (key) => {
|
|
268
178
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
269
179
|
await nodeContext?.get(key);
|
|
270
180
|
});
|
|
271
181
|
}
|
|
272
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
273
182
|
this.log.debug('Creating node storage backup...');
|
|
274
183
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
275
184
|
this.log.debug('Created node storage backup');
|
|
276
185
|
}
|
|
277
186
|
catch (error) {
|
|
278
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
279
187
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
280
188
|
if (hasParameter('norestore')) {
|
|
281
189
|
this.log.fatal(`The matterbridge node storage is corrupted. Parameter -norestore found: exiting...`);
|
|
@@ -290,51 +198,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
290
198
|
this.log.fatal('Fatal error creating node storage manager and context for matterbridge');
|
|
291
199
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
292
200
|
}
|
|
293
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
294
201
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
295
|
-
|
|
296
|
-
this.
|
|
297
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
298
|
-
this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
|
|
202
|
+
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode();
|
|
203
|
+
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator();
|
|
299
204
|
this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
300
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
301
205
|
if (hasParameter('logger')) {
|
|
302
206
|
const level = getParameter('logger');
|
|
303
207
|
if (level === 'debug') {
|
|
304
|
-
this.log.logLevel = "debug"
|
|
208
|
+
this.log.logLevel = "debug";
|
|
305
209
|
}
|
|
306
210
|
else if (level === 'info') {
|
|
307
|
-
this.log.logLevel = "info"
|
|
211
|
+
this.log.logLevel = "info";
|
|
308
212
|
}
|
|
309
213
|
else if (level === 'notice') {
|
|
310
|
-
this.log.logLevel = "notice"
|
|
214
|
+
this.log.logLevel = "notice";
|
|
311
215
|
}
|
|
312
216
|
else if (level === 'warn') {
|
|
313
|
-
this.log.logLevel = "warn"
|
|
217
|
+
this.log.logLevel = "warn";
|
|
314
218
|
}
|
|
315
219
|
else if (level === 'error') {
|
|
316
|
-
this.log.logLevel = "error"
|
|
220
|
+
this.log.logLevel = "error";
|
|
317
221
|
}
|
|
318
222
|
else if (level === 'fatal') {
|
|
319
|
-
this.log.logLevel = "fatal"
|
|
223
|
+
this.log.logLevel = "fatal";
|
|
320
224
|
}
|
|
321
225
|
else {
|
|
322
226
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
323
|
-
this.log.logLevel = "info"
|
|
227
|
+
this.log.logLevel = "info";
|
|
324
228
|
}
|
|
325
229
|
}
|
|
326
230
|
else {
|
|
327
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info"
|
|
231
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', "info");
|
|
328
232
|
}
|
|
329
|
-
|
|
330
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
233
|
+
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
331
234
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
332
235
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), this.log.logLevel, true);
|
|
333
236
|
this.matterbridgeInformation.fileLogger = true;
|
|
334
237
|
}
|
|
335
238
|
this.log.notice('Matterbridge is starting...');
|
|
336
239
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
337
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
338
240
|
if (hasParameter('matterlogger')) {
|
|
339
241
|
const level = getParameter('matterlogger');
|
|
340
242
|
if (level === 'debug') {
|
|
@@ -365,7 +267,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
365
267
|
}
|
|
366
268
|
Logger.format = MatterLogFormat.ANSI;
|
|
367
269
|
Logger.setLogger('default', this.createMatterLogger());
|
|
368
|
-
// Create the file logger for matter.js (context: matterFileLog)
|
|
369
270
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
370
271
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
371
272
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -374,25 +275,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
374
275
|
});
|
|
375
276
|
}
|
|
376
277
|
this.log.debug(`Matter logLevel: ${Logger.defaultLogLevel} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
377
|
-
// Set the interface to use for the matter server mdnsInterface
|
|
378
278
|
if (hasParameter('mdnsinterface')) {
|
|
379
279
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
380
280
|
}
|
|
381
281
|
else {
|
|
382
|
-
this.mdnsInterface = await this.nodeContext
|
|
282
|
+
this.mdnsInterface = await this.nodeContext.get('mattermdnsinterface', undefined);
|
|
383
283
|
if (this.mdnsInterface === '')
|
|
384
284
|
this.mdnsInterface = undefined;
|
|
385
285
|
}
|
|
386
|
-
|
|
286
|
+
if (this.mdnsInterface) {
|
|
287
|
+
const networkInterfaces = os.networkInterfaces();
|
|
288
|
+
const availableInterfaces = Object.keys(networkInterfaces);
|
|
289
|
+
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
290
|
+
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
291
|
+
this.mdnsInterface = undefined;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (this.mdnsInterface)
|
|
298
|
+
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
387
299
|
if (hasParameter('ipv4address')) {
|
|
388
300
|
this.ipv4address = getParameter('ipv4address');
|
|
389
301
|
}
|
|
390
302
|
else {
|
|
391
|
-
this.ipv4address = await this.nodeContext
|
|
303
|
+
this.ipv4address = await this.nodeContext.get('matteripv4address', undefined);
|
|
392
304
|
if (this.ipv4address === '')
|
|
393
305
|
this.ipv4address = undefined;
|
|
394
306
|
}
|
|
395
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
396
307
|
if (hasParameter('ipv6address')) {
|
|
397
308
|
this.ipv6address = getParameter('ipv6address');
|
|
398
309
|
}
|
|
@@ -401,28 +312,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
401
312
|
if (this.ipv6address === '')
|
|
402
313
|
this.ipv6address = undefined;
|
|
403
314
|
}
|
|
404
|
-
// Initialize PluginManager
|
|
405
315
|
this.plugins = new PluginManager(this);
|
|
406
316
|
await this.plugins.loadFromStorage();
|
|
407
|
-
// Initialize DeviceManager
|
|
408
317
|
this.devices = new DeviceManager(this, this.nodeContext);
|
|
409
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
410
318
|
for (const plugin of this.plugins) {
|
|
411
319
|
const packageJson = await this.plugins.parse(plugin);
|
|
412
|
-
if (packageJson === null && !hasParameter('add')) {
|
|
413
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
414
|
-
// We don't do this when the add parameter is set because we shut down the process after adding the plugin
|
|
320
|
+
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
415
321
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
416
322
|
try {
|
|
417
323
|
await this.spawnCommand('npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose']);
|
|
418
324
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
419
325
|
plugin.error = false;
|
|
420
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
421
326
|
}
|
|
422
327
|
catch (error) {
|
|
423
328
|
plugin.error = true;
|
|
424
329
|
plugin.enabled = false;
|
|
425
|
-
this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled
|
|
330
|
+
this.log.error(`Error installing plugin ${plg}${plugin.name}${er}. The plugin is disabled.`, error instanceof Error ? error.message : error);
|
|
426
331
|
}
|
|
427
332
|
}
|
|
428
333
|
this.log.debug(`Creating node storage context for plugin ${plg}${plugin.name}${db}`);
|
|
@@ -434,7 +339,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
434
339
|
await plugin.nodeContext.set('description', plugin.description);
|
|
435
340
|
await plugin.nodeContext.set('author', plugin.author);
|
|
436
341
|
}
|
|
437
|
-
// Log system info and create .matterbridge directory
|
|
438
342
|
await this.logNodeAndSystemInfo();
|
|
439
343
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
440
344
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -442,7 +346,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
442
346
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
443
347
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
444
348
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
445
|
-
// Check node version and throw error
|
|
446
349
|
const minNodeVersion = 18;
|
|
447
350
|
const nodeVersion = process.versions.node;
|
|
448
351
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -450,17 +353,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
450
353
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
451
354
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
452
355
|
}
|
|
453
|
-
// Register process handlers
|
|
454
|
-
this.registerProcessHandlers();
|
|
455
|
-
// Parse command line
|
|
456
356
|
await this.parseCommandLine();
|
|
457
357
|
this.initialized = true;
|
|
458
358
|
}
|
|
459
|
-
/**
|
|
460
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
461
|
-
* @private
|
|
462
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
463
|
-
*/
|
|
464
359
|
async parseCommandLine() {
|
|
465
360
|
if (hasParameter('help')) {
|
|
466
361
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -567,75 +462,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
567
462
|
return;
|
|
568
463
|
}
|
|
569
464
|
if (hasParameter('factoryreset')) {
|
|
570
|
-
|
|
571
|
-
// Delete old matter storage file
|
|
572
|
-
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
573
|
-
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
574
|
-
await fs.unlink(file);
|
|
575
|
-
}
|
|
576
|
-
catch (err) {
|
|
577
|
-
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
578
|
-
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
try {
|
|
582
|
-
// Delete matter node storage directory with its subdirectories
|
|
583
|
-
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
584
|
-
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
585
|
-
await fs.rm(dir, { recursive: true });
|
|
586
|
-
}
|
|
587
|
-
catch (err) {
|
|
588
|
-
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
589
|
-
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
try {
|
|
593
|
-
// Delete node storage directory with its subdirectories
|
|
594
|
-
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
595
|
-
this.log.info(`Removing storage directory: ${dir}`);
|
|
596
|
-
await fs.rm(dir, { recursive: true });
|
|
597
|
-
}
|
|
598
|
-
catch (err) {
|
|
599
|
-
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
600
|
-
this.log.error(`Error removing storage directory: ${err}`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
604
|
-
this.nodeContext = undefined;
|
|
605
|
-
this.nodeStorage = undefined;
|
|
606
|
-
this.plugins.clear();
|
|
607
|
-
this.devices.clear();
|
|
608
|
-
this.emit('shutdown');
|
|
465
|
+
await this.shutdownProcessAndFactoryReset();
|
|
609
466
|
return;
|
|
610
467
|
}
|
|
611
|
-
// Start the matter storage and create the matterbridge context
|
|
612
468
|
try {
|
|
613
|
-
await this.startMatterStorage(
|
|
469
|
+
await this.startMatterStorage();
|
|
614
470
|
}
|
|
615
471
|
catch (error) {
|
|
616
472
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
617
473
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
618
474
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.log.info('Resetting Matterbridge commissioning information...');
|
|
622
|
-
await this.matterbridgeContext?.clearAll();
|
|
623
|
-
await this.stopMatterStorage();
|
|
624
|
-
this.log.info('Reset done! Remove the device from the controller.');
|
|
625
|
-
this.emit('shutdown');
|
|
475
|
+
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
476
|
+
await this.shutdownProcessAndReset();
|
|
626
477
|
return;
|
|
627
478
|
}
|
|
628
|
-
|
|
629
|
-
if (!this.edge && hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
479
|
+
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
630
480
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
631
481
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
632
482
|
if (plugin) {
|
|
633
|
-
|
|
483
|
+
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
484
|
+
if (!matterStorageManager)
|
|
634
485
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
await
|
|
486
|
+
await matterStorageManager?.createContext('events')?.clearAll();
|
|
487
|
+
await matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
488
|
+
await matterStorageManager?.createContext('root')?.clearAll();
|
|
489
|
+
await matterStorageManager?.createContext('sessions')?.clearAll();
|
|
490
|
+
await matterStorageManager?.createContext('persist')?.clearAll();
|
|
639
491
|
this.log.info(`Reset commissionig for plugin ${plg}${plugin.name}${nf} done! Remove the device from the controller.`);
|
|
640
492
|
}
|
|
641
493
|
else {
|
|
@@ -645,74 +497,55 @@ export class Matterbridge extends EventEmitter {
|
|
|
645
497
|
this.emit('shutdown');
|
|
646
498
|
return;
|
|
647
499
|
}
|
|
648
|
-
// Initialize frontend
|
|
649
500
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
650
|
-
await this.
|
|
651
|
-
// Check each 60 minutes the latest versions
|
|
501
|
+
await this.frontend.start(getIntParameter('frontend'));
|
|
652
502
|
this.checkUpdateInterval = setInterval(() => {
|
|
653
503
|
this.getMatterbridgeLatestVersion();
|
|
654
504
|
for (const plugin of this.plugins) {
|
|
655
505
|
this.getPluginLatestVersion(plugin);
|
|
656
506
|
}
|
|
507
|
+
this.frontend.wssSendRefreshRequired();
|
|
657
508
|
}, 60 * 60 * 1000);
|
|
658
|
-
// Start the matterbridge in mode test
|
|
659
509
|
if (hasParameter('test')) {
|
|
660
510
|
this.bridgeMode = 'bridge';
|
|
661
|
-
|
|
511
|
+
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
662
512
|
return;
|
|
663
513
|
}
|
|
664
|
-
// Start the matterbridge in mode controller
|
|
665
514
|
if (hasParameter('controller')) {
|
|
666
515
|
this.bridgeMode = 'controller';
|
|
667
516
|
await this.startController();
|
|
668
517
|
return;
|
|
669
518
|
}
|
|
670
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
671
519
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
672
520
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
673
521
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
674
522
|
}
|
|
675
|
-
// Start matterbridge in bridge mode
|
|
676
523
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
677
524
|
this.bridgeMode = 'bridge';
|
|
678
|
-
MatterbridgeDevice.bridgeMode = 'bridge';
|
|
679
525
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
680
526
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
681
527
|
await this.startBridge();
|
|
682
528
|
return;
|
|
683
529
|
}
|
|
684
|
-
// Start matterbridge in childbridge mode
|
|
685
530
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
686
531
|
this.bridgeMode = 'childbridge';
|
|
687
|
-
MatterbridgeDevice.bridgeMode = 'childbridge';
|
|
688
532
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
689
533
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
690
534
|
await this.startChildbridge();
|
|
691
535
|
return;
|
|
692
536
|
}
|
|
693
537
|
}
|
|
694
|
-
/**
|
|
695
|
-
* Asynchronously loads and starts the registered plugins.
|
|
696
|
-
*
|
|
697
|
-
* This method is responsible for initializing and staarting all enabled plugins.
|
|
698
|
-
* It ensures that each plugin is properly loaded and started before the ridge starts.
|
|
699
|
-
*
|
|
700
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
701
|
-
*/
|
|
702
538
|
async startPlugins() {
|
|
703
|
-
// Check, load and start the plugins
|
|
704
539
|
for (const plugin of this.plugins) {
|
|
705
540
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
706
541
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
707
|
-
// Check if the plugin is available
|
|
708
542
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
709
543
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
710
544
|
plugin.enabled = false;
|
|
711
545
|
plugin.error = true;
|
|
712
546
|
continue;
|
|
713
547
|
}
|
|
714
|
-
|
|
715
|
-
this.getPluginLatestVersion(plugin); // No await do it asyncronously
|
|
548
|
+
this.getPluginLatestVersion(plugin);
|
|
716
549
|
if (!plugin.enabled) {
|
|
717
550
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
|
|
718
551
|
continue;
|
|
@@ -722,31 +555,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
722
555
|
plugin.loaded = false;
|
|
723
556
|
plugin.started = false;
|
|
724
557
|
plugin.configured = false;
|
|
725
|
-
plugin.connected = undefined;
|
|
726
558
|
plugin.registeredDevices = undefined;
|
|
727
559
|
plugin.addedDevices = undefined;
|
|
728
560
|
plugin.qrPairingCode = undefined;
|
|
729
561
|
plugin.manualPairingCode = undefined;
|
|
730
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
562
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
731
563
|
}
|
|
732
|
-
this.wssSendRefreshRequired();
|
|
564
|
+
this.frontend.wssSendRefreshRequired();
|
|
733
565
|
}
|
|
734
|
-
/**
|
|
735
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
736
|
-
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
737
|
-
*/
|
|
738
566
|
registerProcessHandlers() {
|
|
739
567
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
740
568
|
process.removeAllListeners('uncaughtException');
|
|
741
569
|
process.removeAllListeners('unhandledRejection');
|
|
742
570
|
this.exceptionHandler = async (error) => {
|
|
743
571
|
this.log.fatal('Unhandled Exception detected at:', error.stack || error, rs);
|
|
744
|
-
// await this.cleanup('Unhandled Exception detected, cleaning up...');
|
|
745
572
|
};
|
|
746
573
|
process.on('uncaughtException', this.exceptionHandler);
|
|
747
574
|
this.rejectionHandler = async (reason, promise) => {
|
|
748
575
|
this.log.fatal('Unhandled Rejection detected at:', promise, 'reason:', reason instanceof Error ? reason.stack : reason, rs);
|
|
749
|
-
// await this.cleanup('Unhandled Rejection detected, cleaning up...');
|
|
750
576
|
};
|
|
751
577
|
process.on('unhandledRejection', this.rejectionHandler);
|
|
752
578
|
this.log.debug(`Registering SIGINT and SIGTERM signal handlers...`);
|
|
@@ -759,9 +585,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
759
585
|
};
|
|
760
586
|
process.on('SIGTERM', this.sigtermHandler);
|
|
761
587
|
}
|
|
762
|
-
/**
|
|
763
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
764
|
-
*/
|
|
765
588
|
deregisterProcesslHandlers() {
|
|
766
589
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
767
590
|
if (this.exceptionHandler)
|
|
@@ -778,15 +601,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
778
601
|
process.off('SIGTERM', this.sigtermHandler);
|
|
779
602
|
this.sigtermHandler = undefined;
|
|
780
603
|
}
|
|
781
|
-
/**
|
|
782
|
-
* Logs the node and system information.
|
|
783
|
-
*/
|
|
784
604
|
async logNodeAndSystemInfo() {
|
|
785
|
-
// IP address information
|
|
786
605
|
const networkInterfaces = os.networkInterfaces();
|
|
606
|
+
this.systemInformation.interfaceName = '';
|
|
787
607
|
this.systemInformation.ipv4Address = '';
|
|
788
608
|
this.systemInformation.ipv6Address = '';
|
|
789
609
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
610
|
+
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
611
|
+
continue;
|
|
790
612
|
if (!interfaceDetails) {
|
|
791
613
|
break;
|
|
792
614
|
}
|
|
@@ -802,7 +624,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
802
624
|
this.systemInformation.macAddress = detail.mac;
|
|
803
625
|
}
|
|
804
626
|
}
|
|
805
|
-
if (this.systemInformation.ipv4Address !== ''
|
|
627
|
+
if (this.systemInformation.ipv4Address !== '' || this.systemInformation.ipv6Address !== '') {
|
|
806
628
|
this.log.debug(`Using interface: '${this.systemInformation.interfaceName}'`);
|
|
807
629
|
this.log.debug(`- with MAC address: '${this.systemInformation.macAddress}'`);
|
|
808
630
|
this.log.debug(`- with IPv4 address: '${this.systemInformation.ipv4Address}'`);
|
|
@@ -810,22 +632,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
810
632
|
break;
|
|
811
633
|
}
|
|
812
634
|
}
|
|
813
|
-
// Node information
|
|
814
635
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
815
636
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
816
637
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
817
638
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
818
|
-
// Host system information
|
|
819
639
|
this.systemInformation.hostname = os.hostname();
|
|
820
640
|
this.systemInformation.user = os.userInfo().username;
|
|
821
|
-
this.systemInformation.osType = os.type();
|
|
822
|
-
this.systemInformation.osRelease = os.release();
|
|
823
|
-
this.systemInformation.osPlatform = os.platform();
|
|
824
|
-
this.systemInformation.osArch = os.arch();
|
|
825
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
826
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
827
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
828
|
-
// Log the system information
|
|
641
|
+
this.systemInformation.osType = os.type();
|
|
642
|
+
this.systemInformation.osRelease = os.release();
|
|
643
|
+
this.systemInformation.osPlatform = os.platform();
|
|
644
|
+
this.systemInformation.osArch = os.arch();
|
|
645
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
646
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
647
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
829
648
|
this.log.debug('Host System Information:');
|
|
830
649
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
831
650
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -841,19 +660,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
841
660
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
842
661
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
843
662
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
844
|
-
// Home directory
|
|
845
663
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
846
664
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
847
665
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
848
|
-
// Package root directory
|
|
849
666
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
850
667
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
851
668
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
852
669
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
853
|
-
// Global node_modules directory
|
|
854
670
|
if (this.nodeContext)
|
|
855
671
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
856
|
-
// First run of Matterbridge so the node storage is empty
|
|
857
672
|
if (this.globalModulesDirectory === '') {
|
|
858
673
|
try {
|
|
859
674
|
this.globalModulesDirectory = await this.getGlobalNodeModules();
|
|
@@ -877,7 +692,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
877
692
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
878
693
|
});
|
|
879
694
|
}
|
|
880
|
-
// Create the data directory .matterbridge in the home directory
|
|
881
695
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
882
696
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
883
697
|
try {
|
|
@@ -901,7 +715,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
901
715
|
}
|
|
902
716
|
}
|
|
903
717
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
904
|
-
// Create the plugin directory Matterbridge in the home directory
|
|
905
718
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
906
719
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
907
720
|
try {
|
|
@@ -925,28 +738,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
925
738
|
}
|
|
926
739
|
}
|
|
927
740
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
928
|
-
// Matterbridge version
|
|
929
741
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
930
742
|
this.matterbridgeVersion = packageJson.version;
|
|
931
743
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeVersion;
|
|
932
744
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
933
|
-
// Matterbridge latest version
|
|
934
745
|
if (this.nodeContext)
|
|
935
746
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', '');
|
|
936
747
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
937
748
|
this.getMatterbridgeLatestVersion();
|
|
938
|
-
// Current working directory
|
|
939
749
|
const currentDir = process.cwd();
|
|
940
750
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
941
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
942
751
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
943
752
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
944
753
|
}
|
|
945
|
-
/**
|
|
946
|
-
* Retrieves the latest version of a package from the npm registry.
|
|
947
|
-
* @param packageName - The name of the package.
|
|
948
|
-
* @returns A Promise that resolves to the latest version of the package.
|
|
949
|
-
*/
|
|
950
754
|
async getLatestVersion(packageName) {
|
|
951
755
|
return new Promise((resolve, reject) => {
|
|
952
756
|
this.execRunningCount++;
|
|
@@ -961,10 +765,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
961
765
|
});
|
|
962
766
|
});
|
|
963
767
|
}
|
|
964
|
-
/**
|
|
965
|
-
* Retrieves the path to the global Node.js modules directory.
|
|
966
|
-
* @returns A promise that resolves to the path of the global Node.js modules directory.
|
|
967
|
-
*/
|
|
968
768
|
async getGlobalNodeModules() {
|
|
969
769
|
return new Promise((resolve, reject) => {
|
|
970
770
|
this.execRunningCount++;
|
|
@@ -979,11 +779,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
979
779
|
});
|
|
980
780
|
});
|
|
981
781
|
}
|
|
982
|
-
/**
|
|
983
|
-
* Retrieves the latest version of Matterbridge and performs necessary actions based on the version comparison.
|
|
984
|
-
* @private
|
|
985
|
-
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
986
|
-
*/
|
|
987
782
|
async getMatterbridgeLatestVersion() {
|
|
988
783
|
this.getLatestVersion('matterbridge')
|
|
989
784
|
.then(async (matterbridgeLatestVersion) => {
|
|
@@ -1000,19 +795,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1000
795
|
})
|
|
1001
796
|
.catch((error) => {
|
|
1002
797
|
this.log.error(`Error getting Matterbridge latest version: ${error.message}`);
|
|
1003
|
-
// error.stack && this.log.debug(error.stack);
|
|
1004
798
|
});
|
|
1005
799
|
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Retrieves the latest version of a plugin and updates the plugin's latestVersion property.
|
|
1008
|
-
* If the plugin's version is different from the latest version, logs a warning message.
|
|
1009
|
-
* If the plugin's version is the same as the latest version, logs an info message.
|
|
1010
|
-
* If there is an error retrieving the latest version, logs an error message.
|
|
1011
|
-
*
|
|
1012
|
-
* @private
|
|
1013
|
-
* @param {RegisteredPlugin} plugin - The plugin for which to retrieve the latest version.
|
|
1014
|
-
* @returns {Promise<void>} A promise that resolves when the latest version is retrieved and actions are performed.
|
|
1015
|
-
*/
|
|
1016
800
|
async getPluginLatestVersion(plugin) {
|
|
1017
801
|
this.getLatestVersion(plugin.name)
|
|
1018
802
|
.then(async (latestVersion) => {
|
|
@@ -1024,54 +808,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1024
808
|
})
|
|
1025
809
|
.catch((error) => {
|
|
1026
810
|
this.log.error(`Error getting ${plg}${plugin.name}${er} latest version: ${error.message}`);
|
|
1027
|
-
// error.stack && this.log.debug(error.stack);
|
|
1028
811
|
});
|
|
1029
812
|
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1032
|
-
*
|
|
1033
|
-
* @returns {Function} The MatterLogger function.
|
|
1034
|
-
*/
|
|
1035
813
|
createMatterLogger() {
|
|
1036
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
814
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
1037
815
|
return (_level, formattedLog) => {
|
|
1038
816
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
1039
817
|
const message = formattedLog.slice(65);
|
|
1040
818
|
matterLogger.logName = logger;
|
|
1041
819
|
switch (_level) {
|
|
1042
820
|
case MatterLogLevel.DEBUG:
|
|
1043
|
-
matterLogger.log("debug"
|
|
821
|
+
matterLogger.log("debug", message);
|
|
1044
822
|
break;
|
|
1045
823
|
case MatterLogLevel.INFO:
|
|
1046
|
-
matterLogger.log("info"
|
|
824
|
+
matterLogger.log("info", message);
|
|
1047
825
|
break;
|
|
1048
826
|
case MatterLogLevel.NOTICE:
|
|
1049
|
-
matterLogger.log("notice"
|
|
827
|
+
matterLogger.log("notice", message);
|
|
1050
828
|
break;
|
|
1051
829
|
case MatterLogLevel.WARN:
|
|
1052
|
-
matterLogger.log("warn"
|
|
830
|
+
matterLogger.log("warn", message);
|
|
1053
831
|
break;
|
|
1054
832
|
case MatterLogLevel.ERROR:
|
|
1055
|
-
matterLogger.log("error"
|
|
833
|
+
matterLogger.log("error", message);
|
|
1056
834
|
break;
|
|
1057
835
|
case MatterLogLevel.FATAL:
|
|
1058
|
-
matterLogger.log("fatal"
|
|
836
|
+
matterLogger.log("fatal", message);
|
|
1059
837
|
break;
|
|
1060
838
|
default:
|
|
1061
|
-
matterLogger.log("debug"
|
|
839
|
+
matterLogger.log("debug", message);
|
|
1062
840
|
break;
|
|
1063
841
|
}
|
|
1064
842
|
};
|
|
1065
843
|
}
|
|
1066
|
-
/**
|
|
1067
|
-
* Creates a Matter File Logger.
|
|
1068
|
-
*
|
|
1069
|
-
* @param {string} filePath - The path to the log file.
|
|
1070
|
-
* @param {boolean} [unlink=false] - Whether to unlink the log file before creating a new one.
|
|
1071
|
-
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1072
|
-
*/
|
|
1073
844
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1074
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1075
845
|
let fileSize = 0;
|
|
1076
846
|
if (unlink) {
|
|
1077
847
|
try {
|
|
@@ -1120,86 +890,113 @@ export class Matterbridge extends EventEmitter {
|
|
|
1120
890
|
}
|
|
1121
891
|
};
|
|
1122
892
|
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Update matterbridge and cleanup.
|
|
1125
|
-
*/
|
|
1126
|
-
async updateProcess() {
|
|
1127
|
-
await this.cleanup('updating...', false);
|
|
1128
|
-
}
|
|
1129
|
-
/**
|
|
1130
|
-
* Restarts the process by spawning a new process and exiting the current process.
|
|
1131
|
-
*/
|
|
1132
893
|
async restartProcess() {
|
|
1133
894
|
await this.cleanup('restarting...', true);
|
|
1134
895
|
}
|
|
1135
|
-
/**
|
|
1136
|
-
* Shut down the process by exiting the current process.
|
|
1137
|
-
*/
|
|
1138
896
|
async shutdownProcess() {
|
|
1139
897
|
await this.cleanup('shutting down...', false);
|
|
1140
898
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
899
|
+
async updateProcess() {
|
|
900
|
+
this.log.info('Updating matterbridge...');
|
|
901
|
+
try {
|
|
902
|
+
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
903
|
+
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
this.log.error('Error updating matterbridge:', error instanceof Error ? error.message : error);
|
|
907
|
+
}
|
|
908
|
+
this.frontend.wssSendRestartRequired();
|
|
909
|
+
await this.cleanup('updating...', false);
|
|
910
|
+
}
|
|
1144
911
|
async unregisterAndShutdownProcess() {
|
|
1145
912
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1146
|
-
for (const plugin of this.plugins
|
|
1147
|
-
|
|
1148
|
-
await this.removeAllBridgedEndpoints(plugin.name);
|
|
1149
|
-
else
|
|
1150
|
-
await this.removeAllBridgedDevices(plugin.name);
|
|
913
|
+
for (const plugin of this.plugins) {
|
|
914
|
+
await this.removeAllBridgedEndpoints(plugin.name);
|
|
1151
915
|
}
|
|
1152
916
|
await this.cleanup('unregistered all devices and shutting down...', false);
|
|
1153
917
|
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Shut down the process and reset.
|
|
1156
|
-
*/
|
|
1157
918
|
async shutdownProcessAndReset() {
|
|
919
|
+
this.log.info('Resetting Matterbridge commissioning information...');
|
|
920
|
+
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
921
|
+
await this.matterStorageManager?.createContext('fabrics')?.clearAll();
|
|
922
|
+
await this.matterStorageManager?.createContext('root')?.clearAll();
|
|
923
|
+
await this.matterStorageManager?.createContext('sessions')?.clearAll();
|
|
924
|
+
await this.matterbridgeContext?.clearAll();
|
|
925
|
+
await this.stopMatterStorage();
|
|
926
|
+
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1158
927
|
await this.cleanup('shutting down with reset...', false);
|
|
1159
928
|
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Shut down the process and factory reset.
|
|
1162
|
-
*/
|
|
1163
929
|
async shutdownProcessAndFactoryReset() {
|
|
930
|
+
try {
|
|
931
|
+
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
932
|
+
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
933
|
+
await fs.unlink(file);
|
|
934
|
+
const backup = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup.json');
|
|
935
|
+
this.log.info(`Unlinking old matter storage backup file: ${backup}`);
|
|
936
|
+
await fs.unlink(backup);
|
|
937
|
+
}
|
|
938
|
+
catch (err) {
|
|
939
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
940
|
+
this.log.error(`Error unlinking old matter storage file: ${err}`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
945
|
+
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
946
|
+
await fs.rm(dir, { recursive: true });
|
|
947
|
+
const backup = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
|
|
948
|
+
this.log.info(`Removing matter node storage backup directory: ${backup}`);
|
|
949
|
+
await fs.rm(backup, { recursive: true });
|
|
950
|
+
}
|
|
951
|
+
catch (err) {
|
|
952
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
953
|
+
this.log.error(`Error removing matter storage directory: ${err}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
try {
|
|
957
|
+
const dir = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
958
|
+
this.log.info(`Removing storage directory: ${dir}`);
|
|
959
|
+
await fs.rm(dir, { recursive: true });
|
|
960
|
+
const backup = path.join(this.matterbridgeDirectory, 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.backup');
|
|
961
|
+
this.log.info(`Removing storage backup directory: ${backup}`);
|
|
962
|
+
await fs.rm(backup, { recursive: true });
|
|
963
|
+
}
|
|
964
|
+
catch (err) {
|
|
965
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
966
|
+
this.log.error(`Error removing storage directory: ${err}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
970
|
+
this.nodeContext = undefined;
|
|
971
|
+
this.nodeStorage = undefined;
|
|
972
|
+
this.plugins.clear();
|
|
973
|
+
this.devices.clear();
|
|
1164
974
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1165
975
|
}
|
|
1166
|
-
/**
|
|
1167
|
-
* Cleans up the Matterbridge instance.
|
|
1168
|
-
* @param message - The cleanup message.
|
|
1169
|
-
* @param restart - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1170
|
-
* @returns A promise that resolves when the cleanup is completed.
|
|
1171
|
-
*/
|
|
1172
976
|
async cleanup(message, restart = false) {
|
|
1173
977
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1174
978
|
this.hasCleanupStarted = true;
|
|
1175
979
|
this.log.info(message);
|
|
1176
|
-
// Deregisters the process handlers
|
|
1177
|
-
this.deregisterProcesslHandlers();
|
|
1178
|
-
// Clear the start matter interval
|
|
1179
980
|
if (this.startMatterInterval) {
|
|
1180
981
|
clearInterval(this.startMatterInterval);
|
|
1181
982
|
this.startMatterInterval = undefined;
|
|
1182
983
|
this.log.debug('Start matter interval cleared');
|
|
1183
984
|
}
|
|
1184
|
-
// Clear the check update interval
|
|
1185
985
|
if (this.checkUpdateInterval) {
|
|
1186
986
|
clearInterval(this.checkUpdateInterval);
|
|
1187
987
|
this.checkUpdateInterval = undefined;
|
|
1188
988
|
this.log.debug('Check update interval cleared');
|
|
1189
989
|
}
|
|
1190
|
-
// Clear the configure timeout
|
|
1191
990
|
if (this.configureTimeout) {
|
|
1192
991
|
clearTimeout(this.configureTimeout);
|
|
1193
992
|
this.configureTimeout = undefined;
|
|
1194
993
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1195
994
|
}
|
|
1196
|
-
// Clear the reachability timeout
|
|
1197
995
|
if (this.reachabilityTimeout) {
|
|
1198
996
|
clearTimeout(this.reachabilityTimeout);
|
|
1199
997
|
this.reachabilityTimeout = undefined;
|
|
1200
998
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1201
999
|
}
|
|
1202
|
-
// Calling the shutdown method of each plugin and clear the reachability timeout
|
|
1203
1000
|
for (const plugin of this.plugins) {
|
|
1204
1001
|
if (!plugin.enabled || plugin.error)
|
|
1205
1002
|
continue;
|
|
@@ -1210,86 +1007,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
1210
1007
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1211
1008
|
}
|
|
1212
1009
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
for (const plugin of this.plugins) {
|
|
1220
|
-
if (plugin.storageContext) {
|
|
1221
|
-
await this.convertStorage(plugin.storageContext, plugin.name);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1010
|
+
this.frontend.stop();
|
|
1011
|
+
this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1012
|
+
if (this.bridgeMode === 'bridge') {
|
|
1013
|
+
if (this.serverNode) {
|
|
1014
|
+
await this.stopServerNode(this.serverNode);
|
|
1015
|
+
this.log.info(`Stopped matter server node for Matterbridge`);
|
|
1224
1016
|
}
|
|
1225
1017
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
this.log.debug('Frontend http server closed successfully');
|
|
1232
|
-
}
|
|
1233
|
-
// Close the https server
|
|
1234
|
-
if (this.httpsServer) {
|
|
1235
|
-
this.httpsServer.close();
|
|
1236
|
-
this.httpsServer.removeAllListeners();
|
|
1237
|
-
this.httpsServer = undefined;
|
|
1238
|
-
this.log.debug('Frontend https server closed successfully');
|
|
1239
|
-
}
|
|
1240
|
-
// Remove listeners from the express app
|
|
1241
|
-
if (this.expressApp) {
|
|
1242
|
-
this.expressApp.removeAllListeners();
|
|
1243
|
-
this.expressApp = undefined;
|
|
1244
|
-
this.log.debug('Frontend app closed successfully');
|
|
1245
|
-
}
|
|
1246
|
-
// Close the WebSocket server
|
|
1247
|
-
if (this.webSocketServer) {
|
|
1248
|
-
// Close all active connections
|
|
1249
|
-
this.webSocketServer.clients.forEach((client) => {
|
|
1250
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
1251
|
-
client.close();
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
this.webSocketServer.close((error) => {
|
|
1255
|
-
if (error) {
|
|
1256
|
-
this.log.error(`Error closing WebSocket server: ${error}`);
|
|
1257
|
-
}
|
|
1258
|
-
else {
|
|
1259
|
-
this.log.debug('WebSocket server closed successfully');
|
|
1018
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1019
|
+
for (const plugin of this.plugins.array()) {
|
|
1020
|
+
if (plugin.serverNode) {
|
|
1021
|
+
await this.stopServerNode(plugin.serverNode);
|
|
1022
|
+
this.log.info(`Stopped matter server node for ${plugin.name}`);
|
|
1260
1023
|
}
|
|
1261
|
-
}
|
|
1262
|
-
this.webSocketServer = undefined;
|
|
1024
|
+
}
|
|
1263
1025
|
}
|
|
1264
|
-
|
|
1265
|
-
await this.stopMatterServer();
|
|
1266
|
-
// Closing matter storage
|
|
1026
|
+
this.log.info('Stopped matter server nodes');
|
|
1267
1027
|
await this.stopMatterStorage();
|
|
1268
|
-
// Remove the matterfilelogger
|
|
1269
1028
|
try {
|
|
1270
1029
|
Logger.removeLogger('matterfilelogger');
|
|
1271
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1272
1030
|
}
|
|
1273
1031
|
catch (error) {
|
|
1274
|
-
// this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
1275
1032
|
}
|
|
1276
|
-
// Serialize registeredDevices
|
|
1277
1033
|
if (this.nodeStorage && this.nodeContext) {
|
|
1278
1034
|
this.log.info('Saving registered devices...');
|
|
1279
1035
|
const serializedRegisteredDevices = [];
|
|
1280
1036
|
this.devices.forEach(async (device) => {
|
|
1281
1037
|
const serializedMatterbridgeDevice = device.serialize();
|
|
1282
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1283
1038
|
if (serializedMatterbridgeDevice)
|
|
1284
1039
|
serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1285
1040
|
});
|
|
1286
1041
|
await this.nodeContext.set('devices', serializedRegisteredDevices);
|
|
1287
1042
|
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1288
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1289
1043
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1290
1044
|
await this.nodeContext.close();
|
|
1291
1045
|
this.nodeContext = undefined;
|
|
1292
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1293
1046
|
for (const plugin of this.plugins) {
|
|
1294
1047
|
if (plugin.nodeContext) {
|
|
1295
1048
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1306,6 +1059,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1306
1059
|
}
|
|
1307
1060
|
this.plugins.clear();
|
|
1308
1061
|
this.devices.clear();
|
|
1062
|
+
this.deregisterProcesslHandlers();
|
|
1309
1063
|
if (restart) {
|
|
1310
1064
|
if (message === 'updating...') {
|
|
1311
1065
|
this.log.info('Cleanup completed. Updating...');
|
|
@@ -1319,38 +1073,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1319
1073
|
}
|
|
1320
1074
|
}
|
|
1321
1075
|
else {
|
|
1322
|
-
if (message === 'shutting down with reset...' || message === 'shutting down with factory reset...') {
|
|
1323
|
-
try {
|
|
1324
|
-
// Delete old matter storage file
|
|
1325
|
-
const file = path.join(this.matterbridgeDirectory, 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.json');
|
|
1326
|
-
this.log.info(`Unlinking old matter storage file: ${file}`);
|
|
1327
|
-
await fs.unlink(file);
|
|
1328
|
-
}
|
|
1329
|
-
catch (error) {
|
|
1330
|
-
this.log.debug(`Error resetting old matter storage file: ${error}`);
|
|
1331
|
-
}
|
|
1332
|
-
try {
|
|
1333
|
-
// Delete matter node storage directory with its subdirectories
|
|
1334
|
-
const dir = path.join(this.matterbridgeDirectory, 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : ''));
|
|
1335
|
-
this.log.info(`Removing matter node storage directory: ${dir}`);
|
|
1336
|
-
await fs.rm(dir, { recursive: true });
|
|
1337
|
-
}
|
|
1338
|
-
catch (error) {
|
|
1339
|
-
this.log.debug(`Error resetting matter node storage file: ${error}`);
|
|
1340
|
-
}
|
|
1341
|
-
this.log.info('Reset done! Remove all paired fabrics from the controllers.');
|
|
1342
|
-
}
|
|
1343
|
-
if (message === 'shutting down with factory reset...') {
|
|
1344
|
-
try {
|
|
1345
|
-
// Delete node storage directory with its subdirectories
|
|
1346
|
-
this.log.info('Resetting Matterbridge storage...');
|
|
1347
|
-
await fs.rm(path.join(this.matterbridgeDirectory, this.nodeStorageName), { recursive: true });
|
|
1348
|
-
}
|
|
1349
|
-
catch (error) {
|
|
1350
|
-
this.log.debug(`Error resetting Matterbridge storage: ${error}`);
|
|
1351
|
-
}
|
|
1352
|
-
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1353
|
-
}
|
|
1354
1076
|
this.log.notice('Cleanup completed. Shutting down...');
|
|
1355
1077
|
Matterbridge.instance = undefined;
|
|
1356
1078
|
this.emit('shutdown');
|
|
@@ -1359,190 +1081,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
1359
1081
|
this.initialized = false;
|
|
1360
1082
|
}
|
|
1361
1083
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
async removeAllBridgedEndpoints(pluginName) {
|
|
1372
|
-
// Nothing to do here
|
|
1373
|
-
}
|
|
1374
|
-
/**
|
|
1375
|
-
* Adds a bridged device to the Matterbridge.
|
|
1376
|
-
* @param pluginName - The name of the plugin.
|
|
1377
|
-
* @param device - The bridged device to add.
|
|
1378
|
-
* @returns {Promise<void>} - A promise that resolves when the device is added.
|
|
1379
|
-
*/
|
|
1380
|
-
async addBridgedDevice(pluginName, device) {
|
|
1381
|
-
this.log.debug(`Adding bridged device ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1382
|
-
// Check if the plugin is registered
|
|
1383
|
-
const plugin = this.plugins.get(pluginName);
|
|
1384
|
-
if (!plugin) {
|
|
1385
|
-
this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
// Register and add the device to matterbridge aggregator in bridge mode
|
|
1389
|
-
if (this.bridgeMode === 'bridge') {
|
|
1390
|
-
if (!this.matterAggregator) {
|
|
1391
|
-
this.log.error(`Adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er} error: matterAggregator not found`);
|
|
1392
|
-
return;
|
|
1393
|
-
}
|
|
1394
|
-
this.matterAggregator.addBridgedDevice(device);
|
|
1395
|
-
}
|
|
1396
|
-
// The first time create the commissioning server and the aggregator for DynamicPlatform
|
|
1397
|
-
// Register and add the device in childbridge mode
|
|
1398
|
-
if (this.bridgeMode === 'childbridge') {
|
|
1399
|
-
if (plugin.type === 'AccessoryPlatform') {
|
|
1400
|
-
// Check if the plugin is locked with the commissioning server
|
|
1401
|
-
if (!plugin.locked) {
|
|
1402
|
-
plugin.locked = true;
|
|
1403
|
-
plugin.storageContext = await this.importCommissioningServerContext(plugin.name, device);
|
|
1404
|
-
this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
|
|
1405
|
-
plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
1406
|
-
this.log.debug(`Adding device ${dev}${device.name}${db} to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
1407
|
-
plugin.commissioningServer.addDevice(device);
|
|
1408
|
-
plugin.device = device;
|
|
1409
|
-
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
1410
|
-
await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
if (plugin.type === 'DynamicPlatform') {
|
|
1414
|
-
// Check if the plugin is locked with the commissioning server and the aggregator
|
|
1415
|
-
if (!plugin.locked) {
|
|
1416
|
-
plugin.locked = true;
|
|
1417
|
-
this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`);
|
|
1418
|
-
plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, plugin.description);
|
|
1419
|
-
this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`);
|
|
1420
|
-
plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name);
|
|
1421
|
-
this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`);
|
|
1422
|
-
plugin.aggregator = await this.createMatterAggregator(plugin.storageContext, plugin.name);
|
|
1423
|
-
this.log.debug(`Adding matter aggregator to commissioning server for plugin ${plg}${plugin.name}${db}`);
|
|
1424
|
-
plugin.commissioningServer.addDevice(plugin.aggregator);
|
|
1425
|
-
this.log.debug(`Adding commissioning server to matter server for plugin ${plg}${plugin.name}${db}`);
|
|
1426
|
-
await this.matterServer?.addCommissioningServer(plugin.commissioningServer, { uniqueStorageKey: plugin.name });
|
|
1427
|
-
}
|
|
1428
|
-
plugin.aggregator?.addBridgedDevice(device);
|
|
1429
|
-
}
|
|
1084
|
+
async createAccessoryPlugin(plugin, device, start = false) {
|
|
1085
|
+
if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1086
|
+
plugin.locked = true;
|
|
1087
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
1088
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1089
|
+
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
|
|
1090
|
+
await plugin.serverNode.add(device);
|
|
1091
|
+
if (start)
|
|
1092
|
+
await this.startServerNode(plugin.serverNode);
|
|
1430
1093
|
}
|
|
1431
|
-
if (plugin.registeredDevices !== undefined)
|
|
1432
|
-
plugin.registeredDevices++;
|
|
1433
|
-
if (plugin.addedDevices !== undefined)
|
|
1434
|
-
plugin.addedDevices++;
|
|
1435
|
-
// Add the device to the DeviceManager
|
|
1436
|
-
this.devices.set(device);
|
|
1437
|
-
this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1438
1094
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const plugin = this.plugins.get(pluginName);
|
|
1449
|
-
if (!plugin) {
|
|
1450
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1451
|
-
return;
|
|
1452
|
-
}
|
|
1453
|
-
// Remove the device from matterbridge aggregator in bridge mode
|
|
1454
|
-
if (this.bridgeMode === 'bridge') {
|
|
1455
|
-
if (!this.matterAggregator) {
|
|
1456
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator not found`);
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
if (device.number !== undefined) {
|
|
1460
|
-
device.setBridgedDeviceReachability(false);
|
|
1461
|
-
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1462
|
-
}
|
|
1463
|
-
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerShutDownEvent({});
|
|
1464
|
-
// device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerLeaveEvent({});
|
|
1465
|
-
this.matterAggregator?.removeBridgedDevice(device);
|
|
1466
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1467
|
-
if (plugin.registeredDevices !== undefined)
|
|
1468
|
-
plugin.registeredDevices--;
|
|
1469
|
-
if (plugin.addedDevices !== undefined)
|
|
1470
|
-
plugin.addedDevices--;
|
|
1471
|
-
}
|
|
1472
|
-
// Remove the device in childbridge mode
|
|
1473
|
-
if (this.bridgeMode === 'childbridge') {
|
|
1474
|
-
if (plugin.type === 'AccessoryPlatform') {
|
|
1475
|
-
if (!plugin.commissioningServer) {
|
|
1476
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: commissioning server not found`);
|
|
1477
|
-
return;
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
else if (plugin.type === 'DynamicPlatform') {
|
|
1481
|
-
if (!plugin.aggregator) {
|
|
1482
|
-
this.log.error(`Error removing bridged device ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
|
|
1483
|
-
return;
|
|
1484
|
-
}
|
|
1485
|
-
if (device.number !== undefined) {
|
|
1486
|
-
device.setBridgedDeviceReachability(false);
|
|
1487
|
-
device.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
|
|
1488
|
-
}
|
|
1489
|
-
plugin.aggregator.removeBridgedDevice(device);
|
|
1490
|
-
}
|
|
1491
|
-
this.log.info(`Removed bridged device(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1492
|
-
if (plugin.registeredDevices !== undefined)
|
|
1493
|
-
plugin.registeredDevices--;
|
|
1494
|
-
if (plugin.addedDevices !== undefined)
|
|
1495
|
-
plugin.addedDevices--;
|
|
1496
|
-
// Remove the commissioning server
|
|
1497
|
-
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0 && plugin.commissioningServer) {
|
|
1498
|
-
this.matterServer?.removeCommissioningServer(plugin.commissioningServer);
|
|
1499
|
-
plugin.commissioningServer = undefined;
|
|
1500
|
-
this.log.info(`Removed commissioning server for plugin ${plg}${pluginName}${nf}`);
|
|
1501
|
-
}
|
|
1095
|
+
async createDynamicPlugin(plugin, start = false) {
|
|
1096
|
+
if (!plugin.locked) {
|
|
1097
|
+
plugin.locked = true;
|
|
1098
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1099
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1100
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1101
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1102
|
+
if (start)
|
|
1103
|
+
await this.startServerNode(plugin.serverNode);
|
|
1502
1104
|
}
|
|
1503
|
-
// Remove the device from the DeviceManager
|
|
1504
|
-
this.devices.remove(device);
|
|
1505
|
-
}
|
|
1506
|
-
/**
|
|
1507
|
-
* Removes all bridged devices associated with a specific plugin.
|
|
1508
|
-
*
|
|
1509
|
-
* @param pluginName - The name of the plugin.
|
|
1510
|
-
* @returns A promise that resolves when all devices have been removed.
|
|
1511
|
-
*/
|
|
1512
|
-
async removeAllBridgedDevices(pluginName) {
|
|
1513
|
-
this.log.debug(`Removing all bridged devices for plugin ${plg}${pluginName}${db}`);
|
|
1514
|
-
this.devices.forEach(async (device) => {
|
|
1515
|
-
if (device.plugin === pluginName) {
|
|
1516
|
-
await this.removeBridgedDevice(pluginName, device);
|
|
1517
|
-
}
|
|
1518
|
-
});
|
|
1519
1105
|
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Starts the Matterbridge in bridge mode.
|
|
1522
|
-
* @private
|
|
1523
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1524
|
-
*/
|
|
1525
1106
|
async startBridge() {
|
|
1526
|
-
|
|
1527
|
-
if (!this.storageManager)
|
|
1107
|
+
if (!this.matterStorageManager)
|
|
1528
1108
|
throw new Error('No storage manager initialized');
|
|
1529
1109
|
if (!this.matterbridgeContext)
|
|
1530
1110
|
throw new Error('No storage context initialized');
|
|
1531
|
-
this.
|
|
1532
|
-
this.
|
|
1533
|
-
|
|
1534
|
-
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
1535
|
-
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
|
|
1536
|
-
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
1537
|
-
this.commissioningServer.addDevice(this.matterAggregator);
|
|
1538
|
-
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
1539
|
-
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
1111
|
+
this.serverNode = await this.createServerNode(this.matterbridgeContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1112
|
+
this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
|
|
1113
|
+
await this.serverNode.add(this.aggregatorNode);
|
|
1540
1114
|
await this.startPlugins();
|
|
1541
1115
|
this.log.debug('Starting start matter interval in bridge mode');
|
|
1542
1116
|
let failCount = 0;
|
|
1543
1117
|
this.startMatterInterval = setInterval(async () => {
|
|
1544
1118
|
for (const plugin of this.plugins) {
|
|
1545
|
-
// new code to not start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1546
1119
|
if (!plugin.enabled)
|
|
1547
1120
|
continue;
|
|
1548
1121
|
if (plugin.error) {
|
|
@@ -1567,54 +1140,51 @@ export class Matterbridge extends EventEmitter {
|
|
|
1567
1140
|
clearInterval(this.startMatterInterval);
|
|
1568
1141
|
this.startMatterInterval = undefined;
|
|
1569
1142
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1570
|
-
|
|
1571
|
-
await this.startMatterServer();
|
|
1572
|
-
this.log.notice('Matter server started');
|
|
1573
|
-
// Show the QR code for commissioning or log the already commissioned message
|
|
1574
|
-
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
1575
|
-
// Configure the plugins
|
|
1143
|
+
this.startServerNode(this.serverNode);
|
|
1576
1144
|
this.configureTimeout = setTimeout(async () => {
|
|
1577
1145
|
for (const plugin of this.plugins) {
|
|
1578
1146
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1579
1147
|
continue;
|
|
1580
1148
|
try {
|
|
1581
|
-
await this.plugins.configure(plugin);
|
|
1149
|
+
await this.plugins.configure(plugin);
|
|
1582
1150
|
}
|
|
1583
1151
|
catch (error) {
|
|
1584
1152
|
plugin.error = true;
|
|
1585
1153
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1586
1154
|
}
|
|
1587
1155
|
}
|
|
1588
|
-
this.wssSendRefreshRequired();
|
|
1156
|
+
this.frontend.wssSendRefreshRequired();
|
|
1589
1157
|
}, 30 * 1000);
|
|
1590
|
-
// Setting reachability to true
|
|
1591
1158
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1592
1159
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1593
|
-
if (this.
|
|
1594
|
-
this.
|
|
1595
|
-
if (this.
|
|
1596
|
-
this.setAggregatorReachability(this.
|
|
1160
|
+
if (this.serverNode)
|
|
1161
|
+
this.setServerNodeReachability(this.serverNode, true);
|
|
1162
|
+
if (this.aggregatorNode)
|
|
1163
|
+
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1164
|
+
this.frontend.wssSendRefreshRequired();
|
|
1597
1165
|
}, 60 * 1000);
|
|
1598
1166
|
}, 1000);
|
|
1599
1167
|
}
|
|
1600
|
-
/**
|
|
1601
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1602
|
-
* @private
|
|
1603
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1604
|
-
*/
|
|
1605
1168
|
async startChildbridge() {
|
|
1606
|
-
|
|
1607
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1608
|
-
if (!this.storageManager)
|
|
1169
|
+
if (!this.matterStorageManager)
|
|
1609
1170
|
throw new Error('No storage manager initialized');
|
|
1610
|
-
|
|
1171
|
+
for (const plugin of this.plugins) {
|
|
1172
|
+
if (!plugin.enabled)
|
|
1173
|
+
continue;
|
|
1174
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
1175
|
+
plugin.locked = true;
|
|
1176
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
|
|
1177
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1178
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1179
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1611
1182
|
await this.startPlugins();
|
|
1612
1183
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1613
1184
|
let failCount = 0;
|
|
1614
1185
|
this.startMatterInterval = setInterval(async () => {
|
|
1615
1186
|
let allStarted = true;
|
|
1616
1187
|
for (const plugin of this.plugins) {
|
|
1617
|
-
// Prevents to start the bridge if one plugin is in error cause the controllers will delete the devices loosing all the configuration
|
|
1618
1188
|
if (!plugin.enabled)
|
|
1619
1189
|
continue;
|
|
1620
1190
|
if (plugin.error) {
|
|
@@ -1642,23 +1212,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
1212
|
clearInterval(this.startMatterInterval);
|
|
1643
1213
|
this.startMatterInterval = undefined;
|
|
1644
1214
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1645
|
-
// Start the Matter server
|
|
1646
|
-
await this.startMatterServer();
|
|
1647
|
-
this.log.notice('Matter server started');
|
|
1648
|
-
// Configure the plugins
|
|
1649
1215
|
this.configureTimeout = setTimeout(async () => {
|
|
1650
1216
|
for (const plugin of this.plugins) {
|
|
1651
1217
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
1652
1218
|
continue;
|
|
1653
1219
|
try {
|
|
1654
|
-
await this.plugins.configure(plugin);
|
|
1220
|
+
await this.plugins.configure(plugin);
|
|
1655
1221
|
}
|
|
1656
1222
|
catch (error) {
|
|
1657
1223
|
plugin.error = true;
|
|
1658
1224
|
this.log.error(`Error configuring plugin ${plg}${plugin.name}${er}`, error);
|
|
1659
1225
|
}
|
|
1660
1226
|
}
|
|
1661
|
-
this.wssSendRefreshRequired();
|
|
1227
|
+
this.frontend.wssSendRefreshRequired();
|
|
1662
1228
|
}, 30 * 1000);
|
|
1663
1229
|
for (const plugin of this.plugins) {
|
|
1664
1230
|
if (!plugin.enabled || plugin.error)
|
|
@@ -1667,8 +1233,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1667
1233
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't add any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1668
1234
|
continue;
|
|
1669
1235
|
}
|
|
1670
|
-
if (!plugin.
|
|
1671
|
-
this.log.error(`
|
|
1236
|
+
if (!plugin.serverNode) {
|
|
1237
|
+
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1672
1238
|
continue;
|
|
1673
1239
|
}
|
|
1674
1240
|
if (!plugin.storageContext) {
|
|
@@ -1679,919 +1245,338 @@ export class Matterbridge extends EventEmitter {
|
|
|
1679
1245
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1680
1246
|
continue;
|
|
1681
1247
|
}
|
|
1682
|
-
|
|
1683
|
-
// Setting reachability to true
|
|
1248
|
+
this.startServerNode(plugin.serverNode);
|
|
1684
1249
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1685
1250
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${db}`);
|
|
1686
|
-
if (plugin.
|
|
1687
|
-
this.
|
|
1251
|
+
if (plugin.serverNode)
|
|
1252
|
+
this.setServerNodeReachability(plugin.serverNode, true);
|
|
1688
1253
|
if (plugin.type === 'AccessoryPlatform' && plugin.device)
|
|
1689
1254
|
this.setDeviceReachability(plugin.device, true);
|
|
1690
|
-
if (plugin.type === 'DynamicPlatform' && plugin.
|
|
1691
|
-
this.setAggregatorReachability(plugin.
|
|
1255
|
+
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1256
|
+
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1257
|
+
this.frontend.wssSendRefreshRequired();
|
|
1692
1258
|
}, 60 * 1000);
|
|
1693
1259
|
}
|
|
1694
1260
|
}, 1000);
|
|
1695
1261
|
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Starts the Matterbridge controller.
|
|
1698
|
-
* @private
|
|
1699
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1700
|
-
*/
|
|
1701
1262
|
async startController() {
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
1707
|
-
this.
|
|
1708
|
-
this.
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
this.log.
|
|
1715
|
-
|
|
1716
|
-
this.log.info('
|
|
1717
|
-
|
|
1718
|
-
|
|
1263
|
+
}
|
|
1264
|
+
async startMatterStorage() {
|
|
1265
|
+
this.log.info(`Starting matter node storage...`);
|
|
1266
|
+
this.matterStorageService = this.environment.get(StorageService);
|
|
1267
|
+
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
1268
|
+
this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
|
|
1269
|
+
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1270
|
+
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, 'Matterbridge aggregator');
|
|
1271
|
+
this.log.info('Matter node storage started');
|
|
1272
|
+
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1273
|
+
}
|
|
1274
|
+
async backupMatterStorage(storageName, backupName) {
|
|
1275
|
+
this.log.info('Creating matter node storage backup...');
|
|
1276
|
+
await copyDirectory(storageName, backupName);
|
|
1277
|
+
this.log.info('Created matter node storage backup');
|
|
1278
|
+
}
|
|
1279
|
+
async stopMatterStorage() {
|
|
1280
|
+
this.log.info('Closing matter node storage...');
|
|
1281
|
+
this.matterStorageManager?.close();
|
|
1282
|
+
this.matterStorageService = undefined;
|
|
1283
|
+
this.matterStorageManager = undefined;
|
|
1284
|
+
this.matterbridgeContext = undefined;
|
|
1285
|
+
this.log.info('Matter node storage closed');
|
|
1286
|
+
}
|
|
1287
|
+
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
|
|
1288
|
+
if (!this.matterStorageService)
|
|
1289
|
+
throw new Error('No storage service initialized');
|
|
1290
|
+
this.log.info(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
|
|
1291
|
+
const storageManager = await this.matterStorageService.open(pluginName);
|
|
1292
|
+
const storageContext = storageManager.createContext('persist');
|
|
1293
|
+
const random = randomBytes(8).toString('hex');
|
|
1294
|
+
await storageContext.set('storeId', pluginName);
|
|
1295
|
+
await storageContext.set('deviceName', deviceName);
|
|
1296
|
+
await storageContext.set('deviceType', deviceType);
|
|
1297
|
+
await storageContext.set('vendorId', vendorId);
|
|
1298
|
+
await storageContext.set('vendorName', vendorName.slice(0, 32));
|
|
1299
|
+
await storageContext.set('productId', productId);
|
|
1300
|
+
await storageContext.set('productName', productName.slice(0, 32));
|
|
1301
|
+
await storageContext.set('nodeLabel', productName.slice(0, 32));
|
|
1302
|
+
await storageContext.set('productLabel', productName.slice(0, 32));
|
|
1303
|
+
await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : 'SN' + random));
|
|
1304
|
+
await storageContext.set('uniqueId', await storageContext.get('uniqueId', 'UI' + random));
|
|
1305
|
+
await storageContext.set('softwareVersion', this.matterbridgeVersion !== '' && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
|
|
1306
|
+
await storageContext.set('softwareVersionString', this.matterbridgeVersion !== '' ? this.matterbridgeVersion : '1.0.0');
|
|
1307
|
+
await storageContext.set('hardwareVersion', this.systemInformation.osRelease !== '' && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
|
|
1308
|
+
await storageContext.set('hardwareVersionString', this.systemInformation.osRelease !== '' ? this.systemInformation.osRelease : '1.0.0');
|
|
1309
|
+
this.log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`);
|
|
1310
|
+
this.log.debug(`- storeId: ${await storageContext.get('storeId')}`);
|
|
1311
|
+
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
|
|
1312
|
+
this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
1313
|
+
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
|
|
1314
|
+
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1315
|
+
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1316
|
+
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1317
|
+
return storageContext;
|
|
1318
|
+
}
|
|
1319
|
+
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1320
|
+
const storeId = await storageContext.get('storeId');
|
|
1321
|
+
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
1322
|
+
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
|
|
1323
|
+
this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
1324
|
+
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
|
|
1325
|
+
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1326
|
+
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1327
|
+
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1328
|
+
const serverNode = await ServerNode.create({
|
|
1329
|
+
id: storeId,
|
|
1330
|
+
network: {
|
|
1331
|
+
listeningAddressIpv4: this.ipv4address,
|
|
1332
|
+
listeningAddressIpv6: this.ipv6address,
|
|
1333
|
+
port,
|
|
1334
|
+
},
|
|
1335
|
+
commissioning: {
|
|
1336
|
+
passcode,
|
|
1337
|
+
discriminator,
|
|
1338
|
+
},
|
|
1339
|
+
productDescription: {
|
|
1340
|
+
name: await storageContext.get('deviceName'),
|
|
1341
|
+
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1342
|
+
},
|
|
1343
|
+
basicInformation: {
|
|
1344
|
+
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1345
|
+
vendorName: await storageContext.get('vendorName'),
|
|
1346
|
+
productId: await storageContext.get('productId'),
|
|
1347
|
+
productName: await storageContext.get('productName'),
|
|
1348
|
+
productLabel: await storageContext.get('productName'),
|
|
1349
|
+
nodeLabel: await storageContext.get('productName'),
|
|
1350
|
+
serialNumber: await storageContext.get('serialNumber'),
|
|
1351
|
+
uniqueId: await storageContext.get('uniqueId'),
|
|
1352
|
+
softwareVersion: await storageContext.get('softwareVersion'),
|
|
1353
|
+
softwareVersionString: await storageContext.get('softwareVersionString'),
|
|
1354
|
+
hardwareVersion: await storageContext.get('hardwareVersion'),
|
|
1355
|
+
hardwareVersionString: await storageContext.get('hardwareVersionString'),
|
|
1356
|
+
},
|
|
1719
1357
|
});
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1358
|
+
const sanitizeFabrics = (fabrics) => {
|
|
1359
|
+
const sanitizedFabrics = this.sanitizeFabricInformations(Array.from(Object.values(fabrics)));
|
|
1360
|
+
this.log.info(`Fabrics: ${debugStringify(sanitizedFabrics)}`);
|
|
1361
|
+
if (this.bridgeMode === 'bridge') {
|
|
1362
|
+
this.matterbridgeFabricInformations = sanitizedFabrics;
|
|
1363
|
+
this.matterbridgePaired = true;
|
|
1364
|
+
}
|
|
1365
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1366
|
+
const plugin = this.plugins.get(storeId);
|
|
1367
|
+
if (plugin) {
|
|
1368
|
+
plugin.fabricInformations = sanitizedFabrics;
|
|
1369
|
+
plugin.paired = true;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
|
|
1374
|
+
serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
|
|
1375
|
+
serverNode.lifecycle.online.on(() => {
|
|
1376
|
+
this.log.notice(`Server node for ${storeId} is online`);
|
|
1377
|
+
if (!serverNode.lifecycle.isCommissioned) {
|
|
1378
|
+
this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
|
|
1379
|
+
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
1380
|
+
if (this.bridgeMode === 'bridge') {
|
|
1381
|
+
this.matterbridgeQrPairingCode = qrPairingCode;
|
|
1382
|
+
this.matterbridgeManualPairingCode = manualPairingCode;
|
|
1383
|
+
this.matterbridgeFabricInformations = [];
|
|
1384
|
+
this.matterbridgeSessionInformations = [];
|
|
1385
|
+
this.matterbridgePaired = false;
|
|
1386
|
+
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1387
|
+
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1388
|
+
}
|
|
1389
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1390
|
+
const plugin = this.plugins.get(storeId);
|
|
1391
|
+
if (plugin) {
|
|
1392
|
+
plugin.qrPairingCode = qrPairingCode;
|
|
1393
|
+
plugin.manualPairingCode = manualPairingCode;
|
|
1394
|
+
plugin.fabricInformations = [];
|
|
1395
|
+
plugin.sessionInformations = [];
|
|
1396
|
+
plugin.paired = false;
|
|
1397
|
+
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1398
|
+
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1737
1401
|
}
|
|
1738
1402
|
else {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
throw new Error('Discriminator value must be less than 4096');
|
|
1742
|
-
setupPin = this.mattercontrollerContext.get('pin', 20202021);
|
|
1403
|
+
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
1404
|
+
sanitizeFabrics(serverNode.state.commissioning.fabrics);
|
|
1743
1405
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1406
|
+
this.frontend.wssSendRefreshRequired();
|
|
1407
|
+
});
|
|
1408
|
+
serverNode.lifecycle.offline.on(() => {
|
|
1409
|
+
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1410
|
+
if (this.bridgeMode === 'bridge') {
|
|
1411
|
+
this.matterbridgeQrPairingCode = undefined;
|
|
1412
|
+
this.matterbridgeManualPairingCode = undefined;
|
|
1413
|
+
this.matterbridgeFabricInformations = [];
|
|
1414
|
+
this.matterbridgeSessionInformations = [];
|
|
1415
|
+
this.matterbridgePaired = false;
|
|
1746
1416
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1417
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1418
|
+
const plugin = this.plugins.get(storeId);
|
|
1419
|
+
if (plugin) {
|
|
1420
|
+
plugin.qrPairingCode = undefined;
|
|
1421
|
+
plugin.manualPairingCode = undefined;
|
|
1422
|
+
plugin.fabricInformations = [];
|
|
1423
|
+
plugin.sessionInformations = [];
|
|
1424
|
+
plugin.paired = false;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
this.frontend.wssSendRefreshRequired();
|
|
1428
|
+
});
|
|
1429
|
+
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1430
|
+
let action = '';
|
|
1431
|
+
switch (fabricAction) {
|
|
1432
|
+
case FabricAction.Added:
|
|
1433
|
+
action = 'added';
|
|
1434
|
+
break;
|
|
1435
|
+
case FabricAction.Removed:
|
|
1436
|
+
action = 'removed';
|
|
1437
|
+
break;
|
|
1438
|
+
case FabricAction.Updated:
|
|
1439
|
+
action = 'updated';
|
|
1440
|
+
break;
|
|
1441
|
+
}
|
|
1442
|
+
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1443
|
+
sanitizeFabrics(serverNode.state.commissioning.fabrics);
|
|
1444
|
+
this.frontend.wssSendRefreshRequired();
|
|
1445
|
+
});
|
|
1446
|
+
const sanitizeSessions = (sessions) => {
|
|
1447
|
+
const sanitizedSessions = this.sanitizeSessionInformation(sessions.map((session) => ({
|
|
1448
|
+
...session,
|
|
1449
|
+
secure: session.name.startsWith('secure'),
|
|
1450
|
+
})));
|
|
1451
|
+
this.log.debug(`Sessions: ${debugStringify(sanitizedSessions)}`);
|
|
1452
|
+
if (this.bridgeMode === 'bridge') {
|
|
1453
|
+
this.matterbridgeSessionInformations = sanitizedSessions;
|
|
1454
|
+
}
|
|
1455
|
+
if (this.bridgeMode === 'childbridge') {
|
|
1456
|
+
const plugin = this.plugins.get(storeId);
|
|
1457
|
+
if (plugin) {
|
|
1458
|
+
plugin.sessionInformations = sanitizedSessions;
|
|
1459
|
+
}
|
|
1770
1460
|
}
|
|
1461
|
+
};
|
|
1462
|
+
serverNode.events.sessions.opened.on((session) => {
|
|
1463
|
+
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1464
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1465
|
+
this.frontend.wssSendRefreshRequired();
|
|
1466
|
+
});
|
|
1467
|
+
serverNode.events.sessions.closed.on((session) => {
|
|
1468
|
+
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1469
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1470
|
+
this.frontend.wssSendRefreshRequired();
|
|
1471
|
+
});
|
|
1472
|
+
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1473
|
+
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1474
|
+
sanitizeSessions(Object.values(serverNode.state.sessions.sessions));
|
|
1475
|
+
this.frontend.wssSendRefreshRequired();
|
|
1476
|
+
});
|
|
1477
|
+
this.log.info(`Created server node for ${storeId}`);
|
|
1478
|
+
return serverNode;
|
|
1479
|
+
}
|
|
1480
|
+
async startServerNode(matterServerNode) {
|
|
1481
|
+
if (!matterServerNode)
|
|
1771
1482
|
return;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1778
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1483
|
+
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1484
|
+
await matterServerNode.start();
|
|
1485
|
+
}
|
|
1486
|
+
async stopServerNode(matterServerNode) {
|
|
1487
|
+
if (!matterServerNode)
|
|
1779
1488
|
return;
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1783
|
-
for (const nodeId of nodeIds) {
|
|
1784
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1785
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1786
|
-
autoSubscribe: false,
|
|
1787
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) => this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1788
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) => this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1789
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1790
|
-
switch (info) {
|
|
1791
|
-
case NodeStateInformation.Connected:
|
|
1792
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1793
|
-
break;
|
|
1794
|
-
case NodeStateInformation.Disconnected:
|
|
1795
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1796
|
-
break;
|
|
1797
|
-
case NodeStateInformation.Reconnecting:
|
|
1798
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1799
|
-
break;
|
|
1800
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1801
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1802
|
-
break;
|
|
1803
|
-
case NodeStateInformation.StructureChanged:
|
|
1804
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1805
|
-
break;
|
|
1806
|
-
case NodeStateInformation.Decommissioned:
|
|
1807
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1808
|
-
break;
|
|
1809
|
-
default:
|
|
1810
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1811
|
-
break;
|
|
1812
|
-
}
|
|
1813
|
-
},
|
|
1814
|
-
});
|
|
1815
|
-
node.logStructure();
|
|
1816
|
-
// Get the interaction client
|
|
1817
|
-
this.log.info('Getting the interaction client');
|
|
1818
|
-
const interactionClient = await node.getInteractionClient();
|
|
1819
|
-
let cluster;
|
|
1820
|
-
let attributes;
|
|
1821
|
-
// Log BasicInformationCluster
|
|
1822
|
-
cluster = BasicInformationCluster;
|
|
1823
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1824
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1825
|
-
});
|
|
1826
|
-
if (attributes.length > 0)
|
|
1827
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1828
|
-
attributes.forEach((attribute) => {
|
|
1829
|
-
this.log.info(`- 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}`);
|
|
1830
|
-
});
|
|
1831
|
-
// Log PowerSourceCluster
|
|
1832
|
-
cluster = PowerSourceCluster;
|
|
1833
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1834
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1835
|
-
});
|
|
1836
|
-
if (attributes.length > 0)
|
|
1837
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1838
|
-
attributes.forEach((attribute) => {
|
|
1839
|
-
this.log.info(`- 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}`);
|
|
1840
|
-
});
|
|
1841
|
-
// Log ThreadNetworkDiagnostics
|
|
1842
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1843
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1844
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1845
|
-
});
|
|
1846
|
-
if (attributes.length > 0)
|
|
1847
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1848
|
-
attributes.forEach((attribute) => {
|
|
1849
|
-
this.log.info(`- 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}`);
|
|
1850
|
-
});
|
|
1851
|
-
// Log SwitchCluster
|
|
1852
|
-
cluster = SwitchCluster;
|
|
1853
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1854
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1855
|
-
});
|
|
1856
|
-
if (attributes.length > 0)
|
|
1857
|
-
this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1858
|
-
attributes.forEach((attribute) => {
|
|
1859
|
-
this.log.info(`- 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}`);
|
|
1860
|
-
});
|
|
1861
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1862
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1863
|
-
ignoreInitialTriggers: false,
|
|
1864
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) => this.log.info(`***${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}`),
|
|
1865
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1866
|
-
this.log.info(`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`);
|
|
1867
|
-
},
|
|
1868
|
-
});
|
|
1869
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1870
|
-
}
|
|
1489
|
+
this.log.notice(`Closing ${matterServerNode.id} server node`);
|
|
1490
|
+
await matterServerNode.close();
|
|
1871
1491
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
async startMatterStorage(storageType, storageName) {
|
|
1882
|
-
this.log.debug(`Starting matter ${storageType} storage ${CYAN}${storageName}${db}`);
|
|
1883
|
-
if (storageType === 'disk') {
|
|
1884
|
-
const storageDisk = new StorageBackendDisk(storageName);
|
|
1885
|
-
this.storageManager = new StorageManager(storageDisk);
|
|
1886
|
-
}
|
|
1887
|
-
else if (storageType === 'json') {
|
|
1888
|
-
if (!storageName.endsWith('.json'))
|
|
1889
|
-
storageName += '.json';
|
|
1890
|
-
const storageJson = new StorageBackendJsonFile(storageName);
|
|
1891
|
-
this.storageManager = new StorageManager(storageJson);
|
|
1892
|
-
}
|
|
1893
|
-
else {
|
|
1894
|
-
this.log.error(`Unsupported matter storage type ${storageType}`);
|
|
1895
|
-
await this.cleanup('Unsupported matter storage type');
|
|
1492
|
+
async createAggregatorNode(storageContext) {
|
|
1493
|
+
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
|
|
1494
|
+
const aggregatorNode = new EndpointNode(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1495
|
+
return aggregatorNode;
|
|
1496
|
+
}
|
|
1497
|
+
async addBridgedEndpoint(pluginName, device) {
|
|
1498
|
+
const plugin = this.plugins.get(pluginName);
|
|
1499
|
+
if (!plugin) {
|
|
1500
|
+
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
1896
1501
|
return;
|
|
1897
1502
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
this.
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
}
|
|
1503
|
+
if (this.bridgeMode === 'bridge') {
|
|
1504
|
+
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1505
|
+
if (!this.aggregatorNode)
|
|
1506
|
+
this.log.error('Aggregator node not found for Matterbridge');
|
|
1507
|
+
await this.aggregatorNode?.add(device);
|
|
1904
1508
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
this.log.fatal(`Please delete it and rename ${storageName.replace('.json', '.backup.json')} to ${storageName} and try to restart Matterbridge.`);
|
|
1909
|
-
await this.cleanup('Matter storage initialize error and -norestore parameter found!');
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
await this.restoreMatterStorage(storageName.replace('.json', '') + '.backup.json', storageName);
|
|
1913
|
-
try {
|
|
1914
|
-
await this.storageManager.initialize();
|
|
1915
|
-
this.log.notice('Matter storage initialized from the backup file');
|
|
1509
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
1510
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
1511
|
+
this.createAccessoryPlugin(plugin, device);
|
|
1916
1512
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1513
|
+
if (plugin.type === 'DynamicPlatform') {
|
|
1514
|
+
plugin.locked = true;
|
|
1515
|
+
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} aggregator node`);
|
|
1516
|
+
if (!plugin.aggregatorNode)
|
|
1517
|
+
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${db}`);
|
|
1518
|
+
await plugin.aggregatorNode?.add(device);
|
|
1921
1519
|
}
|
|
1922
1520
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1521
|
+
if (plugin.registeredDevices !== undefined)
|
|
1522
|
+
plugin.registeredDevices++;
|
|
1523
|
+
if (plugin.addedDevices !== undefined)
|
|
1524
|
+
plugin.addedDevices++;
|
|
1525
|
+
this.devices.set(device);
|
|
1526
|
+
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}`);
|
|
1928
1527
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
*/
|
|
1935
|
-
async convertStorage(context, pluginName) {
|
|
1936
|
-
if (this.edge !== false)
|
|
1528
|
+
async removeBridgedEndpoint(pluginName, device) {
|
|
1529
|
+
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
1530
|
+
const plugin = this.plugins.get(pluginName);
|
|
1531
|
+
if (!plugin) {
|
|
1532
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1937
1533
|
return;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
if ((await nodeStorage.createContext('root').createContext('generalDiagnostics').get('rebootCount', -1)) >= 0) {
|
|
1943
|
-
this.log.info(`Matter node storage already converted to Matterbridge edge for ${plg}${pluginName}${nf}`);
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
else {
|
|
1947
|
-
this.log.notice(`Converting matter node storage to Matterbridge edge for ${plg}${pluginName}${nt}...`);
|
|
1948
|
-
}
|
|
1949
|
-
// Read FabricManager from the old storage and get FabricManager.fabrics and FabricManager.nextFabricIndex
|
|
1950
|
-
const fabricManagerContext = context.createContext('FabricManager');
|
|
1951
|
-
const fabrics = (await fabricManagerContext.get('fabrics', []));
|
|
1952
|
-
const nextFabricIndex = await fabricManagerContext.get('nextFabricIndex', 0);
|
|
1953
|
-
// Read EventHandler from the old storage
|
|
1954
|
-
const eventHandlerContext = context.createContext('EventHandler');
|
|
1955
|
-
// Read SessionManager from the old storage
|
|
1956
|
-
const sessionManagerContext = context.createContext('SessionManager');
|
|
1957
|
-
// Read EndpointStructure from the old storage
|
|
1958
|
-
const endpointStructureContext = context.createContext('EndpointStructure');
|
|
1959
|
-
// Read generalCommissioning from the old storage
|
|
1960
|
-
const generalCommissioningContext = context.createContext('Cluster-0-48');
|
|
1961
|
-
// Read basicInformation from the old storage
|
|
1962
|
-
const basicInformationContext = context.createContext('Cluster-0-40');
|
|
1963
|
-
const fabricInfo = {};
|
|
1964
|
-
const fabricInfoArray = [];
|
|
1965
|
-
const nocArray = [];
|
|
1966
|
-
const trcArray = [];
|
|
1967
|
-
const aclArray = [];
|
|
1968
|
-
this.log.info(`Found ${CYAN}${fabrics.length}${nf} fabrics (nextFabricIndex ${CYAN}${nextFabricIndex}${nf}) for ${plg}${pluginName}${nf}:`);
|
|
1969
|
-
if (fabrics.length === 0 || nextFabricIndex === 0) {
|
|
1970
|
-
this.log.notice(`If you want to try out matterbridge edge add -edge to the command line and pair it to your controller(s).`);
|
|
1534
|
+
}
|
|
1535
|
+
if (this.bridgeMode === 'bridge') {
|
|
1536
|
+
if (!this.aggregatorNode) {
|
|
1537
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1971
1538
|
return;
|
|
1972
1539
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
rootNodeId: fabric.rootNodeId,
|
|
1980
|
-
rootVendorId: fabric.rootVendorId,
|
|
1981
|
-
label: fabric.label,
|
|
1982
|
-
};
|
|
1983
|
-
fabricInfoArray.push({
|
|
1984
|
-
fabricIndex: fabric.fabricIndex,
|
|
1985
|
-
fabricId: fabric.fabricId,
|
|
1986
|
-
nodeId: fabric.nodeId,
|
|
1987
|
-
vendorId: fabric.rootVendorId,
|
|
1988
|
-
rootPublicKey: fabric.rootPublicKey,
|
|
1989
|
-
label: fabric.label,
|
|
1990
|
-
});
|
|
1991
|
-
nocArray.push({ noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex });
|
|
1992
|
-
trcArray.push(fabric.rootCert);
|
|
1993
|
-
this.log.info(`- updating ACL for fabricIndex ${fabric.fabricIndex}:`, fabric.scopedClusterData);
|
|
1994
|
-
const acl = fabric.scopedClusterData.get(0x1f)?.get('acl');
|
|
1995
|
-
if (acl && acl.value.length > 0) {
|
|
1996
|
-
aclArray.push(acl.value[0]);
|
|
1997
|
-
this.log.info(`- ACL updated to ${debugStringify(acl.value)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
|
|
1998
|
-
}
|
|
1999
|
-
else {
|
|
2000
|
-
const defaultAcl = { fabricIndex: fabric.fabricIndex, privilege: 5, authMode: 2, subjects: [fabric.rootNodeId], targets: null };
|
|
2001
|
-
aclArray.push(defaultAcl);
|
|
2002
|
-
this.log.info(`- ACL updated to default ${debugStringify(defaultAcl)}${nf} for fabricIndex ${CYAN}${fabric.fabricIndex}${nf}`);
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
await nodeStorage.createContext('fabrics').set('fabrics', fabrics);
|
|
2006
|
-
await nodeStorage.createContext('fabrics').set('nextFabricIndex', nextFabricIndex);
|
|
2007
|
-
await nodeStorage.createContext('sessions').set('resumptionRecords', await sessionManagerContext.get('resumptionRecords', []));
|
|
2008
|
-
await nodeStorage.createContext('events').set('lastEventNumber', await eventHandlerContext.get('lastEventNumber', 1));
|
|
2009
|
-
await nodeStorage.createContext('root').set('__number__', 0);
|
|
2010
|
-
await nodeStorage.createContext('root').createContext('commissioning').set('enabled', true);
|
|
2011
|
-
await nodeStorage.createContext('root').createContext('commissioning').set('commissioned', true);
|
|
2012
|
-
await nodeStorage.createContext('root').createContext('commissioning').set('fabrics', fabricInfo);
|
|
2013
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('commissionedFabrics', fabricInfoArray.length);
|
|
2014
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('fabrics', fabricInfoArray);
|
|
2015
|
-
// operationalCredentials.nocs ==>> [{noc: fabric.operationalCert, icac: null, fabricIndex: fabric.fabricIndex }]
|
|
2016
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('nocs', nocArray);
|
|
2017
|
-
// operationalCredentials.trustedRootCertificates ==>> ["{\"__object__\":\"Uint8Array\",\"__value__\":\"" + fabric.rootCert + "\"}"]
|
|
2018
|
-
await nodeStorage.createContext('root').createContext('operationalCredentials').set('trustedRootCertificates', trcArray);
|
|
2019
|
-
// ACL updated, updating ACL manager { fabricIndex: 3, privilege: 5, authMode: 2, subjects: [ 18446744060825763897 ], targets: null }
|
|
2020
|
-
// From fabric.rootNodeId if fabric.scopedClusterData.get(0x1f).get('acl') is empty
|
|
2021
|
-
// [{"fabricIndex":3,"privilege":5,"authMode":2,"subjects":["{\"__object__\":\"BigInt\",\"__value__\":\"18446744060825763897\"}"],"targets":null}]
|
|
2022
|
-
await nodeStorage.createContext('root').createContext('accessControl').set('acl', aclArray);
|
|
2023
|
-
await nodeStorage
|
|
2024
|
-
.createContext('root')
|
|
2025
|
-
.createContext('generalCommissioning')
|
|
2026
|
-
.set('breadcrumb', await generalCommissioningContext.get('breadcrumb', BigInt(0)));
|
|
2027
|
-
await nodeStorage
|
|
2028
|
-
.createContext('root')
|
|
2029
|
-
.createContext('basicInformation')
|
|
2030
|
-
.set('location', await basicInformationContext.get('location', 'XX'));
|
|
2031
|
-
await nodeStorage.createContext('root').createContext('network').set('ble', false);
|
|
2032
|
-
await nodeStorage
|
|
2033
|
-
.createContext('root')
|
|
2034
|
-
.createContext('network')
|
|
2035
|
-
.set('operationalPort', await context.get('port', 5540));
|
|
2036
|
-
await nodeStorage
|
|
2037
|
-
.createContext('root')
|
|
2038
|
-
.createContext('productDescription')
|
|
2039
|
-
.set('productId', await context.get('productId', 0x8000));
|
|
2040
|
-
await nodeStorage
|
|
2041
|
-
.createContext('root')
|
|
2042
|
-
.createContext('productDescription')
|
|
2043
|
-
.set('vendorId', await context.get('vendorId', 0xfff1));
|
|
2044
|
-
/*
|
|
2045
|
-
"Matterbridge.EndpointStructure": {
|
|
2046
|
-
"unique_d60ca095a002f160-index_0": 1,
|
|
2047
|
-
"unique_d60ca095a002f160-index_0-custom_Switch0": 2,
|
|
2048
|
-
"unique_d60ca095a002f160-index_0-custom_Outlet0": 3,
|
|
2049
|
-
"unique_d60ca095a002f160-index_0-custom_Light0": 4,
|
|
2050
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa": 2,
|
|
2051
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_PowerSource": 3,
|
|
2052
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:0": 4,
|
|
2053
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:1": 5,
|
|
2054
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:2": 6,
|
|
2055
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_light:3": 7,
|
|
2056
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:0": 8,
|
|
2057
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:1": 9,
|
|
2058
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:2": 10,
|
|
2059
|
-
"unique_d60ca095a002f160-index_0-unique_7ddb4752b982ee108d5928e934f0fcfa-custom_meter:3": 11,
|
|
2060
|
-
"nextEndpointId": 5
|
|
2061
|
-
},
|
|
2062
|
-
*/
|
|
2063
|
-
const rootDeviceName = (await context.get('deviceName', '')).replace(/[ .]/g, '');
|
|
2064
|
-
this.log.info(`Converting ${pluginName}.EndpointStructure to root.parts.${rootDeviceName}...`);
|
|
2065
|
-
for (const key of await endpointStructureContext.keys()) {
|
|
2066
|
-
if (key === 'nextEndpointId') {
|
|
2067
|
-
await nodeStorage.createContext('root').set('__nextNumber__', await endpointStructureContext.get(key));
|
|
2068
|
-
continue;
|
|
2069
|
-
}
|
|
2070
|
-
const parts = key.split('-');
|
|
2071
|
-
const number = await endpointStructureContext.get(key);
|
|
2072
|
-
if (parts.length === 2) {
|
|
2073
|
-
this.log.debug(`Converting bridge Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.__number__:${CYAN}${number}${db}`);
|
|
2074
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).set('__number__', number);
|
|
2075
|
-
}
|
|
2076
|
-
else if (parts.length === 3 && parts[2].startsWith('unique_')) {
|
|
2077
|
-
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
2078
|
-
if (device && device.deviceName && device.maybeNumber) {
|
|
2079
|
-
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.__number__:${CYAN}${device.maybeNumber}${db}`);
|
|
2080
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(device.deviceName.replace(/[ .]/g, '')).set('__number__', device.maybeNumber);
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
else if (parts.length === 4 && parts[2].startsWith('unique_') && parts[3].startsWith('custom_')) {
|
|
2084
|
-
const device = this.devices.get(parts[2].replace('unique_', ''));
|
|
2085
|
-
if (device && device.deviceName && device.maybeNumber) {
|
|
2086
|
-
const childEndpointName = parts[3].replace('custom_', '');
|
|
2087
|
-
const childEndpoint = device.getChildEndpointByName(childEndpointName);
|
|
2088
|
-
this.log.debug(`Converting unique Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${device.deviceName.replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${childEndpoint?.number}${db}`);
|
|
2089
|
-
await nodeStorage
|
|
2090
|
-
.createContext('root')
|
|
2091
|
-
.createContext('parts')
|
|
2092
|
-
.createContext(rootDeviceName)
|
|
2093
|
-
.createContext('parts')
|
|
2094
|
-
.createContext(device.deviceName.replace(/[ .]/g, ''))
|
|
2095
|
-
.createContext('parts')
|
|
2096
|
-
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2097
|
-
.set('__number__', childEndpoint?.number);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
else if (parts.length === 3 && parts[2].startsWith('custom_')) {
|
|
2101
|
-
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
|
|
2102
|
-
await nodeStorage.createContext('root').createContext('parts').createContext(rootDeviceName).createContext('parts').createContext(parts[2].replace('custom_', '').replace(/[ .]/g, '')).set('__number__', number);
|
|
2103
|
-
}
|
|
2104
|
-
else if (parts.length === 4 && parts[2].startsWith('custom_') && parts[3].startsWith('custom_')) {
|
|
2105
|
-
this.log.debug(`Converting custom Matterbridge.EndpointStructure:${key}:${number} to root.parts.${rootDeviceName}.parts.${parts[2].replace('custom_', '').replace(/[ .]/g, '')}.parts.${parts[3].replace('custom_', '').replace(/[ .]/g, '')}.__number__:${CYAN}${number}${db}`);
|
|
2106
|
-
await nodeStorage
|
|
2107
|
-
.createContext('root')
|
|
2108
|
-
.createContext('parts')
|
|
2109
|
-
.createContext(rootDeviceName)
|
|
2110
|
-
.createContext('parts')
|
|
2111
|
-
.createContext(parts[2].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2112
|
-
.createContext('parts')
|
|
2113
|
-
.createContext(parts[3].replace('custom_', '').replace(/[ .]/g, ''))
|
|
2114
|
-
.set('__number__', number);
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
await nodeStorage.createContext('persist').set('converted', true);
|
|
2118
|
-
await nodeStorage.createContext('persist').set('deviceName', await context.get('deviceName'));
|
|
2119
|
-
await nodeStorage.createContext('persist').set('deviceType', await context.get('deviceType'));
|
|
2120
|
-
await nodeStorage.createContext('persist').set('vendorId', await context.get('vendorId'));
|
|
2121
|
-
await nodeStorage.createContext('persist').set('vendorName', await context.get('vendorName'));
|
|
2122
|
-
await nodeStorage.createContext('persist').set('productId', await context.get('productId'));
|
|
2123
|
-
await nodeStorage.createContext('persist').set('productName', await context.get('productName'));
|
|
2124
|
-
await nodeStorage.createContext('persist').set('nodeLabel', await context.get('nodeLabel'));
|
|
2125
|
-
await nodeStorage.createContext('persist').set('productLabel', await context.get('productLabel'));
|
|
2126
|
-
await nodeStorage.createContext('persist').set('serialNumber', 'SN' + (await context.get('serialNumber')));
|
|
2127
|
-
await nodeStorage.createContext('persist').set('uniqueId', await context.get('uniqueId'));
|
|
2128
|
-
await nodeStorage.createContext('persist').set('softwareVersion', await context.get('softwareVersion'));
|
|
2129
|
-
await nodeStorage.createContext('persist').set('softwareVersionString', await context.get('softwareVersionString'));
|
|
2130
|
-
await nodeStorage.createContext('persist').set('hardwareVersion', await context.get('hardwareVersion'));
|
|
2131
|
-
await nodeStorage.createContext('persist').set('hardwareVersionString', await context.get('hardwareVersionString'));
|
|
2132
|
-
await context.set('converted', true);
|
|
2133
|
-
this.log.notice(`Matter storage converted to Matterbridge edge for ${plg}${pluginName}${nt}`);
|
|
2134
|
-
this.log.notice(`If you want to try out matterbridge edge add -edge to the command line.`);
|
|
2135
|
-
this.log.notice(`All fabrics have been converted to the new storage format.`);
|
|
2136
|
-
}
|
|
2137
|
-
catch (error) {
|
|
2138
|
-
this.log.error(`convertStorage error converting matter storage to Matterbridge edge for ${plg}${pluginName}${er}:`, error);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
/**
|
|
2142
|
-
* Makes a backup copy of the specified matter JSON storage file.
|
|
2143
|
-
*
|
|
2144
|
-
* @param storageName - The name of the JSON storage file to be backed up.
|
|
2145
|
-
* @param backupName - The name of the backup file to be created.
|
|
2146
|
-
*/
|
|
2147
|
-
async backupMatterStorage(storageName, backupName) {
|
|
2148
|
-
try {
|
|
2149
|
-
this.log.debug(`Making backup copy of ${storageName}`);
|
|
2150
|
-
await fs.copyFile(storageName, backupName);
|
|
2151
|
-
this.log.debug(`Successfully backed up ${storageName} to ${backupName}`);
|
|
1540
|
+
await device.delete();
|
|
1541
|
+
this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1542
|
+
if (plugin.registeredDevices !== undefined)
|
|
1543
|
+
plugin.registeredDevices--;
|
|
1544
|
+
if (plugin.addedDevices !== undefined)
|
|
1545
|
+
plugin.addedDevices--;
|
|
2152
1546
|
}
|
|
2153
|
-
|
|
2154
|
-
if (
|
|
2155
|
-
if (err.code === 'ENOENT') {
|
|
2156
|
-
this.log.debug(`No existing file to back up for ${storageName}. This is expected on the first run.`);
|
|
2157
|
-
}
|
|
2158
|
-
else {
|
|
2159
|
-
this.log.error(`Error making backup copy of ${storageName}: ${err.message}`);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
else {
|
|
2163
|
-
this.log.error(`An unexpected error occurred during the backup of ${storageName}: ${String(err)}`);
|
|
1547
|
+
else if (this.bridgeMode === 'childbridge') {
|
|
1548
|
+
if (plugin.type === 'AccessoryPlatform') {
|
|
2164
1549
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
*
|
|
2170
|
-
* @param backupName - The name of the backup file to restore from.
|
|
2171
|
-
* @param storageName - The name of the JSON storage file to restored.
|
|
2172
|
-
*/
|
|
2173
|
-
async restoreMatterStorage(backupName, storageName) {
|
|
2174
|
-
try {
|
|
2175
|
-
this.log.notice(`Restoring the backup copy of ${storageName}`);
|
|
2176
|
-
await fs.copyFile(backupName, storageName);
|
|
2177
|
-
this.log.notice(`Successfully restored ${backupName} to ${storageName}`);
|
|
2178
|
-
}
|
|
2179
|
-
catch (err) {
|
|
2180
|
-
if (err instanceof Error && 'code' in err) {
|
|
2181
|
-
if (err.code === 'ENOENT') {
|
|
2182
|
-
this.log.info(`No existing file to restore: ${backupName}.`);
|
|
2183
|
-
}
|
|
2184
|
-
else {
|
|
2185
|
-
this.log.error(`Error restoring ${backupName}: ${err.message}`);
|
|
1550
|
+
else if (plugin.type === 'DynamicPlatform') {
|
|
1551
|
+
if (!plugin.aggregatorNode) {
|
|
1552
|
+
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1553
|
+
return;
|
|
2186
1554
|
}
|
|
1555
|
+
await device.delete();
|
|
2187
1556
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
this.log.debug('Storage closed');
|
|
2201
|
-
this.storageManager = undefined;
|
|
2202
|
-
this.matterbridgeContext = undefined;
|
|
2203
|
-
this.mattercontrollerContext = undefined;
|
|
2204
|
-
}
|
|
2205
|
-
/**
|
|
2206
|
-
* Creates a Matter server using the provided storage manager and the provided mdnsInterface.
|
|
2207
|
-
* @param storageManager The storage manager to be used by the Matter server.
|
|
2208
|
-
*
|
|
2209
|
-
*/
|
|
2210
|
-
async createMatterServer(storageManager) {
|
|
2211
|
-
this.log.debug('Creating matter server');
|
|
2212
|
-
// Validate mdnsInterface
|
|
2213
|
-
if (this.mdnsInterface) {
|
|
2214
|
-
const networkInterfaces = os.networkInterfaces();
|
|
2215
|
-
const availableInterfaces = Object.keys(networkInterfaces);
|
|
2216
|
-
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
2217
|
-
this.log.error(`Invalid mdnsInterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
2218
|
-
this.mdnsInterface = undefined;
|
|
2219
|
-
}
|
|
2220
|
-
else {
|
|
2221
|
-
this.log.info(`Using mdnsInterface '${this.mdnsInterface}' for the Matter server MdnsBroadcaster.`);
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
const matterServer = new MatterServer(storageManager, { mdnsInterface: this.mdnsInterface });
|
|
2225
|
-
this.log.debug(`Created matter server with mdnsInterface: ${this.mdnsInterface ?? 'all available interfaces'}`);
|
|
2226
|
-
return matterServer;
|
|
2227
|
-
}
|
|
2228
|
-
/**
|
|
2229
|
-
* Starts the Matter server.
|
|
2230
|
-
* If the Matter server is not initialized, it logs an error and performs cleanup.
|
|
2231
|
-
*/
|
|
2232
|
-
async startMatterServer() {
|
|
2233
|
-
if (!this.matterServer) {
|
|
2234
|
-
this.log.error('No matter server initialized');
|
|
2235
|
-
await this.cleanup('No matter server initialized');
|
|
2236
|
-
return;
|
|
2237
|
-
}
|
|
2238
|
-
this.log.debug('Starting matter server...');
|
|
2239
|
-
await this.matterServer.start();
|
|
2240
|
-
this.log.debug('Started matter server');
|
|
2241
|
-
// this.commissioningServer?.getRootEndpoint() && logEndpoint(this.commissioningServer?.getRootEndpoint());
|
|
2242
|
-
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Stops the Matter server, commissioningServer and commissioningController.
|
|
2245
|
-
*/
|
|
2246
|
-
async stopMatterServer() {
|
|
2247
|
-
this.log.debug('Stopping matter commissioningServer');
|
|
2248
|
-
await this.commissioningServer?.close();
|
|
2249
|
-
this.log.debug('Stopping matter commissioningController');
|
|
2250
|
-
await this.commissioningController?.close();
|
|
2251
|
-
this.log.debug('Stopping matter server');
|
|
2252
|
-
await this.matterServer?.close();
|
|
2253
|
-
this.log.debug('Matter server closed');
|
|
2254
|
-
this.commissioningController = undefined;
|
|
2255
|
-
this.commissioningServer = undefined;
|
|
2256
|
-
this.matterAggregator = undefined;
|
|
2257
|
-
this.matterServer = undefined;
|
|
2258
|
-
}
|
|
2259
|
-
/**
|
|
2260
|
-
* Creates a Matter Aggregator.
|
|
2261
|
-
* @param {StorageContext} context - The storage context.
|
|
2262
|
-
* @returns {Aggregator} - The created Matter Aggregator.
|
|
2263
|
-
*/
|
|
2264
|
-
async createMatterAggregator(context, pluginName) {
|
|
2265
|
-
this.log.debug(`Creating matter aggregator for ${plg}${pluginName}${db}`);
|
|
2266
|
-
const matterAggregator = new Aggregator();
|
|
2267
|
-
return matterAggregator;
|
|
2268
|
-
}
|
|
2269
|
-
/**
|
|
2270
|
-
* Creates a matter commissioning server.
|
|
2271
|
-
*
|
|
2272
|
-
* @param {StorageContext} context - The storage context.
|
|
2273
|
-
* @param {string} pluginName - The name of the commissioning server.
|
|
2274
|
-
* @returns {CommissioningServer} The created commissioning server.
|
|
2275
|
-
*/
|
|
2276
|
-
async createCommisioningServer(context, pluginName) {
|
|
2277
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db}`);
|
|
2278
|
-
const deviceName = await context.get('deviceName');
|
|
2279
|
-
const deviceType = await context.get('deviceType');
|
|
2280
|
-
const vendorId = await context.get('vendorId');
|
|
2281
|
-
const vendorName = await context.get('vendorName'); // Home app = Manufacturer
|
|
2282
|
-
const productId = await context.get('productId');
|
|
2283
|
-
const productName = await context.get('productName'); // Home app = Model
|
|
2284
|
-
const serialNumber = await context.get('serialNumber');
|
|
2285
|
-
const uniqueId = await context.get('uniqueId');
|
|
2286
|
-
const softwareVersion = await context.get('softwareVersion', 1);
|
|
2287
|
-
const softwareVersionString = await context.get('softwareVersionString', '1.0.0'); // Home app = Firmware Revision
|
|
2288
|
-
const hardwareVersion = await context.get('hardwareVersion', 1);
|
|
2289
|
-
const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0');
|
|
2290
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`);
|
|
2291
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`);
|
|
2292
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`);
|
|
2293
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`);
|
|
2294
|
-
this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${CYAN}${this.port}${db} discriminator ${CYAN}${this.discriminator}${db} passcode ${CYAN}${this.passcode}${db} `);
|
|
2295
|
-
// Validate ipv4address
|
|
2296
|
-
if (this.ipv4address) {
|
|
2297
|
-
const networkInterfaces = os.networkInterfaces();
|
|
2298
|
-
const availableAddresses = Object.values(networkInterfaces)
|
|
2299
|
-
.flat()
|
|
2300
|
-
.filter((iface) => iface !== undefined && iface.family === 'IPv4' && !iface.internal)
|
|
2301
|
-
.map((iface) => iface.address);
|
|
2302
|
-
if (!availableAddresses.includes(this.ipv4address)) {
|
|
2303
|
-
this.log.error(`Invalid ipv4address: ${this.ipv4address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
|
|
2304
|
-
this.mdnsInterface = undefined;
|
|
2305
|
-
}
|
|
2306
|
-
else {
|
|
2307
|
-
this.log.info(`Using ipv4address '${this.ipv4address}' for the Matter commissioning server.`);
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
// Validate ipv6address
|
|
2311
|
-
if (this.ipv6address) {
|
|
2312
|
-
const networkInterfaces = os.networkInterfaces();
|
|
2313
|
-
const availableAddresses = Object.values(networkInterfaces)
|
|
2314
|
-
.flat()
|
|
2315
|
-
.filter((iface) => iface !== undefined && iface.family === 'IPv6' && !iface.internal)
|
|
2316
|
-
.map((iface) => iface.address);
|
|
2317
|
-
if (!availableAddresses.includes(this.ipv6address)) {
|
|
2318
|
-
this.log.error(`Invalid ipv6address: ${this.ipv6address}. Available addresses are: ${availableAddresses.join(', ')}. Using all available addresses.`);
|
|
2319
|
-
this.mdnsInterface = undefined;
|
|
2320
|
-
}
|
|
2321
|
-
else {
|
|
2322
|
-
this.log.info(`Using ipv6address '${this.ipv6address}' for the Matter commissioning server.`);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
const commissioningServer = new CommissioningServer({
|
|
2326
|
-
port: this.port++,
|
|
2327
|
-
listeningAddressIpv4: this.ipv4address,
|
|
2328
|
-
listeningAddressIpv6: this.ipv6address,
|
|
2329
|
-
passcode: this.passcode,
|
|
2330
|
-
discriminator: this.discriminator,
|
|
2331
|
-
deviceName,
|
|
2332
|
-
deviceType,
|
|
2333
|
-
basicInformation: {
|
|
2334
|
-
vendorId: VendorId(vendorId),
|
|
2335
|
-
vendorName,
|
|
2336
|
-
productId,
|
|
2337
|
-
productName,
|
|
2338
|
-
nodeLabel: productName,
|
|
2339
|
-
productLabel: productName,
|
|
2340
|
-
softwareVersion,
|
|
2341
|
-
softwareVersionString, // Home app = Firmware Revision
|
|
2342
|
-
hardwareVersion,
|
|
2343
|
-
hardwareVersionString,
|
|
2344
|
-
uniqueId,
|
|
2345
|
-
serialNumber,
|
|
2346
|
-
reachable: true,
|
|
2347
|
-
},
|
|
2348
|
-
activeSessionsChangedCallback: (fabricIndex) => {
|
|
2349
|
-
const sessionInformations = commissioningServer.getActiveSessionInformation(fabricIndex);
|
|
2350
|
-
let connected = false;
|
|
2351
|
-
sessionInformations.forEach((session) => {
|
|
2352
|
-
this.log.info(`Active session changed on fabric ${zb}${fabricIndex}${nf} id ${zb}${session.fabric?.fabricId}${nf} vendor ${zb}${session.fabric?.rootVendorId}${nf} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} for ${plg}${pluginName}${nf}`, debugStringify(session));
|
|
2353
|
-
if (session.isPeerActive === true && session.secure === true && session.numberOfActiveSubscriptions >= 1) {
|
|
2354
|
-
this.log.notice(`Controller ${zb}${session.fabric?.rootVendorId}${nt} ${this.getVendorIdName(session.fabric?.rootVendorId)} ${session.fabric?.label} connected to ${plg}${pluginName}${nt} on session ${session.name}`);
|
|
2355
|
-
connected = true;
|
|
2356
|
-
}
|
|
2357
|
-
});
|
|
2358
|
-
if (connected) {
|
|
2359
|
-
if (this.bridgeMode === 'bridge') {
|
|
2360
|
-
this.matterbridgePaired = true;
|
|
2361
|
-
this.matterbridgeConnected = true;
|
|
2362
|
-
this.matterbridgeSessionInformations = this.sanitizeSessionInformation(sessionInformations);
|
|
2363
|
-
}
|
|
2364
|
-
if (this.bridgeMode === 'childbridge') {
|
|
2365
|
-
const plugin = this.plugins.get(pluginName);
|
|
2366
|
-
if (plugin) {
|
|
2367
|
-
plugin.paired = true;
|
|
2368
|
-
plugin.connected = true;
|
|
2369
|
-
plugin.sessionInformations = this.sanitizeSessionInformation(sessionInformations);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
else {
|
|
2374
|
-
if (this.bridgeMode === 'bridge') {
|
|
2375
|
-
this.matterbridgeSessionInformations = [];
|
|
2376
|
-
}
|
|
2377
|
-
if (this.bridgeMode === 'childbridge') {
|
|
2378
|
-
const plugin = this.plugins.get(pluginName);
|
|
2379
|
-
if (plugin) {
|
|
2380
|
-
plugin.sessionInformations = [];
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
this.wssSendRefreshRequired();
|
|
2385
|
-
},
|
|
2386
|
-
commissioningChangedCallback: async (fabricIndex) => {
|
|
2387
|
-
const fabricInfo = commissioningServer.getCommissionedFabricInformation(fabricIndex);
|
|
2388
|
-
this.log.debug(`Commissioning changed on fabric ${zb}${fabricIndex}${db} for ${plg}${pluginName}${db}`, debugStringify(fabricInfo));
|
|
2389
|
-
if (commissioningServer.getCommissionedFabricInformation().length === 0) {
|
|
2390
|
-
this.log.warn(`Commissioning removed from fabric ${zb}${fabricIndex}${wr} for ${plg}${pluginName}${wr}. Resetting the commissioning server ...`);
|
|
2391
|
-
await commissioningServer.factoryReset();
|
|
2392
|
-
if (pluginName === 'Matterbridge') {
|
|
2393
|
-
await this.matterbridgeContext?.clearAll();
|
|
2394
|
-
this.matterbridgeFabricInformations = [];
|
|
2395
|
-
this.matterbridgeSessionInformations = [];
|
|
2396
|
-
this.matterbridgePaired = false;
|
|
2397
|
-
this.matterbridgeConnected = false;
|
|
2398
|
-
}
|
|
2399
|
-
else {
|
|
2400
|
-
for (const plugin of this.plugins) {
|
|
2401
|
-
if (plugin.name === pluginName) {
|
|
2402
|
-
await plugin.platform?.onShutdown('Commissioning removed by the controller');
|
|
2403
|
-
plugin.fabricInformations = [];
|
|
2404
|
-
plugin.sessionInformations = [];
|
|
2405
|
-
plugin.paired = false;
|
|
2406
|
-
plugin.connected = false;
|
|
2407
|
-
await plugin.storageContext?.clearAll();
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
this.log.warn(`Restart to activate the pairing for ${plg}${pluginName}${wr}.`);
|
|
2412
|
-
}
|
|
2413
|
-
else {
|
|
2414
|
-
const fabricInfo = commissioningServer.getCommissionedFabricInformation();
|
|
2415
|
-
if (pluginName === 'Matterbridge') {
|
|
2416
|
-
this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2417
|
-
this.matterbridgePaired = true;
|
|
2418
|
-
}
|
|
2419
|
-
else {
|
|
2420
|
-
const plugin = this.plugins.get(pluginName);
|
|
2421
|
-
if (plugin) {
|
|
2422
|
-
plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2423
|
-
plugin.paired = true;
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
this.wssSendRefreshRequired();
|
|
2428
|
-
},
|
|
2429
|
-
});
|
|
2430
|
-
if (this.passcode !== undefined)
|
|
2431
|
-
this.passcode++;
|
|
2432
|
-
if (this.discriminator !== undefined)
|
|
2433
|
-
this.discriminator++;
|
|
2434
|
-
commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`));
|
|
2435
|
-
return commissioningServer;
|
|
2436
|
-
}
|
|
2437
|
-
/**
|
|
2438
|
-
* Creates a commissioning server storage context.
|
|
2439
|
-
*
|
|
2440
|
-
* @param pluginName - The name of the plugin.
|
|
2441
|
-
* @param deviceName - The name of the device.
|
|
2442
|
-
* @param deviceType - The type of the device.
|
|
2443
|
-
* @param vendorId - The vendor ID.
|
|
2444
|
-
* @param vendorName - The vendor name.
|
|
2445
|
-
* @param productId - The product ID.
|
|
2446
|
-
* @param productName - The product name.
|
|
2447
|
-
* @param serialNumber - The serial number of the device (optional).
|
|
2448
|
-
* @param uniqueId - The unique ID of the device (optional).
|
|
2449
|
-
* @param softwareVersion - The software version of the device (optional).
|
|
2450
|
-
* @param softwareVersionString - The software version string of the device (optional).
|
|
2451
|
-
* @param hardwareVersion - The hardware version of the device (optional).
|
|
2452
|
-
* @param hardwareVersionString - The hardware version string of the device (optional).
|
|
2453
|
-
* @returns The storage context for the commissioning server.
|
|
2454
|
-
*/
|
|
2455
|
-
async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
|
|
2456
|
-
if (!this.storageManager)
|
|
2457
|
-
throw new Error('No storage manager initialized');
|
|
2458
|
-
this.log.debug(`Creating commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2459
|
-
const random = randomBytes(8).toString('hex');
|
|
2460
|
-
const storageContext = this.storageManager.createContext(pluginName);
|
|
2461
|
-
await storageContext.set('deviceName', deviceName);
|
|
2462
|
-
await storageContext.set('deviceType', deviceType);
|
|
2463
|
-
await storageContext.set('vendorId', vendorId);
|
|
2464
|
-
await storageContext.set('vendorName', vendorName.slice(0, 32));
|
|
2465
|
-
await storageContext.set('productId', productId);
|
|
2466
|
-
await storageContext.set('productName', productName.slice(0, 32));
|
|
2467
|
-
await storageContext.set('nodeLabel', productName.slice(0, 32));
|
|
2468
|
-
await storageContext.set('productLabel', productName.slice(0, 32));
|
|
2469
|
-
await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
|
|
2470
|
-
await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
|
|
2471
|
-
await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
|
|
2472
|
-
await storageContext.set('softwareVersionString', this.matterbridgeVersion ?? '1.0.0');
|
|
2473
|
-
await storageContext.set('hardwareVersion', this.systemInformation.osRelease && this.systemInformation.osRelease.includes('.') ? parseInt(this.systemInformation.osRelease.split('.')[0], 10) : 1);
|
|
2474
|
-
await storageContext.set('hardwareVersionString', this.systemInformation.osRelease ?? '1.0.0');
|
|
2475
|
-
this.log.debug(`Created commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2476
|
-
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
2477
|
-
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2478
|
-
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2479
|
-
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2480
|
-
return storageContext;
|
|
2481
|
-
}
|
|
2482
|
-
/**
|
|
2483
|
-
* Imports the commissioning server context for a specific plugin and device.
|
|
2484
|
-
* @param pluginName - The name of the plugin.
|
|
2485
|
-
* @param device - The MatterbridgeDevice object representing the device.
|
|
2486
|
-
* @returns The commissioning server context.
|
|
2487
|
-
* @throws Error if the BasicInformationCluster is not found.
|
|
2488
|
-
*/
|
|
2489
|
-
async importCommissioningServerContext(pluginName, device) {
|
|
2490
|
-
this.log.debug(`Importing matter commissioning server storage context from device for ${plg}${pluginName}${db}`);
|
|
2491
|
-
const basic = device.getClusterServer(BasicInformationCluster);
|
|
2492
|
-
if (!basic) {
|
|
2493
|
-
this.log.error('importCommissioningServerContext error: cannot find the BasicInformationCluster');
|
|
2494
|
-
process.exit(1);
|
|
2495
|
-
}
|
|
2496
|
-
if (!this.storageManager) {
|
|
2497
|
-
this.log.error('importCommissioningServerContext error: no storage manager initialized');
|
|
2498
|
-
process.exit(1);
|
|
2499
|
-
}
|
|
2500
|
-
this.log.debug(`Importing commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2501
|
-
const storageContext = this.storageManager.createContext(pluginName);
|
|
2502
|
-
await storageContext.set('deviceName', basic.getNodeLabelAttribute());
|
|
2503
|
-
await storageContext.set('deviceType', DeviceTypeId(device.deviceType));
|
|
2504
|
-
await storageContext.set('vendorId', basic.getVendorIdAttribute());
|
|
2505
|
-
await storageContext.set('vendorName', basic.getVendorNameAttribute());
|
|
2506
|
-
await storageContext.set('productId', basic.getProductIdAttribute());
|
|
2507
|
-
await storageContext.set('productName', basic.getProductNameAttribute());
|
|
2508
|
-
await storageContext.set('nodeLabel', basic.getNodeLabelAttribute());
|
|
2509
|
-
await storageContext.set('productLabel', basic.getNodeLabelAttribute());
|
|
2510
|
-
await storageContext.set('serialNumber', basic.attributes.serialNumber?.getLocal());
|
|
2511
|
-
await storageContext.set('uniqueId', basic.attributes.uniqueId?.getLocal());
|
|
2512
|
-
await storageContext.set('softwareVersion', basic.getSoftwareVersionAttribute());
|
|
2513
|
-
await storageContext.set('softwareVersionString', basic.getSoftwareVersionStringAttribute());
|
|
2514
|
-
await storageContext.set('hardwareVersion', basic.getHardwareVersionAttribute());
|
|
2515
|
-
await storageContext.set('hardwareVersionString', basic.getHardwareVersionStringAttribute());
|
|
2516
|
-
this.log.debug(`Imported commissioning server storage context for ${plg}${pluginName}${db}`);
|
|
2517
|
-
this.log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
|
|
2518
|
-
this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2519
|
-
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2520
|
-
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2521
|
-
return storageContext;
|
|
2522
|
-
}
|
|
2523
|
-
/**
|
|
2524
|
-
* Shows the commissioning server QR code for a given plugin.
|
|
2525
|
-
* @param {CommissioningServer} commissioningServer - The commissioning server instance.
|
|
2526
|
-
* @param {StorageContext} storageContext - The storage context instance.
|
|
2527
|
-
* @param {NodeStorage} nodeContext - The node storage instance.
|
|
2528
|
-
* @param {string} pluginName - The name of the plugin of Matterbridge in bridge mode.
|
|
2529
|
-
* @returns {Promise<void>} - A promise that resolves when the QR code is shown.
|
|
2530
|
-
*/
|
|
2531
|
-
async showCommissioningQRCode(commissioningServer, storageContext, nodeContext, pluginName) {
|
|
2532
|
-
if (!commissioningServer || !storageContext || !nodeContext || !pluginName) {
|
|
2533
|
-
this.log.error(`showCommissioningQRCode error: commissioningServer: ${!commissioningServer} storageContext: ${!storageContext} nodeContext: ${!nodeContext} pluginName: ${pluginName}`);
|
|
2534
|
-
await this.cleanup('No storage initialized in showCommissioningQRCode');
|
|
2535
|
-
return;
|
|
2536
|
-
}
|
|
2537
|
-
if (!commissioningServer.isCommissioned()) {
|
|
2538
|
-
const { qrPairingCode, manualPairingCode } = commissioningServer.getPairingCode();
|
|
2539
|
-
const QrCode = new QrCodeSchema();
|
|
2540
|
-
this.log.info(`*The commissioning server on port ${commissioningServer.getPort()} for ${plg}${pluginName}${nf} is not commissioned. Pair it scanning the QR code:\n\n`);
|
|
2541
|
-
// eslint-disable-next-line no-console
|
|
2542
|
-
if (this.log.logLevel === "debug" /* LogLevel.DEBUG */ || this.log.logLevel === "info" /* LogLevel.INFO */)
|
|
2543
|
-
console.log(`${QrCode.encode(qrPairingCode)}\n`);
|
|
2544
|
-
this.log.info(`${plg}${pluginName}${nf} \n\nqrPairingCode: ${qrPairingCode} \n\nManual pairing code: ${manualPairingCode}\n`);
|
|
2545
|
-
if (pluginName === 'Matterbridge') {
|
|
2546
|
-
this.matterbridgeQrPairingCode = qrPairingCode;
|
|
2547
|
-
this.matterbridgeManualPairingCode = manualPairingCode;
|
|
2548
|
-
this.matterbridgeFabricInformations = [];
|
|
2549
|
-
this.matterbridgeSessionInformations = [];
|
|
2550
|
-
this.matterbridgePaired = false;
|
|
2551
|
-
this.matterbridgeConnected = false;
|
|
2552
|
-
}
|
|
2553
|
-
if (pluginName !== 'Matterbridge') {
|
|
2554
|
-
const plugin = this.plugins.get(pluginName);
|
|
2555
|
-
if (plugin) {
|
|
2556
|
-
plugin.qrPairingCode = qrPairingCode;
|
|
2557
|
-
plugin.manualPairingCode = manualPairingCode;
|
|
2558
|
-
plugin.fabricInformations = [];
|
|
2559
|
-
plugin.sessionInformations = [];
|
|
2560
|
-
plugin.paired = false;
|
|
2561
|
-
plugin.connected = false;
|
|
1557
|
+
this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1558
|
+
if (plugin.registeredDevices !== undefined)
|
|
1559
|
+
plugin.registeredDevices--;
|
|
1560
|
+
if (plugin.addedDevices !== undefined)
|
|
1561
|
+
plugin.addedDevices--;
|
|
1562
|
+
if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
|
|
1563
|
+
if (plugin.serverNode) {
|
|
1564
|
+
await this.stopServerNode(plugin.serverNode);
|
|
1565
|
+
plugin.locked = false;
|
|
1566
|
+
plugin.aggregatorNode = undefined;
|
|
1567
|
+
plugin.serverNode = undefined;
|
|
1568
|
+
this.log.info(`Stopped server node for plugin ${plg}${pluginName}${nf}`);
|
|
2562
1569
|
}
|
|
2563
1570
|
}
|
|
2564
1571
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
this.log.info(`- fabric index ${zb}${info.fabricIndex}${nf} id ${zb}${info.fabricId}${nf} vendor ${zb}${info.rootVendorId}${nf} ${this.getVendorIdName(info.rootVendorId)} ${info.label}`);
|
|
2572
|
-
});
|
|
2573
|
-
if (pluginName === 'Matterbridge') {
|
|
2574
|
-
this.matterbridgeFabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2575
|
-
this.matterbridgeSessionInformations = [];
|
|
2576
|
-
this.matterbridgePaired = true;
|
|
2577
|
-
}
|
|
2578
|
-
if (pluginName !== 'Matterbridge') {
|
|
2579
|
-
const plugin = this.plugins.get(pluginName);
|
|
2580
|
-
if (plugin) {
|
|
2581
|
-
plugin.fabricInformations = this.sanitizeFabricInformations(fabricInfo);
|
|
2582
|
-
plugin.sessionInformations = [];
|
|
2583
|
-
plugin.paired = true;
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
1572
|
+
this.devices.remove(device);
|
|
1573
|
+
}
|
|
1574
|
+
async removeAllBridgedEndpoints(pluginName) {
|
|
1575
|
+
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
|
|
1576
|
+
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
1577
|
+
await this.removeBridgedEndpoint(pluginName, device);
|
|
2586
1578
|
}
|
|
2587
|
-
this.wssSendRefreshRequired();
|
|
2588
1579
|
}
|
|
2589
|
-
/**
|
|
2590
|
-
* Sanitizes the fabric information by converting bigint properties to string cause res.json doesn't know bigint.
|
|
2591
|
-
*
|
|
2592
|
-
* @param fabricInfo - The array of exposed fabric information objects.
|
|
2593
|
-
* @returns An array of sanitized exposed fabric information objects.
|
|
2594
|
-
*/
|
|
2595
1580
|
sanitizeFabricInformations(fabricInfo) {
|
|
2596
1581
|
return fabricInfo.map((info) => {
|
|
2597
1582
|
return {
|
|
@@ -2605,12 +1590,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2605
1590
|
};
|
|
2606
1591
|
});
|
|
2607
1592
|
}
|
|
2608
|
-
/**
|
|
2609
|
-
* Sanitizes the session information by converting bigint properties to string.
|
|
2610
|
-
*
|
|
2611
|
-
* @param sessionInfo - The array of session information objects.
|
|
2612
|
-
* @returns An array of sanitized session information objects.
|
|
2613
|
-
*/
|
|
2614
1593
|
sanitizeSessionInformation(sessionInfo) {
|
|
2615
1594
|
return sessionInfo
|
|
2616
1595
|
.filter((session) => session.isPeerActive)
|
|
@@ -2638,48 +1617,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2638
1617
|
};
|
|
2639
1618
|
});
|
|
2640
1619
|
}
|
|
2641
|
-
|
|
2642
|
-
* Sets the reachability of a commissioning server and trigger.
|
|
2643
|
-
*
|
|
2644
|
-
* @param {CommissioningServer} commissioningServer - The commissioning server to set the reachability for.
|
|
2645
|
-
* @param {boolean} reachable - The new reachability status.
|
|
2646
|
-
*/
|
|
2647
|
-
setCommissioningServerReachability(commissioningServer, reachable) {
|
|
2648
|
-
const basicInformationCluster = commissioningServer?.getRootClusterServer(BasicInformationCluster);
|
|
2649
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2650
|
-
basicInformationCluster.setReachableAttribute(reachable);
|
|
2651
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2652
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
1620
|
+
setServerNodeReachability(serverNode, reachable) {
|
|
2653
1621
|
}
|
|
2654
|
-
|
|
2655
|
-
* Sets the reachability of the specified matter aggregator and its bridged devices and trigger.
|
|
2656
|
-
* @param {Aggregator} matterAggregator - The matter aggregator to set the reachability for.
|
|
2657
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2658
|
-
*/
|
|
2659
|
-
setAggregatorReachability(matterAggregator, reachable) {
|
|
2660
|
-
const basicInformationCluster = matterAggregator.getClusterServer(BasicInformationCluster);
|
|
2661
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2662
|
-
basicInformationCluster.setReachableAttribute(reachable);
|
|
2663
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2664
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2665
|
-
matterAggregator.getBridgedDevices().forEach((device) => {
|
|
2666
|
-
this.log.debug(`Setting reachability to true for bridged device: ${dev}${device.name}${nf}`);
|
|
2667
|
-
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.setReachableAttribute(reachable);
|
|
2668
|
-
device.getClusterServer(BridgedDeviceBasicInformationCluster)?.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2669
|
-
});
|
|
1622
|
+
setAggregatorReachability(aggregatorNode, reachable) {
|
|
2670
1623
|
}
|
|
2671
|
-
/**
|
|
2672
|
-
* Sets the reachability of a device and trigger.
|
|
2673
|
-
*
|
|
2674
|
-
* @param {MatterbridgeDevice} device - The device to set the reachability for.
|
|
2675
|
-
* @param {boolean} reachable - The new reachability status of the device.
|
|
2676
|
-
*/
|
|
2677
1624
|
setDeviceReachability(device, reachable) {
|
|
2678
|
-
const basicInformationCluster = device.getClusterServer(BasicInformationCluster);
|
|
2679
|
-
if (basicInformationCluster && basicInformationCluster.attributes.reachable !== undefined)
|
|
2680
|
-
basicInformationCluster.setReachableAttribute(reachable);
|
|
2681
|
-
if (basicInformationCluster && basicInformationCluster.triggerReachableChangedEvent)
|
|
2682
|
-
basicInformationCluster.triggerReachableChangedEvent({ reachableNewValue: reachable });
|
|
2683
1625
|
}
|
|
2684
1626
|
getVendorIdName = (vendorId) => {
|
|
2685
1627
|
if (!vendorId)
|
|
@@ -2722,71 +1664,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2722
1664
|
}
|
|
2723
1665
|
return vendorName;
|
|
2724
1666
|
};
|
|
2725
|
-
/**
|
|
2726
|
-
* Retrieves the base registered plugins sanitized for res.json().
|
|
2727
|
-
* @returns {BaseRegisteredPlugin[]} A promise that resolves to an array of BaseRegisteredPlugin objects.
|
|
2728
|
-
*/
|
|
2729
|
-
async getBaseRegisteredPlugins() {
|
|
2730
|
-
const baseRegisteredPlugins = [];
|
|
2731
|
-
for (const plugin of this.plugins) {
|
|
2732
|
-
baseRegisteredPlugins.push({
|
|
2733
|
-
path: plugin.path,
|
|
2734
|
-
type: plugin.type,
|
|
2735
|
-
name: plugin.name,
|
|
2736
|
-
version: plugin.version,
|
|
2737
|
-
description: plugin.description,
|
|
2738
|
-
author: plugin.author,
|
|
2739
|
-
latestVersion: plugin.latestVersion,
|
|
2740
|
-
locked: plugin.locked,
|
|
2741
|
-
error: plugin.error,
|
|
2742
|
-
enabled: plugin.enabled,
|
|
2743
|
-
loaded: plugin.loaded,
|
|
2744
|
-
started: plugin.started,
|
|
2745
|
-
configured: plugin.configured,
|
|
2746
|
-
paired: plugin.paired,
|
|
2747
|
-
connected: plugin.connected,
|
|
2748
|
-
fabricInformations: plugin.fabricInformations,
|
|
2749
|
-
sessionInformations: plugin.sessionInformations,
|
|
2750
|
-
registeredDevices: plugin.registeredDevices,
|
|
2751
|
-
addedDevices: plugin.addedDevices,
|
|
2752
|
-
qrPairingCode: plugin.qrPairingCode,
|
|
2753
|
-
manualPairingCode: plugin.manualPairingCode,
|
|
2754
|
-
configJson: plugin.configJson,
|
|
2755
|
-
schemaJson: plugin.schemaJson,
|
|
2756
|
-
});
|
|
2757
|
-
}
|
|
2758
|
-
return baseRegisteredPlugins;
|
|
2759
|
-
}
|
|
2760
|
-
/**
|
|
2761
|
-
* Spawns a child process with the given command and arguments.
|
|
2762
|
-
* @param {string} command - The command to execute.
|
|
2763
|
-
* @param {string[]} args - The arguments to pass to the command (default: []).
|
|
2764
|
-
* @returns {Promise<boolean>} A promise that resolves when the child process exits successfully, or rejects if there is an error.
|
|
2765
|
-
*/
|
|
2766
1667
|
async spawnCommand(command, args = []) {
|
|
2767
|
-
/*
|
|
2768
|
-
npm > npm.cmd on windows
|
|
2769
|
-
cmd.exe ['dir'] on windows
|
|
2770
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge']);
|
|
2771
|
-
process.on('unhandledRejection', (reason, promise) => {
|
|
2772
|
-
this.log.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
2773
|
-
});
|
|
2774
|
-
|
|
2775
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: changed 38 packages in 4s
|
|
2776
|
-
spawn - [14:27:21.125] [Matterbridge:spawn]: 10 packages are looking for funding run `npm fund` for details
|
|
2777
|
-
debug - [14:27:21.131] [Matterbridge]: Child process exited with code 0 and signal null
|
|
2778
|
-
debug - [14:27:21.131] [Matterbridge]: Child process stdio streams have closed with code 0
|
|
2779
|
-
*/
|
|
2780
1668
|
const cmdLine = command + ' ' + args.join(' ');
|
|
2781
1669
|
if (process.platform === 'win32' && command === 'npm') {
|
|
2782
|
-
// Must be spawn('cmd.exe', ['/c', 'npm -g install <package>']);
|
|
2783
1670
|
const argstring = 'npm ' + args.join(' ');
|
|
2784
1671
|
args.splice(0, args.length, '/c', argstring);
|
|
2785
1672
|
command = 'cmd.exe';
|
|
2786
1673
|
}
|
|
2787
|
-
// Decide when using sudo on linux
|
|
2788
|
-
// When you need sudo: Spawn stderr: npm error Error: EACCES: permission denied
|
|
2789
|
-
// When you don't need sudo: Failed to start child process "npm install -g matterbridge-eve-door": spawn sudo ENOENT
|
|
2790
1674
|
if (hasParameter('sudo') || (process.platform === 'linux' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo'))) {
|
|
2791
1675
|
args.unshift(command);
|
|
2792
1676
|
command = 'sudo';
|
|
@@ -2801,7 +1685,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2801
1685
|
reject(err);
|
|
2802
1686
|
});
|
|
2803
1687
|
childProcess.on('close', (code, signal) => {
|
|
2804
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
|
|
1688
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process closed with code ${code} and signal ${signal}`);
|
|
2805
1689
|
if (code === 0) {
|
|
2806
1690
|
if (cmdLine.startsWith('npm install -g'))
|
|
2807
1691
|
this.log.notice(`Package ${cmdLine.replace('npm install -g ', '').replace('--verbose', '').replace('--omit=dev', '')} installed correctly`);
|
|
@@ -2814,7 +1698,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2814
1698
|
}
|
|
2815
1699
|
});
|
|
2816
1700
|
childProcess.on('exit', (code, signal) => {
|
|
2817
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
1701
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', `child process exited with code ${code} and signal ${signal}`);
|
|
2818
1702
|
if (code === 0) {
|
|
2819
1703
|
this.log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
|
|
2820
1704
|
resolve(true);
|
|
@@ -2832,1060 +1716,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
2832
1716
|
childProcess.stdout.on('data', (data) => {
|
|
2833
1717
|
const message = data.toString().trim();
|
|
2834
1718
|
this.log.debug(`Spawn output (stdout): ${message}`);
|
|
2835
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
1719
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
2836
1720
|
});
|
|
2837
1721
|
}
|
|
2838
1722
|
if (childProcess.stderr) {
|
|
2839
1723
|
childProcess.stderr.on('data', (data) => {
|
|
2840
1724
|
const message = data.toString().trim();
|
|
2841
1725
|
this.log.debug(`Spawn verbose (stderr): ${message}`);
|
|
2842
|
-
this.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
});
|
|
2846
|
-
}
|
|
2847
|
-
/**
|
|
2848
|
-
* Sends a WebSocket message to all connected clients.
|
|
2849
|
-
*
|
|
2850
|
-
* @param {string} level - The logger level of the message: debug info notice warn error fatal...
|
|
2851
|
-
* @param {string} time - The time string of the message
|
|
2852
|
-
* @param {string} name - The logger name of the message
|
|
2853
|
-
* @param {string} message - The content of the message.
|
|
2854
|
-
*/
|
|
2855
|
-
wssSendMessage(level, time, name, message) {
|
|
2856
|
-
if (!level || !time || !name || !message)
|
|
2857
|
-
return;
|
|
2858
|
-
// Remove ANSI escape codes from the message
|
|
2859
|
-
// eslint-disable-next-line no-control-regex
|
|
2860
|
-
message = message.replace(/\x1B\[[0-9;]*[m|s|u|K]/g, '');
|
|
2861
|
-
// Remove leading asterisks from the message
|
|
2862
|
-
message = message.replace(/^\*+/, '');
|
|
2863
|
-
// Replace all occurrences of \t and \n
|
|
2864
|
-
message = message.replace(/[\t\n]/g, '');
|
|
2865
|
-
// Remove non-printable characters
|
|
2866
|
-
// eslint-disable-next-line no-control-regex
|
|
2867
|
-
message = message.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2868
|
-
// Replace all occurrences of \" with "
|
|
2869
|
-
message = message.replace(/\\"/g, '"');
|
|
2870
|
-
// Define the maximum allowed length for continuous characters without a space
|
|
2871
|
-
const maxContinuousLength = 100;
|
|
2872
|
-
const keepStartLength = 20;
|
|
2873
|
-
const keepEndLength = 20;
|
|
2874
|
-
// Split the message into words
|
|
2875
|
-
message = message
|
|
2876
|
-
.split(' ')
|
|
2877
|
-
.map((word) => {
|
|
2878
|
-
// If the word length exceeds the max continuous length, insert spaces and truncate
|
|
2879
|
-
if (word.length > maxContinuousLength) {
|
|
2880
|
-
return word.slice(0, keepStartLength) + ' ... ' + word.slice(-keepEndLength);
|
|
2881
|
-
}
|
|
2882
|
-
return word;
|
|
2883
|
-
})
|
|
2884
|
-
.join(' ');
|
|
2885
|
-
// Send the message to all connected clients
|
|
2886
|
-
this.webSocketServer?.clients.forEach((client) => {
|
|
2887
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
2888
|
-
client.send(JSON.stringify({ id: WS_ID_LOG, src: 'Matterbridge', level, time, name, message }));
|
|
2889
|
-
}
|
|
2890
|
-
});
|
|
2891
|
-
}
|
|
2892
|
-
/**
|
|
2893
|
-
* Sends a need to refresh WebSocket message to all connected clients.
|
|
2894
|
-
*
|
|
2895
|
-
*/
|
|
2896
|
-
wssSendRefreshRequired() {
|
|
2897
|
-
this.matterbridgeInformation.refreshRequired = true;
|
|
2898
|
-
// Send the message to all connected clients
|
|
2899
|
-
this.webSocketServer?.clients.forEach((client) => {
|
|
2900
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
2901
|
-
client.send(JSON.stringify({ id: WS_ID_REFRESH_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'refresh_required', params: {} }));
|
|
2902
|
-
}
|
|
2903
|
-
});
|
|
2904
|
-
}
|
|
2905
|
-
/**
|
|
2906
|
-
* Sends a need to restart WebSocket message to all connected clients.
|
|
2907
|
-
*
|
|
2908
|
-
*/
|
|
2909
|
-
wssSendRestartRequired() {
|
|
2910
|
-
this.matterbridgeInformation.restartRequired = true;
|
|
2911
|
-
// Send the message to all connected clients
|
|
2912
|
-
this.webSocketServer?.clients.forEach((client) => {
|
|
2913
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
2914
|
-
client.send(JSON.stringify({ id: WS_ID_RESTART_NEEDED, src: 'Matterbridge', dst: 'Frontend', method: 'restart_required', params: {} }));
|
|
2915
|
-
}
|
|
2916
|
-
});
|
|
2917
|
-
}
|
|
2918
|
-
/**
|
|
2919
|
-
* Initializes the frontend of Matterbridge.
|
|
2920
|
-
*
|
|
2921
|
-
* @param port The port number to run the frontend server on. Default is 8283.
|
|
2922
|
-
*/
|
|
2923
|
-
async initializeFrontend(port = 8283) {
|
|
2924
|
-
let initializeError = false;
|
|
2925
|
-
this.log.debug(`Initializing the frontend ${hasParameter('ssl') ? 'https' : 'http'} server on port ${YELLOW}${port}${db}`);
|
|
2926
|
-
// Create the express app that serves the frontend
|
|
2927
|
-
this.expressApp = express();
|
|
2928
|
-
// Log all requests to the server for debugging
|
|
2929
|
-
/*
|
|
2930
|
-
if (hasParameter('homedir')) {
|
|
2931
|
-
this.expressApp.use((req, res, next) => {
|
|
2932
|
-
this.log.debug(`Received request on expressApp: ${req.method} ${req.url}`);
|
|
2933
|
-
next();
|
|
2934
|
-
});
|
|
2935
|
-
}
|
|
2936
|
-
*/
|
|
2937
|
-
// Serve static files from '/static' endpoint
|
|
2938
|
-
this.expressApp.use(express.static(path.join(this.rootDirectory, 'frontend/build')));
|
|
2939
|
-
if (!hasParameter('ssl')) {
|
|
2940
|
-
// Create an HTTP server and attach the express app
|
|
2941
|
-
this.httpServer = createServer(this.expressApp);
|
|
2942
|
-
// Listen on the specified port
|
|
2943
|
-
if (hasParameter('ingress')) {
|
|
2944
|
-
this.httpServer.listen(port, '0.0.0.0', () => {
|
|
2945
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
2946
|
-
});
|
|
2947
|
-
}
|
|
2948
|
-
else {
|
|
2949
|
-
this.httpServer.listen(port, () => {
|
|
2950
|
-
if (this.systemInformation.ipv4Address !== '')
|
|
2951
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
2952
|
-
if (this.systemInformation.ipv6Address !== '')
|
|
2953
|
-
this.log.info(`The frontend http server is listening on ${UNDERLINE}http://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
2954
|
-
});
|
|
2955
|
-
}
|
|
2956
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2957
|
-
this.httpServer.on('error', (error) => {
|
|
2958
|
-
this.log.error(`Frontend http server error listening on ${port}`);
|
|
2959
|
-
switch (error.code) {
|
|
2960
|
-
case 'EACCES':
|
|
2961
|
-
this.log.error(`Port ${port} requires elevated privileges`);
|
|
2962
|
-
break;
|
|
2963
|
-
case 'EADDRINUSE':
|
|
2964
|
-
this.log.error(`Port ${port} is already in use`);
|
|
2965
|
-
break;
|
|
2966
|
-
}
|
|
2967
|
-
initializeError = true;
|
|
2968
|
-
return;
|
|
2969
|
-
});
|
|
2970
|
-
}
|
|
2971
|
-
else {
|
|
2972
|
-
// Load the SSL certificate, the private key and optionally the CA certificate
|
|
2973
|
-
let cert;
|
|
2974
|
-
try {
|
|
2975
|
-
cert = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/cert.pem'), 'utf8');
|
|
2976
|
-
this.log.info(`Loaded certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}`);
|
|
2977
|
-
}
|
|
2978
|
-
catch (error) {
|
|
2979
|
-
this.log.error(`Error reading certificate file ${path.join(this.matterbridgeDirectory, 'certs/cert.pem')}: ${error}`);
|
|
2980
|
-
return;
|
|
2981
|
-
}
|
|
2982
|
-
let key;
|
|
2983
|
-
try {
|
|
2984
|
-
key = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/key.pem'), 'utf8');
|
|
2985
|
-
this.log.info(`Loaded key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}`);
|
|
2986
|
-
}
|
|
2987
|
-
catch (error) {
|
|
2988
|
-
this.log.error(`Error reading key file ${path.join(this.matterbridgeDirectory, 'certs/key.pem')}: ${error}`);
|
|
2989
|
-
return;
|
|
2990
|
-
}
|
|
2991
|
-
let ca;
|
|
2992
|
-
try {
|
|
2993
|
-
ca = await fs.readFile(path.join(this.matterbridgeDirectory, 'certs/ca.pem'), 'utf8');
|
|
2994
|
-
this.log.info(`Loaded CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')}`);
|
|
2995
|
-
}
|
|
2996
|
-
catch (error) {
|
|
2997
|
-
this.log.info(`CA certificate file ${path.join(this.matterbridgeDirectory, 'certs/ca.pem')} not loaded: ${error}`);
|
|
2998
|
-
}
|
|
2999
|
-
const serverOptions = { cert, key, ca };
|
|
3000
|
-
// Create an HTTPS server with the SSL certificate and private key (ca is optional) and attach the express app
|
|
3001
|
-
this.httpsServer = https.createServer(serverOptions, this.expressApp);
|
|
3002
|
-
// Listen on the specified port
|
|
3003
|
-
if (hasParameter('ingress')) {
|
|
3004
|
-
this.httpsServer.listen(port, '0.0.0.0', () => {
|
|
3005
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://0.0.0.0:${port}${UNDERLINEOFF}${rs}`);
|
|
3006
|
-
});
|
|
3007
|
-
}
|
|
3008
|
-
else {
|
|
3009
|
-
this.httpsServer.listen(port, () => {
|
|
3010
|
-
if (this.systemInformation.ipv4Address !== '')
|
|
3011
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://${this.systemInformation.ipv4Address}:${port}${UNDERLINEOFF}${rs}`);
|
|
3012
|
-
if (this.systemInformation.ipv6Address !== '')
|
|
3013
|
-
this.log.info(`The frontend https server is listening on ${UNDERLINE}https://[${this.systemInformation.ipv6Address}]:${port}${UNDERLINEOFF}${rs}`);
|
|
3014
|
-
});
|
|
3015
|
-
}
|
|
3016
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3017
|
-
this.httpsServer.on('error', (error) => {
|
|
3018
|
-
this.log.error(`Frontend https server error listening on ${port}`);
|
|
3019
|
-
switch (error.code) {
|
|
3020
|
-
case 'EACCES':
|
|
3021
|
-
this.log.error(`Port ${port} requires elevated privileges`);
|
|
3022
|
-
break;
|
|
3023
|
-
case 'EADDRINUSE':
|
|
3024
|
-
this.log.error(`Port ${port} is already in use`);
|
|
3025
|
-
break;
|
|
3026
|
-
}
|
|
3027
|
-
initializeError = true;
|
|
3028
|
-
return;
|
|
3029
|
-
});
|
|
3030
|
-
}
|
|
3031
|
-
if (initializeError)
|
|
3032
|
-
return;
|
|
3033
|
-
// Createe a WebSocket server and attach it to the http or https server
|
|
3034
|
-
const wssPort = port;
|
|
3035
|
-
const wssHost = hasParameter('ssl') ? `wss://${this.systemInformation.ipv4Address}:${wssPort}` : `ws://${this.systemInformation.ipv4Address}:${wssPort}`;
|
|
3036
|
-
this.webSocketServer = new WebSocketServer(hasParameter('ssl') ? { server: this.httpsServer } : { server: this.httpServer });
|
|
3037
|
-
this.webSocketServer.on('connection', (ws, request) => {
|
|
3038
|
-
const clientIp = request.socket.remoteAddress;
|
|
3039
|
-
AnsiLogger.setGlobalCallback(this.wssSendMessage.bind(this), "debug" /* LogLevel.DEBUG */);
|
|
3040
|
-
this.log.info(`WebSocketServer client "${clientIp}" connected to Matterbridge`);
|
|
3041
|
-
ws.on('message', (message) => {
|
|
3042
|
-
this.log.debug(`WebSocket client message: ${message}`);
|
|
3043
|
-
this.matterbridgeMessageHandler(ws, message);
|
|
3044
|
-
});
|
|
3045
|
-
ws.on('ping', () => {
|
|
3046
|
-
this.log.debug('WebSocket client ping');
|
|
3047
|
-
ws.pong();
|
|
3048
|
-
});
|
|
3049
|
-
ws.on('pong', () => {
|
|
3050
|
-
this.log.debug('WebSocket client pong');
|
|
3051
|
-
});
|
|
3052
|
-
ws.on('close', () => {
|
|
3053
|
-
this.log.info('WebSocket client disconnected');
|
|
3054
|
-
if (this.webSocketServer?.clients.size === 0) {
|
|
3055
|
-
AnsiLogger.setGlobalCallback(undefined);
|
|
3056
|
-
this.log.debug('All WebSocket clients disconnected. WebSocketServer logger global callback removed');
|
|
3057
|
-
}
|
|
3058
|
-
});
|
|
3059
|
-
ws.on('error', (error) => {
|
|
3060
|
-
this.log.error(`WebSocket client error: ${error}`);
|
|
3061
|
-
});
|
|
3062
|
-
});
|
|
3063
|
-
this.webSocketServer.on('close', () => {
|
|
3064
|
-
this.log.debug(`WebSocketServer closed`);
|
|
3065
|
-
});
|
|
3066
|
-
this.webSocketServer.on('listening', () => {
|
|
3067
|
-
this.log.info(`The WebSocketServer is listening on ${UNDERLINE}${wssHost}${UNDERLINEOFF}${rs}`);
|
|
3068
|
-
});
|
|
3069
|
-
this.webSocketServer.on('error', (ws, error) => {
|
|
3070
|
-
this.log.error(`WebSocketServer error: ${error}`);
|
|
3071
|
-
});
|
|
3072
|
-
// Endpoint to validate login code
|
|
3073
|
-
this.expressApp.post('/api/login', express.json(), async (req, res) => {
|
|
3074
|
-
const { password } = req.body;
|
|
3075
|
-
this.log.debug('The frontend sent /api/login', password);
|
|
3076
|
-
if (!this.nodeContext) {
|
|
3077
|
-
this.log.error('/api/login nodeContext not found');
|
|
3078
|
-
res.json({ valid: false });
|
|
3079
|
-
return;
|
|
3080
|
-
}
|
|
3081
|
-
try {
|
|
3082
|
-
const storedPassword = await this.nodeContext.get('password', '');
|
|
3083
|
-
if (storedPassword === '' || password === storedPassword) {
|
|
3084
|
-
this.log.debug('/api/login password valid');
|
|
3085
|
-
res.json({ valid: true });
|
|
3086
|
-
}
|
|
3087
|
-
else {
|
|
3088
|
-
this.log.warn('/api/login error wrong password');
|
|
3089
|
-
res.json({ valid: false });
|
|
3090
|
-
}
|
|
3091
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3092
|
-
}
|
|
3093
|
-
catch (error) {
|
|
3094
|
-
this.log.error('/api/login error getting password');
|
|
3095
|
-
res.json({ valid: false });
|
|
3096
|
-
}
|
|
3097
|
-
});
|
|
3098
|
-
// Endpoint to provide health check
|
|
3099
|
-
this.expressApp.get('/health', (req, res) => {
|
|
3100
|
-
this.log.debug('Express received /health');
|
|
3101
|
-
const healthStatus = {
|
|
3102
|
-
status: 'ok', // Indicate service is healthy
|
|
3103
|
-
uptime: process.uptime(), // Server uptime in seconds
|
|
3104
|
-
timestamp: new Date().toISOString(), // Current timestamp
|
|
3105
|
-
};
|
|
3106
|
-
res.status(200).json(healthStatus);
|
|
3107
|
-
});
|
|
3108
|
-
// Endpoint to provide settings
|
|
3109
|
-
this.expressApp.get('/api/settings', express.json(), async (req, res) => {
|
|
3110
|
-
this.log.debug('The frontend sent /api/settings');
|
|
3111
|
-
this.matterbridgeInformation.bridgeMode = this.bridgeMode;
|
|
3112
|
-
this.matterbridgeInformation.restartMode = this.restartMode;
|
|
3113
|
-
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
3114
|
-
this.matterbridgeInformation.matterLoggerLevel = Logger.defaultLogLevel;
|
|
3115
|
-
this.matterbridgeInformation.mattermdnsinterface = (await this.nodeContext?.get('mattermdnsinterface', '')) || '';
|
|
3116
|
-
this.matterbridgeInformation.matteripv4address = (await this.nodeContext?.get('matteripv4address', '')) || '';
|
|
3117
|
-
this.matterbridgeInformation.matteripv6address = (await this.nodeContext?.get('matteripv6address', '')) || '';
|
|
3118
|
-
this.matterbridgeInformation.matterPort = (await this.nodeContext?.get('matterport', 5540)) ?? 5540;
|
|
3119
|
-
this.matterbridgeInformation.matterDiscriminator = await this.nodeContext?.get('matterdiscriminator');
|
|
3120
|
-
this.matterbridgeInformation.matterPasscode = await this.nodeContext?.get('matterpasscode');
|
|
3121
|
-
this.matterbridgeInformation.matterbridgePaired = this.matterbridgePaired;
|
|
3122
|
-
this.matterbridgeInformation.matterbridgeConnected = this.matterbridgeConnected;
|
|
3123
|
-
this.matterbridgeInformation.matterbridgeQrPairingCode = this.matterbridgeQrPairingCode;
|
|
3124
|
-
this.matterbridgeInformation.matterbridgeManualPairingCode = this.matterbridgeManualPairingCode;
|
|
3125
|
-
this.matterbridgeInformation.matterbridgeFabricInformations = this.matterbridgeFabricInformations;
|
|
3126
|
-
this.matterbridgeInformation.matterbridgeSessionInformations = Array.from(this.matterbridgeSessionInformations.values());
|
|
3127
|
-
this.matterbridgeInformation.profile = this.profile;
|
|
3128
|
-
const response = { systemInformation: this.systemInformation, matterbridgeInformation: this.matterbridgeInformation };
|
|
3129
|
-
// this.log.debug('Response:', debugStringify(response));
|
|
3130
|
-
res.json(response);
|
|
3131
|
-
});
|
|
3132
|
-
// Endpoint to provide plugins
|
|
3133
|
-
this.expressApp.get('/api/plugins', async (req, res) => {
|
|
3134
|
-
this.log.debug('The frontend sent /api/plugins');
|
|
3135
|
-
const response = await this.getBaseRegisteredPlugins();
|
|
3136
|
-
// this.log.debug('Response:', debugStringify(response));
|
|
3137
|
-
res.json(response);
|
|
3138
|
-
});
|
|
3139
|
-
// Endpoint to provide devices
|
|
3140
|
-
this.expressApp.get('/api/devices', (req, res) => {
|
|
3141
|
-
this.log.debug('The frontend sent /api/devices');
|
|
3142
|
-
const devices = [];
|
|
3143
|
-
this.devices.forEach(async (device) => {
|
|
3144
|
-
const pluginName = device.plugin ?? 'Unknown';
|
|
3145
|
-
if (this.edge)
|
|
3146
|
-
device = EndpointServer.forEndpoint(device);
|
|
3147
|
-
let name = device.getClusterServer(BasicInformationCluster)?.attributes.nodeLabel?.getLocal();
|
|
3148
|
-
if (!name)
|
|
3149
|
-
name = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.nodeLabel?.getLocal() ?? 'Unknown';
|
|
3150
|
-
let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
|
|
3151
|
-
if (!serial)
|
|
3152
|
-
serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
|
|
3153
|
-
let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
|
|
3154
|
-
if (!productUrl)
|
|
3155
|
-
productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
|
|
3156
|
-
let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
|
|
3157
|
-
if (!uniqueId)
|
|
3158
|
-
uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
|
|
3159
|
-
const cluster = this.getClusterTextFromDevice(device);
|
|
3160
|
-
devices.push({
|
|
3161
|
-
pluginName,
|
|
3162
|
-
type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
|
|
3163
|
-
endpoint: device.number,
|
|
3164
|
-
name,
|
|
3165
|
-
serial,
|
|
3166
|
-
productUrl,
|
|
3167
|
-
configUrl: device.configUrl,
|
|
3168
|
-
uniqueId,
|
|
3169
|
-
cluster: cluster,
|
|
1726
|
+
this.frontend.wssSendMessage('spawn', this.log.now(), 'Matterbridge:spawn', message);
|
|
3170
1727
|
});
|
|
3171
|
-
});
|
|
3172
|
-
// this.log.debug('Response:', debugStringify(data));
|
|
3173
|
-
res.json(devices);
|
|
3174
|
-
});
|
|
3175
|
-
// Endpoint to provide the cluster servers of the devices
|
|
3176
|
-
this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
|
|
3177
|
-
const selectedPluginName = req.params.selectedPluginName;
|
|
3178
|
-
const selectedDeviceEndpoint = parseInt(req.params.selectedDeviceEndpoint, 10);
|
|
3179
|
-
this.log.debug(`The frontend sent /api/devices_clusters plugin:${selectedPluginName} endpoint:${selectedDeviceEndpoint}`);
|
|
3180
|
-
if (selectedPluginName === 'none') {
|
|
3181
|
-
res.json([]);
|
|
3182
|
-
return;
|
|
3183
1728
|
}
|
|
3184
|
-
const data = [];
|
|
3185
|
-
this.devices.forEach(async (device) => {
|
|
3186
|
-
const pluginName = device.plugin;
|
|
3187
|
-
if (this.edge)
|
|
3188
|
-
device = EndpointServer.forEndpoint(device);
|
|
3189
|
-
if (pluginName === selectedPluginName && device.number === selectedDeviceEndpoint) {
|
|
3190
|
-
const clusterServers = device.getAllClusterServers();
|
|
3191
|
-
clusterServers.forEach((clusterServer) => {
|
|
3192
|
-
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
3193
|
-
if (clusterServer.name === 'EveHistory')
|
|
3194
|
-
return;
|
|
3195
|
-
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
3196
|
-
let attributeValue;
|
|
3197
|
-
try {
|
|
3198
|
-
if (typeof value.getLocal() === 'object')
|
|
3199
|
-
attributeValue = stringify(value.getLocal());
|
|
3200
|
-
else
|
|
3201
|
-
attributeValue = value.getLocal().toString();
|
|
3202
|
-
}
|
|
3203
|
-
catch (error) {
|
|
3204
|
-
attributeValue = 'Fabric-Scoped';
|
|
3205
|
-
this.log.debug(`GetLocal value ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3206
|
-
// console.log(error);
|
|
3207
|
-
}
|
|
3208
|
-
data.push({
|
|
3209
|
-
endpoint: device.number ? device.number.toString() : '...',
|
|
3210
|
-
clusterName: clusterServer.name,
|
|
3211
|
-
clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
|
|
3212
|
-
attributeName: key,
|
|
3213
|
-
attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
|
|
3214
|
-
attributeValue,
|
|
3215
|
-
});
|
|
3216
|
-
});
|
|
3217
|
-
});
|
|
3218
|
-
device.getChildEndpoints().forEach((childEndpoint) => {
|
|
3219
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3220
|
-
const name = this.edge ? childEndpoint.endpoint?.id : childEndpoint.uniqueStorageKey;
|
|
3221
|
-
const clusterServers = childEndpoint.getAllClusterServers();
|
|
3222
|
-
clusterServers.forEach((clusterServer) => {
|
|
3223
|
-
Object.entries(clusterServer.attributes).forEach(([key, value]) => {
|
|
3224
|
-
if (clusterServer.name === 'EveHistory')
|
|
3225
|
-
return;
|
|
3226
|
-
// this.log.debug(`***--clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute:${key}(${value.id}) ${value.isFixed} ${value.isWritable} ${value.isWritable}`);
|
|
3227
|
-
let attributeValue;
|
|
3228
|
-
try {
|
|
3229
|
-
if (typeof value.getLocal() === 'object')
|
|
3230
|
-
attributeValue = stringify(value.getLocal());
|
|
3231
|
-
else
|
|
3232
|
-
attributeValue = value.getLocal().toString();
|
|
3233
|
-
}
|
|
3234
|
-
catch (error) {
|
|
3235
|
-
attributeValue = 'Unavailable';
|
|
3236
|
-
this.log.debug(`GetLocal error ${error} in clusterServer: ${clusterServer.name}(${clusterServer.id}) attribute: ${key}(${value.id})`);
|
|
3237
|
-
// console.log(error);
|
|
3238
|
-
}
|
|
3239
|
-
data.push({
|
|
3240
|
-
endpoint: (childEndpoint.number ? childEndpoint.number.toString() : '...') + (name ? ' (' + name + ')' : ''),
|
|
3241
|
-
clusterName: clusterServer.name,
|
|
3242
|
-
clusterId: '0x' + clusterServer.id.toString(16).padStart(2, '0'),
|
|
3243
|
-
attributeName: key,
|
|
3244
|
-
attributeId: '0x' + value.id.toString(16).padStart(2, '0'),
|
|
3245
|
-
attributeValue,
|
|
3246
|
-
});
|
|
3247
|
-
});
|
|
3248
|
-
});
|
|
3249
|
-
});
|
|
3250
|
-
}
|
|
3251
|
-
});
|
|
3252
|
-
res.json(data);
|
|
3253
1729
|
});
|
|
3254
|
-
// Endpoint to view the log
|
|
3255
|
-
this.expressApp.get('/api/view-log', async (req, res) => {
|
|
3256
|
-
this.log.debug('The frontend sent /api/log');
|
|
3257
|
-
try {
|
|
3258
|
-
const data = await fs.readFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'utf8');
|
|
3259
|
-
res.type('text/plain');
|
|
3260
|
-
res.send(data);
|
|
3261
|
-
}
|
|
3262
|
-
catch (error) {
|
|
3263
|
-
this.log.error(`Error reading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
3264
|
-
res.status(500).send('Error reading log file');
|
|
3265
|
-
}
|
|
3266
|
-
});
|
|
3267
|
-
// Endpoint to download the matterbridge log
|
|
3268
|
-
this.expressApp.get('/api/download-mblog', async (req, res) => {
|
|
3269
|
-
this.log.debug('The frontend sent /api/download-mblog');
|
|
3270
|
-
try {
|
|
3271
|
-
await fs.access(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), fs.constants.F_OK);
|
|
3272
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3273
|
-
}
|
|
3274
|
-
catch (error) {
|
|
3275
|
-
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
3276
|
-
}
|
|
3277
|
-
res.download(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), 'matterbridge.log', (error) => {
|
|
3278
|
-
if (error) {
|
|
3279
|
-
this.log.error(`Error downloading log file ${this.matterbrideLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
3280
|
-
res.status(500).send('Error downloading the matterbridge log file');
|
|
3281
|
-
}
|
|
3282
|
-
});
|
|
3283
|
-
});
|
|
3284
|
-
// Endpoint to download the matter log
|
|
3285
|
-
this.expressApp.get('/api/download-mjlog', async (req, res) => {
|
|
3286
|
-
this.log.debug('The frontend sent /api/download-mjlog');
|
|
3287
|
-
try {
|
|
3288
|
-
await fs.access(path.join(this.matterbridgeDirectory, this.matterLoggerFile), fs.constants.F_OK);
|
|
3289
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3290
|
-
}
|
|
3291
|
-
catch (error) {
|
|
3292
|
-
fs.appendFile(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'Enable the log on file in the settings to enable the file logger');
|
|
3293
|
-
}
|
|
3294
|
-
res.download(path.join(this.matterbridgeDirectory, this.matterLoggerFile), 'matter.log', (error) => {
|
|
3295
|
-
if (error) {
|
|
3296
|
-
this.log.error(`Error downloading log file ${this.matterLoggerFile}: ${error instanceof Error ? error.message : error}`);
|
|
3297
|
-
res.status(500).send('Error downloading the matter log file');
|
|
3298
|
-
}
|
|
3299
|
-
});
|
|
3300
|
-
});
|
|
3301
|
-
// Endpoint to download the matter storage file
|
|
3302
|
-
this.expressApp.get('/api/download-mjstorage', (req, res) => {
|
|
3303
|
-
this.log.debug('The frontend sent /api/download-mjstorage');
|
|
3304
|
-
res.download(path.join(this.matterbridgeDirectory, this.matterStorageName), 'matterbridge.json', (error) => {
|
|
3305
|
-
if (error) {
|
|
3306
|
-
this.log.error(`Error downloading log file ${this.matterStorageName}: ${error instanceof Error ? error.message : error}`);
|
|
3307
|
-
res.status(500).send('Error downloading the matter storage file');
|
|
3308
|
-
}
|
|
3309
|
-
});
|
|
3310
|
-
});
|
|
3311
|
-
// Endpoint to download the matterbridge storage directory
|
|
3312
|
-
this.expressApp.get('/api/download-mbstorage', async (req, res) => {
|
|
3313
|
-
this.log.debug('The frontend sent /api/download-mbstorage');
|
|
3314
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), path.join(this.matterbridgeDirectory, this.nodeStorageName));
|
|
3315
|
-
res.download(path.join(os.tmpdir(), `matterbridge.${this.nodeStorageName}.zip`), `matterbridge.${this.nodeStorageName}.zip`, (error) => {
|
|
3316
|
-
if (error) {
|
|
3317
|
-
this.log.error(`Error downloading file ${`matterbridge.${this.nodeStorageName}.zip`}: ${error instanceof Error ? error.message : error}`);
|
|
3318
|
-
res.status(500).send('Error downloading the matterbridge storage file');
|
|
3319
|
-
}
|
|
3320
|
-
});
|
|
3321
|
-
});
|
|
3322
|
-
// Endpoint to download the matterbridge plugin directory
|
|
3323
|
-
this.expressApp.get('/api/download-pluginstorage', async (req, res) => {
|
|
3324
|
-
this.log.debug('The frontend sent /api/download-pluginstorage');
|
|
3325
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), this.matterbridgePluginDirectory);
|
|
3326
|
-
res.download(path.join(os.tmpdir(), `matterbridge.pluginstorage.zip`), `matterbridge.pluginstorage.zip`, (error) => {
|
|
3327
|
-
if (error) {
|
|
3328
|
-
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
3329
|
-
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
3330
|
-
}
|
|
3331
|
-
});
|
|
3332
|
-
});
|
|
3333
|
-
// Endpoint to download the matterbridge plugin config files
|
|
3334
|
-
this.expressApp.get('/api/download-pluginconfig', async (req, res) => {
|
|
3335
|
-
this.log.debug('The frontend sent /api/download-pluginconfig');
|
|
3336
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3337
|
-
// await createZip(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, 'certs', '*.*')), path.relative(process.cwd(), path.join(this.matterbridgeDirectory, '*.config.json')));
|
|
3338
|
-
res.download(path.join(os.tmpdir(), `matterbridge.pluginconfig.zip`), `matterbridge.pluginconfig.zip`, (error) => {
|
|
3339
|
-
if (error) {
|
|
3340
|
-
this.log.error(`Error downloading file matterbridge.pluginstorage.zip: ${error instanceof Error ? error.message : error}`);
|
|
3341
|
-
res.status(500).send('Error downloading the matterbridge plugin storage file');
|
|
3342
|
-
}
|
|
3343
|
-
});
|
|
3344
|
-
});
|
|
3345
|
-
// Endpoint to download the matterbridge plugin config files
|
|
3346
|
-
this.expressApp.get('/api/download-backup', async (req, res) => {
|
|
3347
|
-
this.log.debug('The frontend sent /api/download-backup');
|
|
3348
|
-
res.download(path.join(os.tmpdir(), `matterbridge.backup.zip`), `matterbridge.backup.zip`, (error) => {
|
|
3349
|
-
if (error) {
|
|
3350
|
-
this.log.error(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
3351
|
-
res.status(500).send(`Error downloading file matterbridge.backup.zip: ${error instanceof Error ? error.message : error}`);
|
|
3352
|
-
}
|
|
3353
|
-
});
|
|
3354
|
-
});
|
|
3355
|
-
// Endpoint to receive commands
|
|
3356
|
-
this.expressApp.post('/api/command/:command/:param', express.json(), async (req, res) => {
|
|
3357
|
-
const command = req.params.command;
|
|
3358
|
-
let param = req.params.param;
|
|
3359
|
-
this.log.debug(`The frontend sent /api/command/${command}/${param}`);
|
|
3360
|
-
if (!command) {
|
|
3361
|
-
res.status(400).json({ error: 'No command provided' });
|
|
3362
|
-
return;
|
|
3363
|
-
}
|
|
3364
|
-
this.log.debug(`Received frontend command: ${command}:${param}`);
|
|
3365
|
-
// Handle the command setpassword from Settings
|
|
3366
|
-
if (command === 'setpassword') {
|
|
3367
|
-
const password = param.slice(1, -1); // Remove the first and last characters
|
|
3368
|
-
this.log.debug('setpassword', param, password);
|
|
3369
|
-
await this.nodeContext?.set('password', password);
|
|
3370
|
-
res.json({ message: 'Command received' });
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
// Handle the command setbridgemode from Settings
|
|
3374
|
-
if (command === 'setbridgemode') {
|
|
3375
|
-
this.log.debug(`setbridgemode: ${param}`);
|
|
3376
|
-
this.wssSendRestartRequired();
|
|
3377
|
-
await this.nodeContext?.set('bridgeMode', param);
|
|
3378
|
-
res.json({ message: 'Command received' });
|
|
3379
|
-
return;
|
|
3380
|
-
}
|
|
3381
|
-
// Handle the command backup from Settings
|
|
3382
|
-
if (command === 'backup') {
|
|
3383
|
-
this.log.notice(`Prepairing the backup...`);
|
|
3384
|
-
await createZip(path.join(os.tmpdir(), `matterbridge.backup.zip`), path.join(this.matterbridgeDirectory), path.join(this.matterbridgePluginDirectory));
|
|
3385
|
-
this.log.notice(`Backup ready to be downloaded.`);
|
|
3386
|
-
res.json({ message: 'Command received' });
|
|
3387
|
-
return;
|
|
3388
|
-
}
|
|
3389
|
-
// Handle the command setmbloglevel from Settings
|
|
3390
|
-
if (command === 'setmbloglevel') {
|
|
3391
|
-
this.log.debug('Matterbridge log level:', param);
|
|
3392
|
-
if (param === 'Debug') {
|
|
3393
|
-
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
3394
|
-
}
|
|
3395
|
-
else if (param === 'Info') {
|
|
3396
|
-
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
3397
|
-
}
|
|
3398
|
-
else if (param === 'Notice') {
|
|
3399
|
-
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
3400
|
-
}
|
|
3401
|
-
else if (param === 'Warn') {
|
|
3402
|
-
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
3403
|
-
}
|
|
3404
|
-
else if (param === 'Error') {
|
|
3405
|
-
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
3406
|
-
}
|
|
3407
|
-
else if (param === 'Fatal') {
|
|
3408
|
-
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
3409
|
-
}
|
|
3410
|
-
await this.nodeContext?.set('matterbridgeLogLevel', this.log.logLevel);
|
|
3411
|
-
MatterbridgeDevice.logLevel = this.log.logLevel;
|
|
3412
|
-
this.plugins.logLevel = this.log.logLevel;
|
|
3413
|
-
for (const plugin of this.plugins) {
|
|
3414
|
-
if (!plugin.platform || !plugin.platform.config)
|
|
3415
|
-
continue;
|
|
3416
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
3417
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
3418
|
-
}
|
|
3419
|
-
res.json({ message: 'Command received' });
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
// Handle the command setmbloglevel from Settings
|
|
3423
|
-
if (command === 'setmjloglevel') {
|
|
3424
|
-
this.log.debug('Matter.js log level:', param);
|
|
3425
|
-
if (param === 'Debug') {
|
|
3426
|
-
Logger.defaultLogLevel = MatterLogLevel.DEBUG;
|
|
3427
|
-
}
|
|
3428
|
-
else if (param === 'Info') {
|
|
3429
|
-
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3430
|
-
}
|
|
3431
|
-
else if (param === 'Notice') {
|
|
3432
|
-
Logger.defaultLogLevel = MatterLogLevel.NOTICE;
|
|
3433
|
-
}
|
|
3434
|
-
else if (param === 'Warn') {
|
|
3435
|
-
Logger.defaultLogLevel = MatterLogLevel.WARN;
|
|
3436
|
-
}
|
|
3437
|
-
else if (param === 'Error') {
|
|
3438
|
-
Logger.defaultLogLevel = MatterLogLevel.ERROR;
|
|
3439
|
-
}
|
|
3440
|
-
else if (param === 'Fatal') {
|
|
3441
|
-
Logger.defaultLogLevel = MatterLogLevel.FATAL;
|
|
3442
|
-
}
|
|
3443
|
-
await this.nodeContext?.set('matterLogLevel', Logger.defaultLogLevel);
|
|
3444
|
-
res.json({ message: 'Command received' });
|
|
3445
|
-
return;
|
|
3446
|
-
}
|
|
3447
|
-
// Handle the command setmdnsinterface from Settings
|
|
3448
|
-
if (command === 'setmdnsinterface') {
|
|
3449
|
-
param = param.slice(1, -1); // Remove the first and last characters *mdns*
|
|
3450
|
-
this.matterbridgeInformation.mattermdnsinterface = param;
|
|
3451
|
-
this.log.debug('Matter.js mdns interface:', param === '' ? 'All interfaces' : param);
|
|
3452
|
-
await this.nodeContext?.set('mattermdnsinterface', param);
|
|
3453
|
-
res.json({ message: 'Command received' });
|
|
3454
|
-
return;
|
|
3455
|
-
}
|
|
3456
|
-
// Handle the command setipv4address from Settings
|
|
3457
|
-
if (command === 'setipv4address') {
|
|
3458
|
-
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
3459
|
-
this.matterbridgeInformation.matteripv4address = param;
|
|
3460
|
-
this.log.debug('Matter.js ipv4 address:', param === '' ? 'All ipv4 addresses' : param);
|
|
3461
|
-
await this.nodeContext?.set('matteripv4address', param);
|
|
3462
|
-
res.json({ message: 'Command received' });
|
|
3463
|
-
return;
|
|
3464
|
-
}
|
|
3465
|
-
// Handle the command setipv6address from Settings
|
|
3466
|
-
if (command === 'setipv6address') {
|
|
3467
|
-
param = param.slice(1, -1); // Remove the first and last characters *ip*
|
|
3468
|
-
this.matterbridgeInformation.matteripv6address = param;
|
|
3469
|
-
this.log.debug('Matter.js ipv6 address:', param === '' ? 'All ipv6 addresses' : param);
|
|
3470
|
-
await this.nodeContext?.set('matteripv6address', param);
|
|
3471
|
-
res.json({ message: 'Command received' });
|
|
3472
|
-
return;
|
|
3473
|
-
}
|
|
3474
|
-
// Handle the command setmatterport from Settings
|
|
3475
|
-
if (command === 'setmatterport') {
|
|
3476
|
-
const port = Math.min(Math.max(parseInt(param), 5540), 5560);
|
|
3477
|
-
this.matterbridgeInformation.matterPort = port;
|
|
3478
|
-
this.log.debug(`Set matter commissioning port to ${CYAN}${port}${db}`);
|
|
3479
|
-
await this.nodeContext?.set('matterport', port);
|
|
3480
|
-
res.json({ message: 'Command received' });
|
|
3481
|
-
return;
|
|
3482
|
-
}
|
|
3483
|
-
// Handle the command setmatterdiscriminator from Settings
|
|
3484
|
-
if (command === 'setmatterdiscriminator') {
|
|
3485
|
-
const discriminator = Math.min(Math.max(parseInt(param), 1000), 4095);
|
|
3486
|
-
this.matterbridgeInformation.matterDiscriminator = discriminator;
|
|
3487
|
-
this.log.debug(`Set matter commissioning discriminator to ${CYAN}${discriminator}${db}`);
|
|
3488
|
-
await this.nodeContext?.set('matterdiscriminator', discriminator);
|
|
3489
|
-
res.json({ message: 'Command received' });
|
|
3490
|
-
return;
|
|
3491
|
-
}
|
|
3492
|
-
// Handle the command setmatterpasscode from Settings
|
|
3493
|
-
if (command === 'setmatterpasscode') {
|
|
3494
|
-
const passcode = Math.min(Math.max(parseInt(param), 10000000), 90000000);
|
|
3495
|
-
this.matterbridgeInformation.matterPasscode = passcode;
|
|
3496
|
-
this.log.debug(`Set matter commissioning passcode to ${CYAN}${passcode}${db}`);
|
|
3497
|
-
await this.nodeContext?.set('matterpasscode', passcode);
|
|
3498
|
-
res.json({ message: 'Command received' });
|
|
3499
|
-
return;
|
|
3500
|
-
}
|
|
3501
|
-
// Handle the command setmbloglevel from Settings
|
|
3502
|
-
if (command === 'setmblogfile') {
|
|
3503
|
-
this.log.debug('Matterbridge file log:', param);
|
|
3504
|
-
this.matterbridgeInformation.fileLogger = param === 'true';
|
|
3505
|
-
await this.nodeContext?.set('matterbridgeFileLog', param === 'true');
|
|
3506
|
-
// Create the file logger for matterbridge
|
|
3507
|
-
if (param === 'true')
|
|
3508
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbrideLoggerFile), "debug" /* LogLevel.DEBUG */, true);
|
|
3509
|
-
else
|
|
3510
|
-
AnsiLogger.setGlobalLogfile(undefined);
|
|
3511
|
-
res.json({ message: 'Command received' });
|
|
3512
|
-
return;
|
|
3513
|
-
}
|
|
3514
|
-
// Handle the command setmbloglevel from Settings
|
|
3515
|
-
if (command === 'setmjlogfile') {
|
|
3516
|
-
this.log.debug('Matter file log:', param);
|
|
3517
|
-
this.matterbridgeInformation.matterFileLogger = param === 'true';
|
|
3518
|
-
await this.nodeContext?.set('matterFileLog', param === 'true');
|
|
3519
|
-
if (param === 'true') {
|
|
3520
|
-
try {
|
|
3521
|
-
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
3522
|
-
defaultLogLevel: MatterLogLevel.DEBUG,
|
|
3523
|
-
logFormat: MatterLogFormat.PLAIN,
|
|
3524
|
-
});
|
|
3525
|
-
}
|
|
3526
|
-
catch (error) {
|
|
3527
|
-
this.log.debug(`Error adding the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
else {
|
|
3531
|
-
try {
|
|
3532
|
-
Logger.removeLogger('matterfilelogger');
|
|
3533
|
-
}
|
|
3534
|
-
catch (error) {
|
|
3535
|
-
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${er}: ${error instanceof Error ? error.message : error}`);
|
|
3536
|
-
}
|
|
3537
|
-
}
|
|
3538
|
-
res.json({ message: 'Command received' });
|
|
3539
|
-
return;
|
|
3540
|
-
}
|
|
3541
|
-
// Handle the command unregister from Settings
|
|
3542
|
-
if (command === 'unregister') {
|
|
3543
|
-
await this.unregisterAndShutdownProcess();
|
|
3544
|
-
res.json({ message: 'Command received' });
|
|
3545
|
-
return;
|
|
3546
|
-
}
|
|
3547
|
-
// Handle the command reset from Settings
|
|
3548
|
-
if (command === 'reset') {
|
|
3549
|
-
await this.shutdownProcessAndReset();
|
|
3550
|
-
res.json({ message: 'Command received' });
|
|
3551
|
-
return;
|
|
3552
|
-
}
|
|
3553
|
-
// Handle the command factoryreset from Settings
|
|
3554
|
-
if (command === 'factoryreset') {
|
|
3555
|
-
await this.shutdownProcessAndFactoryReset();
|
|
3556
|
-
res.json({ message: 'Command received' });
|
|
3557
|
-
return;
|
|
3558
|
-
}
|
|
3559
|
-
// Handle the command shutdown from Header
|
|
3560
|
-
if (command === 'shutdown') {
|
|
3561
|
-
await this.shutdownProcess();
|
|
3562
|
-
res.json({ message: 'Command received' });
|
|
3563
|
-
return;
|
|
3564
|
-
}
|
|
3565
|
-
// Handle the command restart from Header
|
|
3566
|
-
if (command === 'restart') {
|
|
3567
|
-
await this.restartProcess();
|
|
3568
|
-
res.json({ message: 'Command received' });
|
|
3569
|
-
return;
|
|
3570
|
-
}
|
|
3571
|
-
// Handle the command update from Header
|
|
3572
|
-
if (command === 'update') {
|
|
3573
|
-
this.log.info('Updating matterbridge...');
|
|
3574
|
-
try {
|
|
3575
|
-
await this.spawnCommand('npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose']);
|
|
3576
|
-
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
3577
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3578
|
-
}
|
|
3579
|
-
catch (error) {
|
|
3580
|
-
this.log.error('Error updating matterbridge');
|
|
3581
|
-
}
|
|
3582
|
-
await this.updateProcess();
|
|
3583
|
-
this.wssSendRestartRequired();
|
|
3584
|
-
res.json({ message: 'Command received' });
|
|
3585
|
-
return;
|
|
3586
|
-
}
|
|
3587
|
-
// Handle the command saveconfig from Home
|
|
3588
|
-
if (command === 'saveconfig') {
|
|
3589
|
-
param = param.replace(/\*/g, '\\');
|
|
3590
|
-
this.log.info(`Saving config for plugin ${plg}${param}${nf}...`);
|
|
3591
|
-
// console.log('Req.body:', JSON.stringify(req.body, null, 2));
|
|
3592
|
-
if (!this.plugins.has(param)) {
|
|
3593
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3594
|
-
}
|
|
3595
|
-
else {
|
|
3596
|
-
const plugin = this.plugins.get(param);
|
|
3597
|
-
if (!plugin)
|
|
3598
|
-
return;
|
|
3599
|
-
this.plugins.saveConfigFromJson(plugin, req.body);
|
|
3600
|
-
}
|
|
3601
|
-
this.wssSendRestartRequired();
|
|
3602
|
-
res.json({ message: 'Command received' });
|
|
3603
|
-
return;
|
|
3604
|
-
}
|
|
3605
|
-
// Handle the command installplugin from Home
|
|
3606
|
-
if (command === 'installplugin') {
|
|
3607
|
-
param = param.replace(/\*/g, '\\');
|
|
3608
|
-
this.log.info(`Installing plugin ${plg}${param}${nf}...`);
|
|
3609
|
-
try {
|
|
3610
|
-
await this.spawnCommand('npm', ['install', '-g', param, '--omit=dev', '--verbose']);
|
|
3611
|
-
this.log.info(`Plugin ${plg}${param}${nf} installed. Full restart required.`);
|
|
3612
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3613
|
-
}
|
|
3614
|
-
catch (error) {
|
|
3615
|
-
this.log.error(`Error installing plugin ${plg}${param}${er}`);
|
|
3616
|
-
}
|
|
3617
|
-
this.wssSendRestartRequired();
|
|
3618
|
-
param = param.split('@')[0];
|
|
3619
|
-
// Also add the plugin to matterbridge so no return!
|
|
3620
|
-
if (param === 'matterbridge') {
|
|
3621
|
-
// If we used the command installplugin to install a dev or a specific version of matterbridge we don't want to add it to matterbridge
|
|
3622
|
-
res.json({ message: 'Command received' });
|
|
3623
|
-
return;
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
// Handle the command addplugin from Home
|
|
3627
|
-
if (command === 'addplugin' || command === 'installplugin') {
|
|
3628
|
-
param = param.replace(/\*/g, '\\');
|
|
3629
|
-
const plugin = await this.plugins.add(param);
|
|
3630
|
-
if (plugin) {
|
|
3631
|
-
this.plugins.load(plugin, true, 'The plugin has been added', true); // No await do it in the background
|
|
3632
|
-
}
|
|
3633
|
-
res.json({ message: 'Command received' });
|
|
3634
|
-
this.wssSendRefreshRequired();
|
|
3635
|
-
return;
|
|
3636
|
-
}
|
|
3637
|
-
// Handle the command removeplugin from Home
|
|
3638
|
-
if (command === 'removeplugin') {
|
|
3639
|
-
if (!this.plugins.has(param)) {
|
|
3640
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3641
|
-
}
|
|
3642
|
-
else {
|
|
3643
|
-
const plugin = this.plugins.get(param);
|
|
3644
|
-
await this.plugins.shutdown(plugin, 'The plugin has been removed.', true);
|
|
3645
|
-
await this.plugins.remove(param);
|
|
3646
|
-
}
|
|
3647
|
-
res.json({ message: 'Command received' });
|
|
3648
|
-
this.wssSendRefreshRequired();
|
|
3649
|
-
return;
|
|
3650
|
-
}
|
|
3651
|
-
// Handle the command enableplugin from Home
|
|
3652
|
-
if (command === 'enableplugin') {
|
|
3653
|
-
if (!this.plugins.has(param)) {
|
|
3654
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3655
|
-
}
|
|
3656
|
-
else {
|
|
3657
|
-
const plugin = this.plugins.get(param);
|
|
3658
|
-
if (plugin && !plugin.enabled) {
|
|
3659
|
-
plugin.locked = undefined;
|
|
3660
|
-
plugin.error = undefined;
|
|
3661
|
-
plugin.loaded = undefined;
|
|
3662
|
-
plugin.started = undefined;
|
|
3663
|
-
plugin.configured = undefined;
|
|
3664
|
-
plugin.connected = undefined;
|
|
3665
|
-
plugin.platform = undefined;
|
|
3666
|
-
plugin.registeredDevices = undefined;
|
|
3667
|
-
plugin.addedDevices = undefined;
|
|
3668
|
-
await this.plugins.enable(param);
|
|
3669
|
-
this.plugins.load(plugin, true, 'The plugin has been enabled', true); // No await do it in the background
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
res.json({ message: 'Command received' });
|
|
3673
|
-
this.wssSendRefreshRequired();
|
|
3674
|
-
return;
|
|
3675
|
-
}
|
|
3676
|
-
// Handle the command disableplugin from Home
|
|
3677
|
-
if (command === 'disableplugin') {
|
|
3678
|
-
if (!this.plugins.has(param)) {
|
|
3679
|
-
this.log.warn(`Plugin ${plg}${param}${wr} not found in matterbridge`);
|
|
3680
|
-
}
|
|
3681
|
-
else {
|
|
3682
|
-
const plugin = this.plugins.get(param);
|
|
3683
|
-
if (plugin && plugin.enabled) {
|
|
3684
|
-
await this.plugins.shutdown(plugin, 'The plugin has been disabled.', true);
|
|
3685
|
-
await this.plugins.disable(param);
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
res.json({ message: 'Command received' });
|
|
3689
|
-
this.wssSendRefreshRequired();
|
|
3690
|
-
return;
|
|
3691
|
-
}
|
|
3692
|
-
});
|
|
3693
|
-
// Fallback for routing (must be the last route)
|
|
3694
|
-
this.expressApp.get('*', (req, res) => {
|
|
3695
|
-
this.log.debug('The frontend sent:', req.url);
|
|
3696
|
-
this.log.debug('Response send file:', path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
3697
|
-
res.sendFile(path.join(this.rootDirectory, 'frontend/build/index.html'));
|
|
3698
|
-
});
|
|
3699
|
-
this.log.debug(`Frontend initialized on port ${YELLOW}${port}${db} static ${UNDERLINE}${path.join(this.rootDirectory, 'frontend/build')}${UNDERLINEOFF}${rs}`);
|
|
3700
|
-
}
|
|
3701
|
-
/**
|
|
3702
|
-
* Retrieves the cluster text description from a given device.
|
|
3703
|
-
* @param {MatterbridgeDevice} device - The MatterbridgeDevice object.
|
|
3704
|
-
* @returns {string} The attributes description of the cluster servers in the device.
|
|
3705
|
-
*/
|
|
3706
|
-
getClusterTextFromDevice(device) {
|
|
3707
|
-
const stringifyUserLabel = (endpoint) => {
|
|
3708
|
-
const labelList = endpoint.getClusterServer(UserLabelCluster)?.attributes.labelList.getLocal();
|
|
3709
|
-
if (!labelList)
|
|
3710
|
-
return;
|
|
3711
|
-
const composed = labelList.find((entry) => entry.label === 'composed');
|
|
3712
|
-
if (composed)
|
|
3713
|
-
return 'Composed: ' + composed.value;
|
|
3714
|
-
else
|
|
3715
|
-
return '';
|
|
3716
|
-
};
|
|
3717
|
-
const stringifyFixedLabel = (endpoint) => {
|
|
3718
|
-
const labelList = endpoint.getClusterServer(FixedLabelCluster)?.attributes.labelList.getLocal();
|
|
3719
|
-
if (!labelList)
|
|
3720
|
-
return;
|
|
3721
|
-
const composed = labelList.find((entry) => entry.label === 'composed');
|
|
3722
|
-
if (composed)
|
|
3723
|
-
return 'Composed: ' + composed.value;
|
|
3724
|
-
else
|
|
3725
|
-
return '';
|
|
3726
|
-
};
|
|
3727
|
-
let attributes = '';
|
|
3728
|
-
// this.log.debug(`***getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3729
|
-
const clusterServers = device.getAllClusterServers();
|
|
3730
|
-
clusterServers.forEach((clusterServer) => {
|
|
3731
|
-
try {
|
|
3732
|
-
// this.log.debug(`**--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3733
|
-
if (clusterServer.name === 'OnOff')
|
|
3734
|
-
attributes += `OnOff: ${clusterServer.attributes.onOff.getLocal()} `;
|
|
3735
|
-
if (clusterServer.name === 'Switch')
|
|
3736
|
-
attributes += `Position: ${clusterServer.attributes.currentPosition.getLocal()} `;
|
|
3737
|
-
if (clusterServer.name === 'WindowCovering')
|
|
3738
|
-
attributes += `Cover position: ${clusterServer.attributes.currentPositionLiftPercent100ths.getLocal() / 100}% `;
|
|
3739
|
-
if (clusterServer.name === 'DoorLock')
|
|
3740
|
-
attributes += `State: ${clusterServer.attributes.lockState.getLocal() === 1 ? 'Locked' : 'Not locked'} `;
|
|
3741
|
-
if (clusterServer.name === 'Thermostat')
|
|
3742
|
-
attributes += `Temperature: ${clusterServer.attributes.localTemperature.getLocal() / 100}°C `;
|
|
3743
|
-
if (clusterServer.name === 'LevelControl')
|
|
3744
|
-
attributes += `Level: ${clusterServer.attributes.currentLevel.getLocal()}% `;
|
|
3745
|
-
if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentX)
|
|
3746
|
-
attributes += `X: ${Math.round(clusterServer.attributes.currentX.getLocal())} Y: ${Math.round(clusterServer.attributes.currentY.getLocal())} `;
|
|
3747
|
-
if (clusterServer.name === 'ColorControl' && clusterServer.attributes.currentHue)
|
|
3748
|
-
attributes += `Hue: ${Math.round(clusterServer.attributes.currentHue.getLocal())} Saturation: ${Math.round(clusterServer.attributes.currentSaturation.getLocal())}% `;
|
|
3749
|
-
if (clusterServer.name === 'ColorControl' && clusterServer.attributes.colorTemperatureMireds)
|
|
3750
|
-
attributes += `ColorTemp: ${Math.round(clusterServer.attributes.colorTemperatureMireds.getLocal())} `;
|
|
3751
|
-
if (clusterServer.name === 'BooleanState')
|
|
3752
|
-
attributes += `Contact: ${clusterServer.attributes.stateValue.getLocal()} `;
|
|
3753
|
-
if (clusterServer.name === 'BooleanStateConfiguration' && clusterServer.attributes.alarmsActive)
|
|
3754
|
-
attributes += `Active alarms: ${stringify(clusterServer.attributes.alarmsActive.getLocal())} `;
|
|
3755
|
-
if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.smokeState)
|
|
3756
|
-
attributes += `Smoke: ${clusterServer.attributes.smokeState.getLocal()} `;
|
|
3757
|
-
if (clusterServer.name === 'SmokeCoAlarm' && clusterServer.attributes.coState)
|
|
3758
|
-
attributes += `Co: ${clusterServer.attributes.coState.getLocal()} `;
|
|
3759
|
-
if (clusterServer.name === 'FanControl')
|
|
3760
|
-
attributes += `Mode: ${clusterServer.attributes.fanMode.getLocal()} Speed: ${clusterServer.attributes.percentCurrent.getLocal()} `;
|
|
3761
|
-
if (clusterServer.name === 'FanControl' && clusterServer.attributes.speedCurrent)
|
|
3762
|
-
attributes += `MultiSpeed: ${clusterServer.attributes.speedCurrent.getLocal()} `;
|
|
3763
|
-
if (clusterServer.name === 'OccupancySensing')
|
|
3764
|
-
attributes += `Occupancy: ${clusterServer.attributes.occupancy.getLocal().occupied} `;
|
|
3765
|
-
if (clusterServer.name === 'IlluminanceMeasurement')
|
|
3766
|
-
attributes += `Illuminance: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3767
|
-
if (clusterServer.name === 'AirQuality')
|
|
3768
|
-
attributes += `Air quality: ${clusterServer.attributes.airQuality.getLocal()} `;
|
|
3769
|
-
if (clusterServer.name === 'TvocMeasurement')
|
|
3770
|
-
attributes += `Voc: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3771
|
-
if (clusterServer.name === 'TemperatureMeasurement')
|
|
3772
|
-
attributes += `Temperature: ${clusterServer.attributes.measuredValue.getLocal() / 100}°C `;
|
|
3773
|
-
if (clusterServer.name === 'RelativeHumidityMeasurement')
|
|
3774
|
-
attributes += `Humidity: ${clusterServer.attributes.measuredValue.getLocal() / 100}% `;
|
|
3775
|
-
if (clusterServer.name === 'PressureMeasurement')
|
|
3776
|
-
attributes += `Pressure: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3777
|
-
if (clusterServer.name === 'FlowMeasurement')
|
|
3778
|
-
attributes += `Flow: ${clusterServer.attributes.measuredValue.getLocal()} `;
|
|
3779
|
-
if (clusterServer.name === 'FixedLabel')
|
|
3780
|
-
attributes += `${stringifyFixedLabel(device)} `;
|
|
3781
|
-
if (clusterServer.name === 'UserLabel')
|
|
3782
|
-
attributes += `${stringifyUserLabel(device)} `;
|
|
3783
|
-
// this.log.debug(`*--clusterServer: ${clusterServer.id} (${clusterServer.name})`);
|
|
3784
|
-
}
|
|
3785
|
-
catch (error) {
|
|
3786
|
-
this.log.error(`getClusterTextFromDevice with ${clusterServer.name} error: ${error}`);
|
|
3787
|
-
}
|
|
3788
|
-
});
|
|
3789
|
-
// this.log.debug(`*getClusterTextFromDevice: ${device.deviceName} (${device.name})`);
|
|
3790
|
-
return attributes;
|
|
3791
|
-
}
|
|
3792
|
-
/**
|
|
3793
|
-
* Initializes the Matterbridge instance as extension for zigbee2mqtt.
|
|
3794
|
-
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3795
|
-
*
|
|
3796
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
3797
|
-
*/
|
|
3798
|
-
async startExtension(dataPath, extensionVersion, port = 5540) {
|
|
3799
|
-
// Set the bridge mode
|
|
3800
|
-
this.bridgeMode = 'bridge';
|
|
3801
|
-
// Set the first port to use
|
|
3802
|
-
this.port = port;
|
|
3803
|
-
// Set Matterbridge logger
|
|
3804
|
-
this.log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "info" /* LogLevel.INFO */ });
|
|
3805
|
-
this.log.debug('Matterbridge extension is starting...');
|
|
3806
|
-
// Initialize NodeStorage
|
|
3807
|
-
this.matterbridgeDirectory = dataPath;
|
|
3808
|
-
this.log.debug('Creating node storage manager dir: ' + path.join(this.matterbridgeDirectory, 'node_storage'));
|
|
3809
|
-
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'node_storage'), logging: false });
|
|
3810
|
-
this.log.debug('Creating node storage context for matterbridge: matterbridge');
|
|
3811
|
-
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
3812
|
-
const plugin = {
|
|
3813
|
-
path: '',
|
|
3814
|
-
type: 'DynamicPlatform',
|
|
3815
|
-
name: 'MatterbridgeExtension',
|
|
3816
|
-
version: '1.0.0',
|
|
3817
|
-
description: 'Matterbridge extension',
|
|
3818
|
-
author: 'https://github.com/Luligu',
|
|
3819
|
-
enabled: false,
|
|
3820
|
-
registeredDevices: 0,
|
|
3821
|
-
addedDevices: 0,
|
|
3822
|
-
};
|
|
3823
|
-
this.plugins.set(plugin);
|
|
3824
|
-
this.plugins.saveToStorage();
|
|
3825
|
-
// Log system info and create .matterbridge directory
|
|
3826
|
-
await this.logNodeAndSystemInfo();
|
|
3827
|
-
this.matterbridgeDirectory = dataPath;
|
|
3828
|
-
// Set matter.js logger level and format
|
|
3829
|
-
Logger.defaultLogLevel = MatterLogLevel.INFO;
|
|
3830
|
-
Logger.format = MatterLogFormat.ANSI;
|
|
3831
|
-
// Start the storage and create matterbridgeContext
|
|
3832
|
-
await this.startMatterStorage('json', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
3833
|
-
if (!this.storageManager)
|
|
3834
|
-
return false;
|
|
3835
|
-
this.matterbridgeContext = await this.createCommissioningServerContext('Matterbridge', 'Matterbridge zigbee2MQTT', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'zigbee2MQTT Matter extension');
|
|
3836
|
-
if (!this.matterbridgeContext)
|
|
3837
|
-
return false;
|
|
3838
|
-
await this.matterbridgeContext.set('softwareVersion', 1);
|
|
3839
|
-
await this.matterbridgeContext.set('softwareVersionString', this.matterbridgeVersion);
|
|
3840
|
-
await this.matterbridgeContext.set('hardwareVersion', 1);
|
|
3841
|
-
await this.matterbridgeContext.set('hardwareVersionString', extensionVersion); // Update with the extension version
|
|
3842
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
3843
|
-
this.log.debug(`Creating commissioning server for ${plg}Matterbridge${db}`);
|
|
3844
|
-
this.commissioningServer = await this.createCommisioningServer(this.matterbridgeContext, 'Matterbridge');
|
|
3845
|
-
this.log.debug(`Creating matter aggregator for ${plg}Matterbridge${db}`);
|
|
3846
|
-
this.matterAggregator = await this.createMatterAggregator(this.matterbridgeContext, 'Matterbridge');
|
|
3847
|
-
this.log.debug('Adding matterbridge aggregator to commissioning server');
|
|
3848
|
-
this.commissioningServer.addDevice(this.matterAggregator);
|
|
3849
|
-
this.log.debug('Adding matterbridge commissioning server to matter server');
|
|
3850
|
-
await this.matterServer.addCommissioningServer(this.commissioningServer, { uniqueStorageKey: 'Matterbridge' });
|
|
3851
|
-
await this.startMatterServer();
|
|
3852
|
-
this.log.info('Matter server started');
|
|
3853
|
-
await this.showCommissioningQRCode(this.commissioningServer, this.matterbridgeContext, this.nodeContext, 'Matterbridge');
|
|
3854
|
-
// Set reachability to true and trigger event after 60 seconds
|
|
3855
|
-
setTimeout(() => {
|
|
3856
|
-
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
3857
|
-
if (this.commissioningServer)
|
|
3858
|
-
this.setCommissioningServerReachability(this.commissioningServer, true);
|
|
3859
|
-
if (this.matterAggregator)
|
|
3860
|
-
this.setAggregatorReachability(this.matterAggregator, true);
|
|
3861
|
-
}, 60 * 1000);
|
|
3862
|
-
return this.commissioningServer.isCommissioned();
|
|
3863
|
-
}
|
|
3864
|
-
/**
|
|
3865
|
-
* Close the Matterbridge instance as extension for zigbee2mqtt.
|
|
3866
|
-
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3867
|
-
*
|
|
3868
|
-
* @returns A Promise that resolves when the initialization is complete.
|
|
3869
|
-
*/
|
|
3870
|
-
async stopExtension() {
|
|
3871
|
-
// Closing matter
|
|
3872
|
-
await this.stopMatterServer();
|
|
3873
|
-
// Clearing the session manager
|
|
3874
|
-
// this.matterbridgeContext?.createContext('SessionManager').clear();
|
|
3875
|
-
// Closing storage
|
|
3876
|
-
await this.stopMatterStorage();
|
|
3877
|
-
this.log.info('Matter server stopped');
|
|
3878
|
-
}
|
|
3879
|
-
/**
|
|
3880
|
-
* Checks if the extension is commissioned.
|
|
3881
|
-
* @deprecated This method is deprecated and will be removed in a future version.
|
|
3882
|
-
*
|
|
3883
|
-
* @returns {boolean} Returns true if the extension is commissioned, false otherwise.
|
|
3884
|
-
*/
|
|
3885
|
-
isExtensionCommissioned() {
|
|
3886
|
-
if (!this.commissioningServer)
|
|
3887
|
-
return false;
|
|
3888
|
-
return this.commissioningServer.isCommissioned();
|
|
3889
1730
|
}
|
|
3890
1731
|
}
|
|
3891
|
-
//# sourceMappingURL=matterbridge.js.map
|