matterbridge 3.2.8-dev-20250920-1a6178d → 3.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +91 -2
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +34 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +30 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.js.map +1 -0
- package/dist/deviceManager.d.ts +112 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +94 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +60 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +55 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +25 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +228 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +429 -38
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +572 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +51 -3
- 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 +442 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +775 -50
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +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 +1515 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1373 -55
- 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 +380 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +304 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +190 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +270 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +249 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +172 -11
- 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 +33 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +68 -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/frontend/build/assets/index.js +1 -1
- package/frontend/build/index.html +12 -12
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2023-12-29
|
|
7
|
+
* @version 1.6.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node.js modules
|
|
1
25
|
import os from 'node:os';
|
|
2
26
|
import path from 'node:path';
|
|
3
27
|
import { promises as fs } from 'node:fs';
|
|
4
28
|
import EventEmitter from 'node:events';
|
|
5
29
|
import { inspect } from 'node:util';
|
|
30
|
+
// AnsiLogger module
|
|
6
31
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
|
|
32
|
+
// NodeStorage module
|
|
7
33
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
+
// @matter
|
|
8
35
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
9
36
|
import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
10
37
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
11
38
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
12
39
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
|
|
14
42
|
import { withTimeout, waiter, wait } from './utils/wait.js';
|
|
15
43
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -19,6 +47,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
19
47
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
20
48
|
import { Frontend } from './frontend.js';
|
|
21
49
|
import { addVirtualDevices } from './helpers.js';
|
|
50
|
+
/**
|
|
51
|
+
* Represents the Matterbridge application.
|
|
52
|
+
*/
|
|
22
53
|
export class Matterbridge extends EventEmitter {
|
|
23
54
|
systemInformation = {
|
|
24
55
|
interfaceName: '',
|
|
@@ -59,7 +90,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
59
90
|
shellySysUpdate: false,
|
|
60
91
|
shellyMainUpdate: false,
|
|
61
92
|
profile: getParameter('profile'),
|
|
62
|
-
loggerLevel: "info"
|
|
93
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
63
94
|
fileLogger: false,
|
|
64
95
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
65
96
|
matterFileLogger: false,
|
|
@@ -87,16 +118,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
87
118
|
profile = getParameter('profile');
|
|
88
119
|
shutdown = false;
|
|
89
120
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
90
|
-
|
|
121
|
+
// Matterbridge logger
|
|
122
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
91
123
|
matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
92
|
-
|
|
124
|
+
// Matter logger
|
|
125
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
93
126
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
94
127
|
plugins = new PluginManager(this);
|
|
95
128
|
devices = new DeviceManager(this);
|
|
96
129
|
frontend = new Frontend(this);
|
|
130
|
+
// Matterbridge storage
|
|
97
131
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
98
132
|
nodeStorage;
|
|
99
133
|
nodeContext;
|
|
134
|
+
// Cleanup
|
|
100
135
|
hasCleanupStarted = false;
|
|
101
136
|
initialized = false;
|
|
102
137
|
startMatterInterval;
|
|
@@ -109,19 +144,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
109
144
|
sigtermHandler;
|
|
110
145
|
exceptionHandler;
|
|
111
146
|
rejectionHandler;
|
|
147
|
+
// Matter environment
|
|
112
148
|
environment = Environment.default;
|
|
149
|
+
// Matter storage
|
|
113
150
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
114
151
|
matterStorageService;
|
|
115
152
|
matterStorageManager;
|
|
116
153
|
matterbridgeContext;
|
|
117
154
|
controllerContext;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
155
|
+
// Matter parameters
|
|
156
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
157
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
158
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
159
|
+
port; // first server node port
|
|
160
|
+
passcode; // first server node passcode
|
|
161
|
+
discriminator; // first server node discriminator
|
|
162
|
+
certification; // device certification
|
|
163
|
+
// Matter nodes
|
|
125
164
|
serverNode;
|
|
126
165
|
aggregatorNode;
|
|
127
166
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -131,18 +170,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
131
170
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
132
171
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
133
172
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
173
|
+
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
134
174
|
advertisingNodes = new Map();
|
|
135
175
|
static instance;
|
|
176
|
+
// We load asyncronously so is private
|
|
136
177
|
constructor() {
|
|
137
178
|
super();
|
|
138
179
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
139
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Retrieves the list of Matterbridge devices.
|
|
183
|
+
*
|
|
184
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
185
|
+
*/
|
|
140
186
|
getDevices() {
|
|
141
187
|
return this.devices.array();
|
|
142
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Retrieves the list of registered plugins.
|
|
191
|
+
*
|
|
192
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
193
|
+
*/
|
|
143
194
|
getPlugins() {
|
|
144
195
|
return this.plugins.array();
|
|
145
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
199
|
+
*
|
|
200
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
201
|
+
*/
|
|
146
202
|
async setLogLevel(logLevel) {
|
|
147
203
|
if (this.log)
|
|
148
204
|
this.log.logLevel = logLevel;
|
|
@@ -156,19 +212,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
156
212
|
for (const plugin of this.plugins) {
|
|
157
213
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
158
214
|
continue;
|
|
159
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
160
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
215
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
216
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
217
|
+
}
|
|
218
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
219
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
220
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
221
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
222
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
223
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
167
224
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
168
225
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
169
226
|
}
|
|
227
|
+
//* ************************************************************************************************************************************ */
|
|
228
|
+
// loadInstance() and cleanup() methods */
|
|
229
|
+
//* ************************************************************************************************************************************ */
|
|
230
|
+
/**
|
|
231
|
+
* Loads an instance of the Matterbridge class.
|
|
232
|
+
* If an instance already exists, return that instance.
|
|
233
|
+
*
|
|
234
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
235
|
+
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
236
|
+
*/
|
|
170
237
|
static async loadInstance(initialize = false) {
|
|
171
238
|
if (!Matterbridge.instance) {
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
172
240
|
if (hasParameter('debug'))
|
|
173
241
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
174
242
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -177,8 +245,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
177
245
|
}
|
|
178
246
|
return Matterbridge.instance;
|
|
179
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Call cleanup() and dispose MdnsService.
|
|
250
|
+
*
|
|
251
|
+
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
252
|
+
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
253
|
+
*
|
|
254
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
255
|
+
*/
|
|
180
256
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
181
257
|
this.log.info(`Destroy instance...`);
|
|
258
|
+
// Save server nodes to close
|
|
182
259
|
const servers = [];
|
|
183
260
|
if (this.bridgeMode === 'bridge') {
|
|
184
261
|
if (this.serverNode)
|
|
@@ -196,72 +273,106 @@ export class Matterbridge extends EventEmitter {
|
|
|
196
273
|
servers.push(device.serverNode);
|
|
197
274
|
}
|
|
198
275
|
}
|
|
276
|
+
// Let any already‐queued microtasks run first
|
|
199
277
|
await Promise.resolve();
|
|
278
|
+
// Wait for the cleanup to finish
|
|
200
279
|
await wait(pause, 'destroyInstance start', true);
|
|
280
|
+
// Cleanup
|
|
201
281
|
await this.cleanup('destroying instance...', false, timeout);
|
|
282
|
+
// Close servers mdns service
|
|
202
283
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
203
284
|
for (const server of servers) {
|
|
204
285
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
205
286
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
206
287
|
}
|
|
288
|
+
// Let any already‐queued microtasks run first
|
|
207
289
|
await Promise.resolve();
|
|
290
|
+
// Wait for the cleanup to finish
|
|
208
291
|
await wait(pause, 'destroyInstance stop', true);
|
|
209
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Initializes the Matterbridge application.
|
|
295
|
+
*
|
|
296
|
+
* @remarks
|
|
297
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
298
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
299
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
300
|
+
*
|
|
301
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
302
|
+
*/
|
|
210
303
|
async initialize() {
|
|
304
|
+
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
305
|
+
// Emit the initialize_started event
|
|
211
306
|
this.emit('initialize_started');
|
|
307
|
+
// Set the restart mode
|
|
212
308
|
if (hasParameter('service'))
|
|
213
309
|
this.restartMode = 'service';
|
|
214
310
|
if (hasParameter('docker'))
|
|
215
311
|
this.restartMode = 'docker';
|
|
312
|
+
// Set the matterbridge home directory
|
|
216
313
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
217
314
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
218
315
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
316
|
+
// Set the matterbridge directory
|
|
219
317
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
220
318
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
221
319
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
222
320
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
223
321
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
322
|
+
// Set the matterbridge plugin directory
|
|
224
323
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
225
324
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
226
325
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
326
|
+
// Set the matterbridge cert directory
|
|
227
327
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
228
328
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
229
329
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
330
|
+
// Set the matterbridge root directory
|
|
230
331
|
const { fileURLToPath } = await import('node:url');
|
|
231
332
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
232
333
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
233
334
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
335
|
+
// Setup the matter environment
|
|
234
336
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
235
337
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
236
338
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
237
339
|
this.environment.vars.set('runtime.signals', false);
|
|
238
340
|
this.environment.vars.set('runtime.exitcode', false);
|
|
341
|
+
// Register process handlers
|
|
239
342
|
this.registerProcessHandlers();
|
|
343
|
+
// Initialize nodeStorage and nodeContext
|
|
240
344
|
try {
|
|
241
345
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
242
346
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
243
347
|
this.log.debug('Creating node storage context for matterbridge');
|
|
244
348
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
349
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
350
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
245
351
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
246
352
|
for (const key of keys) {
|
|
247
353
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
354
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
248
355
|
await this.nodeStorage?.storage.get(key);
|
|
249
356
|
}
|
|
250
357
|
const storages = await this.nodeStorage.getStorageNames();
|
|
251
358
|
for (const storage of storages) {
|
|
252
359
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
253
360
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
361
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
363
|
const keys = (await nodeContext?.storage.keys());
|
|
255
364
|
keys.forEach(async (key) => {
|
|
256
365
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
257
366
|
await nodeContext?.get(key);
|
|
258
367
|
});
|
|
259
368
|
}
|
|
369
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
260
370
|
this.log.debug('Creating node storage backup...');
|
|
261
371
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
262
372
|
this.log.debug('Created node storage backup');
|
|
263
373
|
}
|
|
264
374
|
catch (error) {
|
|
375
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
265
376
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
266
377
|
if (hasParameter('norestore')) {
|
|
267
378
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -275,14 +386,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
275
386
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
276
387
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
277
388
|
}
|
|
389
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
278
390
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
391
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
279
392
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
393
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
280
394
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
395
|
+
// Certificate management
|
|
281
396
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
282
397
|
try {
|
|
283
398
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
284
399
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
285
400
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
401
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
286
402
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
287
403
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
288
404
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -311,11 +427,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
311
427
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
312
428
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
313
429
|
}
|
|
430
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
314
431
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
315
432
|
this.passcode = pairingFileJson.passcode;
|
|
316
433
|
this.discriminator = pairingFileJson.discriminator;
|
|
317
434
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
318
435
|
}
|
|
436
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
319
437
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
320
438
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
321
439
|
this.certification = {
|
|
@@ -330,41 +448,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
330
448
|
catch (error) {
|
|
331
449
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
332
450
|
}
|
|
451
|
+
// Store the passcode, discriminator and port in the node context
|
|
333
452
|
await this.nodeContext.set('matterport', this.port);
|
|
334
453
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
335
454
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
336
455
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
456
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
337
457
|
if (hasParameter('logger')) {
|
|
338
458
|
const level = getParameter('logger');
|
|
339
459
|
if (level === 'debug') {
|
|
340
|
-
this.log.logLevel = "debug"
|
|
460
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
341
461
|
}
|
|
342
462
|
else if (level === 'info') {
|
|
343
|
-
this.log.logLevel = "info"
|
|
463
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
344
464
|
}
|
|
345
465
|
else if (level === 'notice') {
|
|
346
|
-
this.log.logLevel = "notice"
|
|
466
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
347
467
|
}
|
|
348
468
|
else if (level === 'warn') {
|
|
349
|
-
this.log.logLevel = "warn"
|
|
469
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
350
470
|
}
|
|
351
471
|
else if (level === 'error') {
|
|
352
|
-
this.log.logLevel = "error"
|
|
472
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
353
473
|
}
|
|
354
474
|
else if (level === 'fatal') {
|
|
355
|
-
this.log.logLevel = "fatal"
|
|
475
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
356
476
|
}
|
|
357
477
|
else {
|
|
358
478
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
359
|
-
this.log.logLevel = "info"
|
|
479
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
360
480
|
}
|
|
361
481
|
}
|
|
362
482
|
else {
|
|
363
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
483
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
364
484
|
}
|
|
365
485
|
this.frontend.logLevel = this.log.logLevel;
|
|
366
486
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
367
487
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
488
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
368
489
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
369
490
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
|
|
370
491
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -373,6 +494,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
373
494
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
374
495
|
if (this.profile !== undefined)
|
|
375
496
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
497
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
376
498
|
if (hasParameter('matterlogger')) {
|
|
377
499
|
const level = getParameter('matterlogger');
|
|
378
500
|
if (level === 'debug') {
|
|
@@ -402,12 +524,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
402
524
|
Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
403
525
|
}
|
|
404
526
|
Logger.format = MatterLogFormat.ANSI;
|
|
527
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
405
528
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
406
529
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
407
530
|
}
|
|
408
531
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
|
|
409
532
|
this.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
410
533
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
534
|
+
// Log network interfaces
|
|
411
535
|
const networkInterfaces = os.networkInterfaces();
|
|
412
536
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
413
537
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -420,6 +544,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
420
544
|
});
|
|
421
545
|
}
|
|
422
546
|
}
|
|
547
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
423
548
|
if (hasParameter('mdnsinterface')) {
|
|
424
549
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
425
550
|
}
|
|
@@ -428,6 +553,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
428
553
|
if (this.mdnsInterface === '')
|
|
429
554
|
this.mdnsInterface = undefined;
|
|
430
555
|
}
|
|
556
|
+
// Validate mdnsInterface
|
|
431
557
|
if (this.mdnsInterface) {
|
|
432
558
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
433
559
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -440,6 +566,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
440
566
|
}
|
|
441
567
|
if (this.mdnsInterface)
|
|
442
568
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
569
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
443
570
|
if (hasParameter('ipv4address')) {
|
|
444
571
|
this.ipv4address = getParameter('ipv4address');
|
|
445
572
|
}
|
|
@@ -448,6 +575,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
448
575
|
if (this.ipv4address === '')
|
|
449
576
|
this.ipv4address = undefined;
|
|
450
577
|
}
|
|
578
|
+
// Validate ipv4address
|
|
451
579
|
if (this.ipv4address) {
|
|
452
580
|
let isValid = false;
|
|
453
581
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -463,6 +591,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
463
591
|
await this.nodeContext.remove('matteripv4address');
|
|
464
592
|
}
|
|
465
593
|
}
|
|
594
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
466
595
|
if (hasParameter('ipv6address')) {
|
|
467
596
|
this.ipv6address = getParameter('ipv6address');
|
|
468
597
|
}
|
|
@@ -471,6 +600,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
600
|
if (this.ipv6address === '')
|
|
472
601
|
this.ipv6address = undefined;
|
|
473
602
|
}
|
|
603
|
+
// Validate ipv6address
|
|
474
604
|
if (this.ipv6address) {
|
|
475
605
|
let isValid = false;
|
|
476
606
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -479,6 +609,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
479
609
|
isValid = true;
|
|
480
610
|
break;
|
|
481
611
|
}
|
|
612
|
+
/* istanbul ignore next */
|
|
482
613
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
483
614
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
484
615
|
isValid = true;
|
|
@@ -491,6 +622,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
491
622
|
await this.nodeContext.remove('matteripv6address');
|
|
492
623
|
}
|
|
493
624
|
}
|
|
625
|
+
// Initialize the virtual mode
|
|
494
626
|
if (hasParameter('novirtual')) {
|
|
495
627
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
496
628
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -499,12 +631,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
499
631
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
500
632
|
}
|
|
501
633
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
634
|
+
// Initialize PluginManager
|
|
502
635
|
this.plugins.logLevel = this.log.logLevel;
|
|
503
636
|
await this.plugins.loadFromStorage();
|
|
637
|
+
// Initialize DeviceManager
|
|
504
638
|
this.devices.logLevel = this.log.logLevel;
|
|
639
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
505
640
|
for (const plugin of this.plugins) {
|
|
506
641
|
const packageJson = await this.plugins.parse(plugin);
|
|
507
642
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
643
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
644
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
508
645
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
509
646
|
try {
|
|
510
647
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -527,6 +664,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
527
664
|
await plugin.nodeContext.set('description', plugin.description);
|
|
528
665
|
await plugin.nodeContext.set('author', plugin.author);
|
|
529
666
|
}
|
|
667
|
+
// Log system info and create .matterbridge directory
|
|
530
668
|
await this.logNodeAndSystemInfo();
|
|
531
669
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
532
670
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -534,6 +672,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
534
672
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
535
673
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
536
674
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
675
|
+
// Check node version and throw error
|
|
537
676
|
const minNodeVersion = 18;
|
|
538
677
|
const nodeVersion = process.versions.node;
|
|
539
678
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -541,10 +680,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
541
680
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
542
681
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
543
682
|
}
|
|
683
|
+
// Parse command line
|
|
544
684
|
await this.parseCommandLine();
|
|
685
|
+
// Emit the initialize_completed event
|
|
545
686
|
this.emit('initialize_completed');
|
|
546
687
|
this.initialized = true;
|
|
547
688
|
}
|
|
689
|
+
/**
|
|
690
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
691
|
+
*
|
|
692
|
+
* @private
|
|
693
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
694
|
+
*/
|
|
548
695
|
async parseCommandLine() {
|
|
549
696
|
if (hasParameter('help')) {
|
|
550
697
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -606,6 +753,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
606
753
|
}
|
|
607
754
|
index++;
|
|
608
755
|
}
|
|
756
|
+
/*
|
|
757
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
758
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
759
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
760
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
761
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
762
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
763
|
+
} else {
|
|
764
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
765
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
*/
|
|
609
769
|
this.shutdown = true;
|
|
610
770
|
return;
|
|
611
771
|
}
|
|
@@ -655,6 +815,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
655
815
|
this.shutdown = true;
|
|
656
816
|
return;
|
|
657
817
|
}
|
|
818
|
+
// Start the matter storage and create the matterbridge context
|
|
658
819
|
try {
|
|
659
820
|
await this.startMatterStorage();
|
|
660
821
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -670,18 +831,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
670
831
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
671
832
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
672
833
|
}
|
|
834
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
673
835
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
674
836
|
this.initialized = true;
|
|
675
837
|
await this.shutdownProcessAndReset();
|
|
676
838
|
this.shutdown = true;
|
|
677
839
|
return;
|
|
678
840
|
}
|
|
841
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
679
842
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
680
843
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
681
844
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
682
845
|
if (plugin) {
|
|
683
846
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
684
847
|
if (!matterStorageManager) {
|
|
848
|
+
/* istanbul ignore next */
|
|
685
849
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
686
850
|
}
|
|
687
851
|
else {
|
|
@@ -700,37 +864,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
700
864
|
this.shutdown = true;
|
|
701
865
|
return;
|
|
702
866
|
}
|
|
867
|
+
// Initialize frontend
|
|
703
868
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
704
869
|
await this.frontend.start(getIntParameter('frontend'));
|
|
870
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
705
871
|
clearTimeout(this.checkUpdateTimeout);
|
|
706
872
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
707
873
|
const { checkUpdates } = await import('./update.js');
|
|
708
874
|
checkUpdates(this);
|
|
709
875
|
}, 30 * 1000).unref();
|
|
876
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
710
877
|
clearInterval(this.checkUpdateInterval);
|
|
711
878
|
this.checkUpdateInterval = setInterval(async () => {
|
|
712
879
|
const { checkUpdates } = await import('./update.js');
|
|
713
880
|
checkUpdates(this);
|
|
714
881
|
}, 12 * 60 * 60 * 1000).unref();
|
|
882
|
+
// Start the matterbridge in mode test
|
|
715
883
|
if (hasParameter('test')) {
|
|
716
884
|
this.bridgeMode = 'bridge';
|
|
717
885
|
return;
|
|
718
886
|
}
|
|
887
|
+
// Start the matterbridge in mode controller
|
|
719
888
|
if (hasParameter('controller')) {
|
|
720
889
|
this.bridgeMode = 'controller';
|
|
721
890
|
await this.startController();
|
|
722
891
|
return;
|
|
723
892
|
}
|
|
893
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
724
894
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
725
895
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
726
896
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
727
897
|
}
|
|
898
|
+
// Start matterbridge in bridge mode
|
|
728
899
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
729
900
|
this.bridgeMode = 'bridge';
|
|
730
901
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
731
902
|
await this.startBridge();
|
|
732
903
|
return;
|
|
733
904
|
}
|
|
905
|
+
// Start matterbridge in childbridge mode
|
|
734
906
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
735
907
|
this.bridgeMode = 'childbridge';
|
|
736
908
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -738,10 +910,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
738
910
|
return;
|
|
739
911
|
}
|
|
740
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Asynchronously loads and starts the registered plugins.
|
|
915
|
+
*
|
|
916
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
917
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
918
|
+
*
|
|
919
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
920
|
+
*/
|
|
741
921
|
async startPlugins() {
|
|
922
|
+
// Check, load and start the plugins
|
|
742
923
|
for (const plugin of this.plugins) {
|
|
743
924
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
744
925
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
926
|
+
// Check if the plugin is available
|
|
745
927
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
746
928
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
747
929
|
plugin.enabled = false;
|
|
@@ -758,10 +940,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
758
940
|
plugin.started = false;
|
|
759
941
|
plugin.configured = false;
|
|
760
942
|
plugin.registeredDevices = undefined;
|
|
761
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
943
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
762
944
|
}
|
|
763
945
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
764
946
|
}
|
|
947
|
+
/**
|
|
948
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
949
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
950
|
+
*/
|
|
765
951
|
registerProcessHandlers() {
|
|
766
952
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
767
953
|
process.removeAllListeners('uncaughtException');
|
|
@@ -788,6 +974,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
788
974
|
};
|
|
789
975
|
process.on('SIGTERM', this.sigtermHandler);
|
|
790
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
979
|
+
*/
|
|
791
980
|
deregisterProcessHandlers() {
|
|
792
981
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
793
982
|
if (this.exceptionHandler)
|
|
@@ -804,12 +993,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
804
993
|
process.off('SIGTERM', this.sigtermHandler);
|
|
805
994
|
this.sigtermHandler = undefined;
|
|
806
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Logs the node and system information.
|
|
998
|
+
*/
|
|
807
999
|
async logNodeAndSystemInfo() {
|
|
1000
|
+
// IP address information
|
|
808
1001
|
const networkInterfaces = os.networkInterfaces();
|
|
809
1002
|
this.systemInformation.interfaceName = '';
|
|
810
1003
|
this.systemInformation.ipv4Address = '';
|
|
811
1004
|
this.systemInformation.ipv6Address = '';
|
|
812
1005
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
1006
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
813
1007
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
814
1008
|
continue;
|
|
815
1009
|
if (!interfaceDetails) {
|
|
@@ -835,19 +1029,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
835
1029
|
break;
|
|
836
1030
|
}
|
|
837
1031
|
}
|
|
1032
|
+
// Node information
|
|
838
1033
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
839
1034
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
840
1035
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
841
1036
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1037
|
+
// Host system information
|
|
842
1038
|
this.systemInformation.hostname = os.hostname();
|
|
843
1039
|
this.systemInformation.user = os.userInfo().username;
|
|
844
|
-
this.systemInformation.osType = os.type();
|
|
845
|
-
this.systemInformation.osRelease = os.release();
|
|
846
|
-
this.systemInformation.osPlatform = os.platform();
|
|
847
|
-
this.systemInformation.osArch = os.arch();
|
|
848
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
849
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
850
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1040
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1041
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1042
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1043
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1044
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1045
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1046
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1047
|
+
// Log the system information
|
|
851
1048
|
this.log.debug('Host System Information:');
|
|
852
1049
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
853
1050
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -863,14 +1060,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
863
1060
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
864
1061
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
865
1062
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1063
|
+
// Log directories
|
|
866
1064
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
867
1065
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
868
1066
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
869
1067
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
870
1068
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1069
|
+
// Global node_modules directory
|
|
871
1070
|
if (this.nodeContext)
|
|
872
1071
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
873
1072
|
if (this.globalModulesDirectory === '') {
|
|
1073
|
+
// First run of Matterbridge so the node storage is empty
|
|
874
1074
|
this.log.debug(`Getting global node_modules directory...`);
|
|
875
1075
|
try {
|
|
876
1076
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -883,6 +1083,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
883
1083
|
}
|
|
884
1084
|
}
|
|
885
1085
|
else {
|
|
1086
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
886
1087
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
887
1088
|
try {
|
|
888
1089
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -894,58 +1095,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
894
1095
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
895
1096
|
}
|
|
896
1097
|
}
|
|
1098
|
+
// Matterbridge version
|
|
897
1099
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
898
1100
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
899
1101
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
900
1102
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1103
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
901
1104
|
if (this.nodeContext)
|
|
902
1105
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
903
1106
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1107
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
904
1108
|
if (this.nodeContext)
|
|
905
1109
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
906
1110
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1111
|
+
// Current working directory
|
|
907
1112
|
const currentDir = process.cwd();
|
|
908
1113
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1114
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
909
1115
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
910
1116
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
911
1117
|
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1120
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1121
|
+
*
|
|
1122
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1123
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1124
|
+
*/
|
|
912
1125
|
createDestinationMatterLogger(fileLogger) {
|
|
913
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1126
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
914
1127
|
if (fileLogger) {
|
|
915
1128
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, this.matterLoggerFile);
|
|
916
1129
|
}
|
|
917
1130
|
return (text, message) => {
|
|
1131
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
918
1132
|
const logger = text.slice(44, 44 + 20).trim();
|
|
919
1133
|
const msg = text.slice(65);
|
|
920
1134
|
this.matterLog.logName = logger;
|
|
921
1135
|
switch (message.level) {
|
|
922
1136
|
case MatterLogLevel.DEBUG:
|
|
923
|
-
this.matterLog.log("debug"
|
|
1137
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
924
1138
|
break;
|
|
925
1139
|
case MatterLogLevel.INFO:
|
|
926
|
-
this.matterLog.log("info"
|
|
1140
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
927
1141
|
break;
|
|
928
1142
|
case MatterLogLevel.NOTICE:
|
|
929
|
-
this.matterLog.log("notice"
|
|
1143
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
930
1144
|
break;
|
|
931
1145
|
case MatterLogLevel.WARN:
|
|
932
|
-
this.matterLog.log("warn"
|
|
1146
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
933
1147
|
break;
|
|
934
1148
|
case MatterLogLevel.ERROR:
|
|
935
|
-
this.matterLog.log("error"
|
|
1149
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
936
1150
|
break;
|
|
937
1151
|
case MatterLogLevel.FATAL:
|
|
938
|
-
this.matterLog.log("fatal"
|
|
1152
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
939
1153
|
break;
|
|
940
1154
|
}
|
|
941
1155
|
};
|
|
942
1156
|
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1159
|
+
*
|
|
1160
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1161
|
+
*/
|
|
943
1162
|
async restartProcess() {
|
|
944
1163
|
await this.cleanup('restarting...', true);
|
|
945
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Shut down the process (/api/shutdown).
|
|
1167
|
+
*
|
|
1168
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1169
|
+
*/
|
|
946
1170
|
async shutdownProcess() {
|
|
947
1171
|
await this.cleanup('shutting down...', false);
|
|
948
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1175
|
+
*
|
|
1176
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1177
|
+
*/
|
|
949
1178
|
async updateProcess() {
|
|
950
1179
|
this.log.info('Updating matterbridge...');
|
|
951
1180
|
try {
|
|
@@ -959,6 +1188,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
959
1188
|
this.frontend.wssSendRestartRequired();
|
|
960
1189
|
await this.cleanup('updating...', false);
|
|
961
1190
|
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1193
|
+
*
|
|
1194
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1195
|
+
*
|
|
1196
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1197
|
+
*/
|
|
962
1198
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
963
1199
|
this.log.info('Unregistering all devices and shutting down...');
|
|
964
1200
|
for (const plugin of this.plugins.array()) {
|
|
@@ -970,46 +1206,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
970
1206
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
971
1207
|
}
|
|
972
1208
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
973
|
-
await wait(timeout);
|
|
1209
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
974
1210
|
this.log.debug('Cleaning up and shutting down...');
|
|
975
1211
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
976
1212
|
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1215
|
+
*
|
|
1216
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1217
|
+
*/
|
|
977
1218
|
async shutdownProcessAndReset() {
|
|
978
1219
|
await this.cleanup('shutting down with reset...', false);
|
|
979
1220
|
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1223
|
+
*
|
|
1224
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1225
|
+
*/
|
|
980
1226
|
async shutdownProcessAndFactoryReset() {
|
|
981
1227
|
await this.cleanup('shutting down with factory reset...', false);
|
|
982
1228
|
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Cleans up the Matterbridge instance.
|
|
1231
|
+
*
|
|
1232
|
+
* @param {string} message - The cleanup message.
|
|
1233
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1234
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1235
|
+
*
|
|
1236
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1237
|
+
*/
|
|
983
1238
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
984
1239
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
985
1240
|
this.emit('cleanup_started');
|
|
986
1241
|
this.hasCleanupStarted = true;
|
|
987
1242
|
this.log.info(message);
|
|
1243
|
+
// Clear the start matter interval
|
|
988
1244
|
if (this.startMatterInterval) {
|
|
989
1245
|
clearInterval(this.startMatterInterval);
|
|
990
1246
|
this.startMatterInterval = undefined;
|
|
991
1247
|
this.log.debug('Start matter interval cleared');
|
|
992
1248
|
}
|
|
1249
|
+
// Clear the check update timeout
|
|
993
1250
|
if (this.checkUpdateTimeout) {
|
|
994
1251
|
clearTimeout(this.checkUpdateTimeout);
|
|
995
1252
|
this.checkUpdateTimeout = undefined;
|
|
996
1253
|
this.log.debug('Check update timeout cleared');
|
|
997
1254
|
}
|
|
1255
|
+
// Clear the check update interval
|
|
998
1256
|
if (this.checkUpdateInterval) {
|
|
999
1257
|
clearInterval(this.checkUpdateInterval);
|
|
1000
1258
|
this.checkUpdateInterval = undefined;
|
|
1001
1259
|
this.log.debug('Check update interval cleared');
|
|
1002
1260
|
}
|
|
1261
|
+
// Clear the configure timeout
|
|
1003
1262
|
if (this.configureTimeout) {
|
|
1004
1263
|
clearTimeout(this.configureTimeout);
|
|
1005
1264
|
this.configureTimeout = undefined;
|
|
1006
1265
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1007
1266
|
}
|
|
1267
|
+
// Clear the reachability timeout
|
|
1008
1268
|
if (this.reachabilityTimeout) {
|
|
1009
1269
|
clearTimeout(this.reachabilityTimeout);
|
|
1010
1270
|
this.reachabilityTimeout = undefined;
|
|
1011
1271
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1012
1272
|
}
|
|
1273
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1013
1274
|
for (const plugin of this.plugins) {
|
|
1014
1275
|
if (!plugin.enabled || plugin.error)
|
|
1015
1276
|
continue;
|
|
@@ -1020,6 +1281,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1020
1281
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1021
1282
|
}
|
|
1022
1283
|
}
|
|
1284
|
+
// Stop matter server nodes
|
|
1023
1285
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1024
1286
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1025
1287
|
await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
|
|
@@ -1044,6 +1306,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1044
1306
|
}
|
|
1045
1307
|
}
|
|
1046
1308
|
this.log.notice('Stopped matter server nodes');
|
|
1309
|
+
// Matter commisioning reset
|
|
1047
1310
|
if (message === 'shutting down with reset...') {
|
|
1048
1311
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1049
1312
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1053,6 +1316,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1053
1316
|
await this.matterbridgeContext?.clearAll();
|
|
1054
1317
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1055
1318
|
}
|
|
1319
|
+
// Unregister all devices
|
|
1056
1320
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1057
1321
|
if (this.bridgeMode === 'bridge') {
|
|
1058
1322
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1070,12 +1334,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
1070
1334
|
}
|
|
1071
1335
|
this.log.info('Matter storage reset done!');
|
|
1072
1336
|
}
|
|
1337
|
+
// Stop matter storage
|
|
1073
1338
|
await this.stopMatterStorage();
|
|
1339
|
+
// Stop the frontend
|
|
1074
1340
|
await this.frontend.stop();
|
|
1341
|
+
// Close the matterbridge node storage and context
|
|
1075
1342
|
if (this.nodeStorage && this.nodeContext) {
|
|
1343
|
+
/*
|
|
1344
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1345
|
+
this.log.info('Saving registered devices...');
|
|
1346
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1347
|
+
this.devices.forEach(async (device) => {
|
|
1348
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1349
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1350
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1351
|
+
});
|
|
1352
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1353
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1354
|
+
*/
|
|
1355
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1076
1356
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1077
1357
|
await this.nodeContext.close();
|
|
1078
1358
|
this.nodeContext = undefined;
|
|
1359
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1079
1360
|
for (const plugin of this.plugins) {
|
|
1080
1361
|
if (plugin.nodeContext) {
|
|
1081
1362
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1092,8 +1373,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1092
1373
|
}
|
|
1093
1374
|
this.plugins.clear();
|
|
1094
1375
|
this.devices.clear();
|
|
1376
|
+
// Factory reset
|
|
1095
1377
|
if (message === 'shutting down with factory reset...') {
|
|
1096
1378
|
try {
|
|
1379
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1097
1380
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1098
1381
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1099
1382
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1102,11 +1385,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1102
1385
|
await fs.rm(backup, { recursive: true });
|
|
1103
1386
|
}
|
|
1104
1387
|
catch (error) {
|
|
1388
|
+
// istanbul ignore next if
|
|
1105
1389
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1106
1390
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1107
1391
|
}
|
|
1108
1392
|
}
|
|
1109
1393
|
try {
|
|
1394
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1110
1395
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1111
1396
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1112
1397
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1115,18 +1400,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1115
1400
|
await fs.rm(backup, { recursive: true });
|
|
1116
1401
|
}
|
|
1117
1402
|
catch (error) {
|
|
1403
|
+
// istanbul ignore next if
|
|
1118
1404
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1119
1405
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1120
1406
|
}
|
|
1121
1407
|
}
|
|
1122
1408
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1123
1409
|
}
|
|
1410
|
+
// Deregisters the process handlers
|
|
1124
1411
|
this.deregisterProcessHandlers();
|
|
1125
1412
|
if (restart) {
|
|
1126
1413
|
if (message === 'updating...') {
|
|
1127
1414
|
this.log.info('Cleanup completed. Updating...');
|
|
1128
1415
|
Matterbridge.instance = undefined;
|
|
1129
|
-
this.emit('update');
|
|
1416
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1130
1417
|
}
|
|
1131
1418
|
else if (message === 'restarting...') {
|
|
1132
1419
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1147,6 +1434,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1147
1434
|
this.log.debug('Cleanup already started...');
|
|
1148
1435
|
}
|
|
1149
1436
|
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Creates and configures the server node for a single not bridged device.
|
|
1439
|
+
*
|
|
1440
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1441
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1442
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1443
|
+
*/
|
|
1150
1444
|
async createDeviceServerNode(plugin, device) {
|
|
1151
1445
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1152
1446
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1157,6 +1451,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1157
1451
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1158
1452
|
}
|
|
1159
1453
|
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1456
|
+
*
|
|
1457
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1458
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1459
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1460
|
+
*/
|
|
1160
1461
|
async createAccessoryPlugin(plugin, device) {
|
|
1161
1462
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1162
1463
|
plugin.locked = true;
|
|
@@ -1167,6 +1468,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1167
1468
|
await plugin.serverNode.add(device);
|
|
1168
1469
|
}
|
|
1169
1470
|
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
1473
|
+
*
|
|
1474
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1475
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
1476
|
+
*/
|
|
1170
1477
|
async createDynamicPlugin(plugin) {
|
|
1171
1478
|
if (!plugin.locked) {
|
|
1172
1479
|
plugin.locked = true;
|
|
@@ -1176,7 +1483,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1176
1483
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1177
1484
|
}
|
|
1178
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Starts the Matterbridge in bridge mode.
|
|
1488
|
+
*
|
|
1489
|
+
* @private
|
|
1490
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1491
|
+
*/
|
|
1179
1492
|
async startBridge() {
|
|
1493
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1180
1494
|
if (!this.matterStorageManager)
|
|
1181
1495
|
throw new Error('No storage manager initialized');
|
|
1182
1496
|
if (!this.matterbridgeContext)
|
|
@@ -1215,13 +1529,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1215
1529
|
clearInterval(this.startMatterInterval);
|
|
1216
1530
|
this.startMatterInterval = undefined;
|
|
1217
1531
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1218
|
-
|
|
1532
|
+
// Start the Matter server node
|
|
1533
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1534
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1219
1535
|
for (const device of this.devices.array()) {
|
|
1220
1536
|
if (device.mode === 'server' && device.serverNode) {
|
|
1221
1537
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1222
|
-
this.startServerNode(device.serverNode);
|
|
1538
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1223
1539
|
}
|
|
1224
1540
|
}
|
|
1541
|
+
// Configure the plugins
|
|
1225
1542
|
this.configureTimeout = setTimeout(async () => {
|
|
1226
1543
|
for (const plugin of this.plugins.array()) {
|
|
1227
1544
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1239,16 +1556,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
1239
1556
|
}
|
|
1240
1557
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1241
1558
|
}, 30 * 1000).unref();
|
|
1559
|
+
// Setting reachability to true
|
|
1242
1560
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1243
1561
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1244
1562
|
if (this.aggregatorNode)
|
|
1245
1563
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1246
1564
|
}, 60 * 1000).unref();
|
|
1565
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1247
1566
|
this.emit('bridge_started');
|
|
1248
1567
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1568
|
+
this.frontend.wssSendRefreshRequired('settings');
|
|
1249
1569
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1250
1570
|
}, this.startMatterIntervalMs);
|
|
1251
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1574
|
+
*
|
|
1575
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1576
|
+
*
|
|
1577
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1578
|
+
*/
|
|
1252
1579
|
async startChildbridge(delay = 1000) {
|
|
1253
1580
|
if (!this.matterStorageManager)
|
|
1254
1581
|
throw new Error('No storage manager initialized');
|
|
@@ -1289,8 +1616,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1289
1616
|
clearInterval(this.startMatterInterval);
|
|
1290
1617
|
this.startMatterInterval = undefined;
|
|
1291
1618
|
if (delay > 0)
|
|
1292
|
-
await wait(delay);
|
|
1619
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1293
1620
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1621
|
+
// Configure the plugins
|
|
1294
1622
|
this.configureTimeout = setTimeout(async () => {
|
|
1295
1623
|
for (const plugin of this.plugins.array()) {
|
|
1296
1624
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1327,27 +1655,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1327
1655
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1328
1656
|
continue;
|
|
1329
1657
|
}
|
|
1330
|
-
|
|
1658
|
+
// Start the Matter server node
|
|
1659
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1660
|
+
// Setting reachability to true
|
|
1331
1661
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1332
1662
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf} type ${plugin.type} server node ${plugin.serverNode !== undefined} aggregator node ${plugin.aggregatorNode !== undefined} device ${plugin.device !== undefined}`);
|
|
1333
1663
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1334
1664
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1335
1665
|
}, 60 * 1000).unref();
|
|
1336
1666
|
}
|
|
1667
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1337
1668
|
for (const device of this.devices.array()) {
|
|
1338
1669
|
if (device.mode === 'server' && device.serverNode) {
|
|
1339
1670
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1340
|
-
this.startServerNode(device.serverNode);
|
|
1671
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1341
1672
|
}
|
|
1342
1673
|
}
|
|
1674
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1343
1675
|
this.emit('childbridge_started');
|
|
1344
1676
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1677
|
+
this.frontend.wssSendRefreshRequired('settings');
|
|
1345
1678
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1346
1679
|
}, this.startMatterIntervalMs);
|
|
1347
1680
|
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Starts the Matterbridge controller.
|
|
1683
|
+
*
|
|
1684
|
+
* @private
|
|
1685
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1686
|
+
*/
|
|
1348
1687
|
async startController() {
|
|
1688
|
+
/*
|
|
1689
|
+
if (!this.matterStorageManager) {
|
|
1690
|
+
this.log.error('No storage manager initialized');
|
|
1691
|
+
await this.cleanup('No storage manager initialized');
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1695
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1696
|
+
if (!this.controllerContext) {
|
|
1697
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1698
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1703
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1704
|
+
this.log.info('Creating matter commissioning controller');
|
|
1705
|
+
this.commissioningController = new CommissioningController({
|
|
1706
|
+
autoConnect: false,
|
|
1707
|
+
});
|
|
1708
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1709
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1710
|
+
|
|
1711
|
+
this.log.info('Starting matter server');
|
|
1712
|
+
await this.matterServer.start();
|
|
1713
|
+
this.log.info('Matter server started');
|
|
1714
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1715
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1716
|
+
regulatoryCountryCode: 'XX',
|
|
1717
|
+
};
|
|
1718
|
+
const commissioningController = new CommissioningController({
|
|
1719
|
+
environment: {
|
|
1720
|
+
environment,
|
|
1721
|
+
id: uniqueId,
|
|
1722
|
+
},
|
|
1723
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1724
|
+
adminFabricLabel,
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
if (hasParameter('pairingcode')) {
|
|
1728
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1729
|
+
const pairingCode = getParameter('pairingcode');
|
|
1730
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1731
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1732
|
+
|
|
1733
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1734
|
+
if (pairingCode !== undefined) {
|
|
1735
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1736
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1737
|
+
longDiscriminator = undefined;
|
|
1738
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1739
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1740
|
+
} else {
|
|
1741
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1742
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1743
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1744
|
+
}
|
|
1745
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1746
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const options = {
|
|
1750
|
+
commissioning: commissioningOptions,
|
|
1751
|
+
discovery: {
|
|
1752
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1753
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1754
|
+
},
|
|
1755
|
+
passcode: setupPin,
|
|
1756
|
+
} as NodeCommissioningOptions;
|
|
1757
|
+
this.log.info('Commissioning with options:', options);
|
|
1758
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1759
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1760
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1761
|
+
} // (hasParameter('pairingcode'))
|
|
1762
|
+
|
|
1763
|
+
if (hasParameter('unpairall')) {
|
|
1764
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1765
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1766
|
+
for (const nodeId of nodeIds) {
|
|
1767
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1768
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1769
|
+
}
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
if (hasParameter('discover')) {
|
|
1774
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1775
|
+
// console.log(discover);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1779
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1784
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1785
|
+
for (const nodeId of nodeIds) {
|
|
1786
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1787
|
+
|
|
1788
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1789
|
+
autoSubscribe: false,
|
|
1790
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1791
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1792
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1793
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1794
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1795
|
+
switch (info) {
|
|
1796
|
+
case NodeStateInformation.Connected:
|
|
1797
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1798
|
+
break;
|
|
1799
|
+
case NodeStateInformation.Disconnected:
|
|
1800
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1801
|
+
break;
|
|
1802
|
+
case NodeStateInformation.Reconnecting:
|
|
1803
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1804
|
+
break;
|
|
1805
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1806
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1807
|
+
break;
|
|
1808
|
+
case NodeStateInformation.StructureChanged:
|
|
1809
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1810
|
+
break;
|
|
1811
|
+
case NodeStateInformation.Decommissioned:
|
|
1812
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1813
|
+
break;
|
|
1814
|
+
default:
|
|
1815
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1816
|
+
break;
|
|
1817
|
+
}
|
|
1818
|
+
},
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1821
|
+
node.logStructure();
|
|
1822
|
+
|
|
1823
|
+
// Get the interaction client
|
|
1824
|
+
this.log.info('Getting the interaction client');
|
|
1825
|
+
const interactionClient = await node.getInteractionClient();
|
|
1826
|
+
let cluster;
|
|
1827
|
+
let attributes;
|
|
1828
|
+
|
|
1829
|
+
// Log BasicInformationCluster
|
|
1830
|
+
cluster = BasicInformationCluster;
|
|
1831
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1832
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1833
|
+
});
|
|
1834
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1835
|
+
attributes.forEach((attribute) => {
|
|
1836
|
+
this.log.info(
|
|
1837
|
+
`- 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}`,
|
|
1838
|
+
);
|
|
1839
|
+
});
|
|
1840
|
+
|
|
1841
|
+
// Log PowerSourceCluster
|
|
1842
|
+
cluster = PowerSourceCluster;
|
|
1843
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1844
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1845
|
+
});
|
|
1846
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1847
|
+
attributes.forEach((attribute) => {
|
|
1848
|
+
this.log.info(
|
|
1849
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1850
|
+
);
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
// Log ThreadNetworkDiagnostics
|
|
1854
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1855
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1856
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1857
|
+
});
|
|
1858
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1859
|
+
attributes.forEach((attribute) => {
|
|
1860
|
+
this.log.info(
|
|
1861
|
+
`- 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}`,
|
|
1862
|
+
);
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
// Log SwitchCluster
|
|
1866
|
+
cluster = SwitchCluster;
|
|
1867
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1868
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1869
|
+
});
|
|
1870
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1871
|
+
attributes.forEach((attribute) => {
|
|
1872
|
+
this.log.info(
|
|
1873
|
+
`- 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}`,
|
|
1874
|
+
);
|
|
1875
|
+
});
|
|
1876
|
+
|
|
1877
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1878
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1879
|
+
ignoreInitialTriggers: false,
|
|
1880
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1881
|
+
this.log.info(
|
|
1882
|
+
`***${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}`,
|
|
1883
|
+
),
|
|
1884
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1885
|
+
this.log.info(
|
|
1886
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1887
|
+
);
|
|
1888
|
+
},
|
|
1889
|
+
});
|
|
1890
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1891
|
+
}
|
|
1892
|
+
*/
|
|
1349
1893
|
}
|
|
1894
|
+
/** */
|
|
1895
|
+
/** Matter.js methods */
|
|
1896
|
+
/** */
|
|
1897
|
+
/**
|
|
1898
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1899
|
+
*
|
|
1900
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1901
|
+
*/
|
|
1350
1902
|
async startMatterStorage() {
|
|
1903
|
+
// Setup Matter storage
|
|
1351
1904
|
this.log.info(`Starting matter node storage...`);
|
|
1352
1905
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1353
1906
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1355,8 +1908,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1355
1908
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1356
1909
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1357
1910
|
this.log.info('Matter node storage started');
|
|
1911
|
+
// Backup matter storage since it is created/opened correctly
|
|
1358
1912
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1359
1913
|
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1916
|
+
*
|
|
1917
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1918
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1919
|
+
* @private
|
|
1920
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1921
|
+
*/
|
|
1360
1922
|
async backupMatterStorage(storageName, backupName) {
|
|
1361
1923
|
this.log.info('Creating matter node storage backup...');
|
|
1362
1924
|
try {
|
|
@@ -1367,6 +1929,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1367
1929
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1368
1930
|
}
|
|
1369
1931
|
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Stops the matter storage.
|
|
1934
|
+
*
|
|
1935
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1936
|
+
*/
|
|
1370
1937
|
async stopMatterStorage() {
|
|
1371
1938
|
this.log.info('Closing matter node storage...');
|
|
1372
1939
|
await this.matterStorageManager?.close();
|
|
@@ -1375,6 +1942,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1375
1942
|
this.matterbridgeContext = undefined;
|
|
1376
1943
|
this.log.info('Matter node storage closed');
|
|
1377
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Creates a server node storage context.
|
|
1947
|
+
*
|
|
1948
|
+
* @param {string} storeId - The storeId.
|
|
1949
|
+
* @param {string} deviceName - The name of the device.
|
|
1950
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1951
|
+
* @param {number} vendorId - The vendor ID.
|
|
1952
|
+
* @param {string} vendorName - The vendor name.
|
|
1953
|
+
* @param {number} productId - The product ID.
|
|
1954
|
+
* @param {string} productName - The product name.
|
|
1955
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1956
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1957
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1958
|
+
*/
|
|
1378
1959
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1379
1960
|
const { randomBytes } = await import('node:crypto');
|
|
1380
1961
|
if (!this.matterStorageService)
|
|
@@ -1414,6 +1995,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1414
1995
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1415
1996
|
return storageContext;
|
|
1416
1997
|
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Creates a server node.
|
|
2000
|
+
*
|
|
2001
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2002
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2003
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2004
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2005
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2006
|
+
*/
|
|
1417
2007
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1418
2008
|
const storeId = await storageContext.get('storeId');
|
|
1419
2009
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1423,24 +2013,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1423
2013
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1424
2014
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1425
2015
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2016
|
+
/**
|
|
2017
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2018
|
+
*/
|
|
1426
2019
|
const serverNode = await ServerNode.create({
|
|
2020
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1427
2021
|
id: storeId,
|
|
2022
|
+
// Provide Network relevant configuration like the port
|
|
2023
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1428
2024
|
network: {
|
|
1429
2025
|
listeningAddressIpv4: this.ipv4address,
|
|
1430
2026
|
listeningAddressIpv6: this.ipv6address,
|
|
1431
2027
|
port,
|
|
1432
2028
|
},
|
|
2029
|
+
// Provide the certificate for the device
|
|
1433
2030
|
operationalCredentials: {
|
|
1434
2031
|
certification: this.certification,
|
|
1435
2032
|
},
|
|
2033
|
+
// Provide Commissioning relevant settings
|
|
2034
|
+
// Optional for development/testing purposes
|
|
1436
2035
|
commissioning: {
|
|
1437
2036
|
passcode,
|
|
1438
2037
|
discriminator,
|
|
1439
2038
|
},
|
|
2039
|
+
// Provide Node announcement settings
|
|
2040
|
+
// Optional: If Ommitted some development defaults are used
|
|
1440
2041
|
productDescription: {
|
|
1441
2042
|
name: await storageContext.get('deviceName'),
|
|
1442
2043
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1443
2044
|
},
|
|
2045
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2046
|
+
// Optional: If Omitted some development defaults are used
|
|
1444
2047
|
basicInformation: {
|
|
1445
2048
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1446
2049
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1457,17 +2060,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1457
2060
|
reachable: true,
|
|
1458
2061
|
},
|
|
1459
2062
|
});
|
|
2063
|
+
/**
|
|
2064
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2065
|
+
* This means: It is added to the first fabric.
|
|
2066
|
+
*/
|
|
1460
2067
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1461
2068
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1462
2069
|
this.advertisingNodes.delete(storeId);
|
|
1463
2070
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1464
2071
|
});
|
|
2072
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1465
2073
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1466
2074
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1467
2075
|
this.advertisingNodes.delete(storeId);
|
|
1468
2076
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1469
2077
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1470
2078
|
});
|
|
2079
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1471
2080
|
serverNode.lifecycle.online.on(async () => {
|
|
1472
2081
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1473
2082
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1478,13 +2087,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1478
2087
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1479
2088
|
}
|
|
1480
2089
|
else {
|
|
2090
|
+
// istanbul ignore next
|
|
1481
2091
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2092
|
+
// istanbul ignore next
|
|
1482
2093
|
this.advertisingNodes.delete(storeId);
|
|
1483
2094
|
}
|
|
1484
2095
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1485
2096
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1486
2097
|
this.emit('online', storeId);
|
|
1487
2098
|
});
|
|
2099
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1488
2100
|
serverNode.lifecycle.offline.on(() => {
|
|
1489
2101
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1490
2102
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1492,11 +2104,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1492
2104
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1493
2105
|
this.emit('offline', storeId);
|
|
1494
2106
|
});
|
|
2107
|
+
/**
|
|
2108
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2109
|
+
* information is needed.
|
|
2110
|
+
*/
|
|
1495
2111
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1496
2112
|
let action = '';
|
|
1497
2113
|
switch (fabricAction) {
|
|
1498
2114
|
case FabricAction.Added:
|
|
1499
|
-
this.advertisingNodes.delete(storeId);
|
|
2115
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1500
2116
|
action = 'added';
|
|
1501
2117
|
break;
|
|
1502
2118
|
case FabricAction.Removed:
|
|
@@ -1509,14 +2125,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1509
2125
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1510
2126
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1511
2127
|
});
|
|
2128
|
+
/**
|
|
2129
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2130
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2131
|
+
*/
|
|
1512
2132
|
serverNode.events.sessions.opened.on((session) => {
|
|
1513
2133
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1514
2134
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1515
2135
|
});
|
|
2136
|
+
/**
|
|
2137
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2138
|
+
*/
|
|
1516
2139
|
serverNode.events.sessions.closed.on((session) => {
|
|
1517
2140
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1518
2141
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1519
2142
|
});
|
|
2143
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1520
2144
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1521
2145
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1522
2146
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1524,6 +2148,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1524
2148
|
this.log.info(`Created server node for ${storeId}`);
|
|
1525
2149
|
return serverNode;
|
|
1526
2150
|
}
|
|
2151
|
+
/**
|
|
2152
|
+
* Gets the matter sanitized data of the specified server node.
|
|
2153
|
+
*
|
|
2154
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2155
|
+
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2156
|
+
*/
|
|
1527
2157
|
getServerNodeData(serverNode) {
|
|
1528
2158
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1529
2159
|
return {
|
|
@@ -1540,12 +2170,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1540
2170
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1541
2171
|
};
|
|
1542
2172
|
}
|
|
2173
|
+
/**
|
|
2174
|
+
* Starts the specified server node.
|
|
2175
|
+
*
|
|
2176
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2177
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2178
|
+
*/
|
|
1543
2179
|
async startServerNode(matterServerNode) {
|
|
1544
2180
|
if (!matterServerNode)
|
|
1545
2181
|
return;
|
|
1546
2182
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1547
2183
|
await matterServerNode.start();
|
|
1548
2184
|
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Stops the specified server node.
|
|
2187
|
+
*
|
|
2188
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2189
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2190
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2191
|
+
*/
|
|
1549
2192
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1550
2193
|
if (!matterServerNode)
|
|
1551
2194
|
return;
|
|
@@ -1558,13 +2201,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1558
2201
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1559
2202
|
}
|
|
1560
2203
|
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Creates an aggregator node with the specified storage context.
|
|
2206
|
+
*
|
|
2207
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2208
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2209
|
+
*/
|
|
1561
2210
|
async createAggregatorNode(storageContext) {
|
|
1562
2211
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1563
2212
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1564
2213
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1565
2214
|
return aggregatorNode;
|
|
1566
2215
|
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2218
|
+
*
|
|
2219
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2220
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2221
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2222
|
+
*/
|
|
1567
2223
|
async addBridgedEndpoint(pluginName, device) {
|
|
2224
|
+
// Check if the plugin is registered
|
|
1568
2225
|
const plugin = this.plugins.get(pluginName);
|
|
1569
2226
|
if (!plugin) {
|
|
1570
2227
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1584,6 +2241,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1584
2241
|
}
|
|
1585
2242
|
else if (this.bridgeMode === 'bridge') {
|
|
1586
2243
|
if (device.mode === 'matter') {
|
|
2244
|
+
// Register and add the device to the matterbridge server node
|
|
1587
2245
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1588
2246
|
if (!this.serverNode) {
|
|
1589
2247
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1600,6 +2258,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1600
2258
|
}
|
|
1601
2259
|
}
|
|
1602
2260
|
else {
|
|
2261
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1603
2262
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1604
2263
|
if (!this.aggregatorNode) {
|
|
1605
2264
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1617,6 +2276,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1617
2276
|
}
|
|
1618
2277
|
}
|
|
1619
2278
|
else if (this.bridgeMode === 'childbridge') {
|
|
2279
|
+
// Register and add the device to the plugin server node
|
|
1620
2280
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1621
2281
|
try {
|
|
1622
2282
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1640,10 +2300,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1640
2300
|
return;
|
|
1641
2301
|
}
|
|
1642
2302
|
}
|
|
2303
|
+
// Register and add the device to the plugin aggregator node
|
|
1643
2304
|
if (plugin.type === 'DynamicPlatform') {
|
|
1644
2305
|
try {
|
|
1645
2306
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1646
2307
|
await this.createDynamicPlugin(plugin);
|
|
2308
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1647
2309
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1648
2310
|
if (!plugin.aggregatorNode) {
|
|
1649
2311
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1664,17 +2326,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1664
2326
|
}
|
|
1665
2327
|
if (plugin.registeredDevices !== undefined)
|
|
1666
2328
|
plugin.registeredDevices++;
|
|
2329
|
+
// Add the device to the DeviceManager
|
|
1667
2330
|
this.devices.set(device);
|
|
2331
|
+
// Subscribe to the reachable$Changed event
|
|
1668
2332
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1669
2333
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1670
2334
|
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2337
|
+
*
|
|
2338
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2339
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2340
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2341
|
+
*/
|
|
1671
2342
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1672
2343
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2344
|
+
// Check if the plugin is registered
|
|
1673
2345
|
const plugin = this.plugins.get(pluginName);
|
|
1674
2346
|
if (!plugin) {
|
|
1675
2347
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1676
2348
|
return;
|
|
1677
2349
|
}
|
|
2350
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1678
2351
|
if (this.bridgeMode === 'bridge') {
|
|
1679
2352
|
if (!this.aggregatorNode) {
|
|
1680
2353
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1687,6 +2360,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1687
2360
|
}
|
|
1688
2361
|
else if (this.bridgeMode === 'childbridge') {
|
|
1689
2362
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2363
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1690
2364
|
}
|
|
1691
2365
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1692
2366
|
if (!plugin.aggregatorNode) {
|
|
@@ -1699,8 +2373,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1699
2373
|
if (plugin.registeredDevices !== undefined)
|
|
1700
2374
|
plugin.registeredDevices--;
|
|
1701
2375
|
}
|
|
2376
|
+
// Remove the device from the DeviceManager
|
|
1702
2377
|
this.devices.remove(device);
|
|
1703
2378
|
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2381
|
+
*
|
|
2382
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2383
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2384
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2385
|
+
*
|
|
2386
|
+
* @remarks
|
|
2387
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2388
|
+
* It also applies a delay between each removal if specified.
|
|
2389
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2390
|
+
*/
|
|
1704
2391
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1705
2392
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1706
2393
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1711,6 +2398,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1711
2398
|
if (delay > 0)
|
|
1712
2399
|
await wait(2000);
|
|
1713
2400
|
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2403
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2404
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2405
|
+
*
|
|
2406
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2407
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2408
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2409
|
+
*/
|
|
1714
2410
|
async subscribeAttributeChanged(plugin, device) {
|
|
1715
2411
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId)
|
|
1716
2412
|
return;
|
|
@@ -1718,16 +2414,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1718
2414
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1719
2415
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1720
2416
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2417
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1721
2418
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BasicInformationServer', 'reachable', reachable);
|
|
1722
2419
|
});
|
|
1723
2420
|
}
|
|
1724
2421
|
if (device.hasClusterServer(BridgedDeviceBasicInformationServer)) {
|
|
1725
2422
|
device.eventsOf(BridgedDeviceBasicInformationServer).reachable$Changed.on((reachable) => {
|
|
1726
2423
|
this.log.info(`Bridged endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2424
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1727
2425
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, 'BridgedDeviceBasicInformationServer', 'reachable', reachable);
|
|
1728
2426
|
});
|
|
1729
2427
|
}
|
|
1730
2428
|
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2431
|
+
*
|
|
2432
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2433
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2434
|
+
*/
|
|
1731
2435
|
sanitizeFabricInformations(fabricInfo) {
|
|
1732
2436
|
return fabricInfo.map((info) => {
|
|
1733
2437
|
return {
|
|
@@ -1741,6 +2445,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1741
2445
|
};
|
|
1742
2446
|
});
|
|
1743
2447
|
}
|
|
2448
|
+
/**
|
|
2449
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2450
|
+
*
|
|
2451
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2452
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2453
|
+
*/
|
|
1744
2454
|
sanitizeSessionInformation(sessions) {
|
|
1745
2455
|
return sessions
|
|
1746
2456
|
.filter((session) => session.isPeerActive)
|
|
@@ -1767,7 +2477,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1767
2477
|
};
|
|
1768
2478
|
});
|
|
1769
2479
|
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2482
|
+
*
|
|
2483
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2484
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2485
|
+
*/
|
|
2486
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1770
2487
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2488
|
+
/*
|
|
2489
|
+
for (const child of aggregatorNode.parts) {
|
|
2490
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2491
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2492
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2493
|
+
}
|
|
2494
|
+
*/
|
|
1771
2495
|
}
|
|
1772
2496
|
getVendorIdName = (vendorId) => {
|
|
1773
2497
|
if (!vendorId)
|
|
@@ -1807,10 +2531,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1807
2531
|
case 0x1488:
|
|
1808
2532
|
vendorName = '(ShortcutLabsFlic)';
|
|
1809
2533
|
break;
|
|
1810
|
-
case 65521:
|
|
2534
|
+
case 65521: // 0xFFF1
|
|
1811
2535
|
vendorName = '(MatterTest)';
|
|
1812
2536
|
break;
|
|
1813
2537
|
}
|
|
1814
2538
|
return vendorName;
|
|
1815
2539
|
};
|
|
1816
2540
|
}
|
|
2541
|
+
//# sourceMappingURL=matterbridge.js.map
|