matterbridge 3.3.0-dev-20251003-626ea2f → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.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 +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -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 +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -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 +100 -9
- 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/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.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 +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -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 +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -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 +232 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +454 -61
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +514 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.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 +430 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +788 -68
- 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 +1747 -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 +761 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +630 -17
- 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 +1534 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1398 -58
- 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 +402 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +340 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +201 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +30 -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 +13 -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/jestHelpers.d.ts +137 -0
- package/dist/utils/jestHelpers.d.ts.map +1 -0
- package/dist/utils/jestHelpers.js +153 -3
- package/dist/utils/jestHelpers.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 +34 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +69 -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,23 +1,54 @@
|
|
|
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, or } 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 { 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';
|
|
39
|
+
// Matterbridge
|
|
12
40
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
|
|
13
41
|
import { withTimeout, waiter, wait } from './utils/wait.js';
|
|
14
|
-
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
42
|
+
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ, } from './matterbridgeTypes.js';
|
|
15
43
|
import { PluginManager } from './pluginManager.js';
|
|
16
44
|
import { DeviceManager } from './deviceManager.js';
|
|
17
45
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
18
46
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
19
47
|
import { Frontend } from './frontend.js';
|
|
20
48
|
import { addVirtualDevices } from './helpers.js';
|
|
49
|
+
/**
|
|
50
|
+
* Represents the Matterbridge application.
|
|
51
|
+
*/
|
|
21
52
|
export class Matterbridge extends EventEmitter {
|
|
22
53
|
systemInformation = {
|
|
23
54
|
interfaceName: '',
|
|
@@ -58,7 +89,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
58
89
|
shellySysUpdate: false,
|
|
59
90
|
shellyMainUpdate: false,
|
|
60
91
|
profile: getParameter('profile'),
|
|
61
|
-
loggerLevel: "info"
|
|
92
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
62
93
|
fileLogger: false,
|
|
63
94
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
64
95
|
matterFileLogger: false,
|
|
@@ -86,16 +117,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
86
117
|
profile = getParameter('profile');
|
|
87
118
|
shutdown = false;
|
|
88
119
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
// Matterbridge logger
|
|
121
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
122
|
+
// Matter logger
|
|
123
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
124
|
+
// Managers
|
|
93
125
|
plugins = new PluginManager(this);
|
|
94
126
|
devices = new DeviceManager(this);
|
|
95
127
|
frontend = new Frontend(this);
|
|
96
|
-
|
|
128
|
+
// Matterbridge storage
|
|
97
129
|
nodeStorage;
|
|
98
130
|
nodeContext;
|
|
131
|
+
// Cleanup
|
|
99
132
|
hasCleanupStarted = false;
|
|
100
133
|
initialized = false;
|
|
101
134
|
startMatterInterval;
|
|
@@ -108,19 +141,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
108
141
|
sigtermHandler;
|
|
109
142
|
exceptionHandler;
|
|
110
143
|
rejectionHandler;
|
|
144
|
+
// Matter environment
|
|
111
145
|
environment = Environment.default;
|
|
112
|
-
|
|
146
|
+
// Matter storage
|
|
113
147
|
matterStorageService;
|
|
114
148
|
matterStorageManager;
|
|
115
149
|
matterbridgeContext;
|
|
116
150
|
controllerContext;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
// Matter parameters
|
|
152
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'Wi-Fi'
|
|
153
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
154
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
155
|
+
port; // first server node port
|
|
156
|
+
passcode; // first server node passcode
|
|
157
|
+
discriminator; // first server node discriminator
|
|
158
|
+
certification; // device certification
|
|
159
|
+
// Matter nodes
|
|
124
160
|
serverNode;
|
|
125
161
|
aggregatorNode;
|
|
126
162
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -130,12 +166,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
130
166
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
131
167
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
132
168
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
169
|
+
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
133
170
|
advertisingNodes = new Map();
|
|
134
171
|
static instance;
|
|
172
|
+
// We load asyncronously so is private
|
|
135
173
|
constructor() {
|
|
136
174
|
super();
|
|
137
175
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
138
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
179
|
+
*
|
|
180
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
181
|
+
*/
|
|
139
182
|
async setLogLevel(logLevel) {
|
|
140
183
|
if (this.log)
|
|
141
184
|
this.log.logLevel = logLevel;
|
|
@@ -149,19 +192,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
149
192
|
for (const plugin of this.plugins) {
|
|
150
193
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
151
194
|
continue;
|
|
152
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
153
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
195
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
196
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
197
|
+
}
|
|
198
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
199
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
200
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
201
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
202
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
203
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
160
204
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
161
205
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
162
206
|
}
|
|
207
|
+
//* ************************************************************************************************************************************ */
|
|
208
|
+
// loadInstance() and cleanup() methods */
|
|
209
|
+
//* ************************************************************************************************************************************ */
|
|
210
|
+
/**
|
|
211
|
+
* Loads an instance of the Matterbridge class.
|
|
212
|
+
* If an instance already exists, return that instance.
|
|
213
|
+
*
|
|
214
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
215
|
+
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
216
|
+
*/
|
|
163
217
|
static async loadInstance(initialize = false) {
|
|
164
218
|
if (!Matterbridge.instance) {
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
165
220
|
if (hasParameter('debug'))
|
|
166
221
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
167
222
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -170,8 +225,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
170
225
|
}
|
|
171
226
|
return Matterbridge.instance;
|
|
172
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Call cleanup() and dispose MdnsService.
|
|
230
|
+
*
|
|
231
|
+
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
232
|
+
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
233
|
+
*
|
|
234
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
235
|
+
*/
|
|
173
236
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
174
237
|
this.log.info(`Destroy instance...`);
|
|
238
|
+
// Save server nodes to close
|
|
175
239
|
const servers = [];
|
|
176
240
|
if (this.bridgeMode === 'bridge') {
|
|
177
241
|
if (this.serverNode)
|
|
@@ -189,93 +253,132 @@ export class Matterbridge extends EventEmitter {
|
|
|
189
253
|
servers.push(device.serverNode);
|
|
190
254
|
}
|
|
191
255
|
}
|
|
256
|
+
// Let any already‐queued microtasks run first
|
|
192
257
|
await Promise.resolve();
|
|
258
|
+
// Wait for the cleanup to finish
|
|
193
259
|
await wait(pause, 'destroyInstance start', true);
|
|
260
|
+
// Cleanup
|
|
194
261
|
await this.cleanup('destroying instance...', false, timeout);
|
|
262
|
+
// Close servers mdns service
|
|
195
263
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
196
264
|
for (const server of servers) {
|
|
197
265
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
198
266
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
199
267
|
}
|
|
268
|
+
// Let any already‐queued microtasks run first
|
|
200
269
|
await Promise.resolve();
|
|
270
|
+
// Wait for the cleanup to finish
|
|
201
271
|
await wait(pause, 'destroyInstance stop', true);
|
|
202
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Initializes the Matterbridge application.
|
|
275
|
+
*
|
|
276
|
+
* @remarks
|
|
277
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
278
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
279
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
280
|
+
*
|
|
281
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
282
|
+
*/
|
|
203
283
|
async initialize() {
|
|
284
|
+
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
285
|
+
// Emit the initialize_started event
|
|
204
286
|
this.emit('initialize_started');
|
|
287
|
+
// Set the restart mode
|
|
205
288
|
if (hasParameter('service'))
|
|
206
289
|
this.restartMode = 'service';
|
|
207
290
|
if (hasParameter('docker'))
|
|
208
291
|
this.restartMode = 'docker';
|
|
292
|
+
// Set the matterbridge home directory
|
|
209
293
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
210
294
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
211
295
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
296
|
+
// Set the matterbridge directory
|
|
212
297
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
213
298
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
214
299
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
215
300
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
216
301
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
302
|
+
// Set the matterbridge plugin directory
|
|
217
303
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
218
304
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
219
305
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
306
|
+
// Set the matterbridge cert directory
|
|
220
307
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
221
308
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
222
309
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
310
|
+
// Set the matterbridge root directory
|
|
223
311
|
const { fileURLToPath } = await import('node:url');
|
|
224
312
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
225
313
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
226
314
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
315
|
+
// Setup the matter environment
|
|
227
316
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
228
317
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
229
|
-
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory,
|
|
318
|
+
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
230
319
|
this.environment.vars.set('runtime.signals', false);
|
|
231
320
|
this.environment.vars.set('runtime.exitcode', false);
|
|
321
|
+
// Register process handlers
|
|
232
322
|
this.registerProcessHandlers();
|
|
323
|
+
// Initialize nodeStorage and nodeContext
|
|
233
324
|
try {
|
|
234
|
-
this.log.debug(`Creating node storage manager: ${CYAN}${
|
|
235
|
-
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory,
|
|
325
|
+
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
326
|
+
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
236
327
|
this.log.debug('Creating node storage context for matterbridge');
|
|
237
328
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
329
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
330
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
331
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
239
332
|
for (const key of keys) {
|
|
240
333
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
241
335
|
await this.nodeStorage?.storage.get(key);
|
|
242
336
|
}
|
|
243
337
|
const storages = await this.nodeStorage.getStorageNames();
|
|
244
338
|
for (const storage of storages) {
|
|
245
339
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
246
340
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
341
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
342
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
343
|
const keys = (await nodeContext?.storage.keys());
|
|
248
344
|
keys.forEach(async (key) => {
|
|
249
345
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
250
346
|
await nodeContext?.get(key);
|
|
251
347
|
});
|
|
252
348
|
}
|
|
349
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
253
350
|
this.log.debug('Creating node storage backup...');
|
|
254
|
-
await copyDirectory(path.join(this.matterbridgeDirectory,
|
|
351
|
+
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
255
352
|
this.log.debug('Created node storage backup');
|
|
256
353
|
}
|
|
257
354
|
catch (error) {
|
|
355
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
258
356
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
259
357
|
if (hasParameter('norestore')) {
|
|
260
358
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
261
359
|
}
|
|
262
360
|
else {
|
|
263
361
|
this.log.notice(`The matterbridge storage is corrupted. Restoring it with backup...`);
|
|
264
|
-
await copyDirectory(path.join(this.matterbridgeDirectory,
|
|
362
|
+
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
265
363
|
this.log.notice(`The matterbridge storage has been restored with backup`);
|
|
266
364
|
}
|
|
267
365
|
}
|
|
268
366
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
269
367
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
270
368
|
}
|
|
369
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
271
370
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
371
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
272
372
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
373
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
273
374
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
375
|
+
// Certificate management
|
|
274
376
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
275
377
|
try {
|
|
276
378
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
277
379
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
278
380
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
381
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
279
382
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
280
383
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
281
384
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -304,11 +407,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
304
407
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
305
408
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
306
409
|
}
|
|
410
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
307
411
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
308
412
|
this.passcode = pairingFileJson.passcode;
|
|
309
413
|
this.discriminator = pairingFileJson.discriminator;
|
|
310
414
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
311
415
|
}
|
|
416
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
312
417
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
313
418
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
314
419
|
this.certification = {
|
|
@@ -323,49 +428,53 @@ export class Matterbridge extends EventEmitter {
|
|
|
323
428
|
catch (error) {
|
|
324
429
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
325
430
|
}
|
|
431
|
+
// Store the passcode, discriminator and port in the node context
|
|
326
432
|
await this.nodeContext.set('matterport', this.port);
|
|
327
433
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
328
434
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
329
435
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
436
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
330
437
|
if (hasParameter('logger')) {
|
|
331
438
|
const level = getParameter('logger');
|
|
332
439
|
if (level === 'debug') {
|
|
333
|
-
this.log.logLevel = "debug"
|
|
440
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
334
441
|
}
|
|
335
442
|
else if (level === 'info') {
|
|
336
|
-
this.log.logLevel = "info"
|
|
443
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
337
444
|
}
|
|
338
445
|
else if (level === 'notice') {
|
|
339
|
-
this.log.logLevel = "notice"
|
|
446
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
340
447
|
}
|
|
341
448
|
else if (level === 'warn') {
|
|
342
|
-
this.log.logLevel = "warn"
|
|
449
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
343
450
|
}
|
|
344
451
|
else if (level === 'error') {
|
|
345
|
-
this.log.logLevel = "error"
|
|
452
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
346
453
|
}
|
|
347
454
|
else if (level === 'fatal') {
|
|
348
|
-
this.log.logLevel = "fatal"
|
|
455
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
349
456
|
}
|
|
350
457
|
else {
|
|
351
458
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
352
|
-
this.log.logLevel = "info"
|
|
459
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
353
460
|
}
|
|
354
461
|
}
|
|
355
462
|
else {
|
|
356
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
463
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
357
464
|
}
|
|
358
465
|
this.frontend.logLevel = this.log.logLevel;
|
|
359
466
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
360
467
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
468
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
361
469
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
362
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory,
|
|
470
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
363
471
|
this.matterbridgeInformation.fileLogger = true;
|
|
364
472
|
}
|
|
365
473
|
this.log.notice('Matterbridge is starting...');
|
|
366
474
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
367
475
|
if (this.profile !== undefined)
|
|
368
476
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
477
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
369
478
|
if (hasParameter('matterlogger')) {
|
|
370
479
|
const level = getParameter('matterlogger');
|
|
371
480
|
if (level === 'debug') {
|
|
@@ -395,12 +504,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
395
504
|
Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
396
505
|
}
|
|
397
506
|
Logger.format = MatterLogFormat.ANSI;
|
|
507
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
398
508
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
399
509
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
400
510
|
}
|
|
401
511
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
|
|
402
512
|
this.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
403
513
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
514
|
+
// Log network interfaces
|
|
404
515
|
const networkInterfaces = os.networkInterfaces();
|
|
405
516
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
406
517
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -413,6 +524,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
413
524
|
});
|
|
414
525
|
}
|
|
415
526
|
}
|
|
527
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
416
528
|
if (hasParameter('mdnsinterface')) {
|
|
417
529
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
418
530
|
}
|
|
@@ -421,6 +533,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
421
533
|
if (this.mdnsInterface === '')
|
|
422
534
|
this.mdnsInterface = undefined;
|
|
423
535
|
}
|
|
536
|
+
// Validate mdnsInterface
|
|
424
537
|
if (this.mdnsInterface) {
|
|
425
538
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
426
539
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -433,6 +546,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
433
546
|
}
|
|
434
547
|
if (this.mdnsInterface)
|
|
435
548
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
549
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
436
550
|
if (hasParameter('ipv4address')) {
|
|
437
551
|
this.ipv4address = getParameter('ipv4address');
|
|
438
552
|
}
|
|
@@ -441,6 +555,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
441
555
|
if (this.ipv4address === '')
|
|
442
556
|
this.ipv4address = undefined;
|
|
443
557
|
}
|
|
558
|
+
// Validate ipv4address
|
|
444
559
|
if (this.ipv4address) {
|
|
445
560
|
let isValid = false;
|
|
446
561
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -456,6 +571,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
456
571
|
await this.nodeContext.remove('matteripv4address');
|
|
457
572
|
}
|
|
458
573
|
}
|
|
574
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
459
575
|
if (hasParameter('ipv6address')) {
|
|
460
576
|
this.ipv6address = getParameter('ipv6address');
|
|
461
577
|
}
|
|
@@ -464,6 +580,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
464
580
|
if (this.ipv6address === '')
|
|
465
581
|
this.ipv6address = undefined;
|
|
466
582
|
}
|
|
583
|
+
// Validate ipv6address
|
|
467
584
|
if (this.ipv6address) {
|
|
468
585
|
let isValid = false;
|
|
469
586
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -472,6 +589,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
472
589
|
isValid = true;
|
|
473
590
|
break;
|
|
474
591
|
}
|
|
592
|
+
/* istanbul ignore next */
|
|
475
593
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
476
594
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
477
595
|
isValid = true;
|
|
@@ -484,6 +602,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
484
602
|
await this.nodeContext.remove('matteripv6address');
|
|
485
603
|
}
|
|
486
604
|
}
|
|
605
|
+
// Initialize the virtual mode
|
|
487
606
|
if (hasParameter('novirtual')) {
|
|
488
607
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
489
608
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -492,12 +611,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
492
611
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
493
612
|
}
|
|
494
613
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
614
|
+
// Initialize PluginManager
|
|
495
615
|
this.plugins.logLevel = this.log.logLevel;
|
|
496
616
|
await this.plugins.loadFromStorage();
|
|
617
|
+
// Initialize DeviceManager
|
|
497
618
|
this.devices.logLevel = this.log.logLevel;
|
|
619
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
498
620
|
for (const plugin of this.plugins) {
|
|
499
621
|
const packageJson = await this.plugins.parse(plugin);
|
|
500
622
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
623
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
624
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
501
625
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
502
626
|
try {
|
|
503
627
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -520,6 +644,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
520
644
|
await plugin.nodeContext.set('description', plugin.description);
|
|
521
645
|
await plugin.nodeContext.set('author', plugin.author);
|
|
522
646
|
}
|
|
647
|
+
// Log system info and create .matterbridge directory
|
|
523
648
|
await this.logNodeAndSystemInfo();
|
|
524
649
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
525
650
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -527,6 +652,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
527
652
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
528
653
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
529
654
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
655
|
+
// Check node version and throw error
|
|
530
656
|
const minNodeVersion = 18;
|
|
531
657
|
const nodeVersion = process.versions.node;
|
|
532
658
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -534,10 +660,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
534
660
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
535
661
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
536
662
|
}
|
|
663
|
+
// Parse command line
|
|
537
664
|
await this.parseCommandLine();
|
|
665
|
+
// Emit the initialize_completed event
|
|
538
666
|
this.emit('initialize_completed');
|
|
539
667
|
this.initialized = true;
|
|
540
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
671
|
+
*
|
|
672
|
+
* @private
|
|
673
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
674
|
+
*/
|
|
541
675
|
async parseCommandLine() {
|
|
542
676
|
if (hasParameter('help')) {
|
|
543
677
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -599,6 +733,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
599
733
|
}
|
|
600
734
|
index++;
|
|
601
735
|
}
|
|
736
|
+
/*
|
|
737
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
738
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
739
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
740
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
741
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
742
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
743
|
+
} else {
|
|
744
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
745
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
*/
|
|
602
749
|
this.shutdown = true;
|
|
603
750
|
return;
|
|
604
751
|
}
|
|
@@ -648,8 +795,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
648
795
|
this.shutdown = true;
|
|
649
796
|
return;
|
|
650
797
|
}
|
|
798
|
+
// Initialize frontend
|
|
651
799
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
652
800
|
await this.frontend.start(getIntParameter('frontend'));
|
|
801
|
+
// Start the matter storage and create the matterbridge context
|
|
653
802
|
try {
|
|
654
803
|
await this.startMatterStorage();
|
|
655
804
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -665,18 +814,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
665
814
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
666
815
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
667
816
|
}
|
|
817
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
668
818
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
669
819
|
this.initialized = true;
|
|
670
820
|
await this.shutdownProcessAndReset();
|
|
671
821
|
this.shutdown = true;
|
|
672
822
|
return;
|
|
673
823
|
}
|
|
824
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
674
825
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
675
826
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
676
827
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
677
828
|
if (plugin) {
|
|
678
829
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
679
830
|
if (!matterStorageManager) {
|
|
831
|
+
/* istanbul ignore next */
|
|
680
832
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
681
833
|
}
|
|
682
834
|
else {
|
|
@@ -695,35 +847,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
695
847
|
this.shutdown = true;
|
|
696
848
|
return;
|
|
697
849
|
}
|
|
850
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
698
851
|
clearTimeout(this.checkUpdateTimeout);
|
|
699
852
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
700
853
|
const { checkUpdates } = await import('./update.js');
|
|
701
854
|
checkUpdates(this);
|
|
702
855
|
}, 30 * 1000).unref();
|
|
856
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
703
857
|
clearInterval(this.checkUpdateInterval);
|
|
704
858
|
this.checkUpdateInterval = setInterval(async () => {
|
|
705
859
|
const { checkUpdates } = await import('./update.js');
|
|
706
860
|
checkUpdates(this);
|
|
707
861
|
}, 12 * 60 * 60 * 1000).unref();
|
|
862
|
+
// Start the matterbridge in mode test
|
|
708
863
|
if (hasParameter('test')) {
|
|
709
864
|
this.bridgeMode = 'bridge';
|
|
710
865
|
return;
|
|
711
866
|
}
|
|
867
|
+
// Start the matterbridge in mode controller
|
|
712
868
|
if (hasParameter('controller')) {
|
|
713
869
|
this.bridgeMode = 'controller';
|
|
714
870
|
await this.startController();
|
|
715
871
|
return;
|
|
716
872
|
}
|
|
873
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
717
874
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
718
875
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
719
876
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
720
877
|
}
|
|
878
|
+
// Start matterbridge in bridge mode
|
|
721
879
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
722
880
|
this.bridgeMode = 'bridge';
|
|
723
881
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
724
882
|
await this.startBridge();
|
|
725
883
|
return;
|
|
726
884
|
}
|
|
885
|
+
// Start matterbridge in childbridge mode
|
|
727
886
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
728
887
|
this.bridgeMode = 'childbridge';
|
|
729
888
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -731,10 +890,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
731
890
|
return;
|
|
732
891
|
}
|
|
733
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Asynchronously loads and starts the registered plugins.
|
|
895
|
+
*
|
|
896
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
897
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
898
|
+
*
|
|
899
|
+
* @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
|
|
900
|
+
* @param {boolean} [start] - If true, the method will start the plugins after loading them.
|
|
901
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
902
|
+
*/
|
|
734
903
|
async startPlugins(wait = false, start = true) {
|
|
904
|
+
// Check, load and start the plugins
|
|
735
905
|
for (const plugin of this.plugins) {
|
|
736
906
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
737
907
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
908
|
+
// Check if the plugin is available
|
|
738
909
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
739
910
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
740
911
|
plugin.enabled = false;
|
|
@@ -754,10 +925,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
754
925
|
if (wait)
|
|
755
926
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
756
927
|
else
|
|
757
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
928
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
|
|
758
929
|
}
|
|
759
930
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
760
931
|
}
|
|
932
|
+
/**
|
|
933
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
934
|
+
* - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
|
|
935
|
+
* - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
|
|
936
|
+
* - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
|
|
937
|
+
*/
|
|
761
938
|
registerProcessHandlers() {
|
|
762
939
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
763
940
|
process.removeAllListeners('uncaughtException');
|
|
@@ -784,6 +961,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
784
961
|
};
|
|
785
962
|
process.on('SIGTERM', this.sigtermHandler);
|
|
786
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
966
|
+
*/
|
|
787
967
|
deregisterProcessHandlers() {
|
|
788
968
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
789
969
|
if (this.exceptionHandler)
|
|
@@ -800,12 +980,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
800
980
|
process.off('SIGTERM', this.sigtermHandler);
|
|
801
981
|
this.sigtermHandler = undefined;
|
|
802
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Logs the node and system information.
|
|
985
|
+
*/
|
|
803
986
|
async logNodeAndSystemInfo() {
|
|
987
|
+
// IP address information
|
|
804
988
|
const networkInterfaces = os.networkInterfaces();
|
|
805
989
|
this.systemInformation.interfaceName = '';
|
|
806
990
|
this.systemInformation.ipv4Address = '';
|
|
807
991
|
this.systemInformation.ipv6Address = '';
|
|
808
992
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
993
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
809
994
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
810
995
|
continue;
|
|
811
996
|
if (!interfaceDetails) {
|
|
@@ -831,19 +1016,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
831
1016
|
break;
|
|
832
1017
|
}
|
|
833
1018
|
}
|
|
1019
|
+
// Node information
|
|
834
1020
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
835
1021
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
836
1022
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
837
1023
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1024
|
+
// Host system information
|
|
838
1025
|
this.systemInformation.hostname = os.hostname();
|
|
839
1026
|
this.systemInformation.user = os.userInfo().username;
|
|
840
|
-
this.systemInformation.osType = os.type();
|
|
841
|
-
this.systemInformation.osRelease = os.release();
|
|
842
|
-
this.systemInformation.osPlatform = os.platform();
|
|
843
|
-
this.systemInformation.osArch = os.arch();
|
|
844
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
845
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
846
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1027
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1028
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1029
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1030
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1031
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1032
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1033
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1034
|
+
// Log the system information
|
|
847
1035
|
this.log.debug('Host System Information:');
|
|
848
1036
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
849
1037
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -859,14 +1047,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
859
1047
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
860
1048
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
861
1049
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1050
|
+
// Log directories
|
|
862
1051
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
863
1052
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
864
1053
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
865
1054
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
866
1055
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1056
|
+
// Global node_modules directory
|
|
867
1057
|
if (this.nodeContext)
|
|
868
1058
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
869
1059
|
if (this.globalModulesDirectory === '') {
|
|
1060
|
+
// First run of Matterbridge so the node storage is empty
|
|
870
1061
|
this.log.debug(`Getting global node_modules directory...`);
|
|
871
1062
|
try {
|
|
872
1063
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -879,6 +1070,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
879
1070
|
}
|
|
880
1071
|
}
|
|
881
1072
|
else {
|
|
1073
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
882
1074
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
883
1075
|
try {
|
|
884
1076
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -890,58 +1082,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
890
1082
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
891
1083
|
}
|
|
892
1084
|
}
|
|
1085
|
+
// Matterbridge version
|
|
893
1086
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
894
1087
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
895
1088
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
896
1089
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
897
1091
|
if (this.nodeContext)
|
|
898
1092
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
899
1093
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
900
1095
|
if (this.nodeContext)
|
|
901
1096
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
902
1097
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1098
|
+
// Current working directory
|
|
903
1099
|
const currentDir = process.cwd();
|
|
904
1100
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1101
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
905
1102
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
906
1103
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
907
1104
|
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1107
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1108
|
+
*
|
|
1109
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1110
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1111
|
+
*/
|
|
908
1112
|
createDestinationMatterLogger(fileLogger) {
|
|
909
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1113
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
910
1114
|
if (fileLogger) {
|
|
911
|
-
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory,
|
|
1115
|
+
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
912
1116
|
}
|
|
913
1117
|
return (text, message) => {
|
|
1118
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
914
1119
|
const logger = text.slice(44, 44 + 20).trim();
|
|
915
1120
|
const msg = text.slice(65);
|
|
916
1121
|
this.matterLog.logName = logger;
|
|
917
1122
|
switch (message.level) {
|
|
918
1123
|
case MatterLogLevel.DEBUG:
|
|
919
|
-
this.matterLog.log("debug"
|
|
1124
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
920
1125
|
break;
|
|
921
1126
|
case MatterLogLevel.INFO:
|
|
922
|
-
this.matterLog.log("info"
|
|
1127
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
923
1128
|
break;
|
|
924
1129
|
case MatterLogLevel.NOTICE:
|
|
925
|
-
this.matterLog.log("notice"
|
|
1130
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
926
1131
|
break;
|
|
927
1132
|
case MatterLogLevel.WARN:
|
|
928
|
-
this.matterLog.log("warn"
|
|
1133
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
929
1134
|
break;
|
|
930
1135
|
case MatterLogLevel.ERROR:
|
|
931
|
-
this.matterLog.log("error"
|
|
1136
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
932
1137
|
break;
|
|
933
1138
|
case MatterLogLevel.FATAL:
|
|
934
|
-
this.matterLog.log("fatal"
|
|
1139
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
935
1140
|
break;
|
|
936
1141
|
}
|
|
937
1142
|
};
|
|
938
1143
|
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1146
|
+
*
|
|
1147
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1148
|
+
*/
|
|
939
1149
|
async restartProcess() {
|
|
940
1150
|
await this.cleanup('restarting...', true);
|
|
941
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Shut down the process (/api/shutdown).
|
|
1154
|
+
*
|
|
1155
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1156
|
+
*/
|
|
942
1157
|
async shutdownProcess() {
|
|
943
1158
|
await this.cleanup('shutting down...', false);
|
|
944
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1162
|
+
*
|
|
1163
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1164
|
+
*/
|
|
945
1165
|
async updateProcess() {
|
|
946
1166
|
this.log.info('Updating matterbridge...');
|
|
947
1167
|
try {
|
|
@@ -955,6 +1175,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
955
1175
|
this.frontend.wssSendRestartRequired();
|
|
956
1176
|
await this.cleanup('updating...', false);
|
|
957
1177
|
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1180
|
+
*
|
|
1181
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1182
|
+
*
|
|
1183
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1184
|
+
*/
|
|
958
1185
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
959
1186
|
this.log.info('Unregistering all devices and shutting down...');
|
|
960
1187
|
for (const plugin of this.plugins.array()) {
|
|
@@ -966,46 +1193,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
966
1193
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
967
1194
|
}
|
|
968
1195
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
969
|
-
await wait(timeout);
|
|
1196
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
970
1197
|
this.log.debug('Cleaning up and shutting down...');
|
|
971
1198
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
972
1199
|
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1202
|
+
*
|
|
1203
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1204
|
+
*/
|
|
973
1205
|
async shutdownProcessAndReset() {
|
|
974
1206
|
await this.cleanup('shutting down with reset...', false);
|
|
975
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1210
|
+
*
|
|
1211
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1212
|
+
*/
|
|
976
1213
|
async shutdownProcessAndFactoryReset() {
|
|
977
1214
|
await this.cleanup('shutting down with factory reset...', false);
|
|
978
1215
|
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Cleans up the Matterbridge instance.
|
|
1218
|
+
*
|
|
1219
|
+
* @param {string} message - The cleanup message.
|
|
1220
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1221
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1222
|
+
*
|
|
1223
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1224
|
+
*/
|
|
979
1225
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
980
1226
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
981
1227
|
this.emit('cleanup_started');
|
|
982
1228
|
this.hasCleanupStarted = true;
|
|
983
1229
|
this.log.info(message);
|
|
1230
|
+
// Clear the start matter interval
|
|
984
1231
|
if (this.startMatterInterval) {
|
|
985
1232
|
clearInterval(this.startMatterInterval);
|
|
986
1233
|
this.startMatterInterval = undefined;
|
|
987
1234
|
this.log.debug('Start matter interval cleared');
|
|
988
1235
|
}
|
|
1236
|
+
// Clear the check update timeout
|
|
989
1237
|
if (this.checkUpdateTimeout) {
|
|
990
1238
|
clearTimeout(this.checkUpdateTimeout);
|
|
991
1239
|
this.checkUpdateTimeout = undefined;
|
|
992
1240
|
this.log.debug('Check update timeout cleared');
|
|
993
1241
|
}
|
|
1242
|
+
// Clear the check update interval
|
|
994
1243
|
if (this.checkUpdateInterval) {
|
|
995
1244
|
clearInterval(this.checkUpdateInterval);
|
|
996
1245
|
this.checkUpdateInterval = undefined;
|
|
997
1246
|
this.log.debug('Check update interval cleared');
|
|
998
1247
|
}
|
|
1248
|
+
// Clear the configure timeout
|
|
999
1249
|
if (this.configureTimeout) {
|
|
1000
1250
|
clearTimeout(this.configureTimeout);
|
|
1001
1251
|
this.configureTimeout = undefined;
|
|
1002
1252
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1003
1253
|
}
|
|
1254
|
+
// Clear the reachability timeout
|
|
1004
1255
|
if (this.reachabilityTimeout) {
|
|
1005
1256
|
clearTimeout(this.reachabilityTimeout);
|
|
1006
1257
|
this.reachabilityTimeout = undefined;
|
|
1007
1258
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1008
1259
|
}
|
|
1260
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1009
1261
|
for (const plugin of this.plugins) {
|
|
1010
1262
|
if (!plugin.enabled || plugin.error)
|
|
1011
1263
|
continue;
|
|
@@ -1016,6 +1268,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1016
1268
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1017
1269
|
}
|
|
1018
1270
|
}
|
|
1271
|
+
// Stop matter server nodes
|
|
1019
1272
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1020
1273
|
if (timeout > 0) {
|
|
1021
1274
|
this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
|
|
@@ -1042,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1042
1295
|
}
|
|
1043
1296
|
}
|
|
1044
1297
|
this.log.notice('Stopped matter server nodes');
|
|
1298
|
+
// Matter commisioning reset
|
|
1045
1299
|
if (message === 'shutting down with reset...') {
|
|
1046
1300
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1047
1301
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1051,6 +1305,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1051
1305
|
await this.matterbridgeContext?.clearAll();
|
|
1052
1306
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1053
1307
|
}
|
|
1308
|
+
// Unregister all devices
|
|
1054
1309
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1055
1310
|
if (this.bridgeMode === 'bridge') {
|
|
1056
1311
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1068,12 +1323,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
1068
1323
|
}
|
|
1069
1324
|
this.log.info('Matter storage reset done!');
|
|
1070
1325
|
}
|
|
1326
|
+
// Stop matter storage
|
|
1071
1327
|
await this.stopMatterStorage();
|
|
1328
|
+
// Stop the frontend
|
|
1072
1329
|
await this.frontend.stop();
|
|
1330
|
+
// Close the matterbridge node storage and context
|
|
1073
1331
|
if (this.nodeStorage && this.nodeContext) {
|
|
1332
|
+
/*
|
|
1333
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1334
|
+
this.log.info('Saving registered devices...');
|
|
1335
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1336
|
+
this.devices.forEach(async (device) => {
|
|
1337
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1338
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1339
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1340
|
+
});
|
|
1341
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1342
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1343
|
+
*/
|
|
1344
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1074
1345
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1075
1346
|
await this.nodeContext.close();
|
|
1076
1347
|
this.nodeContext = undefined;
|
|
1348
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1077
1349
|
for (const plugin of this.plugins) {
|
|
1078
1350
|
if (plugin.nodeContext) {
|
|
1079
1351
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1090,41 +1362,47 @@ export class Matterbridge extends EventEmitter {
|
|
|
1090
1362
|
}
|
|
1091
1363
|
this.plugins.clear();
|
|
1092
1364
|
this.devices.clear();
|
|
1365
|
+
// Factory reset
|
|
1093
1366
|
if (message === 'shutting down with factory reset...') {
|
|
1094
1367
|
try {
|
|
1095
|
-
|
|
1368
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1369
|
+
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1096
1370
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1097
1371
|
await fs.rm(dir, { recursive: true });
|
|
1098
|
-
const backup = path.join(this.matterbridgeDirectory,
|
|
1372
|
+
const backup = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup');
|
|
1099
1373
|
this.log.info(`Removing matter storage backup directory: ${backup}`);
|
|
1100
1374
|
await fs.rm(backup, { recursive: true });
|
|
1101
1375
|
}
|
|
1102
1376
|
catch (error) {
|
|
1377
|
+
// istanbul ignore next if
|
|
1103
1378
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1104
1379
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1105
1380
|
}
|
|
1106
1381
|
}
|
|
1107
1382
|
try {
|
|
1108
|
-
|
|
1383
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1384
|
+
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1109
1385
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1110
1386
|
await fs.rm(dir, { recursive: true });
|
|
1111
|
-
const backup = path.join(this.matterbridgeDirectory,
|
|
1387
|
+
const backup = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup');
|
|
1112
1388
|
this.log.info(`Removing matterbridge storage backup directory: ${backup}`);
|
|
1113
1389
|
await fs.rm(backup, { recursive: true });
|
|
1114
1390
|
}
|
|
1115
1391
|
catch (error) {
|
|
1392
|
+
// istanbul ignore next if
|
|
1116
1393
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1117
1394
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1118
1395
|
}
|
|
1119
1396
|
}
|
|
1120
1397
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1121
1398
|
}
|
|
1399
|
+
// Deregisters the process handlers
|
|
1122
1400
|
this.deregisterProcessHandlers();
|
|
1123
1401
|
if (restart) {
|
|
1124
1402
|
if (message === 'updating...') {
|
|
1125
1403
|
this.log.info('Cleanup completed. Updating...');
|
|
1126
1404
|
Matterbridge.instance = undefined;
|
|
1127
|
-
this.emit('update');
|
|
1405
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1128
1406
|
}
|
|
1129
1407
|
else if (message === 'restarting...') {
|
|
1130
1408
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1148,7 +1426,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1148
1426
|
this.log.debug('Cleanup already started...');
|
|
1149
1427
|
}
|
|
1150
1428
|
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Starts the Matterbridge in bridge mode.
|
|
1431
|
+
*
|
|
1432
|
+
* @private
|
|
1433
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1434
|
+
*/
|
|
1151
1435
|
async startBridge() {
|
|
1436
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1152
1437
|
if (!this.matterStorageManager)
|
|
1153
1438
|
throw new Error('No storage manager initialized');
|
|
1154
1439
|
if (!this.matterbridgeContext)
|
|
@@ -1187,13 +1472,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1187
1472
|
clearInterval(this.startMatterInterval);
|
|
1188
1473
|
this.startMatterInterval = undefined;
|
|
1189
1474
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1190
|
-
|
|
1475
|
+
// Start the Matter server node
|
|
1476
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1477
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1191
1478
|
for (const device of this.devices.array()) {
|
|
1192
1479
|
if (device.mode === 'server' && device.serverNode) {
|
|
1193
1480
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1194
|
-
this.startServerNode(device.serverNode);
|
|
1481
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1195
1482
|
}
|
|
1196
1483
|
}
|
|
1484
|
+
// Configure the plugins
|
|
1197
1485
|
this.configureTimeout = setTimeout(async () => {
|
|
1198
1486
|
for (const plugin of this.plugins.array()) {
|
|
1199
1487
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1211,28 +1499,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1211
1499
|
}
|
|
1212
1500
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1213
1501
|
}, 30 * 1000).unref();
|
|
1502
|
+
// Setting reachability to true
|
|
1214
1503
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1215
1504
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1216
1505
|
if (this.aggregatorNode)
|
|
1217
1506
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1218
1507
|
}, 60 * 1000).unref();
|
|
1508
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1219
1509
|
this.emit('bridge_started');
|
|
1220
1510
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1221
1511
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1222
1512
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1223
1513
|
}, this.startMatterIntervalMs);
|
|
1224
1514
|
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1517
|
+
*
|
|
1518
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1519
|
+
*
|
|
1520
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1521
|
+
*/
|
|
1225
1522
|
async startChildbridge(delay = 1000) {
|
|
1226
1523
|
if (!this.matterStorageManager)
|
|
1227
1524
|
throw new Error('No storage manager initialized');
|
|
1525
|
+
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1228
1526
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1229
1527
|
await this.startPlugins(true, false);
|
|
1528
|
+
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1230
1529
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1231
1530
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1232
1531
|
if (plugin.type === 'DynamicPlatform')
|
|
1233
1532
|
await this.createDynamicPlugin(plugin);
|
|
1234
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1533
|
+
this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
|
|
1235
1534
|
}
|
|
1535
|
+
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1236
1536
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1237
1537
|
let failCount = 0;
|
|
1238
1538
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1266,8 +1566,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1266
1566
|
clearInterval(this.startMatterInterval);
|
|
1267
1567
|
this.startMatterInterval = undefined;
|
|
1268
1568
|
if (delay > 0)
|
|
1269
|
-
await wait(delay);
|
|
1569
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1270
1570
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1571
|
+
// Configure the plugins
|
|
1271
1572
|
this.configureTimeout = setTimeout(async () => {
|
|
1272
1573
|
for (const plugin of this.plugins.array()) {
|
|
1273
1574
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1292,6 +1593,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1292
1593
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1293
1594
|
continue;
|
|
1294
1595
|
}
|
|
1596
|
+
// istanbul ignore next if cause is just a safety check
|
|
1295
1597
|
if (!plugin.serverNode) {
|
|
1296
1598
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1297
1599
|
continue;
|
|
@@ -1304,28 +1606,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1304
1606
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1305
1607
|
continue;
|
|
1306
1608
|
}
|
|
1307
|
-
|
|
1609
|
+
// Start the Matter server node
|
|
1610
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1611
|
+
// Setting reachability to true
|
|
1308
1612
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1309
1613
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1310
1614
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1311
1615
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1312
1616
|
}, 60 * 1000).unref();
|
|
1313
1617
|
}
|
|
1618
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1314
1619
|
for (const device of this.devices.array()) {
|
|
1315
1620
|
if (device.mode === 'server' && device.serverNode) {
|
|
1316
1621
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1317
|
-
this.startServerNode(device.serverNode);
|
|
1622
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1318
1623
|
}
|
|
1319
1624
|
}
|
|
1625
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1320
1626
|
this.emit('childbridge_started');
|
|
1321
1627
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1322
1628
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1323
1629
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1324
1630
|
}, this.startMatterIntervalMs);
|
|
1325
1631
|
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Starts the Matterbridge controller.
|
|
1634
|
+
*
|
|
1635
|
+
* @private
|
|
1636
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1637
|
+
*/
|
|
1326
1638
|
async startController() {
|
|
1639
|
+
/*
|
|
1640
|
+
if (!this.matterStorageManager) {
|
|
1641
|
+
this.log.error('No storage manager initialized');
|
|
1642
|
+
await this.cleanup('No storage manager initialized');
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1646
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1647
|
+
if (!this.controllerContext) {
|
|
1648
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1649
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1654
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1655
|
+
this.log.info('Creating matter commissioning controller');
|
|
1656
|
+
this.commissioningController = new CommissioningController({
|
|
1657
|
+
autoConnect: false,
|
|
1658
|
+
});
|
|
1659
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1660
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1661
|
+
|
|
1662
|
+
this.log.info('Starting matter server');
|
|
1663
|
+
await this.matterServer.start();
|
|
1664
|
+
this.log.info('Matter server started');
|
|
1665
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1666
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1667
|
+
regulatoryCountryCode: 'XX',
|
|
1668
|
+
};
|
|
1669
|
+
const commissioningController = new CommissioningController({
|
|
1670
|
+
environment: {
|
|
1671
|
+
environment,
|
|
1672
|
+
id: uniqueId,
|
|
1673
|
+
},
|
|
1674
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1675
|
+
adminFabricLabel,
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
if (hasParameter('pairingcode')) {
|
|
1679
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1680
|
+
const pairingCode = getParameter('pairingcode');
|
|
1681
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1682
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1683
|
+
|
|
1684
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1685
|
+
if (pairingCode !== undefined) {
|
|
1686
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1687
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1688
|
+
longDiscriminator = undefined;
|
|
1689
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1690
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1691
|
+
} else {
|
|
1692
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1693
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1694
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1695
|
+
}
|
|
1696
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1697
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
const options = {
|
|
1701
|
+
commissioning: commissioningOptions,
|
|
1702
|
+
discovery: {
|
|
1703
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1704
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1705
|
+
},
|
|
1706
|
+
passcode: setupPin,
|
|
1707
|
+
} as NodeCommissioningOptions;
|
|
1708
|
+
this.log.info('Commissioning with options:', options);
|
|
1709
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1710
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1711
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1712
|
+
} // (hasParameter('pairingcode'))
|
|
1713
|
+
|
|
1714
|
+
if (hasParameter('unpairall')) {
|
|
1715
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1716
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1717
|
+
for (const nodeId of nodeIds) {
|
|
1718
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1719
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1720
|
+
}
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if (hasParameter('discover')) {
|
|
1725
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1726
|
+
// console.log(discover);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1730
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1735
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1736
|
+
for (const nodeId of nodeIds) {
|
|
1737
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1738
|
+
|
|
1739
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1740
|
+
autoSubscribe: false,
|
|
1741
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1742
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1743
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1744
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1745
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1746
|
+
switch (info) {
|
|
1747
|
+
case NodeStateInformation.Connected:
|
|
1748
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1749
|
+
break;
|
|
1750
|
+
case NodeStateInformation.Disconnected:
|
|
1751
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1752
|
+
break;
|
|
1753
|
+
case NodeStateInformation.Reconnecting:
|
|
1754
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1755
|
+
break;
|
|
1756
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1757
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1758
|
+
break;
|
|
1759
|
+
case NodeStateInformation.StructureChanged:
|
|
1760
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1761
|
+
break;
|
|
1762
|
+
case NodeStateInformation.Decommissioned:
|
|
1763
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1764
|
+
break;
|
|
1765
|
+
default:
|
|
1766
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1767
|
+
break;
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
node.logStructure();
|
|
1773
|
+
|
|
1774
|
+
// Get the interaction client
|
|
1775
|
+
this.log.info('Getting the interaction client');
|
|
1776
|
+
const interactionClient = await node.getInteractionClient();
|
|
1777
|
+
let cluster;
|
|
1778
|
+
let attributes;
|
|
1779
|
+
|
|
1780
|
+
// Log BasicInformationCluster
|
|
1781
|
+
cluster = BasicInformationCluster;
|
|
1782
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1783
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1784
|
+
});
|
|
1785
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1786
|
+
attributes.forEach((attribute) => {
|
|
1787
|
+
this.log.info(
|
|
1788
|
+
`- 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}`,
|
|
1789
|
+
);
|
|
1790
|
+
});
|
|
1791
|
+
|
|
1792
|
+
// Log PowerSourceCluster
|
|
1793
|
+
cluster = PowerSourceCluster;
|
|
1794
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1795
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1796
|
+
});
|
|
1797
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1798
|
+
attributes.forEach((attribute) => {
|
|
1799
|
+
this.log.info(
|
|
1800
|
+
`- 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}`,
|
|
1801
|
+
);
|
|
1802
|
+
});
|
|
1803
|
+
|
|
1804
|
+
// Log ThreadNetworkDiagnostics
|
|
1805
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1806
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1807
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1808
|
+
});
|
|
1809
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1810
|
+
attributes.forEach((attribute) => {
|
|
1811
|
+
this.log.info(
|
|
1812
|
+
`- 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}`,
|
|
1813
|
+
);
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
// Log SwitchCluster
|
|
1817
|
+
cluster = SwitchCluster;
|
|
1818
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1819
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1820
|
+
});
|
|
1821
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1822
|
+
attributes.forEach((attribute) => {
|
|
1823
|
+
this.log.info(
|
|
1824
|
+
`- 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}`,
|
|
1825
|
+
);
|
|
1826
|
+
});
|
|
1827
|
+
|
|
1828
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1829
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1830
|
+
ignoreInitialTriggers: false,
|
|
1831
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1832
|
+
this.log.info(
|
|
1833
|
+
`***${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}`,
|
|
1834
|
+
),
|
|
1835
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1836
|
+
this.log.info(
|
|
1837
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1838
|
+
);
|
|
1839
|
+
},
|
|
1840
|
+
});
|
|
1841
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1842
|
+
}
|
|
1843
|
+
*/
|
|
1327
1844
|
}
|
|
1845
|
+
/** */
|
|
1846
|
+
/** Matter.js methods */
|
|
1847
|
+
/** */
|
|
1848
|
+
/**
|
|
1849
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1850
|
+
*
|
|
1851
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1852
|
+
*/
|
|
1328
1853
|
async startMatterStorage() {
|
|
1854
|
+
// Setup Matter storage
|
|
1329
1855
|
this.log.info(`Starting matter node storage...`);
|
|
1330
1856
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1331
1857
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1333,8 +1859,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1333
1859
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1334
1860
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1335
1861
|
this.log.info('Matter node storage started');
|
|
1336
|
-
|
|
1862
|
+
// Backup matter storage since it is created/opened correctly
|
|
1863
|
+
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1337
1864
|
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1867
|
+
*
|
|
1868
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1869
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1870
|
+
* @private
|
|
1871
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1872
|
+
*/
|
|
1338
1873
|
async backupMatterStorage(storageName, backupName) {
|
|
1339
1874
|
this.log.info('Creating matter node storage backup...');
|
|
1340
1875
|
try {
|
|
@@ -1345,6 +1880,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1345
1880
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1346
1881
|
}
|
|
1347
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Stops the matter storage.
|
|
1885
|
+
*
|
|
1886
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1887
|
+
*/
|
|
1348
1888
|
async stopMatterStorage() {
|
|
1349
1889
|
this.log.info('Closing matter node storage...');
|
|
1350
1890
|
await this.matterStorageManager?.close();
|
|
@@ -1353,6 +1893,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1353
1893
|
this.matterbridgeContext = undefined;
|
|
1354
1894
|
this.log.info('Matter node storage closed');
|
|
1355
1895
|
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Creates a server node storage context.
|
|
1898
|
+
*
|
|
1899
|
+
* @param {string} storeId - The storeId.
|
|
1900
|
+
* @param {string} deviceName - The name of the device.
|
|
1901
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1902
|
+
* @param {number} vendorId - The vendor ID.
|
|
1903
|
+
* @param {string} vendorName - The vendor name.
|
|
1904
|
+
* @param {number} productId - The product ID.
|
|
1905
|
+
* @param {string} productName - The product name.
|
|
1906
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1907
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1908
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1909
|
+
*/
|
|
1356
1910
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1357
1911
|
const { randomBytes } = await import('node:crypto');
|
|
1358
1912
|
if (!this.matterStorageService)
|
|
@@ -1392,6 +1946,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1392
1946
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1393
1947
|
return storageContext;
|
|
1394
1948
|
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Creates a server node.
|
|
1951
|
+
*
|
|
1952
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1953
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
1954
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
1955
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
1956
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1957
|
+
*/
|
|
1395
1958
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1396
1959
|
const storeId = await storageContext.get('storeId');
|
|
1397
1960
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1401,24 +1964,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1401
1964
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1402
1965
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1403
1966
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1967
|
+
/**
|
|
1968
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1969
|
+
*/
|
|
1404
1970
|
const serverNode = await ServerNode.create({
|
|
1971
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1405
1972
|
id: storeId,
|
|
1973
|
+
// Provide Network relevant configuration like the port
|
|
1974
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1406
1975
|
network: {
|
|
1407
1976
|
listeningAddressIpv4: this.ipv4address,
|
|
1408
1977
|
listeningAddressIpv6: this.ipv6address,
|
|
1409
1978
|
port,
|
|
1410
1979
|
},
|
|
1980
|
+
// Provide the certificate for the device
|
|
1411
1981
|
operationalCredentials: {
|
|
1412
1982
|
certification: this.certification,
|
|
1413
1983
|
},
|
|
1984
|
+
// Provide Commissioning relevant settings
|
|
1985
|
+
// Optional for development/testing purposes
|
|
1414
1986
|
commissioning: {
|
|
1415
1987
|
passcode,
|
|
1416
1988
|
discriminator,
|
|
1417
1989
|
},
|
|
1990
|
+
// Provide Node announcement settings
|
|
1991
|
+
// Optional: If Ommitted some development defaults are used
|
|
1418
1992
|
productDescription: {
|
|
1419
1993
|
name: await storageContext.get('deviceName'),
|
|
1420
1994
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1421
1995
|
},
|
|
1996
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
+
// Optional: If Omitted some development defaults are used
|
|
1422
1998
|
basicInformation: {
|
|
1423
1999
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1424
2000
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1435,17 +2011,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1435
2011
|
reachable: true,
|
|
1436
2012
|
},
|
|
1437
2013
|
});
|
|
2014
|
+
/**
|
|
2015
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2016
|
+
* This means: It is added to the first fabric.
|
|
2017
|
+
*/
|
|
1438
2018
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1439
2019
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1440
2020
|
this.advertisingNodes.delete(storeId);
|
|
1441
2021
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1442
2022
|
});
|
|
2023
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1443
2024
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1444
2025
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1445
2026
|
this.advertisingNodes.delete(storeId);
|
|
1446
2027
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1447
2028
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1448
2029
|
});
|
|
2030
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1449
2031
|
serverNode.lifecycle.online.on(async () => {
|
|
1450
2032
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1451
2033
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1456,13 +2038,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1456
2038
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1457
2039
|
}
|
|
1458
2040
|
else {
|
|
2041
|
+
// istanbul ignore next
|
|
1459
2042
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2043
|
+
// istanbul ignore next
|
|
1460
2044
|
this.advertisingNodes.delete(storeId);
|
|
1461
2045
|
}
|
|
1462
2046
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1463
2047
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1464
2048
|
this.emit('online', storeId);
|
|
1465
2049
|
});
|
|
2050
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1466
2051
|
serverNode.lifecycle.offline.on(() => {
|
|
1467
2052
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1468
2053
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1470,11 +2055,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1470
2055
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1471
2056
|
this.emit('offline', storeId);
|
|
1472
2057
|
});
|
|
2058
|
+
/**
|
|
2059
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2060
|
+
* information is needed.
|
|
2061
|
+
*/
|
|
1473
2062
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1474
2063
|
let action = '';
|
|
1475
2064
|
switch (fabricAction) {
|
|
1476
2065
|
case FabricAction.Added:
|
|
1477
|
-
this.advertisingNodes.delete(storeId);
|
|
2066
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1478
2067
|
action = 'added';
|
|
1479
2068
|
break;
|
|
1480
2069
|
case FabricAction.Removed:
|
|
@@ -1487,14 +2076,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1487
2076
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1488
2077
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1489
2078
|
});
|
|
2079
|
+
/**
|
|
2080
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2081
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2082
|
+
*/
|
|
1490
2083
|
serverNode.events.sessions.opened.on((session) => {
|
|
1491
2084
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1492
2085
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1493
2086
|
});
|
|
2087
|
+
/**
|
|
2088
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2089
|
+
*/
|
|
1494
2090
|
serverNode.events.sessions.closed.on((session) => {
|
|
1495
2091
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1496
2092
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1497
2093
|
});
|
|
2094
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1498
2095
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1499
2096
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1500
2097
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1502,6 +2099,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1502
2099
|
this.log.info(`Created server node for ${storeId}`);
|
|
1503
2100
|
return serverNode;
|
|
1504
2101
|
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Gets the matter sanitized data of the specified server node.
|
|
2104
|
+
*
|
|
2105
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2106
|
+
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2107
|
+
*/
|
|
1505
2108
|
getServerNodeData(serverNode) {
|
|
1506
2109
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1507
2110
|
return {
|
|
@@ -1518,12 +2121,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1518
2121
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1519
2122
|
};
|
|
1520
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Starts the specified server node.
|
|
2126
|
+
*
|
|
2127
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2128
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2129
|
+
*/
|
|
1521
2130
|
async startServerNode(matterServerNode) {
|
|
1522
2131
|
if (!matterServerNode)
|
|
1523
2132
|
return;
|
|
1524
2133
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1525
2134
|
await matterServerNode.start();
|
|
1526
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Stops the specified server node.
|
|
2138
|
+
*
|
|
2139
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2140
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2141
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2142
|
+
*/
|
|
1527
2143
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1528
2144
|
if (!matterServerNode)
|
|
1529
2145
|
return;
|
|
@@ -1536,12 +2152,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1536
2152
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1537
2153
|
}
|
|
1538
2154
|
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Creates an aggregator node with the specified storage context.
|
|
2157
|
+
*
|
|
2158
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2159
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2160
|
+
*/
|
|
1539
2161
|
async createAggregatorNode(storageContext) {
|
|
1540
2162
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1541
2163
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1542
2164
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1543
2165
|
return aggregatorNode;
|
|
1544
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2169
|
+
*
|
|
2170
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
2171
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2172
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2173
|
+
*/
|
|
1545
2174
|
async createAccessoryPlugin(plugin, device) {
|
|
1546
2175
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1547
2176
|
plugin.locked = true;
|
|
@@ -1553,6 +2182,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1553
2182
|
await plugin.serverNode.add(device);
|
|
1554
2183
|
}
|
|
1555
2184
|
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2187
|
+
*
|
|
2188
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
2189
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2190
|
+
*/
|
|
1556
2191
|
async createDynamicPlugin(plugin) {
|
|
1557
2192
|
if (!plugin.locked) {
|
|
1558
2193
|
plugin.locked = true;
|
|
@@ -1565,6 +2200,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1565
2200
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1566
2201
|
}
|
|
1567
2202
|
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Creates and configures the server node for a single not bridged device.
|
|
2205
|
+
*
|
|
2206
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
2207
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2208
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2209
|
+
*/
|
|
1568
2210
|
async createDeviceServerNode(plugin, device) {
|
|
1569
2211
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1570
2212
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1575,7 +2217,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1575
2217
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1576
2218
|
}
|
|
1577
2219
|
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2222
|
+
*
|
|
2223
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2224
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2225
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2226
|
+
*/
|
|
1578
2227
|
async addBridgedEndpoint(pluginName, device) {
|
|
2228
|
+
// Check if the plugin is registered
|
|
1579
2229
|
const plugin = this.plugins.get(pluginName);
|
|
1580
2230
|
if (!plugin) {
|
|
1581
2231
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1595,6 +2245,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1595
2245
|
}
|
|
1596
2246
|
else if (this.bridgeMode === 'bridge') {
|
|
1597
2247
|
if (device.mode === 'matter') {
|
|
2248
|
+
// Register and add the device to the matterbridge server node
|
|
1598
2249
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1599
2250
|
if (!this.serverNode) {
|
|
1600
2251
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1611,6 +2262,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1611
2262
|
}
|
|
1612
2263
|
}
|
|
1613
2264
|
else {
|
|
2265
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1614
2266
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1615
2267
|
if (!this.aggregatorNode) {
|
|
1616
2268
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1628,6 +2280,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1628
2280
|
}
|
|
1629
2281
|
}
|
|
1630
2282
|
else if (this.bridgeMode === 'childbridge') {
|
|
2283
|
+
// Register and add the device to the plugin server node
|
|
1631
2284
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1632
2285
|
try {
|
|
1633
2286
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1651,10 +2304,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1651
2304
|
return;
|
|
1652
2305
|
}
|
|
1653
2306
|
}
|
|
2307
|
+
// Register and add the device to the plugin aggregator node
|
|
1654
2308
|
if (plugin.type === 'DynamicPlatform') {
|
|
1655
2309
|
try {
|
|
1656
2310
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1657
2311
|
await this.createDynamicPlugin(plugin);
|
|
2312
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1658
2313
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1659
2314
|
if (!plugin.aggregatorNode) {
|
|
1660
2315
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1675,17 +2330,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1675
2330
|
}
|
|
1676
2331
|
if (plugin.registeredDevices !== undefined)
|
|
1677
2332
|
plugin.registeredDevices++;
|
|
2333
|
+
// Add the device to the DeviceManager
|
|
1678
2334
|
this.devices.set(device);
|
|
2335
|
+
// Subscribe to the attributes changed event
|
|
1679
2336
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1680
2337
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1681
2338
|
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2341
|
+
*
|
|
2342
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2343
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2344
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2345
|
+
*/
|
|
1682
2346
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1683
2347
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2348
|
+
// Check if the plugin is registered
|
|
1684
2349
|
const plugin = this.plugins.get(pluginName);
|
|
1685
2350
|
if (!plugin) {
|
|
1686
2351
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1687
2352
|
return;
|
|
1688
2353
|
}
|
|
2354
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1689
2355
|
if (this.bridgeMode === 'bridge') {
|
|
1690
2356
|
if (!this.aggregatorNode) {
|
|
1691
2357
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1698,6 +2364,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1698
2364
|
}
|
|
1699
2365
|
else if (this.bridgeMode === 'childbridge') {
|
|
1700
2366
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2367
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1701
2368
|
}
|
|
1702
2369
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1703
2370
|
if (!plugin.aggregatorNode) {
|
|
@@ -1710,8 +2377,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1710
2377
|
if (plugin.registeredDevices !== undefined)
|
|
1711
2378
|
plugin.registeredDevices--;
|
|
1712
2379
|
}
|
|
2380
|
+
// Remove the device from the DeviceManager
|
|
1713
2381
|
this.devices.remove(device);
|
|
1714
2382
|
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2385
|
+
*
|
|
2386
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2387
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2388
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2389
|
+
*
|
|
2390
|
+
* @remarks
|
|
2391
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2392
|
+
* It also applies a delay between each removal if specified.
|
|
2393
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2394
|
+
*/
|
|
1715
2395
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1716
2396
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1717
2397
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1722,13 +2402,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1722
2402
|
if (delay > 0)
|
|
1723
2403
|
await wait(2000);
|
|
1724
2404
|
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2407
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2408
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2409
|
+
*
|
|
2410
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2411
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2412
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2413
|
+
*/
|
|
1725
2414
|
async subscribeAttributeChanged(plugin, device) {
|
|
1726
2415
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
1727
2416
|
return;
|
|
1728
2417
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2418
|
+
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
1729
2419
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1730
2420
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1731
2421
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2422
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1732
2423
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
1733
2424
|
});
|
|
1734
2425
|
}
|
|
@@ -1776,6 +2467,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1776
2467
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1777
2468
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1778
2469
|
this.log.debug(`Bridged endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
|
|
2470
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1779
2471
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
1780
2472
|
});
|
|
1781
2473
|
}
|
|
@@ -1785,6 +2477,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1785
2477
|
this.log.debug(`Subscribing to child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1786
2478
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1787
2479
|
this.log.debug(`Bridged child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
|
|
2480
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1788
2481
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1789
2482
|
});
|
|
1790
2483
|
}
|
|
@@ -1792,6 +2485,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1792
2485
|
}
|
|
1793
2486
|
}
|
|
1794
2487
|
}
|
|
2488
|
+
/**
|
|
2489
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2490
|
+
*
|
|
2491
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2492
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2493
|
+
*/
|
|
1795
2494
|
sanitizeFabricInformations(fabricInfo) {
|
|
1796
2495
|
return fabricInfo.map((info) => {
|
|
1797
2496
|
return {
|
|
@@ -1805,6 +2504,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1805
2504
|
};
|
|
1806
2505
|
});
|
|
1807
2506
|
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2509
|
+
*
|
|
2510
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2511
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2512
|
+
*/
|
|
1808
2513
|
sanitizeSessionInformation(sessions) {
|
|
1809
2514
|
return sessions
|
|
1810
2515
|
.filter((session) => session.isPeerActive)
|
|
@@ -1831,7 +2536,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1831
2536
|
};
|
|
1832
2537
|
});
|
|
1833
2538
|
}
|
|
2539
|
+
/**
|
|
2540
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2541
|
+
*
|
|
2542
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2543
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2544
|
+
*/
|
|
2545
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1834
2546
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2547
|
+
/*
|
|
2548
|
+
for (const child of aggregatorNode.parts) {
|
|
2549
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2550
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2551
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2552
|
+
}
|
|
2553
|
+
*/
|
|
1835
2554
|
}
|
|
1836
2555
|
getVendorIdName = (vendorId) => {
|
|
1837
2556
|
if (!vendorId)
|
|
@@ -1871,10 +2590,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1871
2590
|
case 0x1488:
|
|
1872
2591
|
vendorName = '(ShortcutLabsFlic)';
|
|
1873
2592
|
break;
|
|
1874
|
-
case 65521:
|
|
2593
|
+
case 65521: // 0xFFF1
|
|
1875
2594
|
vendorName = '(MatterTest)';
|
|
1876
2595
|
break;
|
|
1877
2596
|
}
|
|
1878
2597
|
return vendorName;
|
|
1879
2598
|
};
|
|
1880
2599
|
}
|
|
2600
|
+
//# sourceMappingURL=matterbridge.js.map
|