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