matterbridge 3.2.0-dev-20250801-f7eb2a2 → 3.2.0
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/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +91 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +78 -3
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +11 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +2 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +87 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +83 -6
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +242 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +91 -7
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +93 -7
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +140 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +113 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +51 -13
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +288 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +298 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +65 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +60 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +313 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +448 -23
- package/dist/frontend.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +59 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +47 -0
- package/dist/globalMatterbridge.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +463 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +802 -50
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1351 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +65 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +709 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +579 -15
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +36 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1354 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1224 -55
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +406 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +344 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +310 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +243 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +197 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +270 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +249 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +117 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +263 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +12 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/hex.d.ts +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +123 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +84 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +94 -7
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +33 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +40 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +56 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +62 -9
- package/dist/utils/wait.js.map +1 -0
- package/docs/README-DEV.md +377 -0
- package/docs/README-DOCKER.md +208 -0
- package/docs/README-NGINX.md +235 -0
- package/docs/README-PODMAN.md +109 -0
- package/docs/README-SERVICE.md +212 -0
- package/docs/README.md +618 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2023-12-29
|
|
7
|
+
* @version 1.6.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node.js modules
|
|
1
25
|
import os from 'node:os';
|
|
2
26
|
import path from 'node:path';
|
|
3
27
|
import { promises as fs } from 'node:fs';
|
|
4
28
|
import EventEmitter from 'node:events';
|
|
5
29
|
import { inspect } from 'node:util';
|
|
30
|
+
// AnsiLogger module
|
|
6
31
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
|
|
32
|
+
// NodeStorage module
|
|
7
33
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
+
// @matter
|
|
8
35
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
9
36
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
10
37
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
11
38
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
12
39
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
|
|
14
42
|
import { withTimeout, waiter, wait } from './utils/wait.js';
|
|
15
43
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -19,6 +47,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
19
47
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
20
48
|
import { Frontend } from './frontend.js';
|
|
21
49
|
import { addVirtualDevices } from './helpers.js';
|
|
50
|
+
/**
|
|
51
|
+
* Represents the Matterbridge application.
|
|
52
|
+
*/
|
|
22
53
|
export class Matterbridge extends EventEmitter {
|
|
23
54
|
systemInformation = {
|
|
24
55
|
interfaceName: '',
|
|
@@ -67,7 +98,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
67
98
|
shellySysUpdate: false,
|
|
68
99
|
shellyMainUpdate: false,
|
|
69
100
|
profile: getParameter('profile'),
|
|
70
|
-
loggerLevel: "info"
|
|
101
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
71
102
|
fileLogger: false,
|
|
72
103
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
73
104
|
matterFileLogger: false,
|
|
@@ -95,15 +126,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
95
126
|
profile = getParameter('profile');
|
|
96
127
|
shutdown = false;
|
|
97
128
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
98
|
-
|
|
129
|
+
// Matterbridge log files
|
|
130
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
99
131
|
matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
100
132
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
101
133
|
plugins = new PluginManager(this);
|
|
102
134
|
devices = new DeviceManager(this);
|
|
103
135
|
frontend = new Frontend(this);
|
|
136
|
+
// Matterbridge storage
|
|
104
137
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
105
138
|
nodeStorage;
|
|
106
139
|
nodeContext;
|
|
140
|
+
// Cleanup
|
|
107
141
|
hasCleanupStarted = false;
|
|
108
142
|
initialized = false;
|
|
109
143
|
execRunningCount = 0;
|
|
@@ -118,19 +152,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
118
152
|
sigtermHandler;
|
|
119
153
|
exceptionHandler;
|
|
120
154
|
rejectionHandler;
|
|
155
|
+
// Matter environment
|
|
121
156
|
environment = Environment.default;
|
|
157
|
+
// Matter storage
|
|
122
158
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
123
159
|
matterStorageService;
|
|
124
160
|
matterStorageManager;
|
|
125
161
|
matterbridgeContext;
|
|
126
162
|
controllerContext;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
163
|
+
// Matter parameters
|
|
164
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
165
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
166
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
167
|
+
port; // first server node port
|
|
168
|
+
passcode; // first server node passcode
|
|
169
|
+
discriminator; // first server node discriminator
|
|
170
|
+
certification; // device certification
|
|
171
|
+
// Matter nodes
|
|
134
172
|
serverNode;
|
|
135
173
|
aggregatorNode;
|
|
136
174
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -141,15 +179,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
141
179
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
142
180
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
143
181
|
static instance;
|
|
182
|
+
// We load asyncronously so is private
|
|
144
183
|
constructor() {
|
|
145
184
|
super();
|
|
146
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Retrieves the list of Matterbridge devices.
|
|
188
|
+
*
|
|
189
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
190
|
+
*/
|
|
147
191
|
getDevices() {
|
|
148
192
|
return this.devices.array();
|
|
149
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Retrieves the list of registered plugins.
|
|
196
|
+
*
|
|
197
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
198
|
+
*/
|
|
150
199
|
getPlugins() {
|
|
151
200
|
return this.plugins.array();
|
|
152
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
204
|
+
*
|
|
205
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
206
|
+
*/
|
|
153
207
|
async setLogLevel(logLevel) {
|
|
154
208
|
if (this.log)
|
|
155
209
|
this.log.logLevel = logLevel;
|
|
@@ -163,19 +217,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
163
217
|
for (const plugin of this.plugins) {
|
|
164
218
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
165
219
|
continue;
|
|
166
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
167
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
220
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
221
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
222
|
+
}
|
|
223
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
224
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
225
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
226
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
227
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
228
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
174
229
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
175
230
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
176
231
|
}
|
|
232
|
+
//* ************************************************************************************************************************************ */
|
|
233
|
+
// loadInstance() and cleanup() methods */
|
|
234
|
+
//* ************************************************************************************************************************************ */
|
|
235
|
+
/**
|
|
236
|
+
* Loads an instance of the Matterbridge class.
|
|
237
|
+
* If an instance already exists, return that instance.
|
|
238
|
+
*
|
|
239
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
240
|
+
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
241
|
+
*/
|
|
177
242
|
static async loadInstance(initialize = false) {
|
|
178
243
|
if (!Matterbridge.instance) {
|
|
244
|
+
// eslint-disable-next-line no-console
|
|
179
245
|
if (hasParameter('debug'))
|
|
180
246
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
181
247
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -184,8 +250,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
184
250
|
}
|
|
185
251
|
return Matterbridge.instance;
|
|
186
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Call cleanup() and dispose MdnsService.
|
|
255
|
+
*
|
|
256
|
+
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
257
|
+
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
258
|
+
*
|
|
259
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
260
|
+
*/
|
|
187
261
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
188
262
|
this.log.info(`Destroy instance...`);
|
|
263
|
+
// Save server nodes to close
|
|
189
264
|
const servers = [];
|
|
190
265
|
if (this.bridgeMode === 'bridge') {
|
|
191
266
|
if (this.serverNode)
|
|
@@ -203,72 +278,105 @@ export class Matterbridge extends EventEmitter {
|
|
|
203
278
|
servers.push(device.serverNode);
|
|
204
279
|
}
|
|
205
280
|
}
|
|
281
|
+
// Let any already‐queued microtasks run first
|
|
206
282
|
await Promise.resolve();
|
|
283
|
+
// Wait for the cleanup to finish
|
|
207
284
|
await wait(pause, 'destroyInstance start', true);
|
|
285
|
+
// Cleanup
|
|
208
286
|
await this.cleanup('destroying instance...', false, timeout);
|
|
287
|
+
// Close servers mdns service
|
|
209
288
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
210
289
|
for (const server of servers) {
|
|
211
290
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
212
291
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
213
292
|
}
|
|
293
|
+
// Let any already‐queued microtasks run first
|
|
214
294
|
await Promise.resolve();
|
|
295
|
+
// Wait for the cleanup to finish
|
|
215
296
|
await wait(pause, 'destroyInstance stop', true);
|
|
216
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Initializes the Matterbridge application.
|
|
300
|
+
*
|
|
301
|
+
* @remarks
|
|
302
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
303
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
304
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
305
|
+
*
|
|
306
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
307
|
+
*/
|
|
217
308
|
async initialize() {
|
|
309
|
+
// Emit the initialize_started event
|
|
218
310
|
this.emit('initialize_started');
|
|
311
|
+
// Set the restart mode
|
|
219
312
|
if (hasParameter('service'))
|
|
220
313
|
this.restartMode = 'service';
|
|
221
314
|
if (hasParameter('docker'))
|
|
222
315
|
this.restartMode = 'docker';
|
|
316
|
+
// Set the matterbridge home directory
|
|
223
317
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
224
318
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
225
319
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
320
|
+
// Set the matterbridge directory
|
|
226
321
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
227
322
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
228
323
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
229
324
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
230
325
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
326
|
+
// Set the matterbridge plugin directory
|
|
231
327
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
232
328
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
233
329
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
330
|
+
// Set the matterbridge cert directory
|
|
234
331
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
235
332
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
236
333
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
334
|
+
// Set the matterbridge root directory
|
|
237
335
|
const { fileURLToPath } = await import('node:url');
|
|
238
336
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
239
337
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
240
338
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
339
|
+
// Setup the matter environment
|
|
241
340
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
242
341
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
243
342
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
244
343
|
this.environment.vars.set('runtime.signals', false);
|
|
245
344
|
this.environment.vars.set('runtime.exitcode', false);
|
|
345
|
+
// Register process handlers
|
|
246
346
|
this.registerProcessHandlers();
|
|
347
|
+
// Initialize nodeStorage and nodeContext
|
|
247
348
|
try {
|
|
248
349
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
249
350
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
250
351
|
this.log.debug('Creating node storage context for matterbridge');
|
|
251
352
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
353
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
354
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
355
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
253
356
|
for (const key of keys) {
|
|
254
357
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
255
359
|
await this.nodeStorage?.storage.get(key);
|
|
256
360
|
}
|
|
257
361
|
const storages = await this.nodeStorage.getStorageNames();
|
|
258
362
|
for (const storage of storages) {
|
|
259
363
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
260
364
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
365
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
366
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
261
367
|
const keys = (await nodeContext?.storage.keys());
|
|
262
368
|
keys.forEach(async (key) => {
|
|
263
369
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
264
370
|
await nodeContext?.get(key);
|
|
265
371
|
});
|
|
266
372
|
}
|
|
373
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
267
374
|
this.log.debug('Creating node storage backup...');
|
|
268
375
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
269
376
|
this.log.debug('Created node storage backup');
|
|
270
377
|
}
|
|
271
378
|
catch (error) {
|
|
379
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
272
380
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
273
381
|
if (hasParameter('norestore')) {
|
|
274
382
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -282,14 +390,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
282
390
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
283
391
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
284
392
|
}
|
|
393
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
285
394
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
395
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
286
396
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
397
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
287
398
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
399
|
+
// Certificate management
|
|
288
400
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
289
401
|
try {
|
|
290
402
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
291
403
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
292
404
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
405
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
293
406
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
294
407
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
295
408
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -318,11 +431,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
318
431
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
319
432
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
320
433
|
}
|
|
434
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
321
435
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
322
436
|
this.passcode = pairingFileJson.passcode;
|
|
323
437
|
this.discriminator = pairingFileJson.discriminator;
|
|
324
438
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
325
439
|
}
|
|
440
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
326
441
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
327
442
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
328
443
|
this.certification = {
|
|
@@ -337,41 +452,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
337
452
|
catch (error) {
|
|
338
453
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
339
454
|
}
|
|
455
|
+
// Store the passcode, discriminator and port in the node context
|
|
340
456
|
await this.nodeContext.set('matterport', this.port);
|
|
341
457
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
342
458
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
343
459
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
460
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
344
461
|
if (hasParameter('logger')) {
|
|
345
462
|
const level = getParameter('logger');
|
|
346
463
|
if (level === 'debug') {
|
|
347
|
-
this.log.logLevel = "debug"
|
|
464
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
348
465
|
}
|
|
349
466
|
else if (level === 'info') {
|
|
350
|
-
this.log.logLevel = "info"
|
|
467
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
351
468
|
}
|
|
352
469
|
else if (level === 'notice') {
|
|
353
|
-
this.log.logLevel = "notice"
|
|
470
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
354
471
|
}
|
|
355
472
|
else if (level === 'warn') {
|
|
356
|
-
this.log.logLevel = "warn"
|
|
473
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
357
474
|
}
|
|
358
475
|
else if (level === 'error') {
|
|
359
|
-
this.log.logLevel = "error"
|
|
476
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
360
477
|
}
|
|
361
478
|
else if (level === 'fatal') {
|
|
362
|
-
this.log.logLevel = "fatal"
|
|
479
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
363
480
|
}
|
|
364
481
|
else {
|
|
365
482
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
366
|
-
this.log.logLevel = "info"
|
|
483
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
367
484
|
}
|
|
368
485
|
}
|
|
369
486
|
else {
|
|
370
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
487
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
371
488
|
}
|
|
372
489
|
this.frontend.logLevel = this.log.logLevel;
|
|
373
490
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
374
491
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
492
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
375
493
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
376
494
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
|
|
377
495
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -380,6 +498,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
380
498
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
381
499
|
if (this.profile !== undefined)
|
|
382
500
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
501
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
383
502
|
if (hasParameter('matterlogger')) {
|
|
384
503
|
const level = getParameter('matterlogger');
|
|
385
504
|
if (level === 'debug') {
|
|
@@ -410,7 +529,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
410
529
|
}
|
|
411
530
|
Logger.format = MatterLogFormat.ANSI;
|
|
412
531
|
Logger.setLogger('default', this.createMatterLogger());
|
|
532
|
+
// Logger.destinations.default.write = this.createMatterLogger();
|
|
413
533
|
this.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
534
|
+
// Create the file logger for matter.js (context: matterFileLog)
|
|
414
535
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
415
536
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
416
537
|
Logger.addLogger('matterfilelogger', await this.createMatterFileLogger(path.join(this.matterbridgeDirectory, this.matterLoggerFile), true), {
|
|
@@ -419,7 +540,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
419
540
|
});
|
|
420
541
|
}
|
|
421
542
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
543
|
+
// Log network interfaces
|
|
422
544
|
const networkInterfaces = os.networkInterfaces();
|
|
545
|
+
// console.log(`Network interfaces:`, networkInterfaces);
|
|
423
546
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
424
547
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
425
548
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -431,6 +554,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
431
554
|
});
|
|
432
555
|
}
|
|
433
556
|
}
|
|
557
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
434
558
|
if (hasParameter('mdnsinterface')) {
|
|
435
559
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
436
560
|
}
|
|
@@ -439,6 +563,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
439
563
|
if (this.mdnsInterface === '')
|
|
440
564
|
this.mdnsInterface = undefined;
|
|
441
565
|
}
|
|
566
|
+
// Validate mdnsInterface
|
|
442
567
|
if (this.mdnsInterface) {
|
|
443
568
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
444
569
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -451,6 +576,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
451
576
|
}
|
|
452
577
|
if (this.mdnsInterface)
|
|
453
578
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
579
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
454
580
|
if (hasParameter('ipv4address')) {
|
|
455
581
|
this.ipv4address = getParameter('ipv4address');
|
|
456
582
|
}
|
|
@@ -459,6 +585,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
459
585
|
if (this.ipv4address === '')
|
|
460
586
|
this.ipv4address = undefined;
|
|
461
587
|
}
|
|
588
|
+
// Validate ipv4address
|
|
462
589
|
if (this.ipv4address) {
|
|
463
590
|
let isValid = false;
|
|
464
591
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -474,6 +601,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
474
601
|
await this.nodeContext.remove('matteripv4address');
|
|
475
602
|
}
|
|
476
603
|
}
|
|
604
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
477
605
|
if (hasParameter('ipv6address')) {
|
|
478
606
|
this.ipv6address = getParameter('ipv6address');
|
|
479
607
|
}
|
|
@@ -482,6 +610,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
482
610
|
if (this.ipv6address === '')
|
|
483
611
|
this.ipv6address = undefined;
|
|
484
612
|
}
|
|
613
|
+
// Validate ipv6address
|
|
485
614
|
if (this.ipv6address) {
|
|
486
615
|
let isValid = false;
|
|
487
616
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -490,6 +619,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
490
619
|
isValid = true;
|
|
491
620
|
break;
|
|
492
621
|
}
|
|
622
|
+
/* istanbul ignore next */
|
|
493
623
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
494
624
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
495
625
|
isValid = true;
|
|
@@ -502,6 +632,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
502
632
|
await this.nodeContext.remove('matteripv6address');
|
|
503
633
|
}
|
|
504
634
|
}
|
|
635
|
+
// Initialize the virtual mode
|
|
505
636
|
if (hasParameter('novirtual')) {
|
|
506
637
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
507
638
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -510,12 +641,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
510
641
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
511
642
|
}
|
|
512
643
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
644
|
+
// Initialize PluginManager
|
|
513
645
|
this.plugins.logLevel = this.log.logLevel;
|
|
514
646
|
await this.plugins.loadFromStorage();
|
|
647
|
+
// Initialize DeviceManager
|
|
515
648
|
this.devices.logLevel = this.log.logLevel;
|
|
649
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
516
650
|
for (const plugin of this.plugins) {
|
|
517
651
|
const packageJson = await this.plugins.parse(plugin);
|
|
518
652
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
653
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
654
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
519
655
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
520
656
|
try {
|
|
521
657
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -538,6 +674,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
538
674
|
await plugin.nodeContext.set('description', plugin.description);
|
|
539
675
|
await plugin.nodeContext.set('author', plugin.author);
|
|
540
676
|
}
|
|
677
|
+
// Log system info and create .matterbridge directory
|
|
541
678
|
await this.logNodeAndSystemInfo();
|
|
542
679
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
543
680
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -545,6 +682,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
545
682
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
546
683
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
547
684
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
685
|
+
// Check node version and throw error
|
|
548
686
|
const minNodeVersion = 18;
|
|
549
687
|
const nodeVersion = process.versions.node;
|
|
550
688
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -552,10 +690,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
552
690
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
553
691
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
554
692
|
}
|
|
693
|
+
// Parse command line
|
|
555
694
|
await this.parseCommandLine();
|
|
695
|
+
// Emit the initialize_completed event
|
|
556
696
|
this.emit('initialize_completed');
|
|
557
697
|
this.initialized = true;
|
|
558
698
|
}
|
|
699
|
+
/**
|
|
700
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
701
|
+
*
|
|
702
|
+
* @private
|
|
703
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
704
|
+
*/
|
|
559
705
|
async parseCommandLine() {
|
|
560
706
|
if (hasParameter('help')) {
|
|
561
707
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -617,6 +763,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
617
763
|
}
|
|
618
764
|
index++;
|
|
619
765
|
}
|
|
766
|
+
/*
|
|
767
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
768
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
769
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
770
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
771
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
772
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
773
|
+
} else {
|
|
774
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
775
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
*/
|
|
620
779
|
this.shutdown = true;
|
|
621
780
|
return;
|
|
622
781
|
}
|
|
@@ -666,6 +825,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
666
825
|
this.shutdown = true;
|
|
667
826
|
return;
|
|
668
827
|
}
|
|
828
|
+
// Start the matter storage and create the matterbridge context
|
|
669
829
|
try {
|
|
670
830
|
await this.startMatterStorage();
|
|
671
831
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -682,18 +842,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
682
842
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
683
843
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
684
844
|
}
|
|
845
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
685
846
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
686
847
|
this.initialized = true;
|
|
687
848
|
await this.shutdownProcessAndReset();
|
|
688
849
|
this.shutdown = true;
|
|
689
850
|
return;
|
|
690
851
|
}
|
|
852
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
691
853
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
692
854
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
693
855
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
694
856
|
if (plugin) {
|
|
695
857
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
696
858
|
if (!matterStorageManager) {
|
|
859
|
+
/* istanbul ignore next */
|
|
697
860
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
698
861
|
}
|
|
699
862
|
else {
|
|
@@ -712,32 +875,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
712
875
|
this.shutdown = true;
|
|
713
876
|
return;
|
|
714
877
|
}
|
|
878
|
+
// Initialize frontend
|
|
715
879
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
716
880
|
await this.frontend.start(getIntParameter('frontend'));
|
|
881
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
717
882
|
clearTimeout(this.checkUpdateTimeout);
|
|
718
883
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
719
884
|
const { checkUpdates } = await import('./update.js');
|
|
720
885
|
checkUpdates(this);
|
|
721
886
|
}, 30 * 1000).unref();
|
|
887
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
722
888
|
clearInterval(this.checkUpdateInterval);
|
|
723
889
|
this.checkUpdateInterval = setInterval(async () => {
|
|
724
890
|
const { checkUpdates } = await import('./update.js');
|
|
725
891
|
checkUpdates(this);
|
|
726
892
|
}, 12 * 60 * 60 * 1000).unref();
|
|
893
|
+
// Start the matterbridge in mode test
|
|
727
894
|
if (hasParameter('test')) {
|
|
728
895
|
this.bridgeMode = 'bridge';
|
|
729
896
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
730
897
|
return;
|
|
731
898
|
}
|
|
899
|
+
// Start the matterbridge in mode controller
|
|
732
900
|
if (hasParameter('controller')) {
|
|
733
901
|
this.bridgeMode = 'controller';
|
|
734
902
|
await this.startController();
|
|
735
903
|
return;
|
|
736
904
|
}
|
|
905
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
737
906
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
738
907
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
739
908
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
740
909
|
}
|
|
910
|
+
// Start matterbridge in bridge mode
|
|
741
911
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
742
912
|
this.bridgeMode = 'bridge';
|
|
743
913
|
MatterbridgeEndpoint.bridgeMode = 'bridge';
|
|
@@ -745,6 +915,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
745
915
|
await this.startBridge();
|
|
746
916
|
return;
|
|
747
917
|
}
|
|
918
|
+
// Start matterbridge in childbridge mode
|
|
748
919
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
749
920
|
this.bridgeMode = 'childbridge';
|
|
750
921
|
MatterbridgeEndpoint.bridgeMode = 'childbridge';
|
|
@@ -753,10 +924,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
753
924
|
return;
|
|
754
925
|
}
|
|
755
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Asynchronously loads and starts the registered plugins.
|
|
929
|
+
*
|
|
930
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
931
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
932
|
+
*
|
|
933
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
934
|
+
*/
|
|
756
935
|
async startPlugins() {
|
|
936
|
+
// Check, load and start the plugins
|
|
757
937
|
for (const plugin of this.plugins) {
|
|
758
938
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
759
939
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
940
|
+
// Check if the plugin is available
|
|
760
941
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
761
942
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
762
943
|
plugin.enabled = false;
|
|
@@ -774,10 +955,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
774
955
|
plugin.configured = false;
|
|
775
956
|
plugin.registeredDevices = undefined;
|
|
776
957
|
plugin.addedDevices = undefined;
|
|
777
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
958
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
778
959
|
}
|
|
779
960
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
780
961
|
}
|
|
962
|
+
/**
|
|
963
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
964
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
965
|
+
*/
|
|
781
966
|
registerProcessHandlers() {
|
|
782
967
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
783
968
|
process.removeAllListeners('uncaughtException');
|
|
@@ -804,6 +989,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
804
989
|
};
|
|
805
990
|
process.on('SIGTERM', this.sigtermHandler);
|
|
806
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
994
|
+
*/
|
|
807
995
|
deregisterProcessHandlers() {
|
|
808
996
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
809
997
|
if (this.exceptionHandler)
|
|
@@ -820,12 +1008,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
820
1008
|
process.off('SIGTERM', this.sigtermHandler);
|
|
821
1009
|
this.sigtermHandler = undefined;
|
|
822
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Logs the node and system information.
|
|
1013
|
+
*/
|
|
823
1014
|
async logNodeAndSystemInfo() {
|
|
1015
|
+
// IP address information
|
|
824
1016
|
const networkInterfaces = os.networkInterfaces();
|
|
825
1017
|
this.systemInformation.interfaceName = '';
|
|
826
1018
|
this.systemInformation.ipv4Address = '';
|
|
827
1019
|
this.systemInformation.ipv6Address = '';
|
|
828
1020
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
1021
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
829
1022
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
830
1023
|
continue;
|
|
831
1024
|
if (!interfaceDetails) {
|
|
@@ -851,19 +1044,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
851
1044
|
break;
|
|
852
1045
|
}
|
|
853
1046
|
}
|
|
1047
|
+
// Node information
|
|
854
1048
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
855
1049
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
856
1050
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
857
1051
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1052
|
+
// Host system information
|
|
858
1053
|
this.systemInformation.hostname = os.hostname();
|
|
859
1054
|
this.systemInformation.user = os.userInfo().username;
|
|
860
|
-
this.systemInformation.osType = os.type();
|
|
861
|
-
this.systemInformation.osRelease = os.release();
|
|
862
|
-
this.systemInformation.osPlatform = os.platform();
|
|
863
|
-
this.systemInformation.osArch = os.arch();
|
|
864
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
865
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
866
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1055
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1056
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1057
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1058
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1059
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1060
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1061
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1062
|
+
// Log the system information
|
|
867
1063
|
this.log.debug('Host System Information:');
|
|
868
1064
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
869
1065
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -879,14 +1075,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
879
1075
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
880
1076
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
881
1077
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1078
|
+
// Log directories
|
|
882
1079
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
883
1080
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
884
1081
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
885
1082
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
886
1083
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1084
|
+
// Global node_modules directory
|
|
887
1085
|
if (this.nodeContext)
|
|
888
1086
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
889
1087
|
if (this.globalModulesDirectory === '') {
|
|
1088
|
+
// First run of Matterbridge so the node storage is empty
|
|
890
1089
|
try {
|
|
891
1090
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
892
1091
|
this.execRunningCount++;
|
|
@@ -896,55 +1095,87 @@ export class Matterbridge extends EventEmitter {
|
|
|
896
1095
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
897
1096
|
}
|
|
898
1097
|
catch (error) {
|
|
1098
|
+
// istanbul ignore next
|
|
899
1099
|
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
900
1100
|
}
|
|
901
1101
|
}
|
|
902
1102
|
else
|
|
903
1103
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1104
|
+
/* removed cause is too expensive for the shelly board and not really needed. Why should the globalModulesDirectory change?
|
|
1105
|
+
else {
|
|
1106
|
+
this.getGlobalNodeModules()
|
|
1107
|
+
.then(async (globalModulesDirectory) => {
|
|
1108
|
+
this.globalModulesDirectory = globalModulesDirectory;
|
|
1109
|
+
this.matterbridgeInformation.globalModulesDirectory = this.globalModulesDirectory;
|
|
1110
|
+
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1111
|
+
await this.nodeContext?.set<string>('globalModulesDirectory', this.globalModulesDirectory);
|
|
1112
|
+
})
|
|
1113
|
+
.catch((error) => {
|
|
1114
|
+
this.log.error(`Error getting global node_modules directory: ${error}`);
|
|
1115
|
+
});
|
|
1116
|
+
}*/
|
|
1117
|
+
// Matterbridge version
|
|
904
1118
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
905
1119
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
906
1120
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
907
1121
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1122
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
908
1123
|
if (this.nodeContext)
|
|
909
1124
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
910
1125
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1126
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
911
1127
|
if (this.nodeContext)
|
|
912
1128
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
913
1129
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1130
|
+
// Current working directory
|
|
914
1131
|
const currentDir = process.cwd();
|
|
915
1132
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1133
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
916
1134
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
917
1135
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
918
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1139
|
+
*
|
|
1140
|
+
* @returns {Function} The MatterLogger function.
|
|
1141
|
+
*/
|
|
919
1142
|
createMatterLogger() {
|
|
920
|
-
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4
|
|
1143
|
+
const matterLogger = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
921
1144
|
return (level, formattedLog) => {
|
|
922
1145
|
const logger = formattedLog.slice(44, 44 + 20).trim();
|
|
923
1146
|
const message = formattedLog.slice(65);
|
|
924
1147
|
matterLogger.logName = logger;
|
|
925
1148
|
switch (level) {
|
|
926
1149
|
case MatterLogLevel.DEBUG:
|
|
927
|
-
matterLogger.log("debug"
|
|
1150
|
+
matterLogger.log("debug" /* LogLevel.DEBUG */, message);
|
|
928
1151
|
break;
|
|
929
1152
|
case MatterLogLevel.INFO:
|
|
930
|
-
matterLogger.log("info"
|
|
1153
|
+
matterLogger.log("info" /* LogLevel.INFO */, message);
|
|
931
1154
|
break;
|
|
932
1155
|
case MatterLogLevel.NOTICE:
|
|
933
|
-
matterLogger.log("notice"
|
|
1156
|
+
matterLogger.log("notice" /* LogLevel.NOTICE */, message);
|
|
934
1157
|
break;
|
|
935
1158
|
case MatterLogLevel.WARN:
|
|
936
|
-
matterLogger.log("warn"
|
|
1159
|
+
matterLogger.log("warn" /* LogLevel.WARN */, message);
|
|
937
1160
|
break;
|
|
938
1161
|
case MatterLogLevel.ERROR:
|
|
939
|
-
matterLogger.log("error"
|
|
1162
|
+
matterLogger.log("error" /* LogLevel.ERROR */, message);
|
|
940
1163
|
break;
|
|
941
1164
|
case MatterLogLevel.FATAL:
|
|
942
|
-
matterLogger.log("fatal"
|
|
1165
|
+
matterLogger.log("fatal" /* LogLevel.FATAL */, message);
|
|
943
1166
|
break;
|
|
944
1167
|
}
|
|
945
1168
|
};
|
|
946
1169
|
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Creates a Matter File Logger.
|
|
1172
|
+
*
|
|
1173
|
+
* @param {string} filePath - The path to the log file.
|
|
1174
|
+
* @param {boolean} [unlink] - Whether to unlink the log file before creating a new one.
|
|
1175
|
+
* @returns {Function} - A function that logs formatted messages to the log file.
|
|
1176
|
+
*/
|
|
947
1177
|
async createMatterFileLogger(filePath, unlink = false) {
|
|
1178
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
948
1179
|
let fileSize = 0;
|
|
949
1180
|
if (unlink) {
|
|
950
1181
|
try {
|
|
@@ -955,10 +1186,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
955
1186
|
}
|
|
956
1187
|
}
|
|
957
1188
|
return async (level, formattedLog) => {
|
|
1189
|
+
/* istanbul ignore if */
|
|
958
1190
|
if (fileSize > 100000000) {
|
|
959
|
-
return;
|
|
1191
|
+
return; // Stop logging if the file size is greater than 100MB
|
|
960
1192
|
}
|
|
961
1193
|
fileSize += formattedLog.length;
|
|
1194
|
+
/* istanbul ignore if */
|
|
962
1195
|
if (fileSize > 100000000) {
|
|
963
1196
|
await fs.appendFile(filePath, `Logging on file has been stopped because the file size is greater than 100MB.` + os.EOL);
|
|
964
1197
|
return;
|
|
@@ -991,12 +1224,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
991
1224
|
}
|
|
992
1225
|
};
|
|
993
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1229
|
+
*
|
|
1230
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1231
|
+
*/
|
|
994
1232
|
async restartProcess() {
|
|
995
1233
|
await this.cleanup('restarting...', true);
|
|
996
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Shut down the process (/api/shutdown).
|
|
1237
|
+
*
|
|
1238
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1239
|
+
*/
|
|
997
1240
|
async shutdownProcess() {
|
|
998
1241
|
await this.cleanup('shutting down...', false);
|
|
999
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1245
|
+
*
|
|
1246
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1247
|
+
*/
|
|
1000
1248
|
async updateProcess() {
|
|
1001
1249
|
this.log.info('Updating matterbridge...');
|
|
1002
1250
|
try {
|
|
@@ -1010,6 +1258,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1010
1258
|
this.frontend.wssSendRestartRequired();
|
|
1011
1259
|
await this.cleanup('updating...', false);
|
|
1012
1260
|
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1263
|
+
*
|
|
1264
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1265
|
+
*
|
|
1266
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1267
|
+
*/
|
|
1013
1268
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
1014
1269
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1015
1270
|
for (const plugin of this.plugins.array()) {
|
|
@@ -1023,46 +1278,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
1023
1278
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
1024
1279
|
}
|
|
1025
1280
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1026
|
-
await wait(timeout);
|
|
1281
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
1027
1282
|
this.log.debug('Cleaning up and shutting down...');
|
|
1028
1283
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
1029
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1287
|
+
*
|
|
1288
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1289
|
+
*/
|
|
1030
1290
|
async shutdownProcessAndReset() {
|
|
1031
1291
|
await this.cleanup('shutting down with reset...', false);
|
|
1032
1292
|
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1295
|
+
*
|
|
1296
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1297
|
+
*/
|
|
1033
1298
|
async shutdownProcessAndFactoryReset() {
|
|
1034
1299
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1035
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Cleans up the Matterbridge instance.
|
|
1303
|
+
*
|
|
1304
|
+
* @param {string} message - The cleanup message.
|
|
1305
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1306
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1307
|
+
*
|
|
1308
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1309
|
+
*/
|
|
1036
1310
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
1037
1311
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1038
1312
|
this.emit('cleanup_started');
|
|
1039
1313
|
this.hasCleanupStarted = true;
|
|
1040
1314
|
this.log.info(message);
|
|
1315
|
+
// Clear the start matter interval
|
|
1041
1316
|
if (this.startMatterInterval) {
|
|
1042
1317
|
clearInterval(this.startMatterInterval);
|
|
1043
1318
|
this.startMatterInterval = undefined;
|
|
1044
1319
|
this.log.debug('Start matter interval cleared');
|
|
1045
1320
|
}
|
|
1321
|
+
// Clear the check update timeout
|
|
1046
1322
|
if (this.checkUpdateTimeout) {
|
|
1047
1323
|
clearTimeout(this.checkUpdateTimeout);
|
|
1048
1324
|
this.checkUpdateTimeout = undefined;
|
|
1049
1325
|
this.log.debug('Check update timeout cleared');
|
|
1050
1326
|
}
|
|
1327
|
+
// Clear the check update interval
|
|
1051
1328
|
if (this.checkUpdateInterval) {
|
|
1052
1329
|
clearInterval(this.checkUpdateInterval);
|
|
1053
1330
|
this.checkUpdateInterval = undefined;
|
|
1054
1331
|
this.log.debug('Check update interval cleared');
|
|
1055
1332
|
}
|
|
1333
|
+
// Clear the configure timeout
|
|
1056
1334
|
if (this.configureTimeout) {
|
|
1057
1335
|
clearTimeout(this.configureTimeout);
|
|
1058
1336
|
this.configureTimeout = undefined;
|
|
1059
1337
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1060
1338
|
}
|
|
1339
|
+
// Clear the reachability timeout
|
|
1061
1340
|
if (this.reachabilityTimeout) {
|
|
1062
1341
|
clearTimeout(this.reachabilityTimeout);
|
|
1063
1342
|
this.reachabilityTimeout = undefined;
|
|
1064
1343
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1065
1344
|
}
|
|
1345
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1066
1346
|
for (const plugin of this.plugins) {
|
|
1067
1347
|
if (!plugin.enabled || plugin.error)
|
|
1068
1348
|
continue;
|
|
@@ -1073,6 +1353,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1073
1353
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1074
1354
|
}
|
|
1075
1355
|
}
|
|
1356
|
+
// Stop matter server nodes
|
|
1076
1357
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1077
1358
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1078
1359
|
await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
|
|
@@ -1097,6 +1378,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1097
1378
|
}
|
|
1098
1379
|
}
|
|
1099
1380
|
this.log.notice('Stopped matter server nodes');
|
|
1381
|
+
// Matter commisioning reset
|
|
1100
1382
|
if (message === 'shutting down with reset...') {
|
|
1101
1383
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1102
1384
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1106,6 +1388,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1106
1388
|
await this.matterbridgeContext?.clearAll();
|
|
1107
1389
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1108
1390
|
}
|
|
1391
|
+
// Unregister all devices
|
|
1109
1392
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1110
1393
|
if (this.bridgeMode === 'bridge') {
|
|
1111
1394
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1123,18 +1406,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1123
1406
|
}
|
|
1124
1407
|
this.log.info('Matter storage reset done!');
|
|
1125
1408
|
}
|
|
1409
|
+
// Stop matter storage
|
|
1126
1410
|
await this.stopMatterStorage();
|
|
1411
|
+
// Stop the frontend
|
|
1127
1412
|
await this.frontend.stop();
|
|
1413
|
+
// Remove the matterfilelogger
|
|
1128
1414
|
try {
|
|
1129
1415
|
Logger.removeLogger('matterfilelogger');
|
|
1130
1416
|
}
|
|
1131
1417
|
catch (error) {
|
|
1132
1418
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1133
1419
|
}
|
|
1420
|
+
// Close the matterbridge node storage and context
|
|
1134
1421
|
if (this.nodeStorage && this.nodeContext) {
|
|
1422
|
+
/*
|
|
1423
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1424
|
+
this.log.info('Saving registered devices...');
|
|
1425
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1426
|
+
this.devices.forEach(async (device) => {
|
|
1427
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1428
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1429
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1430
|
+
});
|
|
1431
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1432
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1433
|
+
*/
|
|
1434
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1135
1435
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1136
1436
|
await this.nodeContext.close();
|
|
1137
1437
|
this.nodeContext = undefined;
|
|
1438
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1138
1439
|
for (const plugin of this.plugins) {
|
|
1139
1440
|
if (plugin.nodeContext) {
|
|
1140
1441
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1151,8 +1452,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1151
1452
|
}
|
|
1152
1453
|
this.plugins.clear();
|
|
1153
1454
|
this.devices.clear();
|
|
1455
|
+
// Factory reset
|
|
1154
1456
|
if (message === 'shutting down with factory reset...') {
|
|
1155
1457
|
try {
|
|
1458
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1156
1459
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1157
1460
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1158
1461
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1161,11 +1464,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1161
1464
|
await fs.rm(backup, { recursive: true });
|
|
1162
1465
|
}
|
|
1163
1466
|
catch (error) {
|
|
1467
|
+
// istanbul ignore next if
|
|
1164
1468
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1165
1469
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1166
1470
|
}
|
|
1167
1471
|
}
|
|
1168
1472
|
try {
|
|
1473
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1169
1474
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1170
1475
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1171
1476
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1174,18 +1479,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1174
1479
|
await fs.rm(backup, { recursive: true });
|
|
1175
1480
|
}
|
|
1176
1481
|
catch (error) {
|
|
1482
|
+
// istanbul ignore next if
|
|
1177
1483
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1178
1484
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1179
1485
|
}
|
|
1180
1486
|
}
|
|
1181
1487
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1182
1488
|
}
|
|
1489
|
+
// Deregisters the process handlers
|
|
1183
1490
|
this.deregisterProcessHandlers();
|
|
1184
1491
|
if (restart) {
|
|
1185
1492
|
if (message === 'updating...') {
|
|
1186
1493
|
this.log.info('Cleanup completed. Updating...');
|
|
1187
1494
|
Matterbridge.instance = undefined;
|
|
1188
|
-
this.emit('update');
|
|
1495
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1189
1496
|
}
|
|
1190
1497
|
else if (message === 'restarting...') {
|
|
1191
1498
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1206,6 +1513,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1206
1513
|
this.log.debug('Cleanup already started...');
|
|
1207
1514
|
}
|
|
1208
1515
|
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Creates and configures the server node for a single not bridged device.
|
|
1518
|
+
*
|
|
1519
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1520
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1521
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1522
|
+
*/
|
|
1209
1523
|
async createDeviceServerNode(plugin, device) {
|
|
1210
1524
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1211
1525
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1216,6 +1530,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1216
1530
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1217
1531
|
}
|
|
1218
1532
|
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1535
|
+
*
|
|
1536
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1537
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1538
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1539
|
+
*/
|
|
1219
1540
|
async createAccessoryPlugin(plugin, device) {
|
|
1220
1541
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1221
1542
|
plugin.locked = true;
|
|
@@ -1227,6 +1548,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1227
1548
|
await plugin.serverNode.add(device);
|
|
1228
1549
|
}
|
|
1229
1550
|
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
1553
|
+
*
|
|
1554
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1555
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
1556
|
+
*/
|
|
1230
1557
|
async createDynamicPlugin(plugin) {
|
|
1231
1558
|
if (!plugin.locked) {
|
|
1232
1559
|
plugin.locked = true;
|
|
@@ -1237,7 +1564,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1237
1564
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1238
1565
|
}
|
|
1239
1566
|
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Starts the Matterbridge in bridge mode.
|
|
1569
|
+
*
|
|
1570
|
+
* @private
|
|
1571
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1572
|
+
*/
|
|
1240
1573
|
async startBridge() {
|
|
1574
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1241
1575
|
if (!this.matterStorageManager)
|
|
1242
1576
|
throw new Error('No storage manager initialized');
|
|
1243
1577
|
if (!this.matterbridgeContext)
|
|
@@ -1276,13 +1610,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1276
1610
|
clearInterval(this.startMatterInterval);
|
|
1277
1611
|
this.startMatterInterval = undefined;
|
|
1278
1612
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1279
|
-
|
|
1613
|
+
// Start the Matter server node
|
|
1614
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1615
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1280
1616
|
for (const device of this.devices.array()) {
|
|
1281
1617
|
if (device.mode === 'server' && device.serverNode) {
|
|
1282
1618
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1283
|
-
this.startServerNode(device.serverNode);
|
|
1619
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1284
1620
|
}
|
|
1285
1621
|
}
|
|
1622
|
+
// Configure the plugins
|
|
1286
1623
|
this.configureTimeout = setTimeout(async () => {
|
|
1287
1624
|
for (const plugin of this.plugins.array()) {
|
|
1288
1625
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1300,16 +1637,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1300
1637
|
}
|
|
1301
1638
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1302
1639
|
}, 30 * 1000).unref();
|
|
1640
|
+
// Setting reachability to true
|
|
1303
1641
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1304
1642
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1305
1643
|
if (this.aggregatorNode)
|
|
1306
1644
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1307
1645
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1308
1646
|
}, 60 * 1000).unref();
|
|
1647
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1309
1648
|
this.emit('bridge_started');
|
|
1310
1649
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1311
1650
|
}, this.startMatterIntervalMs);
|
|
1312
1651
|
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1654
|
+
*
|
|
1655
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1656
|
+
*
|
|
1657
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1658
|
+
*/
|
|
1313
1659
|
async startChildbridge(delay = 1000) {
|
|
1314
1660
|
if (!this.matterStorageManager)
|
|
1315
1661
|
throw new Error('No storage manager initialized');
|
|
@@ -1347,8 +1693,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1347
1693
|
clearInterval(this.startMatterInterval);
|
|
1348
1694
|
this.startMatterInterval = undefined;
|
|
1349
1695
|
if (delay > 0)
|
|
1350
|
-
await wait(delay);
|
|
1696
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1351
1697
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1698
|
+
// Configure the plugins
|
|
1352
1699
|
this.configureTimeout = setTimeout(async () => {
|
|
1353
1700
|
for (const plugin of this.plugins.array()) {
|
|
1354
1701
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1385,7 +1732,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1385
1732
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1386
1733
|
continue;
|
|
1387
1734
|
}
|
|
1388
|
-
|
|
1735
|
+
// Start the Matter server node
|
|
1736
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1737
|
+
// Setting reachability to true
|
|
1389
1738
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1390
1739
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1391
1740
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1393,19 +1742,241 @@ export class Matterbridge extends EventEmitter {
|
|
|
1393
1742
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1394
1743
|
}, 60 * 1000).unref();
|
|
1395
1744
|
}
|
|
1745
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1396
1746
|
for (const device of this.devices.array()) {
|
|
1397
1747
|
if (device.mode === 'server' && device.serverNode) {
|
|
1398
1748
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1399
|
-
this.startServerNode(device.serverNode);
|
|
1749
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1400
1750
|
}
|
|
1401
1751
|
}
|
|
1752
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1402
1753
|
this.emit('childbridge_started');
|
|
1403
1754
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1404
1755
|
}, this.startMatterIntervalMs);
|
|
1405
1756
|
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Starts the Matterbridge controller.
|
|
1759
|
+
*
|
|
1760
|
+
* @private
|
|
1761
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1762
|
+
*/
|
|
1406
1763
|
async startController() {
|
|
1764
|
+
/*
|
|
1765
|
+
if (!this.matterStorageManager) {
|
|
1766
|
+
this.log.error('No storage manager initialized');
|
|
1767
|
+
await this.cleanup('No storage manager initialized');
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1771
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1772
|
+
if (!this.controllerContext) {
|
|
1773
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1774
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1779
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1780
|
+
this.log.info('Creating matter commissioning controller');
|
|
1781
|
+
this.commissioningController = new CommissioningController({
|
|
1782
|
+
autoConnect: false,
|
|
1783
|
+
});
|
|
1784
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1785
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1786
|
+
|
|
1787
|
+
this.log.info('Starting matter server');
|
|
1788
|
+
await this.matterServer.start();
|
|
1789
|
+
this.log.info('Matter server started');
|
|
1790
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1791
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1792
|
+
regulatoryCountryCode: 'XX',
|
|
1793
|
+
};
|
|
1794
|
+
const commissioningController = new CommissioningController({
|
|
1795
|
+
environment: {
|
|
1796
|
+
environment,
|
|
1797
|
+
id: uniqueId,
|
|
1798
|
+
},
|
|
1799
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1800
|
+
adminFabricLabel,
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
if (hasParameter('pairingcode')) {
|
|
1804
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1805
|
+
const pairingCode = getParameter('pairingcode');
|
|
1806
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1807
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1808
|
+
|
|
1809
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1810
|
+
if (pairingCode !== undefined) {
|
|
1811
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1812
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1813
|
+
longDiscriminator = undefined;
|
|
1814
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1815
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1816
|
+
} else {
|
|
1817
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1818
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1819
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1820
|
+
}
|
|
1821
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1822
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
const options = {
|
|
1826
|
+
commissioning: commissioningOptions,
|
|
1827
|
+
discovery: {
|
|
1828
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1829
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1830
|
+
},
|
|
1831
|
+
passcode: setupPin,
|
|
1832
|
+
} as NodeCommissioningOptions;
|
|
1833
|
+
this.log.info('Commissioning with options:', options);
|
|
1834
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1835
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1836
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1837
|
+
} // (hasParameter('pairingcode'))
|
|
1838
|
+
|
|
1839
|
+
if (hasParameter('unpairall')) {
|
|
1840
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1841
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1842
|
+
for (const nodeId of nodeIds) {
|
|
1843
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1844
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1845
|
+
}
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
if (hasParameter('discover')) {
|
|
1850
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1851
|
+
// console.log(discover);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1855
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1860
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1861
|
+
for (const nodeId of nodeIds) {
|
|
1862
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1863
|
+
|
|
1864
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1865
|
+
autoSubscribe: false,
|
|
1866
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1867
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1868
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1869
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1870
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1871
|
+
switch (info) {
|
|
1872
|
+
case NodeStateInformation.Connected:
|
|
1873
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1874
|
+
break;
|
|
1875
|
+
case NodeStateInformation.Disconnected:
|
|
1876
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1877
|
+
break;
|
|
1878
|
+
case NodeStateInformation.Reconnecting:
|
|
1879
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1880
|
+
break;
|
|
1881
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1882
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1883
|
+
break;
|
|
1884
|
+
case NodeStateInformation.StructureChanged:
|
|
1885
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1886
|
+
break;
|
|
1887
|
+
case NodeStateInformation.Decommissioned:
|
|
1888
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1889
|
+
break;
|
|
1890
|
+
default:
|
|
1891
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1894
|
+
},
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
node.logStructure();
|
|
1898
|
+
|
|
1899
|
+
// Get the interaction client
|
|
1900
|
+
this.log.info('Getting the interaction client');
|
|
1901
|
+
const interactionClient = await node.getInteractionClient();
|
|
1902
|
+
let cluster;
|
|
1903
|
+
let attributes;
|
|
1904
|
+
|
|
1905
|
+
// Log BasicInformationCluster
|
|
1906
|
+
cluster = BasicInformationCluster;
|
|
1907
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1908
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1909
|
+
});
|
|
1910
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1911
|
+
attributes.forEach((attribute) => {
|
|
1912
|
+
this.log.info(
|
|
1913
|
+
`- 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}`,
|
|
1914
|
+
);
|
|
1915
|
+
});
|
|
1916
|
+
|
|
1917
|
+
// Log PowerSourceCluster
|
|
1918
|
+
cluster = PowerSourceCluster;
|
|
1919
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1920
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1921
|
+
});
|
|
1922
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1923
|
+
attributes.forEach((attribute) => {
|
|
1924
|
+
this.log.info(
|
|
1925
|
+
`- 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}`,
|
|
1926
|
+
);
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
// Log ThreadNetworkDiagnostics
|
|
1930
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1931
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1932
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1933
|
+
});
|
|
1934
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1935
|
+
attributes.forEach((attribute) => {
|
|
1936
|
+
this.log.info(
|
|
1937
|
+
`- 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}`,
|
|
1938
|
+
);
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
// Log SwitchCluster
|
|
1942
|
+
cluster = SwitchCluster;
|
|
1943
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1944
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1945
|
+
});
|
|
1946
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1947
|
+
attributes.forEach((attribute) => {
|
|
1948
|
+
this.log.info(
|
|
1949
|
+
`- 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}`,
|
|
1950
|
+
);
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1954
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1955
|
+
ignoreInitialTriggers: false,
|
|
1956
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1957
|
+
this.log.info(
|
|
1958
|
+
`***${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}`,
|
|
1959
|
+
),
|
|
1960
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1961
|
+
this.log.info(
|
|
1962
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1963
|
+
);
|
|
1964
|
+
},
|
|
1965
|
+
});
|
|
1966
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1967
|
+
}
|
|
1968
|
+
*/
|
|
1407
1969
|
}
|
|
1970
|
+
/** */
|
|
1971
|
+
/** Matter.js methods */
|
|
1972
|
+
/** */
|
|
1973
|
+
/**
|
|
1974
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1975
|
+
*
|
|
1976
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1977
|
+
*/
|
|
1408
1978
|
async startMatterStorage() {
|
|
1979
|
+
// Setup Matter storage
|
|
1409
1980
|
this.log.info(`Starting matter node storage...`);
|
|
1410
1981
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1411
1982
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1414,8 +1985,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1414
1985
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1415
1986
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1416
1987
|
this.log.info('Matter node storage started');
|
|
1988
|
+
// Backup matter storage since it is created/opened correctly
|
|
1417
1989
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1418
1990
|
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1993
|
+
*
|
|
1994
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1995
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1996
|
+
* @private
|
|
1997
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1998
|
+
*/
|
|
1419
1999
|
async backupMatterStorage(storageName, backupName) {
|
|
1420
2000
|
this.log.info('Creating matter node storage backup...');
|
|
1421
2001
|
try {
|
|
@@ -1426,6 +2006,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1426
2006
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1427
2007
|
}
|
|
1428
2008
|
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Stops the matter storage.
|
|
2011
|
+
*
|
|
2012
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
2013
|
+
*/
|
|
1429
2014
|
async stopMatterStorage() {
|
|
1430
2015
|
this.log.info('Closing matter node storage...');
|
|
1431
2016
|
await this.matterStorageManager?.close();
|
|
@@ -1434,6 +2019,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1434
2019
|
this.matterbridgeContext = undefined;
|
|
1435
2020
|
this.log.info('Matter node storage closed');
|
|
1436
2021
|
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Creates a server node storage context.
|
|
2024
|
+
*
|
|
2025
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2026
|
+
* @param {string} deviceName - The name of the device.
|
|
2027
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
2028
|
+
* @param {number} vendorId - The vendor ID.
|
|
2029
|
+
* @param {string} vendorName - The vendor name.
|
|
2030
|
+
* @param {number} productId - The product ID.
|
|
2031
|
+
* @param {string} productName - The product name.
|
|
2032
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
2033
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
2034
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
2035
|
+
*/
|
|
1437
2036
|
async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1438
2037
|
const { randomBytes } = await import('node:crypto');
|
|
1439
2038
|
if (!this.matterStorageService)
|
|
@@ -1467,6 +2066,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1467
2066
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1468
2067
|
return storageContext;
|
|
1469
2068
|
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Creates a server node.
|
|
2071
|
+
*
|
|
2072
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2073
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2074
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2075
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2076
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2077
|
+
*/
|
|
1470
2078
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1471
2079
|
const storeId = await storageContext.get('storeId');
|
|
1472
2080
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1476,24 +2084,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
2084
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1477
2085
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1478
2086
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2087
|
+
/**
|
|
2088
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2089
|
+
*/
|
|
1479
2090
|
const serverNode = await ServerNode.create({
|
|
2091
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1480
2092
|
id: storeId,
|
|
2093
|
+
// Provide Network relevant configuration like the port
|
|
2094
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1481
2095
|
network: {
|
|
1482
2096
|
listeningAddressIpv4: this.ipv4address,
|
|
1483
2097
|
listeningAddressIpv6: this.ipv6address,
|
|
1484
2098
|
port,
|
|
1485
2099
|
},
|
|
2100
|
+
// Provide the certificate for the device
|
|
1486
2101
|
operationalCredentials: {
|
|
1487
2102
|
certification: this.certification,
|
|
1488
2103
|
},
|
|
2104
|
+
// Provide Commissioning relevant settings
|
|
2105
|
+
// Optional for development/testing purposes
|
|
1489
2106
|
commissioning: {
|
|
1490
2107
|
passcode,
|
|
1491
2108
|
discriminator,
|
|
1492
2109
|
},
|
|
2110
|
+
// Provide Node announcement settings
|
|
2111
|
+
// Optional: If Ommitted some development defaults are used
|
|
1493
2112
|
productDescription: {
|
|
1494
2113
|
name: await storageContext.get('deviceName'),
|
|
1495
2114
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1496
2115
|
},
|
|
2116
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2117
|
+
// Optional: If Omitted some development defaults are used
|
|
1497
2118
|
basicInformation: {
|
|
1498
2119
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1499
2120
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1510,14 +2131,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1510
2131
|
reachable: true,
|
|
1511
2132
|
},
|
|
1512
2133
|
});
|
|
2134
|
+
/**
|
|
2135
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2136
|
+
* This means: It is added to the first fabric.
|
|
2137
|
+
*/
|
|
1513
2138
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1514
2139
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1515
2140
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1516
2141
|
});
|
|
2142
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1517
2143
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1518
2144
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1519
2145
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1520
2146
|
});
|
|
2147
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1521
2148
|
serverNode.lifecycle.online.on(async () => {
|
|
1522
2149
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1523
2150
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1525,9 +2152,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1525
2152
|
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
1526
2153
|
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1527
2154
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2155
|
+
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
1528
2156
|
this.startEndAdvertiseTimer(serverNode);
|
|
1529
2157
|
}
|
|
1530
2158
|
else {
|
|
2159
|
+
// istanbul ignore next
|
|
1531
2160
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
1532
2161
|
}
|
|
1533
2162
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
@@ -1535,14 +2164,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1535
2164
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1536
2165
|
this.emit('online', storeId);
|
|
1537
2166
|
});
|
|
2167
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1538
2168
|
serverNode.lifecycle.offline.on(() => {
|
|
1539
2169
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1540
|
-
this.matterbridgeInformation.matterbridgeEndAdvertise = true;
|
|
2170
|
+
this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
|
|
1541
2171
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1542
2172
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1543
2173
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1544
2174
|
this.emit('offline', storeId);
|
|
1545
2175
|
});
|
|
2176
|
+
/**
|
|
2177
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2178
|
+
* information is needed.
|
|
2179
|
+
*/
|
|
1546
2180
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1547
2181
|
let action = '';
|
|
1548
2182
|
switch (fabricAction) {
|
|
@@ -1559,14 +2193,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1559
2193
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1560
2194
|
this.frontend.wssSendRefreshRequired('fabrics');
|
|
1561
2195
|
});
|
|
2196
|
+
/**
|
|
2197
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2198
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2199
|
+
*/
|
|
1562
2200
|
serverNode.events.sessions.opened.on((session) => {
|
|
1563
2201
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1564
2202
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1565
2203
|
});
|
|
2204
|
+
/**
|
|
2205
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2206
|
+
*/
|
|
1566
2207
|
serverNode.events.sessions.closed.on((session) => {
|
|
1567
2208
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1568
2209
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1569
2210
|
});
|
|
2211
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1570
2212
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1571
2213
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1572
2214
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
@@ -1574,6 +2216,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1574
2216
|
this.log.info(`Created server node for ${storeId}`);
|
|
1575
2217
|
return serverNode;
|
|
1576
2218
|
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2221
|
+
*
|
|
2222
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2223
|
+
*/
|
|
1577
2224
|
startEndAdvertiseTimer(matterServerNode) {
|
|
1578
2225
|
if (this.endAdvertiseTimeout) {
|
|
1579
2226
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -1592,12 +2239,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1592
2239
|
this.log.notice(`Advertising stopped. Restart to commission again.`);
|
|
1593
2240
|
}, 15 * 60 * 1000).unref();
|
|
1594
2241
|
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Starts the specified server node.
|
|
2244
|
+
*
|
|
2245
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2246
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2247
|
+
*/
|
|
1595
2248
|
async startServerNode(matterServerNode) {
|
|
1596
2249
|
if (!matterServerNode)
|
|
1597
2250
|
return;
|
|
1598
2251
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1599
2252
|
await matterServerNode.start();
|
|
1600
2253
|
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Stops the specified server node.
|
|
2256
|
+
*
|
|
2257
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2258
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2259
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2260
|
+
*/
|
|
1601
2261
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1602
2262
|
if (!matterServerNode)
|
|
1603
2263
|
return;
|
|
@@ -1610,6 +2270,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1610
2270
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1611
2271
|
}
|
|
1612
2272
|
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Advertises the specified server node.
|
|
2275
|
+
*
|
|
2276
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2277
|
+
* @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
|
|
2278
|
+
*/
|
|
1613
2279
|
async advertiseServerNode(matterServerNode) {
|
|
1614
2280
|
if (matterServerNode) {
|
|
1615
2281
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1618,19 +2284,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
1618
2284
|
return { qrPairingCode, manualPairingCode };
|
|
1619
2285
|
}
|
|
1620
2286
|
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Stop advertise the specified server node.
|
|
2289
|
+
*
|
|
2290
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2291
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2292
|
+
*/
|
|
1621
2293
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1622
2294
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1623
2295
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
1624
2296
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1625
2297
|
}
|
|
1626
2298
|
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Creates an aggregator node with the specified storage context.
|
|
2301
|
+
*
|
|
2302
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2303
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2304
|
+
*/
|
|
1627
2305
|
async createAggregatorNode(storageContext) {
|
|
1628
2306
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1629
2307
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1630
2308
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1631
2309
|
return aggregatorNode;
|
|
1632
2310
|
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2313
|
+
*
|
|
2314
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2315
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2316
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2317
|
+
*/
|
|
1633
2318
|
async addBridgedEndpoint(pluginName, device) {
|
|
2319
|
+
// Check if the plugin is registered
|
|
1634
2320
|
const plugin = this.plugins.get(pluginName);
|
|
1635
2321
|
if (!plugin) {
|
|
1636
2322
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1650,6 +2336,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1650
2336
|
}
|
|
1651
2337
|
else if (this.bridgeMode === 'bridge') {
|
|
1652
2338
|
if (device.mode === 'matter') {
|
|
2339
|
+
// Register and add the device to the matterbridge server node
|
|
1653
2340
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1654
2341
|
if (!this.serverNode) {
|
|
1655
2342
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1666,6 +2353,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1666
2353
|
}
|
|
1667
2354
|
}
|
|
1668
2355
|
else {
|
|
2356
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1669
2357
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1670
2358
|
if (!this.aggregatorNode) {
|
|
1671
2359
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1683,6 +2371,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1683
2371
|
}
|
|
1684
2372
|
}
|
|
1685
2373
|
else if (this.bridgeMode === 'childbridge') {
|
|
2374
|
+
// Register and add the device to the plugin server node
|
|
1686
2375
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1687
2376
|
try {
|
|
1688
2377
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1706,10 +2395,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1706
2395
|
return;
|
|
1707
2396
|
}
|
|
1708
2397
|
}
|
|
2398
|
+
// Register and add the device to the plugin aggregator node
|
|
1709
2399
|
if (plugin.type === 'DynamicPlatform') {
|
|
1710
2400
|
try {
|
|
1711
2401
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1712
2402
|
await this.createDynamicPlugin(plugin);
|
|
2403
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1713
2404
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1714
2405
|
if (!plugin.aggregatorNode) {
|
|
1715
2406
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1732,17 +2423,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1732
2423
|
plugin.registeredDevices++;
|
|
1733
2424
|
if (plugin.addedDevices !== undefined)
|
|
1734
2425
|
plugin.addedDevices++;
|
|
2426
|
+
// Add the device to the DeviceManager
|
|
1735
2427
|
this.devices.set(device);
|
|
2428
|
+
// Subscribe to the reachable$Changed event
|
|
1736
2429
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1737
2430
|
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}`);
|
|
1738
2431
|
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2434
|
+
*
|
|
2435
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2436
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2437
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2438
|
+
*/
|
|
1739
2439
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1740
2440
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2441
|
+
// Check if the plugin is registered
|
|
1741
2442
|
const plugin = this.plugins.get(pluginName);
|
|
1742
2443
|
if (!plugin) {
|
|
1743
2444
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1744
2445
|
return;
|
|
1745
2446
|
}
|
|
2447
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1746
2448
|
if (this.bridgeMode === 'bridge') {
|
|
1747
2449
|
if (!this.aggregatorNode) {
|
|
1748
2450
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1757,6 +2459,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1757
2459
|
}
|
|
1758
2460
|
else if (this.bridgeMode === 'childbridge') {
|
|
1759
2461
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2462
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1760
2463
|
}
|
|
1761
2464
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1762
2465
|
if (!plugin.aggregatorNode) {
|
|
@@ -1771,8 +2474,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1771
2474
|
if (plugin.addedDevices !== undefined)
|
|
1772
2475
|
plugin.addedDevices--;
|
|
1773
2476
|
}
|
|
2477
|
+
// Remove the device from the DeviceManager
|
|
1774
2478
|
this.devices.remove(device);
|
|
1775
2479
|
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2482
|
+
*
|
|
2483
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2484
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2485
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2486
|
+
*
|
|
2487
|
+
* @remarks
|
|
2488
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2489
|
+
* It also applies a delay between each removal if specified.
|
|
2490
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2491
|
+
*/
|
|
1776
2492
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1777
2493
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1778
2494
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1783,6 +2499,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1783
2499
|
if (delay > 0)
|
|
1784
2500
|
await wait(2000);
|
|
1785
2501
|
}
|
|
2502
|
+
/**
|
|
2503
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2504
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2505
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2506
|
+
*
|
|
2507
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2508
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2509
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2510
|
+
*/
|
|
1786
2511
|
async subscribeAttributeChanged(plugin, device) {
|
|
1787
2512
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1788
2513
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -1798,6 +2523,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1798
2523
|
});
|
|
1799
2524
|
}
|
|
1800
2525
|
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2528
|
+
*
|
|
2529
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2530
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2531
|
+
*/
|
|
1801
2532
|
sanitizeFabricInformations(fabricInfo) {
|
|
1802
2533
|
return fabricInfo.map((info) => {
|
|
1803
2534
|
return {
|
|
@@ -1811,6 +2542,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1811
2542
|
};
|
|
1812
2543
|
});
|
|
1813
2544
|
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2547
|
+
*
|
|
2548
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2549
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2550
|
+
*/
|
|
1814
2551
|
sanitizeSessionInformation(sessions) {
|
|
1815
2552
|
return sessions
|
|
1816
2553
|
.filter((session) => session.isPeerActive)
|
|
@@ -1837,7 +2574,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1837
2574
|
};
|
|
1838
2575
|
});
|
|
1839
2576
|
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2579
|
+
*
|
|
2580
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2581
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2582
|
+
*/
|
|
2583
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1840
2584
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2585
|
+
/*
|
|
2586
|
+
for (const child of aggregatorNode.parts) {
|
|
2587
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2588
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2589
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2590
|
+
}
|
|
2591
|
+
*/
|
|
1841
2592
|
}
|
|
1842
2593
|
getVendorIdName = (vendorId) => {
|
|
1843
2594
|
if (!vendorId)
|
|
@@ -1877,10 +2628,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1877
2628
|
case 0x1488:
|
|
1878
2629
|
vendorName = '(ShortcutLabsFlic)';
|
|
1879
2630
|
break;
|
|
1880
|
-
case 65521:
|
|
2631
|
+
case 65521: // 0xFFF1
|
|
1881
2632
|
vendorName = '(MatterTest)';
|
|
1882
2633
|
break;
|
|
1883
2634
|
}
|
|
1884
2635
|
return vendorName;
|
|
1885
2636
|
};
|
|
1886
2637
|
}
|
|
2638
|
+
//# sourceMappingURL=matterbridge.js.map
|