matterbridge 3.3.0-dev-20251002-bbaa166 → 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/CHANGELOG.md +3 -3
- 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 +458 -64
- 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 +831 -108
- 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,18 +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
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
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
|
+
*/
|
|
145
182
|
async setLogLevel(logLevel) {
|
|
146
183
|
if (this.log)
|
|
147
184
|
this.log.logLevel = logLevel;
|
|
@@ -155,19 +192,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
155
192
|
for (const plugin of this.plugins) {
|
|
156
193
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
157
194
|
continue;
|
|
158
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
159
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 */;
|
|
166
204
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
167
205
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
168
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
|
+
*/
|
|
169
217
|
static async loadInstance(initialize = false) {
|
|
170
218
|
if (!Matterbridge.instance) {
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
171
220
|
if (hasParameter('debug'))
|
|
172
221
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
173
222
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -176,8 +225,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
176
225
|
}
|
|
177
226
|
return Matterbridge.instance;
|
|
178
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
|
+
*/
|
|
179
236
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
180
237
|
this.log.info(`Destroy instance...`);
|
|
238
|
+
// Save server nodes to close
|
|
181
239
|
const servers = [];
|
|
182
240
|
if (this.bridgeMode === 'bridge') {
|
|
183
241
|
if (this.serverNode)
|
|
@@ -195,93 +253,132 @@ export class Matterbridge extends EventEmitter {
|
|
|
195
253
|
servers.push(device.serverNode);
|
|
196
254
|
}
|
|
197
255
|
}
|
|
256
|
+
// Let any already‐queued microtasks run first
|
|
198
257
|
await Promise.resolve();
|
|
258
|
+
// Wait for the cleanup to finish
|
|
199
259
|
await wait(pause, 'destroyInstance start', true);
|
|
260
|
+
// Cleanup
|
|
200
261
|
await this.cleanup('destroying instance...', false, timeout);
|
|
262
|
+
// Close servers mdns service
|
|
201
263
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
202
264
|
for (const server of servers) {
|
|
203
265
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
204
266
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
205
267
|
}
|
|
268
|
+
// Let any already‐queued microtasks run first
|
|
206
269
|
await Promise.resolve();
|
|
270
|
+
// Wait for the cleanup to finish
|
|
207
271
|
await wait(pause, 'destroyInstance stop', true);
|
|
208
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
|
+
*/
|
|
209
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
|
|
210
286
|
this.emit('initialize_started');
|
|
287
|
+
// Set the restart mode
|
|
211
288
|
if (hasParameter('service'))
|
|
212
289
|
this.restartMode = 'service';
|
|
213
290
|
if (hasParameter('docker'))
|
|
214
291
|
this.restartMode = 'docker';
|
|
292
|
+
// Set the matterbridge home directory
|
|
215
293
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
216
294
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
217
295
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
296
|
+
// Set the matterbridge directory
|
|
218
297
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
219
298
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
220
299
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
221
300
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
222
301
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
302
|
+
// Set the matterbridge plugin directory
|
|
223
303
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
224
304
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
225
305
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
306
|
+
// Set the matterbridge cert directory
|
|
226
307
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
227
308
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
228
309
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
310
|
+
// Set the matterbridge root directory
|
|
229
311
|
const { fileURLToPath } = await import('node:url');
|
|
230
312
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
231
313
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
232
314
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
315
|
+
// Setup the matter environment
|
|
233
316
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
234
317
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
235
|
-
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory,
|
|
318
|
+
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
236
319
|
this.environment.vars.set('runtime.signals', false);
|
|
237
320
|
this.environment.vars.set('runtime.exitcode', false);
|
|
321
|
+
// Register process handlers
|
|
238
322
|
this.registerProcessHandlers();
|
|
323
|
+
// Initialize nodeStorage and nodeContext
|
|
239
324
|
try {
|
|
240
|
-
this.log.debug(`Creating node storage manager: ${CYAN}${
|
|
241
|
-
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 });
|
|
242
327
|
this.log.debug('Creating node storage context for matterbridge');
|
|
243
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
|
|
244
331
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
245
332
|
for (const key of keys) {
|
|
246
333
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
335
|
await this.nodeStorage?.storage.get(key);
|
|
248
336
|
}
|
|
249
337
|
const storages = await this.nodeStorage.getStorageNames();
|
|
250
338
|
for (const storage of storages) {
|
|
251
339
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
252
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
|
|
253
343
|
const keys = (await nodeContext?.storage.keys());
|
|
254
344
|
keys.forEach(async (key) => {
|
|
255
345
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
256
346
|
await nodeContext?.get(key);
|
|
257
347
|
});
|
|
258
348
|
}
|
|
349
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
259
350
|
this.log.debug('Creating node storage backup...');
|
|
260
|
-
await copyDirectory(path.join(this.matterbridgeDirectory,
|
|
351
|
+
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
261
352
|
this.log.debug('Created node storage backup');
|
|
262
353
|
}
|
|
263
354
|
catch (error) {
|
|
355
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
264
356
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
265
357
|
if (hasParameter('norestore')) {
|
|
266
358
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
267
359
|
}
|
|
268
360
|
else {
|
|
269
361
|
this.log.notice(`The matterbridge storage is corrupted. Restoring it with backup...`);
|
|
270
|
-
await copyDirectory(path.join(this.matterbridgeDirectory,
|
|
362
|
+
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR));
|
|
271
363
|
this.log.notice(`The matterbridge storage has been restored with backup`);
|
|
272
364
|
}
|
|
273
365
|
}
|
|
274
366
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
275
367
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
276
368
|
}
|
|
369
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
277
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)
|
|
278
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)
|
|
279
374
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
375
|
+
// Certificate management
|
|
280
376
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
281
377
|
try {
|
|
282
378
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
283
379
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
284
380
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
381
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
285
382
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
286
383
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
287
384
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -310,11 +407,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
310
407
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
311
408
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
312
409
|
}
|
|
410
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
313
411
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
314
412
|
this.passcode = pairingFileJson.passcode;
|
|
315
413
|
this.discriminator = pairingFileJson.discriminator;
|
|
316
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.`);
|
|
317
415
|
}
|
|
416
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
318
417
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
319
418
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
320
419
|
this.certification = {
|
|
@@ -329,49 +428,53 @@ export class Matterbridge extends EventEmitter {
|
|
|
329
428
|
catch (error) {
|
|
330
429
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
331
430
|
}
|
|
431
|
+
// Store the passcode, discriminator and port in the node context
|
|
332
432
|
await this.nodeContext.set('matterport', this.port);
|
|
333
433
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
334
434
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
335
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)
|
|
336
437
|
if (hasParameter('logger')) {
|
|
337
438
|
const level = getParameter('logger');
|
|
338
439
|
if (level === 'debug') {
|
|
339
|
-
this.log.logLevel = "debug"
|
|
440
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
340
441
|
}
|
|
341
442
|
else if (level === 'info') {
|
|
342
|
-
this.log.logLevel = "info"
|
|
443
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
343
444
|
}
|
|
344
445
|
else if (level === 'notice') {
|
|
345
|
-
this.log.logLevel = "notice"
|
|
446
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
346
447
|
}
|
|
347
448
|
else if (level === 'warn') {
|
|
348
|
-
this.log.logLevel = "warn"
|
|
449
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
349
450
|
}
|
|
350
451
|
else if (level === 'error') {
|
|
351
|
-
this.log.logLevel = "error"
|
|
452
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
352
453
|
}
|
|
353
454
|
else if (level === 'fatal') {
|
|
354
|
-
this.log.logLevel = "fatal"
|
|
455
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
355
456
|
}
|
|
356
457
|
else {
|
|
357
458
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
358
|
-
this.log.logLevel = "info"
|
|
459
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
359
460
|
}
|
|
360
461
|
}
|
|
361
462
|
else {
|
|
362
|
-
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 */);
|
|
363
464
|
}
|
|
364
465
|
this.frontend.logLevel = this.log.logLevel;
|
|
365
466
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
366
467
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
468
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
367
469
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
368
|
-
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory,
|
|
470
|
+
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
369
471
|
this.matterbridgeInformation.fileLogger = true;
|
|
370
472
|
}
|
|
371
473
|
this.log.notice('Matterbridge is starting...');
|
|
372
474
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
373
475
|
if (this.profile !== undefined)
|
|
374
476
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
477
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
375
478
|
if (hasParameter('matterlogger')) {
|
|
376
479
|
const level = getParameter('matterlogger');
|
|
377
480
|
if (level === 'debug') {
|
|
@@ -401,12 +504,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
401
504
|
Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
402
505
|
}
|
|
403
506
|
Logger.format = MatterLogFormat.ANSI;
|
|
507
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
404
508
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
405
509
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
406
510
|
}
|
|
407
511
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
|
|
408
512
|
this.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
409
513
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
514
|
+
// Log network interfaces
|
|
410
515
|
const networkInterfaces = os.networkInterfaces();
|
|
411
516
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
412
517
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -419,6 +524,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
419
524
|
});
|
|
420
525
|
}
|
|
421
526
|
}
|
|
527
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
422
528
|
if (hasParameter('mdnsinterface')) {
|
|
423
529
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
424
530
|
}
|
|
@@ -427,6 +533,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
427
533
|
if (this.mdnsInterface === '')
|
|
428
534
|
this.mdnsInterface = undefined;
|
|
429
535
|
}
|
|
536
|
+
// Validate mdnsInterface
|
|
430
537
|
if (this.mdnsInterface) {
|
|
431
538
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
432
539
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -439,6 +546,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
439
546
|
}
|
|
440
547
|
if (this.mdnsInterface)
|
|
441
548
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
549
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
442
550
|
if (hasParameter('ipv4address')) {
|
|
443
551
|
this.ipv4address = getParameter('ipv4address');
|
|
444
552
|
}
|
|
@@ -447,6 +555,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
447
555
|
if (this.ipv4address === '')
|
|
448
556
|
this.ipv4address = undefined;
|
|
449
557
|
}
|
|
558
|
+
// Validate ipv4address
|
|
450
559
|
if (this.ipv4address) {
|
|
451
560
|
let isValid = false;
|
|
452
561
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -462,6 +571,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
462
571
|
await this.nodeContext.remove('matteripv4address');
|
|
463
572
|
}
|
|
464
573
|
}
|
|
574
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
465
575
|
if (hasParameter('ipv6address')) {
|
|
466
576
|
this.ipv6address = getParameter('ipv6address');
|
|
467
577
|
}
|
|
@@ -470,6 +580,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
470
580
|
if (this.ipv6address === '')
|
|
471
581
|
this.ipv6address = undefined;
|
|
472
582
|
}
|
|
583
|
+
// Validate ipv6address
|
|
473
584
|
if (this.ipv6address) {
|
|
474
585
|
let isValid = false;
|
|
475
586
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -478,6 +589,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
478
589
|
isValid = true;
|
|
479
590
|
break;
|
|
480
591
|
}
|
|
592
|
+
/* istanbul ignore next */
|
|
481
593
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
482
594
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
483
595
|
isValid = true;
|
|
@@ -490,6 +602,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
490
602
|
await this.nodeContext.remove('matteripv6address');
|
|
491
603
|
}
|
|
492
604
|
}
|
|
605
|
+
// Initialize the virtual mode
|
|
493
606
|
if (hasParameter('novirtual')) {
|
|
494
607
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
495
608
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -498,12 +611,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
498
611
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
499
612
|
}
|
|
500
613
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
614
|
+
// Initialize PluginManager
|
|
501
615
|
this.plugins.logLevel = this.log.logLevel;
|
|
502
616
|
await this.plugins.loadFromStorage();
|
|
617
|
+
// Initialize DeviceManager
|
|
503
618
|
this.devices.logLevel = this.log.logLevel;
|
|
619
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
504
620
|
for (const plugin of this.plugins) {
|
|
505
621
|
const packageJson = await this.plugins.parse(plugin);
|
|
506
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
|
|
507
625
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
508
626
|
try {
|
|
509
627
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -526,6 +644,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
526
644
|
await plugin.nodeContext.set('description', plugin.description);
|
|
527
645
|
await plugin.nodeContext.set('author', plugin.author);
|
|
528
646
|
}
|
|
647
|
+
// Log system info and create .matterbridge directory
|
|
529
648
|
await this.logNodeAndSystemInfo();
|
|
530
649
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
531
650
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -533,6 +652,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
533
652
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
534
653
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
535
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
|
|
536
656
|
const minNodeVersion = 18;
|
|
537
657
|
const nodeVersion = process.versions.node;
|
|
538
658
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -540,10 +660,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
540
660
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
541
661
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
542
662
|
}
|
|
663
|
+
// Parse command line
|
|
543
664
|
await this.parseCommandLine();
|
|
665
|
+
// Emit the initialize_completed event
|
|
544
666
|
this.emit('initialize_completed');
|
|
545
667
|
this.initialized = true;
|
|
546
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
|
+
*/
|
|
547
675
|
async parseCommandLine() {
|
|
548
676
|
if (hasParameter('help')) {
|
|
549
677
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -553,8 +681,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
553
681
|
--childbridge: start Matterbridge in childbridge mode
|
|
554
682
|
--port [port]: start the commissioning server on the given port (default 5540)
|
|
555
683
|
--mdnsinterface [name]: set the interface to use for the matter server mdnsInterface (default all interfaces)
|
|
556
|
-
--ipv4address [address]: set the ipv4 interface address to use for the matter listener (default all
|
|
557
|
-
--ipv6address [address]: set the ipv6 interface address to use for the matter listener (default all
|
|
684
|
+
--ipv4address [address]: set the ipv4 interface address to use for the matter listener (default all addresses)
|
|
685
|
+
--ipv6address [address]: set the ipv6 interface address to use for the matter listener (default all addresses)
|
|
558
686
|
--frontend [port]: start the frontend on the given port (default 8283)
|
|
559
687
|
--logger: set the matterbridge logger level: debug | info | notice | warn | error | fatal (default info)
|
|
560
688
|
--filelogger enable the matterbridge file logger (matterbridge.log)
|
|
@@ -605,6 +733,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
605
733
|
}
|
|
606
734
|
index++;
|
|
607
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
|
+
*/
|
|
608
749
|
this.shutdown = true;
|
|
609
750
|
return;
|
|
610
751
|
}
|
|
@@ -654,8 +795,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
654
795
|
this.shutdown = true;
|
|
655
796
|
return;
|
|
656
797
|
}
|
|
798
|
+
// Initialize frontend
|
|
657
799
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
658
800
|
await this.frontend.start(getIntParameter('frontend'));
|
|
801
|
+
// Start the matter storage and create the matterbridge context
|
|
659
802
|
try {
|
|
660
803
|
await this.startMatterStorage();
|
|
661
804
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -671,18 +814,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
671
814
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
672
815
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
673
816
|
}
|
|
817
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
674
818
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
675
819
|
this.initialized = true;
|
|
676
820
|
await this.shutdownProcessAndReset();
|
|
677
821
|
this.shutdown = true;
|
|
678
822
|
return;
|
|
679
823
|
}
|
|
824
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
680
825
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
681
826
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
682
827
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
683
828
|
if (plugin) {
|
|
684
829
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
685
830
|
if (!matterStorageManager) {
|
|
831
|
+
/* istanbul ignore next */
|
|
686
832
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
687
833
|
}
|
|
688
834
|
else {
|
|
@@ -701,35 +847,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
701
847
|
this.shutdown = true;
|
|
702
848
|
return;
|
|
703
849
|
}
|
|
850
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
704
851
|
clearTimeout(this.checkUpdateTimeout);
|
|
705
852
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
706
853
|
const { checkUpdates } = await import('./update.js');
|
|
707
854
|
checkUpdates(this);
|
|
708
855
|
}, 30 * 1000).unref();
|
|
856
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
709
857
|
clearInterval(this.checkUpdateInterval);
|
|
710
858
|
this.checkUpdateInterval = setInterval(async () => {
|
|
711
859
|
const { checkUpdates } = await import('./update.js');
|
|
712
860
|
checkUpdates(this);
|
|
713
861
|
}, 12 * 60 * 60 * 1000).unref();
|
|
862
|
+
// Start the matterbridge in mode test
|
|
714
863
|
if (hasParameter('test')) {
|
|
715
864
|
this.bridgeMode = 'bridge';
|
|
716
865
|
return;
|
|
717
866
|
}
|
|
867
|
+
// Start the matterbridge in mode controller
|
|
718
868
|
if (hasParameter('controller')) {
|
|
719
869
|
this.bridgeMode = 'controller';
|
|
720
870
|
await this.startController();
|
|
721
871
|
return;
|
|
722
872
|
}
|
|
873
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
723
874
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
724
875
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
725
876
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
726
877
|
}
|
|
878
|
+
// Start matterbridge in bridge mode
|
|
727
879
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
728
880
|
this.bridgeMode = 'bridge';
|
|
729
881
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
730
882
|
await this.startBridge();
|
|
731
883
|
return;
|
|
732
884
|
}
|
|
885
|
+
// Start matterbridge in childbridge mode
|
|
733
886
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
734
887
|
this.bridgeMode = 'childbridge';
|
|
735
888
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -737,10 +890,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
737
890
|
return;
|
|
738
891
|
}
|
|
739
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
|
+
*/
|
|
740
903
|
async startPlugins(wait = false, start = true) {
|
|
904
|
+
// Check, load and start the plugins
|
|
741
905
|
for (const plugin of this.plugins) {
|
|
742
906
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
743
907
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
908
|
+
// Check if the plugin is available
|
|
744
909
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
745
910
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
746
911
|
plugin.enabled = false;
|
|
@@ -760,10 +925,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
760
925
|
if (wait)
|
|
761
926
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
762
927
|
else
|
|
763
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
928
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
|
|
764
929
|
}
|
|
765
930
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
766
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
|
+
*/
|
|
767
938
|
registerProcessHandlers() {
|
|
768
939
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
769
940
|
process.removeAllListeners('uncaughtException');
|
|
@@ -790,6 +961,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
790
961
|
};
|
|
791
962
|
process.on('SIGTERM', this.sigtermHandler);
|
|
792
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
966
|
+
*/
|
|
793
967
|
deregisterProcessHandlers() {
|
|
794
968
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
795
969
|
if (this.exceptionHandler)
|
|
@@ -806,12 +980,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
806
980
|
process.off('SIGTERM', this.sigtermHandler);
|
|
807
981
|
this.sigtermHandler = undefined;
|
|
808
982
|
}
|
|
983
|
+
/**
|
|
984
|
+
* Logs the node and system information.
|
|
985
|
+
*/
|
|
809
986
|
async logNodeAndSystemInfo() {
|
|
987
|
+
// IP address information
|
|
810
988
|
const networkInterfaces = os.networkInterfaces();
|
|
811
989
|
this.systemInformation.interfaceName = '';
|
|
812
990
|
this.systemInformation.ipv4Address = '';
|
|
813
991
|
this.systemInformation.ipv6Address = '';
|
|
814
992
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
993
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
815
994
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
816
995
|
continue;
|
|
817
996
|
if (!interfaceDetails) {
|
|
@@ -837,19 +1016,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
837
1016
|
break;
|
|
838
1017
|
}
|
|
839
1018
|
}
|
|
1019
|
+
// Node information
|
|
840
1020
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
841
1021
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
842
1022
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
843
1023
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1024
|
+
// Host system information
|
|
844
1025
|
this.systemInformation.hostname = os.hostname();
|
|
845
1026
|
this.systemInformation.user = os.userInfo().username;
|
|
846
|
-
this.systemInformation.osType = os.type();
|
|
847
|
-
this.systemInformation.osRelease = os.release();
|
|
848
|
-
this.systemInformation.osPlatform = os.platform();
|
|
849
|
-
this.systemInformation.osArch = os.arch();
|
|
850
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
851
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
852
|
-
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
|
|
853
1035
|
this.log.debug('Host System Information:');
|
|
854
1036
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
855
1037
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -865,14 +1047,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
865
1047
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
866
1048
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
867
1049
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1050
|
+
// Log directories
|
|
868
1051
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
869
1052
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
870
1053
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
871
1054
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
872
1055
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1056
|
+
// Global node_modules directory
|
|
873
1057
|
if (this.nodeContext)
|
|
874
1058
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
875
1059
|
if (this.globalModulesDirectory === '') {
|
|
1060
|
+
// First run of Matterbridge so the node storage is empty
|
|
876
1061
|
this.log.debug(`Getting global node_modules directory...`);
|
|
877
1062
|
try {
|
|
878
1063
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -885,6 +1070,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
885
1070
|
}
|
|
886
1071
|
}
|
|
887
1072
|
else {
|
|
1073
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
888
1074
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
889
1075
|
try {
|
|
890
1076
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -896,58 +1082,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
896
1082
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
897
1083
|
}
|
|
898
1084
|
}
|
|
1085
|
+
// Matterbridge version
|
|
899
1086
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
900
1087
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
901
1088
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
902
1089
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
903
1091
|
if (this.nodeContext)
|
|
904
1092
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
905
1093
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
906
1095
|
if (this.nodeContext)
|
|
907
1096
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
908
1097
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1098
|
+
// Current working directory
|
|
909
1099
|
const currentDir = process.cwd();
|
|
910
1100
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1101
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
911
1102
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
912
1103
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
913
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
|
+
*/
|
|
914
1112
|
createDestinationMatterLogger(fileLogger) {
|
|
915
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1113
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
916
1114
|
if (fileLogger) {
|
|
917
|
-
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory,
|
|
1115
|
+
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
918
1116
|
}
|
|
919
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
|
|
920
1119
|
const logger = text.slice(44, 44 + 20).trim();
|
|
921
1120
|
const msg = text.slice(65);
|
|
922
1121
|
this.matterLog.logName = logger;
|
|
923
1122
|
switch (message.level) {
|
|
924
1123
|
case MatterLogLevel.DEBUG:
|
|
925
|
-
this.matterLog.log("debug"
|
|
1124
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
926
1125
|
break;
|
|
927
1126
|
case MatterLogLevel.INFO:
|
|
928
|
-
this.matterLog.log("info"
|
|
1127
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
929
1128
|
break;
|
|
930
1129
|
case MatterLogLevel.NOTICE:
|
|
931
|
-
this.matterLog.log("notice"
|
|
1130
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
932
1131
|
break;
|
|
933
1132
|
case MatterLogLevel.WARN:
|
|
934
|
-
this.matterLog.log("warn"
|
|
1133
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
935
1134
|
break;
|
|
936
1135
|
case MatterLogLevel.ERROR:
|
|
937
|
-
this.matterLog.log("error"
|
|
1136
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
938
1137
|
break;
|
|
939
1138
|
case MatterLogLevel.FATAL:
|
|
940
|
-
this.matterLog.log("fatal"
|
|
1139
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
941
1140
|
break;
|
|
942
1141
|
}
|
|
943
1142
|
};
|
|
944
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
|
+
*/
|
|
945
1149
|
async restartProcess() {
|
|
946
1150
|
await this.cleanup('restarting...', true);
|
|
947
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Shut down the process (/api/shutdown).
|
|
1154
|
+
*
|
|
1155
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1156
|
+
*/
|
|
948
1157
|
async shutdownProcess() {
|
|
949
1158
|
await this.cleanup('shutting down...', false);
|
|
950
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
|
+
*/
|
|
951
1165
|
async updateProcess() {
|
|
952
1166
|
this.log.info('Updating matterbridge...');
|
|
953
1167
|
try {
|
|
@@ -961,6 +1175,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
961
1175
|
this.frontend.wssSendRestartRequired();
|
|
962
1176
|
await this.cleanup('updating...', false);
|
|
963
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
|
+
*/
|
|
964
1185
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
965
1186
|
this.log.info('Unregistering all devices and shutting down...');
|
|
966
1187
|
for (const plugin of this.plugins.array()) {
|
|
@@ -972,46 +1193,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
972
1193
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
973
1194
|
}
|
|
974
1195
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
975
|
-
await wait(timeout);
|
|
1196
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
976
1197
|
this.log.debug('Cleaning up and shutting down...');
|
|
977
1198
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
978
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
|
+
*/
|
|
979
1205
|
async shutdownProcessAndReset() {
|
|
980
1206
|
await this.cleanup('shutting down with reset...', false);
|
|
981
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
|
+
*/
|
|
982
1213
|
async shutdownProcessAndFactoryReset() {
|
|
983
1214
|
await this.cleanup('shutting down with factory reset...', false);
|
|
984
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
|
+
*/
|
|
985
1225
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
986
1226
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
987
1227
|
this.emit('cleanup_started');
|
|
988
1228
|
this.hasCleanupStarted = true;
|
|
989
1229
|
this.log.info(message);
|
|
1230
|
+
// Clear the start matter interval
|
|
990
1231
|
if (this.startMatterInterval) {
|
|
991
1232
|
clearInterval(this.startMatterInterval);
|
|
992
1233
|
this.startMatterInterval = undefined;
|
|
993
1234
|
this.log.debug('Start matter interval cleared');
|
|
994
1235
|
}
|
|
1236
|
+
// Clear the check update timeout
|
|
995
1237
|
if (this.checkUpdateTimeout) {
|
|
996
1238
|
clearTimeout(this.checkUpdateTimeout);
|
|
997
1239
|
this.checkUpdateTimeout = undefined;
|
|
998
1240
|
this.log.debug('Check update timeout cleared');
|
|
999
1241
|
}
|
|
1242
|
+
// Clear the check update interval
|
|
1000
1243
|
if (this.checkUpdateInterval) {
|
|
1001
1244
|
clearInterval(this.checkUpdateInterval);
|
|
1002
1245
|
this.checkUpdateInterval = undefined;
|
|
1003
1246
|
this.log.debug('Check update interval cleared');
|
|
1004
1247
|
}
|
|
1248
|
+
// Clear the configure timeout
|
|
1005
1249
|
if (this.configureTimeout) {
|
|
1006
1250
|
clearTimeout(this.configureTimeout);
|
|
1007
1251
|
this.configureTimeout = undefined;
|
|
1008
1252
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1009
1253
|
}
|
|
1254
|
+
// Clear the reachability timeout
|
|
1010
1255
|
if (this.reachabilityTimeout) {
|
|
1011
1256
|
clearTimeout(this.reachabilityTimeout);
|
|
1012
1257
|
this.reachabilityTimeout = undefined;
|
|
1013
1258
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1014
1259
|
}
|
|
1260
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1015
1261
|
for (const plugin of this.plugins) {
|
|
1016
1262
|
if (!plugin.enabled || plugin.error)
|
|
1017
1263
|
continue;
|
|
@@ -1022,9 +1268,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1022
1268
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1023
1269
|
}
|
|
1024
1270
|
}
|
|
1271
|
+
// Stop matter server nodes
|
|
1025
1272
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1026
|
-
|
|
1027
|
-
|
|
1273
|
+
if (timeout > 0) {
|
|
1274
|
+
this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
|
|
1275
|
+
await wait(timeout, `Waiting ${timeout}ms for the MessageExchange to finish...`, true);
|
|
1276
|
+
}
|
|
1028
1277
|
if (this.bridgeMode === 'bridge') {
|
|
1029
1278
|
if (this.serverNode) {
|
|
1030
1279
|
await this.stopServerNode(this.serverNode);
|
|
@@ -1046,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1046
1295
|
}
|
|
1047
1296
|
}
|
|
1048
1297
|
this.log.notice('Stopped matter server nodes');
|
|
1298
|
+
// Matter commisioning reset
|
|
1049
1299
|
if (message === 'shutting down with reset...') {
|
|
1050
1300
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1051
1301
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1055,6 +1305,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1055
1305
|
await this.matterbridgeContext?.clearAll();
|
|
1056
1306
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1057
1307
|
}
|
|
1308
|
+
// Unregister all devices
|
|
1058
1309
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1059
1310
|
if (this.bridgeMode === 'bridge') {
|
|
1060
1311
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1072,12 +1323,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
1072
1323
|
}
|
|
1073
1324
|
this.log.info('Matter storage reset done!');
|
|
1074
1325
|
}
|
|
1326
|
+
// Stop matter storage
|
|
1075
1327
|
await this.stopMatterStorage();
|
|
1328
|
+
// Stop the frontend
|
|
1076
1329
|
await this.frontend.stop();
|
|
1330
|
+
// Close the matterbridge node storage and context
|
|
1077
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)
|
|
1078
1345
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1079
1346
|
await this.nodeContext.close();
|
|
1080
1347
|
this.nodeContext = undefined;
|
|
1348
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1081
1349
|
for (const plugin of this.plugins) {
|
|
1082
1350
|
if (plugin.nodeContext) {
|
|
1083
1351
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1094,41 +1362,47 @@ export class Matterbridge extends EventEmitter {
|
|
|
1094
1362
|
}
|
|
1095
1363
|
this.plugins.clear();
|
|
1096
1364
|
this.devices.clear();
|
|
1365
|
+
// Factory reset
|
|
1097
1366
|
if (message === 'shutting down with factory reset...') {
|
|
1098
1367
|
try {
|
|
1099
|
-
|
|
1368
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1369
|
+
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1100
1370
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1101
1371
|
await fs.rm(dir, { recursive: true });
|
|
1102
|
-
const backup = path.join(this.matterbridgeDirectory,
|
|
1372
|
+
const backup = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup');
|
|
1103
1373
|
this.log.info(`Removing matter storage backup directory: ${backup}`);
|
|
1104
1374
|
await fs.rm(backup, { recursive: true });
|
|
1105
1375
|
}
|
|
1106
1376
|
catch (error) {
|
|
1377
|
+
// istanbul ignore next if
|
|
1107
1378
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1108
1379
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1109
1380
|
}
|
|
1110
1381
|
}
|
|
1111
1382
|
try {
|
|
1112
|
-
|
|
1383
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1384
|
+
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1113
1385
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1114
1386
|
await fs.rm(dir, { recursive: true });
|
|
1115
|
-
const backup = path.join(this.matterbridgeDirectory,
|
|
1387
|
+
const backup = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup');
|
|
1116
1388
|
this.log.info(`Removing matterbridge storage backup directory: ${backup}`);
|
|
1117
1389
|
await fs.rm(backup, { recursive: true });
|
|
1118
1390
|
}
|
|
1119
1391
|
catch (error) {
|
|
1392
|
+
// istanbul ignore next if
|
|
1120
1393
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1121
1394
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1122
1395
|
}
|
|
1123
1396
|
}
|
|
1124
1397
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1125
1398
|
}
|
|
1399
|
+
// Deregisters the process handlers
|
|
1126
1400
|
this.deregisterProcessHandlers();
|
|
1127
1401
|
if (restart) {
|
|
1128
1402
|
if (message === 'updating...') {
|
|
1129
1403
|
this.log.info('Cleanup completed. Updating...');
|
|
1130
1404
|
Matterbridge.instance = undefined;
|
|
1131
|
-
this.emit('update');
|
|
1405
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1132
1406
|
}
|
|
1133
1407
|
else if (message === 'restarting...') {
|
|
1134
1408
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1146,39 +1420,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1146
1420
|
this.emit('cleanup_completed');
|
|
1147
1421
|
}
|
|
1148
1422
|
else {
|
|
1149
|
-
this.
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1154
|
-
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
1155
|
-
const context = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
1156
|
-
device.serverNode = await this.createServerNode(context, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1157
|
-
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
|
|
1158
|
-
await device.serverNode.add(device);
|
|
1159
|
-
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
async createAccessoryPlugin(plugin, device) {
|
|
1163
|
-
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1164
|
-
plugin.locked = true;
|
|
1165
|
-
plugin.device = device;
|
|
1166
|
-
plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
1167
|
-
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1168
|
-
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node`);
|
|
1169
|
-
await plugin.serverNode.add(device);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
async createDynamicPlugin(plugin) {
|
|
1173
|
-
if (!plugin.locked) {
|
|
1174
|
-
plugin.locked = true;
|
|
1175
|
-
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
|
|
1176
|
-
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
1177
|
-
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
1178
|
-
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1423
|
+
if (!this.initialized)
|
|
1424
|
+
this.log.debug('Cleanup with instance not initialized...');
|
|
1425
|
+
if (this.hasCleanupStarted)
|
|
1426
|
+
this.log.debug('Cleanup already started...');
|
|
1179
1427
|
}
|
|
1180
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
|
+
*/
|
|
1181
1435
|
async startBridge() {
|
|
1436
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1182
1437
|
if (!this.matterStorageManager)
|
|
1183
1438
|
throw new Error('No storage manager initialized');
|
|
1184
1439
|
if (!this.matterbridgeContext)
|
|
@@ -1217,13 +1472,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1217
1472
|
clearInterval(this.startMatterInterval);
|
|
1218
1473
|
this.startMatterInterval = undefined;
|
|
1219
1474
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1220
|
-
|
|
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'
|
|
1221
1478
|
for (const device of this.devices.array()) {
|
|
1222
1479
|
if (device.mode === 'server' && device.serverNode) {
|
|
1223
1480
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1224
|
-
this.startServerNode(device.serverNode);
|
|
1481
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1225
1482
|
}
|
|
1226
1483
|
}
|
|
1484
|
+
// Configure the plugins
|
|
1227
1485
|
this.configureTimeout = setTimeout(async () => {
|
|
1228
1486
|
for (const plugin of this.plugins.array()) {
|
|
1229
1487
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1241,28 +1499,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1241
1499
|
}
|
|
1242
1500
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1243
1501
|
}, 30 * 1000).unref();
|
|
1502
|
+
// Setting reachability to true
|
|
1244
1503
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1245
1504
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1246
1505
|
if (this.aggregatorNode)
|
|
1247
1506
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1248
1507
|
}, 60 * 1000).unref();
|
|
1508
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1249
1509
|
this.emit('bridge_started');
|
|
1250
1510
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1251
1511
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1252
1512
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1253
1513
|
}, this.startMatterIntervalMs);
|
|
1254
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
|
+
*/
|
|
1255
1522
|
async startChildbridge(delay = 1000) {
|
|
1256
1523
|
if (!this.matterStorageManager)
|
|
1257
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
|
|
1258
1526
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1259
1527
|
await this.startPlugins(true, false);
|
|
1528
|
+
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1260
1529
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1261
1530
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1262
1531
|
if (plugin.type === 'DynamicPlatform')
|
|
1263
1532
|
await this.createDynamicPlugin(plugin);
|
|
1264
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1533
|
+
this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
|
|
1265
1534
|
}
|
|
1535
|
+
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1266
1536
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1267
1537
|
let failCount = 0;
|
|
1268
1538
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1296,8 +1566,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1296
1566
|
clearInterval(this.startMatterInterval);
|
|
1297
1567
|
this.startMatterInterval = undefined;
|
|
1298
1568
|
if (delay > 0)
|
|
1299
|
-
await wait(delay);
|
|
1569
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1300
1570
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1571
|
+
// Configure the plugins
|
|
1301
1572
|
this.configureTimeout = setTimeout(async () => {
|
|
1302
1573
|
for (const plugin of this.plugins.array()) {
|
|
1303
1574
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1322,6 +1593,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1322
1593
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1323
1594
|
continue;
|
|
1324
1595
|
}
|
|
1596
|
+
// istanbul ignore next if cause is just a safety check
|
|
1325
1597
|
if (!plugin.serverNode) {
|
|
1326
1598
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1327
1599
|
continue;
|
|
@@ -1334,28 +1606,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1334
1606
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1335
1607
|
continue;
|
|
1336
1608
|
}
|
|
1337
|
-
|
|
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
|
|
1338
1612
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1339
1613
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1340
1614
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1341
1615
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1342
1616
|
}, 60 * 1000).unref();
|
|
1343
1617
|
}
|
|
1618
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1344
1619
|
for (const device of this.devices.array()) {
|
|
1345
1620
|
if (device.mode === 'server' && device.serverNode) {
|
|
1346
1621
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1347
|
-
this.startServerNode(device.serverNode);
|
|
1622
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1348
1623
|
}
|
|
1349
1624
|
}
|
|
1625
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1350
1626
|
this.emit('childbridge_started');
|
|
1351
1627
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1352
1628
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1353
1629
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1354
1630
|
}, this.startMatterIntervalMs);
|
|
1355
1631
|
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Starts the Matterbridge controller.
|
|
1634
|
+
*
|
|
1635
|
+
* @private
|
|
1636
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1637
|
+
*/
|
|
1356
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
|
+
*/
|
|
1357
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
|
+
*/
|
|
1358
1853
|
async startMatterStorage() {
|
|
1854
|
+
// Setup Matter storage
|
|
1359
1855
|
this.log.info(`Starting matter node storage...`);
|
|
1360
1856
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1361
1857
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1363,8 +1859,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1363
1859
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1364
1860
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1365
1861
|
this.log.info('Matter node storage started');
|
|
1366
|
-
|
|
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'));
|
|
1367
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
|
+
*/
|
|
1368
1873
|
async backupMatterStorage(storageName, backupName) {
|
|
1369
1874
|
this.log.info('Creating matter node storage backup...');
|
|
1370
1875
|
try {
|
|
@@ -1375,6 +1880,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1375
1880
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1376
1881
|
}
|
|
1377
1882
|
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Stops the matter storage.
|
|
1885
|
+
*
|
|
1886
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1887
|
+
*/
|
|
1378
1888
|
async stopMatterStorage() {
|
|
1379
1889
|
this.log.info('Closing matter node storage...');
|
|
1380
1890
|
await this.matterStorageManager?.close();
|
|
@@ -1383,6 +1893,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1383
1893
|
this.matterbridgeContext = undefined;
|
|
1384
1894
|
this.log.info('Matter node storage closed');
|
|
1385
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
|
+
*/
|
|
1386
1910
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1387
1911
|
const { randomBytes } = await import('node:crypto');
|
|
1388
1912
|
if (!this.matterStorageService)
|
|
@@ -1422,6 +1946,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1422
1946
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1423
1947
|
return storageContext;
|
|
1424
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
|
+
*/
|
|
1425
1958
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1426
1959
|
const storeId = await storageContext.get('storeId');
|
|
1427
1960
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1431,24 +1964,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1431
1964
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1432
1965
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1433
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
|
+
*/
|
|
1434
1970
|
const serverNode = await ServerNode.create({
|
|
1971
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1435
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
|
|
1436
1975
|
network: {
|
|
1437
1976
|
listeningAddressIpv4: this.ipv4address,
|
|
1438
1977
|
listeningAddressIpv6: this.ipv6address,
|
|
1439
1978
|
port,
|
|
1440
1979
|
},
|
|
1980
|
+
// Provide the certificate for the device
|
|
1441
1981
|
operationalCredentials: {
|
|
1442
1982
|
certification: this.certification,
|
|
1443
1983
|
},
|
|
1984
|
+
// Provide Commissioning relevant settings
|
|
1985
|
+
// Optional for development/testing purposes
|
|
1444
1986
|
commissioning: {
|
|
1445
1987
|
passcode,
|
|
1446
1988
|
discriminator,
|
|
1447
1989
|
},
|
|
1990
|
+
// Provide Node announcement settings
|
|
1991
|
+
// Optional: If Ommitted some development defaults are used
|
|
1448
1992
|
productDescription: {
|
|
1449
1993
|
name: await storageContext.get('deviceName'),
|
|
1450
1994
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1451
1995
|
},
|
|
1996
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
+
// Optional: If Omitted some development defaults are used
|
|
1452
1998
|
basicInformation: {
|
|
1453
1999
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1454
2000
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1465,17 +2011,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1465
2011
|
reachable: true,
|
|
1466
2012
|
},
|
|
1467
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
|
+
*/
|
|
1468
2018
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1469
2019
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1470
2020
|
this.advertisingNodes.delete(storeId);
|
|
1471
2021
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1472
2022
|
});
|
|
2023
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1473
2024
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1474
2025
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1475
2026
|
this.advertisingNodes.delete(storeId);
|
|
1476
2027
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1477
2028
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1478
2029
|
});
|
|
2030
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1479
2031
|
serverNode.lifecycle.online.on(async () => {
|
|
1480
2032
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1481
2033
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1486,13 +2038,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1486
2038
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1487
2039
|
}
|
|
1488
2040
|
else {
|
|
2041
|
+
// istanbul ignore next
|
|
1489
2042
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2043
|
+
// istanbul ignore next
|
|
1490
2044
|
this.advertisingNodes.delete(storeId);
|
|
1491
2045
|
}
|
|
1492
2046
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1493
2047
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1494
2048
|
this.emit('online', storeId);
|
|
1495
2049
|
});
|
|
2050
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1496
2051
|
serverNode.lifecycle.offline.on(() => {
|
|
1497
2052
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1498
2053
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1500,11 +2055,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1500
2055
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1501
2056
|
this.emit('offline', storeId);
|
|
1502
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
|
+
*/
|
|
1503
2062
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1504
2063
|
let action = '';
|
|
1505
2064
|
switch (fabricAction) {
|
|
1506
2065
|
case FabricAction.Added:
|
|
1507
|
-
this.advertisingNodes.delete(storeId);
|
|
2066
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1508
2067
|
action = 'added';
|
|
1509
2068
|
break;
|
|
1510
2069
|
case FabricAction.Removed:
|
|
@@ -1517,14 +2076,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1517
2076
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1518
2077
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1519
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
|
+
*/
|
|
1520
2083
|
serverNode.events.sessions.opened.on((session) => {
|
|
1521
2084
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1522
2085
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1523
2086
|
});
|
|
2087
|
+
/**
|
|
2088
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2089
|
+
*/
|
|
1524
2090
|
serverNode.events.sessions.closed.on((session) => {
|
|
1525
2091
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1526
2092
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1527
2093
|
});
|
|
2094
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1528
2095
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1529
2096
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1530
2097
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1532,6 +2099,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1532
2099
|
this.log.info(`Created server node for ${storeId}`);
|
|
1533
2100
|
return serverNode;
|
|
1534
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
|
+
*/
|
|
1535
2108
|
getServerNodeData(serverNode) {
|
|
1536
2109
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1537
2110
|
return {
|
|
@@ -1548,12 +2121,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1548
2121
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1549
2122
|
};
|
|
1550
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
|
+
*/
|
|
1551
2130
|
async startServerNode(matterServerNode) {
|
|
1552
2131
|
if (!matterServerNode)
|
|
1553
2132
|
return;
|
|
1554
2133
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1555
2134
|
await matterServerNode.start();
|
|
1556
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
|
+
*/
|
|
1557
2143
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1558
2144
|
if (!matterServerNode)
|
|
1559
2145
|
return;
|
|
@@ -1566,13 +2152,80 @@ export class Matterbridge extends EventEmitter {
|
|
|
1566
2152
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1567
2153
|
}
|
|
1568
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
|
+
*/
|
|
1569
2161
|
async createAggregatorNode(storageContext) {
|
|
1570
2162
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1571
2163
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1572
2164
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1573
2165
|
return aggregatorNode;
|
|
1574
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
|
+
*/
|
|
2174
|
+
async createAccessoryPlugin(plugin, device) {
|
|
2175
|
+
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
2176
|
+
plugin.locked = true;
|
|
2177
|
+
plugin.device = device;
|
|
2178
|
+
this.log.debug(`Creating accessory plugin ${plg}${plugin.name}${db} server node...`);
|
|
2179
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
2180
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
2181
|
+
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to ${plg}${plugin.name}${db} server node...`);
|
|
2182
|
+
await plugin.serverNode.add(device);
|
|
2183
|
+
}
|
|
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
|
+
*/
|
|
2191
|
+
async createDynamicPlugin(plugin) {
|
|
2192
|
+
if (!plugin.locked) {
|
|
2193
|
+
plugin.locked = true;
|
|
2194
|
+
this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
|
|
2195
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, plugin.description);
|
|
2196
|
+
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
2197
|
+
this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
|
|
2198
|
+
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
2199
|
+
this.log.debug(`Adding dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
|
|
2200
|
+
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
2201
|
+
}
|
|
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
|
+
*/
|
|
2210
|
+
async createDeviceServerNode(plugin, device) {
|
|
2211
|
+
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
2212
|
+
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
2213
|
+
const context = await this.createServerNodeContext(device.deviceName.replace(/[ .]/g, ''), device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
|
|
2214
|
+
device.serverNode = await this.createServerNode(context, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
2215
|
+
this.log.debug(`Adding ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node...`);
|
|
2216
|
+
await device.serverNode.add(device);
|
|
2217
|
+
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
2218
|
+
}
|
|
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
|
+
*/
|
|
1575
2227
|
async addBridgedEndpoint(pluginName, device) {
|
|
2228
|
+
// Check if the plugin is registered
|
|
1576
2229
|
const plugin = this.plugins.get(pluginName);
|
|
1577
2230
|
if (!plugin) {
|
|
1578
2231
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1592,6 +2245,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1592
2245
|
}
|
|
1593
2246
|
else if (this.bridgeMode === 'bridge') {
|
|
1594
2247
|
if (device.mode === 'matter') {
|
|
2248
|
+
// Register and add the device to the matterbridge server node
|
|
1595
2249
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1596
2250
|
if (!this.serverNode) {
|
|
1597
2251
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1608,6 +2262,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1608
2262
|
}
|
|
1609
2263
|
}
|
|
1610
2264
|
else {
|
|
2265
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1611
2266
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1612
2267
|
if (!this.aggregatorNode) {
|
|
1613
2268
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1625,6 +2280,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1625
2280
|
}
|
|
1626
2281
|
}
|
|
1627
2282
|
else if (this.bridgeMode === 'childbridge') {
|
|
2283
|
+
// Register and add the device to the plugin server node
|
|
1628
2284
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1629
2285
|
try {
|
|
1630
2286
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1648,10 +2304,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1648
2304
|
return;
|
|
1649
2305
|
}
|
|
1650
2306
|
}
|
|
2307
|
+
// Register and add the device to the plugin aggregator node
|
|
1651
2308
|
if (plugin.type === 'DynamicPlatform') {
|
|
1652
2309
|
try {
|
|
1653
2310
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1654
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
|
|
1655
2313
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1656
2314
|
if (!plugin.aggregatorNode) {
|
|
1657
2315
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1672,17 +2330,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1672
2330
|
}
|
|
1673
2331
|
if (plugin.registeredDevices !== undefined)
|
|
1674
2332
|
plugin.registeredDevices++;
|
|
2333
|
+
// Add the device to the DeviceManager
|
|
1675
2334
|
this.devices.set(device);
|
|
2335
|
+
// Subscribe to the attributes changed event
|
|
1676
2336
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1677
2337
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1678
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
|
+
*/
|
|
1679
2346
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1680
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
|
|
1681
2349
|
const plugin = this.plugins.get(pluginName);
|
|
1682
2350
|
if (!plugin) {
|
|
1683
2351
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1684
2352
|
return;
|
|
1685
2353
|
}
|
|
2354
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1686
2355
|
if (this.bridgeMode === 'bridge') {
|
|
1687
2356
|
if (!this.aggregatorNode) {
|
|
1688
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`);
|
|
@@ -1695,6 +2364,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1695
2364
|
}
|
|
1696
2365
|
else if (this.bridgeMode === 'childbridge') {
|
|
1697
2366
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2367
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1698
2368
|
}
|
|
1699
2369
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1700
2370
|
if (!plugin.aggregatorNode) {
|
|
@@ -1707,8 +2377,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1707
2377
|
if (plugin.registeredDevices !== undefined)
|
|
1708
2378
|
plugin.registeredDevices--;
|
|
1709
2379
|
}
|
|
2380
|
+
// Remove the device from the DeviceManager
|
|
1710
2381
|
this.devices.remove(device);
|
|
1711
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
|
+
*/
|
|
1712
2395
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1713
2396
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1714
2397
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1719,13 +2402,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1719
2402
|
if (delay > 0)
|
|
1720
2403
|
await wait(2000);
|
|
1721
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
|
+
*/
|
|
1722
2414
|
async subscribeAttributeChanged(plugin, device) {
|
|
1723
2415
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
1724
2416
|
return;
|
|
1725
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
|
|
1726
2419
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1727
2420
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1728
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
|
|
1729
2423
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
1730
2424
|
});
|
|
1731
2425
|
}
|
|
@@ -1773,6 +2467,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1773
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...`);
|
|
1774
2468
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1775
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
|
|
1776
2471
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
1777
2472
|
});
|
|
1778
2473
|
}
|
|
@@ -1782,6 +2477,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1782
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...`);
|
|
1783
2478
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1784
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
|
|
1785
2481
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1786
2482
|
});
|
|
1787
2483
|
}
|
|
@@ -1789,6 +2485,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1789
2485
|
}
|
|
1790
2486
|
}
|
|
1791
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
|
+
*/
|
|
1792
2494
|
sanitizeFabricInformations(fabricInfo) {
|
|
1793
2495
|
return fabricInfo.map((info) => {
|
|
1794
2496
|
return {
|
|
@@ -1802,6 +2504,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1802
2504
|
};
|
|
1803
2505
|
});
|
|
1804
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
|
+
*/
|
|
1805
2513
|
sanitizeSessionInformation(sessions) {
|
|
1806
2514
|
return sessions
|
|
1807
2515
|
.filter((session) => session.isPeerActive)
|
|
@@ -1828,7 +2536,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1828
2536
|
};
|
|
1829
2537
|
});
|
|
1830
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
|
|
1831
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
|
+
*/
|
|
1832
2554
|
}
|
|
1833
2555
|
getVendorIdName = (vendorId) => {
|
|
1834
2556
|
if (!vendorId)
|
|
@@ -1868,10 +2590,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1868
2590
|
case 0x1488:
|
|
1869
2591
|
vendorName = '(ShortcutLabsFlic)';
|
|
1870
2592
|
break;
|
|
1871
|
-
case 65521:
|
|
2593
|
+
case 65521: // 0xFFF1
|
|
1872
2594
|
vendorName = '(MatterTest)';
|
|
1873
2595
|
break;
|
|
1874
2596
|
}
|
|
1875
2597
|
return vendorName;
|
|
1876
2598
|
};
|
|
1877
2599
|
}
|
|
2600
|
+
//# sourceMappingURL=matterbridge.js.map
|