matterbridge 3.2.7-dev-20250913-9d0d095 → 3.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- 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 +315 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +456 -25
- package/dist/frontend.js.map +1 -0
- package/dist/globalMatterbridge.d.ts +59 -0
- package/dist/globalMatterbridge.d.ts.map +1 -0
- package/dist/globalMatterbridge.js +47 -0
- package/dist/globalMatterbridge.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +465 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +789 -51
- 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 +1379 -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 +204 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +25 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +270 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +249 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +99 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +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 +40 -0
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/wait.d.ts +54 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2023-12-29
|
|
7
|
+
* @version 1.6.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// Node.js modules
|
|
1
25
|
import os from 'node:os';
|
|
2
26
|
import path from 'node:path';
|
|
3
27
|
import { promises as fs } from 'node:fs';
|
|
4
28
|
import EventEmitter from 'node:events';
|
|
5
29
|
import { inspect } from 'node:util';
|
|
30
|
+
// AnsiLogger module
|
|
6
31
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE } from 'node-ansi-logger';
|
|
32
|
+
// NodeStorage module
|
|
7
33
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
+
// @matter
|
|
8
35
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
9
36
|
import { DeviceCommissioner, FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
10
37
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
11
38
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
12
39
|
import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
|
|
40
|
+
// Matterbridge
|
|
13
41
|
import { getParameter, getIntParameter, hasParameter, copyDirectory, isValidString, parseVersionString, isValidNumber, createDirectory } from './utils/export.js';
|
|
14
42
|
import { withTimeout, waiter, wait } from './utils/wait.js';
|
|
15
43
|
import { dev, plg, typ } from './matterbridgeTypes.js';
|
|
@@ -19,6 +47,9 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
19
47
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
20
48
|
import { Frontend } from './frontend.js';
|
|
21
49
|
import { addVirtualDevices } from './helpers.js';
|
|
50
|
+
/**
|
|
51
|
+
* Represents the Matterbridge application.
|
|
52
|
+
*/
|
|
22
53
|
export class Matterbridge extends EventEmitter {
|
|
23
54
|
systemInformation = {
|
|
24
55
|
interfaceName: '',
|
|
@@ -67,7 +98,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
67
98
|
shellySysUpdate: false,
|
|
68
99
|
shellyMainUpdate: false,
|
|
69
100
|
profile: getParameter('profile'),
|
|
70
|
-
loggerLevel: "info"
|
|
101
|
+
loggerLevel: "info" /* LogLevel.INFO */,
|
|
71
102
|
fileLogger: false,
|
|
72
103
|
matterLoggerLevel: MatterLogLevel.INFO,
|
|
73
104
|
matterFileLogger: false,
|
|
@@ -95,15 +126,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
95
126
|
profile = getParameter('profile');
|
|
96
127
|
shutdown = false;
|
|
97
128
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
98
|
-
|
|
129
|
+
// Matterbridge log files
|
|
130
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
99
131
|
matterbridgeLoggerFile = 'matterbridge' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
100
132
|
matterLoggerFile = 'matter' + (getParameter('profile') ? '.' + getParameter('profile') : '') + '.log';
|
|
101
133
|
plugins = new PluginManager(this);
|
|
102
134
|
devices = new DeviceManager(this);
|
|
103
135
|
frontend = new Frontend(this);
|
|
136
|
+
// Matterbridge storage
|
|
104
137
|
nodeStorageName = 'storage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
105
138
|
nodeStorage;
|
|
106
139
|
nodeContext;
|
|
140
|
+
// Cleanup
|
|
107
141
|
hasCleanupStarted = false;
|
|
108
142
|
initialized = false;
|
|
109
143
|
startMatterInterval;
|
|
@@ -117,20 +151,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
117
151
|
sigtermHandler;
|
|
118
152
|
exceptionHandler;
|
|
119
153
|
rejectionHandler;
|
|
120
|
-
|
|
154
|
+
// Matter logger
|
|
155
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
156
|
+
// Matter environment
|
|
121
157
|
environment = Environment.default;
|
|
158
|
+
// Matter storage
|
|
122
159
|
matterStorageName = 'matterstorage' + (getParameter('profile') ? '.' + getParameter('profile') : '');
|
|
123
160
|
matterStorageService;
|
|
124
161
|
matterStorageManager;
|
|
125
162
|
matterbridgeContext;
|
|
126
163
|
controllerContext;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
164
|
+
// Matter parameters
|
|
165
|
+
mdnsInterface; // matter server node mdnsInterface: e.g. 'eth0' or 'wlan0' or 'WiFi'
|
|
166
|
+
ipv4address; // matter server node listeningAddressIpv4
|
|
167
|
+
ipv6address; // matter server node listeningAddressIpv6
|
|
168
|
+
port; // first server node port
|
|
169
|
+
passcode; // first server node passcode
|
|
170
|
+
discriminator; // first server node discriminator
|
|
171
|
+
certification; // device certification
|
|
172
|
+
// Matter nodes
|
|
134
173
|
serverNode;
|
|
135
174
|
aggregatorNode;
|
|
136
175
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -142,15 +181,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
142
181
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
143
182
|
advertisingNodes = new Map();
|
|
144
183
|
static instance;
|
|
184
|
+
// We load asyncronously so is private
|
|
145
185
|
constructor() {
|
|
146
186
|
super();
|
|
147
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Retrieves the list of Matterbridge devices.
|
|
190
|
+
*
|
|
191
|
+
* @returns {MatterbridgeEndpoint[]} An array of MatterbridgeDevice objects.
|
|
192
|
+
*/
|
|
148
193
|
getDevices() {
|
|
149
194
|
return this.devices.array();
|
|
150
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Retrieves the list of registered plugins.
|
|
198
|
+
*
|
|
199
|
+
* @returns {RegisteredPlugin[]} An array of RegisteredPlugin objects.
|
|
200
|
+
*/
|
|
151
201
|
getPlugins() {
|
|
152
202
|
return this.plugins.array();
|
|
153
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
206
|
+
*
|
|
207
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
208
|
+
*/
|
|
154
209
|
async setLogLevel(logLevel) {
|
|
155
210
|
if (this.log)
|
|
156
211
|
this.log.logLevel = logLevel;
|
|
@@ -164,19 +219,31 @@ export class Matterbridge extends EventEmitter {
|
|
|
164
219
|
for (const plugin of this.plugins) {
|
|
165
220
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
166
221
|
continue;
|
|
167
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
168
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
222
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
223
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
224
|
+
}
|
|
225
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
226
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
227
|
+
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
228
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
229
|
+
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
230
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
175
231
|
AnsiLogger.setGlobalCallback(this.frontend.wssSendMessage.bind(this.frontend), callbackLogLevel);
|
|
176
232
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
177
233
|
}
|
|
234
|
+
//* ************************************************************************************************************************************ */
|
|
235
|
+
// loadInstance() and cleanup() methods */
|
|
236
|
+
//* ************************************************************************************************************************************ */
|
|
237
|
+
/**
|
|
238
|
+
* Loads an instance of the Matterbridge class.
|
|
239
|
+
* If an instance already exists, return that instance.
|
|
240
|
+
*
|
|
241
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
242
|
+
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
243
|
+
*/
|
|
178
244
|
static async loadInstance(initialize = false) {
|
|
179
245
|
if (!Matterbridge.instance) {
|
|
246
|
+
// eslint-disable-next-line no-console
|
|
180
247
|
if (hasParameter('debug'))
|
|
181
248
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
182
249
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -185,8 +252,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
185
252
|
}
|
|
186
253
|
return Matterbridge.instance;
|
|
187
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Call cleanup() and dispose MdnsService.
|
|
257
|
+
*
|
|
258
|
+
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
259
|
+
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
260
|
+
*
|
|
261
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
262
|
+
*/
|
|
188
263
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
189
264
|
this.log.info(`Destroy instance...`);
|
|
265
|
+
// Save server nodes to close
|
|
190
266
|
const servers = [];
|
|
191
267
|
if (this.bridgeMode === 'bridge') {
|
|
192
268
|
if (this.serverNode)
|
|
@@ -204,72 +280,105 @@ export class Matterbridge extends EventEmitter {
|
|
|
204
280
|
servers.push(device.serverNode);
|
|
205
281
|
}
|
|
206
282
|
}
|
|
283
|
+
// Let any already‐queued microtasks run first
|
|
207
284
|
await Promise.resolve();
|
|
285
|
+
// Wait for the cleanup to finish
|
|
208
286
|
await wait(pause, 'destroyInstance start', true);
|
|
287
|
+
// Cleanup
|
|
209
288
|
await this.cleanup('destroying instance...', false, timeout);
|
|
289
|
+
// Close servers mdns service
|
|
210
290
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
211
291
|
for (const server of servers) {
|
|
212
292
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
213
293
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
214
294
|
}
|
|
295
|
+
// Let any already‐queued microtasks run first
|
|
215
296
|
await Promise.resolve();
|
|
297
|
+
// Wait for the cleanup to finish
|
|
216
298
|
await wait(pause, 'destroyInstance stop', true);
|
|
217
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Initializes the Matterbridge application.
|
|
302
|
+
*
|
|
303
|
+
* @remarks
|
|
304
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
305
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
306
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
307
|
+
*
|
|
308
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
309
|
+
*/
|
|
218
310
|
async initialize() {
|
|
311
|
+
// Emit the initialize_started event
|
|
219
312
|
this.emit('initialize_started');
|
|
313
|
+
// Set the restart mode
|
|
220
314
|
if (hasParameter('service'))
|
|
221
315
|
this.restartMode = 'service';
|
|
222
316
|
if (hasParameter('docker'))
|
|
223
317
|
this.restartMode = 'docker';
|
|
318
|
+
// Set the matterbridge home directory
|
|
224
319
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
225
320
|
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
226
321
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
322
|
+
// Set the matterbridge directory
|
|
227
323
|
this.matterbridgeDirectory = path.join(this.homeDirectory, '.matterbridge');
|
|
228
324
|
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
229
325
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
230
326
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
231
327
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
328
|
+
// Set the matterbridge plugin directory
|
|
232
329
|
this.matterbridgePluginDirectory = path.join(this.homeDirectory, 'Matterbridge');
|
|
233
330
|
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
234
331
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
332
|
+
// Set the matterbridge cert directory
|
|
235
333
|
this.matterbridgeCertDirectory = path.join(this.homeDirectory, '.mattercert');
|
|
236
334
|
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
237
335
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
336
|
+
// Set the matterbridge root directory
|
|
238
337
|
const { fileURLToPath } = await import('node:url');
|
|
239
338
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
240
339
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
241
340
|
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
341
|
+
// Setup the matter environment
|
|
242
342
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
243
343
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
244
344
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
|
|
245
345
|
this.environment.vars.set('runtime.signals', false);
|
|
246
346
|
this.environment.vars.set('runtime.exitcode', false);
|
|
347
|
+
// Register process handlers
|
|
247
348
|
this.registerProcessHandlers();
|
|
349
|
+
// Initialize nodeStorage and nodeContext
|
|
248
350
|
try {
|
|
249
351
|
this.log.debug(`Creating node storage manager: ${CYAN}${this.nodeStorageName}${db}`);
|
|
250
352
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, this.nodeStorageName), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
251
353
|
this.log.debug('Creating node storage context for matterbridge');
|
|
252
354
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
355
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
356
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
357
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
254
358
|
for (const key of keys) {
|
|
255
359
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
256
361
|
await this.nodeStorage?.storage.get(key);
|
|
257
362
|
}
|
|
258
363
|
const storages = await this.nodeStorage.getStorageNames();
|
|
259
364
|
for (const storage of storages) {
|
|
260
365
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
261
366
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
367
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
368
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
262
369
|
const keys = (await nodeContext?.storage.keys());
|
|
263
370
|
keys.forEach(async (key) => {
|
|
264
371
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
265
372
|
await nodeContext?.get(key);
|
|
266
373
|
});
|
|
267
374
|
}
|
|
375
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
268
376
|
this.log.debug('Creating node storage backup...');
|
|
269
377
|
await copyDirectory(path.join(this.matterbridgeDirectory, this.nodeStorageName), path.join(this.matterbridgeDirectory, this.nodeStorageName + '.backup'));
|
|
270
378
|
this.log.debug('Created node storage backup');
|
|
271
379
|
}
|
|
272
380
|
catch (error) {
|
|
381
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
273
382
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
274
383
|
if (hasParameter('norestore')) {
|
|
275
384
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -283,14 +392,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
283
392
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
284
393
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
285
394
|
}
|
|
395
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
286
396
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
397
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
287
398
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
399
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
288
400
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
401
|
+
// Certificate management
|
|
289
402
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
290
403
|
try {
|
|
291
404
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
292
405
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
293
406
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
407
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
294
408
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
295
409
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
296
410
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -319,11 +433,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
319
433
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
320
434
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
321
435
|
}
|
|
436
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
322
437
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
323
438
|
this.passcode = pairingFileJson.passcode;
|
|
324
439
|
this.discriminator = pairingFileJson.discriminator;
|
|
325
440
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
326
441
|
}
|
|
442
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
327
443
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
328
444
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
329
445
|
this.certification = {
|
|
@@ -338,41 +454,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
338
454
|
catch (error) {
|
|
339
455
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
340
456
|
}
|
|
457
|
+
// Store the passcode, discriminator and port in the node context
|
|
341
458
|
await this.nodeContext.set('matterport', this.port);
|
|
342
459
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
343
460
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
344
461
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
462
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
345
463
|
if (hasParameter('logger')) {
|
|
346
464
|
const level = getParameter('logger');
|
|
347
465
|
if (level === 'debug') {
|
|
348
|
-
this.log.logLevel = "debug"
|
|
466
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
349
467
|
}
|
|
350
468
|
else if (level === 'info') {
|
|
351
|
-
this.log.logLevel = "info"
|
|
469
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
352
470
|
}
|
|
353
471
|
else if (level === 'notice') {
|
|
354
|
-
this.log.logLevel = "notice"
|
|
472
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
355
473
|
}
|
|
356
474
|
else if (level === 'warn') {
|
|
357
|
-
this.log.logLevel = "warn"
|
|
475
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
358
476
|
}
|
|
359
477
|
else if (level === 'error') {
|
|
360
|
-
this.log.logLevel = "error"
|
|
478
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
361
479
|
}
|
|
362
480
|
else if (level === 'fatal') {
|
|
363
|
-
this.log.logLevel = "fatal"
|
|
481
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
364
482
|
}
|
|
365
483
|
else {
|
|
366
484
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
367
|
-
this.log.logLevel = "info"
|
|
485
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
368
486
|
}
|
|
369
487
|
}
|
|
370
488
|
else {
|
|
371
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" : "info");
|
|
489
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.matterbridgeInformation.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
372
490
|
}
|
|
373
491
|
this.frontend.logLevel = this.log.logLevel;
|
|
374
492
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
375
493
|
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
494
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
376
495
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
377
496
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, this.matterbridgeLoggerFile), this.log.logLevel, true);
|
|
378
497
|
this.matterbridgeInformation.fileLogger = true;
|
|
@@ -381,6 +500,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
381
500
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.matterbridgeInformation.fileLogger}.`);
|
|
382
501
|
if (this.profile !== undefined)
|
|
383
502
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
503
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
384
504
|
if (hasParameter('matterlogger')) {
|
|
385
505
|
const level = getParameter('matterlogger');
|
|
386
506
|
if (level === 'debug') {
|
|
@@ -410,12 +530,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
410
530
|
Logger.level = (await this.nodeContext.get('matterLogLevel', this.matterbridgeInformation.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
411
531
|
}
|
|
412
532
|
Logger.format = MatterLogFormat.ANSI;
|
|
533
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
413
534
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
414
535
|
this.matterbridgeInformation.matterFileLogger = true;
|
|
415
536
|
}
|
|
416
537
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterbridgeInformation.matterFileLogger);
|
|
417
538
|
this.matterbridgeInformation.matterLoggerLevel = Logger.level;
|
|
418
539
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
540
|
+
// Log network interfaces
|
|
419
541
|
const networkInterfaces = os.networkInterfaces();
|
|
420
542
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
421
543
|
const availableInterfaces = Object.keys(networkInterfaces);
|
|
@@ -428,6 +550,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
428
550
|
});
|
|
429
551
|
}
|
|
430
552
|
}
|
|
553
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
431
554
|
if (hasParameter('mdnsinterface')) {
|
|
432
555
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
433
556
|
}
|
|
@@ -436,6 +559,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
436
559
|
if (this.mdnsInterface === '')
|
|
437
560
|
this.mdnsInterface = undefined;
|
|
438
561
|
}
|
|
562
|
+
// Validate mdnsInterface
|
|
439
563
|
if (this.mdnsInterface) {
|
|
440
564
|
if (!availableInterfaces.includes(this.mdnsInterface)) {
|
|
441
565
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaces.join(', ')}. Using all available interfaces.`);
|
|
@@ -448,6 +572,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
448
572
|
}
|
|
449
573
|
if (this.mdnsInterface)
|
|
450
574
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
575
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
451
576
|
if (hasParameter('ipv4address')) {
|
|
452
577
|
this.ipv4address = getParameter('ipv4address');
|
|
453
578
|
}
|
|
@@ -456,6 +581,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
456
581
|
if (this.ipv4address === '')
|
|
457
582
|
this.ipv4address = undefined;
|
|
458
583
|
}
|
|
584
|
+
// Validate ipv4address
|
|
459
585
|
if (this.ipv4address) {
|
|
460
586
|
let isValid = false;
|
|
461
587
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -471,6 +597,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
597
|
await this.nodeContext.remove('matteripv4address');
|
|
472
598
|
}
|
|
473
599
|
}
|
|
600
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
474
601
|
if (hasParameter('ipv6address')) {
|
|
475
602
|
this.ipv6address = getParameter('ipv6address');
|
|
476
603
|
}
|
|
@@ -479,6 +606,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
479
606
|
if (this.ipv6address === '')
|
|
480
607
|
this.ipv6address = undefined;
|
|
481
608
|
}
|
|
609
|
+
// Validate ipv6address
|
|
482
610
|
if (this.ipv6address) {
|
|
483
611
|
let isValid = false;
|
|
484
612
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -487,6 +615,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
487
615
|
isValid = true;
|
|
488
616
|
break;
|
|
489
617
|
}
|
|
618
|
+
/* istanbul ignore next */
|
|
490
619
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6address)) {
|
|
491
620
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
492
621
|
isValid = true;
|
|
@@ -499,6 +628,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
499
628
|
await this.nodeContext.remove('matteripv6address');
|
|
500
629
|
}
|
|
501
630
|
}
|
|
631
|
+
// Initialize the virtual mode
|
|
502
632
|
if (hasParameter('novirtual')) {
|
|
503
633
|
this.matterbridgeInformation.virtualMode = 'disabled';
|
|
504
634
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -507,12 +637,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
507
637
|
this.matterbridgeInformation.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
508
638
|
}
|
|
509
639
|
this.log.debug(`Virtual mode ${this.matterbridgeInformation.virtualMode}.`);
|
|
640
|
+
// Initialize PluginManager
|
|
510
641
|
this.plugins.logLevel = this.log.logLevel;
|
|
511
642
|
await this.plugins.loadFromStorage();
|
|
643
|
+
// Initialize DeviceManager
|
|
512
644
|
this.devices.logLevel = this.log.logLevel;
|
|
645
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
513
646
|
for (const plugin of this.plugins) {
|
|
514
647
|
const packageJson = await this.plugins.parse(plugin);
|
|
515
648
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
649
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
650
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
516
651
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
517
652
|
try {
|
|
518
653
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -535,6 +670,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
535
670
|
await plugin.nodeContext.set('description', plugin.description);
|
|
536
671
|
await plugin.nodeContext.set('author', plugin.author);
|
|
537
672
|
}
|
|
673
|
+
// Log system info and create .matterbridge directory
|
|
538
674
|
await this.logNodeAndSystemInfo();
|
|
539
675
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
540
676
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -542,6 +678,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
542
678
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
543
679
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
544
680
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
681
|
+
// Check node version and throw error
|
|
545
682
|
const minNodeVersion = 18;
|
|
546
683
|
const nodeVersion = process.versions.node;
|
|
547
684
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -549,10 +686,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
549
686
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
550
687
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
551
688
|
}
|
|
689
|
+
// Parse command line
|
|
552
690
|
await this.parseCommandLine();
|
|
691
|
+
// Emit the initialize_completed event
|
|
553
692
|
this.emit('initialize_completed');
|
|
554
693
|
this.initialized = true;
|
|
555
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
697
|
+
*
|
|
698
|
+
* @private
|
|
699
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
700
|
+
*/
|
|
556
701
|
async parseCommandLine() {
|
|
557
702
|
if (hasParameter('help')) {
|
|
558
703
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -614,6 +759,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
614
759
|
}
|
|
615
760
|
index++;
|
|
616
761
|
}
|
|
762
|
+
/*
|
|
763
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
764
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
765
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
766
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
767
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
768
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
769
|
+
} else {
|
|
770
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
771
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
*/
|
|
617
775
|
this.shutdown = true;
|
|
618
776
|
return;
|
|
619
777
|
}
|
|
@@ -663,6 +821,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
663
821
|
this.shutdown = true;
|
|
664
822
|
return;
|
|
665
823
|
}
|
|
824
|
+
// Start the matter storage and create the matterbridge context
|
|
666
825
|
try {
|
|
667
826
|
await this.startMatterStorage();
|
|
668
827
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -679,18 +838,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
679
838
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
680
839
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
681
840
|
}
|
|
841
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
682
842
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
683
843
|
this.initialized = true;
|
|
684
844
|
await this.shutdownProcessAndReset();
|
|
685
845
|
this.shutdown = true;
|
|
686
846
|
return;
|
|
687
847
|
}
|
|
848
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
688
849
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
689
850
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
690
851
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
691
852
|
if (plugin) {
|
|
692
853
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
693
854
|
if (!matterStorageManager) {
|
|
855
|
+
/* istanbul ignore next */
|
|
694
856
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
695
857
|
}
|
|
696
858
|
else {
|
|
@@ -709,37 +871,45 @@ export class Matterbridge extends EventEmitter {
|
|
|
709
871
|
this.shutdown = true;
|
|
710
872
|
return;
|
|
711
873
|
}
|
|
874
|
+
// Initialize frontend
|
|
712
875
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
713
876
|
await this.frontend.start(getIntParameter('frontend'));
|
|
877
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
714
878
|
clearTimeout(this.checkUpdateTimeout);
|
|
715
879
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
716
880
|
const { checkUpdates } = await import('./update.js');
|
|
717
881
|
checkUpdates(this);
|
|
718
882
|
}, 30 * 1000).unref();
|
|
883
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
719
884
|
clearInterval(this.checkUpdateInterval);
|
|
720
885
|
this.checkUpdateInterval = setInterval(async () => {
|
|
721
886
|
const { checkUpdates } = await import('./update.js');
|
|
722
887
|
checkUpdates(this);
|
|
723
888
|
}, 12 * 60 * 60 * 1000).unref();
|
|
889
|
+
// Start the matterbridge in mode test
|
|
724
890
|
if (hasParameter('test')) {
|
|
725
891
|
this.bridgeMode = 'bridge';
|
|
726
892
|
return;
|
|
727
893
|
}
|
|
894
|
+
// Start the matterbridge in mode controller
|
|
728
895
|
if (hasParameter('controller')) {
|
|
729
896
|
this.bridgeMode = 'controller';
|
|
730
897
|
await this.startController();
|
|
731
898
|
return;
|
|
732
899
|
}
|
|
900
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
733
901
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
734
902
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
735
903
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
736
904
|
}
|
|
905
|
+
// Start matterbridge in bridge mode
|
|
737
906
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
738
907
|
this.bridgeMode = 'bridge';
|
|
739
908
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
740
909
|
await this.startBridge();
|
|
741
910
|
return;
|
|
742
911
|
}
|
|
912
|
+
// Start matterbridge in childbridge mode
|
|
743
913
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
744
914
|
this.bridgeMode = 'childbridge';
|
|
745
915
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -747,10 +917,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
747
917
|
return;
|
|
748
918
|
}
|
|
749
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Asynchronously loads and starts the registered plugins.
|
|
922
|
+
*
|
|
923
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
924
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
925
|
+
*
|
|
926
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
927
|
+
*/
|
|
750
928
|
async startPlugins() {
|
|
929
|
+
// Check, load and start the plugins
|
|
751
930
|
for (const plugin of this.plugins) {
|
|
752
931
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
753
932
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
933
|
+
// Check if the plugin is available
|
|
754
934
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
755
935
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
756
936
|
plugin.enabled = false;
|
|
@@ -768,10 +948,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
768
948
|
plugin.configured = false;
|
|
769
949
|
plugin.registeredDevices = undefined;
|
|
770
950
|
plugin.addedDevices = undefined;
|
|
771
|
-
this.plugins.load(plugin, true, 'Matterbridge is starting');
|
|
951
|
+
this.plugins.load(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
|
|
772
952
|
}
|
|
773
953
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
774
954
|
}
|
|
955
|
+
/**
|
|
956
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
957
|
+
* When either of these signals are received, the cleanup method is called with an appropriate message.
|
|
958
|
+
*/
|
|
775
959
|
registerProcessHandlers() {
|
|
776
960
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
777
961
|
process.removeAllListeners('uncaughtException');
|
|
@@ -798,6 +982,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
798
982
|
};
|
|
799
983
|
process.on('SIGTERM', this.sigtermHandler);
|
|
800
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
987
|
+
*/
|
|
801
988
|
deregisterProcessHandlers() {
|
|
802
989
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
803
990
|
if (this.exceptionHandler)
|
|
@@ -814,12 +1001,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
814
1001
|
process.off('SIGTERM', this.sigtermHandler);
|
|
815
1002
|
this.sigtermHandler = undefined;
|
|
816
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Logs the node and system information.
|
|
1006
|
+
*/
|
|
817
1007
|
async logNodeAndSystemInfo() {
|
|
1008
|
+
// IP address information
|
|
818
1009
|
const networkInterfaces = os.networkInterfaces();
|
|
819
1010
|
this.systemInformation.interfaceName = '';
|
|
820
1011
|
this.systemInformation.ipv4Address = '';
|
|
821
1012
|
this.systemInformation.ipv6Address = '';
|
|
822
1013
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
1014
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
823
1015
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
824
1016
|
continue;
|
|
825
1017
|
if (!interfaceDetails) {
|
|
@@ -845,19 +1037,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
845
1037
|
break;
|
|
846
1038
|
}
|
|
847
1039
|
}
|
|
1040
|
+
// Node information
|
|
848
1041
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
849
1042
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
850
1043
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
851
1044
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1045
|
+
// Host system information
|
|
852
1046
|
this.systemInformation.hostname = os.hostname();
|
|
853
1047
|
this.systemInformation.user = os.userInfo().username;
|
|
854
|
-
this.systemInformation.osType = os.type();
|
|
855
|
-
this.systemInformation.osRelease = os.release();
|
|
856
|
-
this.systemInformation.osPlatform = os.platform();
|
|
857
|
-
this.systemInformation.osArch = os.arch();
|
|
858
|
-
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
859
|
-
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB';
|
|
860
|
-
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours';
|
|
1048
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1049
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1050
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1051
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
1052
|
+
this.systemInformation.totalMemory = (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1053
|
+
this.systemInformation.freeMemory = (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB'; // Convert to GB
|
|
1054
|
+
this.systemInformation.systemUptime = (os.uptime() / 60 / 60).toFixed(2) + ' hours'; // Convert to hours
|
|
1055
|
+
// Log the system information
|
|
861
1056
|
this.log.debug('Host System Information:');
|
|
862
1057
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
863
1058
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -873,14 +1068,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
873
1068
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
874
1069
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
875
1070
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1071
|
+
// Log directories
|
|
876
1072
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
877
1073
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
878
1074
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
879
1075
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
880
1076
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1077
|
+
// Global node_modules directory
|
|
881
1078
|
if (this.nodeContext)
|
|
882
1079
|
this.globalModulesDirectory = this.matterbridgeInformation.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
883
1080
|
if (this.globalModulesDirectory === '') {
|
|
1081
|
+
// First run of Matterbridge so the node storage is empty
|
|
884
1082
|
this.log.debug(`Getting global node_modules directory...`);
|
|
885
1083
|
try {
|
|
886
1084
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -893,6 +1091,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
893
1091
|
}
|
|
894
1092
|
}
|
|
895
1093
|
else {
|
|
1094
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
896
1095
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
897
1096
|
try {
|
|
898
1097
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -904,57 +1103,85 @@ export class Matterbridge extends EventEmitter {
|
|
|
904
1103
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
905
1104
|
}
|
|
906
1105
|
}
|
|
1106
|
+
// Matterbridge version
|
|
907
1107
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
908
1108
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
909
1109
|
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
910
1110
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1111
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
911
1112
|
if (this.nodeContext)
|
|
912
1113
|
this.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
913
1114
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1115
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
914
1116
|
if (this.nodeContext)
|
|
915
1117
|
this.matterbridgeDevVersion = this.matterbridgeInformation.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
916
1118
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1119
|
+
// Current working directory
|
|
917
1120
|
const currentDir = process.cwd();
|
|
918
1121
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1122
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
919
1123
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
920
1124
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
921
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1128
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1131
|
+
* @returns {Function} The MatterLogger function.
|
|
1132
|
+
*/
|
|
922
1133
|
createDestinationMatterLogger(fileLogger) {
|
|
923
1134
|
if (fileLogger) {
|
|
924
1135
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, this.matterLoggerFile);
|
|
925
1136
|
}
|
|
926
1137
|
return (text, message) => {
|
|
1138
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
927
1139
|
const logger = text.slice(44, 44 + 20).trim();
|
|
928
1140
|
const msg = text.slice(65);
|
|
929
1141
|
this.matterLog.logName = logger;
|
|
930
1142
|
switch (message.level) {
|
|
931
1143
|
case MatterLogLevel.DEBUG:
|
|
932
|
-
this.matterLog.log("debug"
|
|
1144
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
933
1145
|
break;
|
|
934
1146
|
case MatterLogLevel.INFO:
|
|
935
|
-
this.matterLog.log("info"
|
|
1147
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
936
1148
|
break;
|
|
937
1149
|
case MatterLogLevel.NOTICE:
|
|
938
|
-
this.matterLog.log("notice"
|
|
1150
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
939
1151
|
break;
|
|
940
1152
|
case MatterLogLevel.WARN:
|
|
941
|
-
this.matterLog.log("warn"
|
|
1153
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
942
1154
|
break;
|
|
943
1155
|
case MatterLogLevel.ERROR:
|
|
944
|
-
this.matterLog.log("error"
|
|
1156
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
945
1157
|
break;
|
|
946
1158
|
case MatterLogLevel.FATAL:
|
|
947
|
-
this.matterLog.log("fatal"
|
|
1159
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
948
1160
|
break;
|
|
949
1161
|
}
|
|
950
1162
|
};
|
|
951
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1166
|
+
*
|
|
1167
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1168
|
+
*/
|
|
952
1169
|
async restartProcess() {
|
|
953
1170
|
await this.cleanup('restarting...', true);
|
|
954
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Shut down the process (/api/shutdown).
|
|
1174
|
+
*
|
|
1175
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1176
|
+
*/
|
|
955
1177
|
async shutdownProcess() {
|
|
956
1178
|
await this.cleanup('shutting down...', false);
|
|
957
1179
|
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1182
|
+
*
|
|
1183
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1184
|
+
*/
|
|
958
1185
|
async updateProcess() {
|
|
959
1186
|
this.log.info('Updating matterbridge...');
|
|
960
1187
|
try {
|
|
@@ -968,6 +1195,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
968
1195
|
this.frontend.wssSendRestartRequired();
|
|
969
1196
|
await this.cleanup('updating...', false);
|
|
970
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1200
|
+
*
|
|
1201
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1202
|
+
*
|
|
1203
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1204
|
+
*/
|
|
971
1205
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
972
1206
|
this.log.info('Unregistering all devices and shutting down...');
|
|
973
1207
|
for (const plugin of this.plugins.array()) {
|
|
@@ -981,46 +1215,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
981
1215
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
982
1216
|
}
|
|
983
1217
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
984
|
-
await wait(timeout);
|
|
1218
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
985
1219
|
this.log.debug('Cleaning up and shutting down...');
|
|
986
1220
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
987
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1224
|
+
*
|
|
1225
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1226
|
+
*/
|
|
988
1227
|
async shutdownProcessAndReset() {
|
|
989
1228
|
await this.cleanup('shutting down with reset...', false);
|
|
990
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1232
|
+
*
|
|
1233
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1234
|
+
*/
|
|
991
1235
|
async shutdownProcessAndFactoryReset() {
|
|
992
1236
|
await this.cleanup('shutting down with factory reset...', false);
|
|
993
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Cleans up the Matterbridge instance.
|
|
1240
|
+
*
|
|
1241
|
+
* @param {string} message - The cleanup message.
|
|
1242
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1243
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1244
|
+
*
|
|
1245
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1246
|
+
*/
|
|
994
1247
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
995
1248
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
996
1249
|
this.emit('cleanup_started');
|
|
997
1250
|
this.hasCleanupStarted = true;
|
|
998
1251
|
this.log.info(message);
|
|
1252
|
+
// Clear the start matter interval
|
|
999
1253
|
if (this.startMatterInterval) {
|
|
1000
1254
|
clearInterval(this.startMatterInterval);
|
|
1001
1255
|
this.startMatterInterval = undefined;
|
|
1002
1256
|
this.log.debug('Start matter interval cleared');
|
|
1003
1257
|
}
|
|
1258
|
+
// Clear the check update timeout
|
|
1004
1259
|
if (this.checkUpdateTimeout) {
|
|
1005
1260
|
clearTimeout(this.checkUpdateTimeout);
|
|
1006
1261
|
this.checkUpdateTimeout = undefined;
|
|
1007
1262
|
this.log.debug('Check update timeout cleared');
|
|
1008
1263
|
}
|
|
1264
|
+
// Clear the check update interval
|
|
1009
1265
|
if (this.checkUpdateInterval) {
|
|
1010
1266
|
clearInterval(this.checkUpdateInterval);
|
|
1011
1267
|
this.checkUpdateInterval = undefined;
|
|
1012
1268
|
this.log.debug('Check update interval cleared');
|
|
1013
1269
|
}
|
|
1270
|
+
// Clear the configure timeout
|
|
1014
1271
|
if (this.configureTimeout) {
|
|
1015
1272
|
clearTimeout(this.configureTimeout);
|
|
1016
1273
|
this.configureTimeout = undefined;
|
|
1017
1274
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1018
1275
|
}
|
|
1276
|
+
// Clear the reachability timeout
|
|
1019
1277
|
if (this.reachabilityTimeout) {
|
|
1020
1278
|
clearTimeout(this.reachabilityTimeout);
|
|
1021
1279
|
this.reachabilityTimeout = undefined;
|
|
1022
1280
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1023
1281
|
}
|
|
1282
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1024
1283
|
for (const plugin of this.plugins) {
|
|
1025
1284
|
if (!plugin.enabled || plugin.error)
|
|
1026
1285
|
continue;
|
|
@@ -1031,6 +1290,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1031
1290
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1032
1291
|
}
|
|
1033
1292
|
}
|
|
1293
|
+
// Stop matter server nodes
|
|
1034
1294
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1035
1295
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1036
1296
|
await wait(timeout, 'Waiting for the MessageExchange to finish...', true);
|
|
@@ -1055,6 +1315,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1055
1315
|
}
|
|
1056
1316
|
}
|
|
1057
1317
|
this.log.notice('Stopped matter server nodes');
|
|
1318
|
+
// Matter commisioning reset
|
|
1058
1319
|
if (message === 'shutting down with reset...') {
|
|
1059
1320
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1060
1321
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1064,6 +1325,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1064
1325
|
await this.matterbridgeContext?.clearAll();
|
|
1065
1326
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1066
1327
|
}
|
|
1328
|
+
// Unregister all devices
|
|
1067
1329
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1068
1330
|
if (this.bridgeMode === 'bridge') {
|
|
1069
1331
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1081,18 +1343,36 @@ export class Matterbridge extends EventEmitter {
|
|
|
1081
1343
|
}
|
|
1082
1344
|
this.log.info('Matter storage reset done!');
|
|
1083
1345
|
}
|
|
1346
|
+
// Stop matter storage
|
|
1084
1347
|
await this.stopMatterStorage();
|
|
1348
|
+
// Stop the frontend
|
|
1085
1349
|
await this.frontend.stop();
|
|
1350
|
+
// Remove the matterfilelogger
|
|
1086
1351
|
try {
|
|
1087
1352
|
Logger.removeLogger('matterfilelogger');
|
|
1088
1353
|
}
|
|
1089
1354
|
catch (error) {
|
|
1090
1355
|
this.log.debug(`Error removing the matterfilelogger for file ${CYAN}${path.join(this.matterbridgeDirectory, this.matterLoggerFile)}${db}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1091
1356
|
}
|
|
1357
|
+
// Close the matterbridge node storage and context
|
|
1092
1358
|
if (this.nodeStorage && this.nodeContext) {
|
|
1359
|
+
/*
|
|
1360
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1361
|
+
this.log.info('Saving registered devices...');
|
|
1362
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1363
|
+
this.devices.forEach(async (device) => {
|
|
1364
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1365
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1366
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1367
|
+
});
|
|
1368
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1369
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1370
|
+
*/
|
|
1371
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1093
1372
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1094
1373
|
await this.nodeContext.close();
|
|
1095
1374
|
this.nodeContext = undefined;
|
|
1375
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1096
1376
|
for (const plugin of this.plugins) {
|
|
1097
1377
|
if (plugin.nodeContext) {
|
|
1098
1378
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1109,8 +1389,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1109
1389
|
}
|
|
1110
1390
|
this.plugins.clear();
|
|
1111
1391
|
this.devices.clear();
|
|
1392
|
+
// Factory reset
|
|
1112
1393
|
if (message === 'shutting down with factory reset...') {
|
|
1113
1394
|
try {
|
|
1395
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1114
1396
|
const dir = path.join(this.matterbridgeDirectory, this.matterStorageName);
|
|
1115
1397
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1116
1398
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1119,11 +1401,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1119
1401
|
await fs.rm(backup, { recursive: true });
|
|
1120
1402
|
}
|
|
1121
1403
|
catch (error) {
|
|
1404
|
+
// istanbul ignore next if
|
|
1122
1405
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1123
1406
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1124
1407
|
}
|
|
1125
1408
|
}
|
|
1126
1409
|
try {
|
|
1410
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1127
1411
|
const dir = path.join(this.matterbridgeDirectory, this.nodeStorageName);
|
|
1128
1412
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1129
1413
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1132,18 +1416,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1132
1416
|
await fs.rm(backup, { recursive: true });
|
|
1133
1417
|
}
|
|
1134
1418
|
catch (error) {
|
|
1419
|
+
// istanbul ignore next if
|
|
1135
1420
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1136
1421
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1137
1422
|
}
|
|
1138
1423
|
}
|
|
1139
1424
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1140
1425
|
}
|
|
1426
|
+
// Deregisters the process handlers
|
|
1141
1427
|
this.deregisterProcessHandlers();
|
|
1142
1428
|
if (restart) {
|
|
1143
1429
|
if (message === 'updating...') {
|
|
1144
1430
|
this.log.info('Cleanup completed. Updating...');
|
|
1145
1431
|
Matterbridge.instance = undefined;
|
|
1146
|
-
this.emit('update');
|
|
1432
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1147
1433
|
}
|
|
1148
1434
|
else if (message === 'restarting...') {
|
|
1149
1435
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1164,6 +1450,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1164
1450
|
this.log.debug('Cleanup already started...');
|
|
1165
1451
|
}
|
|
1166
1452
|
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Creates and configures the server node for a single not bridged device.
|
|
1455
|
+
*
|
|
1456
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1457
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1458
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1459
|
+
*/
|
|
1167
1460
|
async createDeviceServerNode(plugin, device) {
|
|
1168
1461
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1169
1462
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1174,6 +1467,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1174
1467
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1175
1468
|
}
|
|
1176
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
1472
|
+
*
|
|
1473
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1474
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
1475
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
1476
|
+
*/
|
|
1177
1477
|
async createAccessoryPlugin(plugin, device) {
|
|
1178
1478
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1179
1479
|
plugin.locked = true;
|
|
@@ -1185,6 +1485,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1185
1485
|
await plugin.serverNode.add(device);
|
|
1186
1486
|
}
|
|
1187
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
1490
|
+
*
|
|
1491
|
+
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
1492
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
1493
|
+
*/
|
|
1188
1494
|
async createDynamicPlugin(plugin) {
|
|
1189
1495
|
if (!plugin.locked) {
|
|
1190
1496
|
plugin.locked = true;
|
|
@@ -1195,7 +1501,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1195
1501
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1196
1502
|
}
|
|
1197
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Starts the Matterbridge in bridge mode.
|
|
1506
|
+
*
|
|
1507
|
+
* @private
|
|
1508
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1509
|
+
*/
|
|
1198
1510
|
async startBridge() {
|
|
1511
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1199
1512
|
if (!this.matterStorageManager)
|
|
1200
1513
|
throw new Error('No storage manager initialized');
|
|
1201
1514
|
if (!this.matterbridgeContext)
|
|
@@ -1234,13 +1547,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1234
1547
|
clearInterval(this.startMatterInterval);
|
|
1235
1548
|
this.startMatterInterval = undefined;
|
|
1236
1549
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1237
|
-
|
|
1550
|
+
// Start the Matter server node
|
|
1551
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1552
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1238
1553
|
for (const device of this.devices.array()) {
|
|
1239
1554
|
if (device.mode === 'server' && device.serverNode) {
|
|
1240
1555
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1241
|
-
this.startServerNode(device.serverNode);
|
|
1556
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1242
1557
|
}
|
|
1243
1558
|
}
|
|
1559
|
+
// Configure the plugins
|
|
1244
1560
|
this.configureTimeout = setTimeout(async () => {
|
|
1245
1561
|
for (const plugin of this.plugins.array()) {
|
|
1246
1562
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1258,16 +1574,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1258
1574
|
}
|
|
1259
1575
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1260
1576
|
}, 30 * 1000).unref();
|
|
1577
|
+
// Setting reachability to true
|
|
1261
1578
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1262
1579
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1263
1580
|
if (this.aggregatorNode)
|
|
1264
1581
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1265
1582
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1266
1583
|
}, 60 * 1000).unref();
|
|
1584
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1267
1585
|
this.emit('bridge_started');
|
|
1268
1586
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1269
1587
|
}, this.startMatterIntervalMs);
|
|
1270
1588
|
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1591
|
+
*
|
|
1592
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1593
|
+
*
|
|
1594
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1595
|
+
*/
|
|
1271
1596
|
async startChildbridge(delay = 1000) {
|
|
1272
1597
|
if (!this.matterStorageManager)
|
|
1273
1598
|
throw new Error('No storage manager initialized');
|
|
@@ -1308,8 +1633,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1308
1633
|
clearInterval(this.startMatterInterval);
|
|
1309
1634
|
this.startMatterInterval = undefined;
|
|
1310
1635
|
if (delay > 0)
|
|
1311
|
-
await wait(delay);
|
|
1636
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1312
1637
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1638
|
+
// Configure the plugins
|
|
1313
1639
|
this.configureTimeout = setTimeout(async () => {
|
|
1314
1640
|
for (const plugin of this.plugins.array()) {
|
|
1315
1641
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1346,7 +1672,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1346
1672
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1347
1673
|
continue;
|
|
1348
1674
|
}
|
|
1349
|
-
|
|
1675
|
+
// Start the Matter server node
|
|
1676
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1677
|
+
// Setting reachability to true
|
|
1350
1678
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1351
1679
|
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}`);
|
|
1352
1680
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
@@ -1354,19 +1682,241 @@ export class Matterbridge extends EventEmitter {
|
|
|
1354
1682
|
this.frontend.wssSendRefreshRequired('reachability');
|
|
1355
1683
|
}, 60 * 1000).unref();
|
|
1356
1684
|
}
|
|
1685
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1357
1686
|
for (const device of this.devices.array()) {
|
|
1358
1687
|
if (device.mode === 'server' && device.serverNode) {
|
|
1359
1688
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1360
|
-
this.startServerNode(device.serverNode);
|
|
1689
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1361
1690
|
}
|
|
1362
1691
|
}
|
|
1692
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1363
1693
|
this.emit('childbridge_started');
|
|
1364
1694
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1365
1695
|
}, this.startMatterIntervalMs);
|
|
1366
1696
|
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Starts the Matterbridge controller.
|
|
1699
|
+
*
|
|
1700
|
+
* @private
|
|
1701
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1702
|
+
*/
|
|
1367
1703
|
async startController() {
|
|
1704
|
+
/*
|
|
1705
|
+
if (!this.matterStorageManager) {
|
|
1706
|
+
this.log.error('No storage manager initialized');
|
|
1707
|
+
await this.cleanup('No storage manager initialized');
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1711
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1712
|
+
if (!this.controllerContext) {
|
|
1713
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1714
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1719
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1720
|
+
this.log.info('Creating matter commissioning controller');
|
|
1721
|
+
this.commissioningController = new CommissioningController({
|
|
1722
|
+
autoConnect: false,
|
|
1723
|
+
});
|
|
1724
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1725
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1726
|
+
|
|
1727
|
+
this.log.info('Starting matter server');
|
|
1728
|
+
await this.matterServer.start();
|
|
1729
|
+
this.log.info('Matter server started');
|
|
1730
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1731
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1732
|
+
regulatoryCountryCode: 'XX',
|
|
1733
|
+
};
|
|
1734
|
+
const commissioningController = new CommissioningController({
|
|
1735
|
+
environment: {
|
|
1736
|
+
environment,
|
|
1737
|
+
id: uniqueId,
|
|
1738
|
+
},
|
|
1739
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1740
|
+
adminFabricLabel,
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
if (hasParameter('pairingcode')) {
|
|
1744
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1745
|
+
const pairingCode = getParameter('pairingcode');
|
|
1746
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1747
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1748
|
+
|
|
1749
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1750
|
+
if (pairingCode !== undefined) {
|
|
1751
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1752
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1753
|
+
longDiscriminator = undefined;
|
|
1754
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1755
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1756
|
+
} else {
|
|
1757
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1758
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1759
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1760
|
+
}
|
|
1761
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1762
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const options = {
|
|
1766
|
+
commissioning: commissioningOptions,
|
|
1767
|
+
discovery: {
|
|
1768
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1769
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1770
|
+
},
|
|
1771
|
+
passcode: setupPin,
|
|
1772
|
+
} as NodeCommissioningOptions;
|
|
1773
|
+
this.log.info('Commissioning with options:', options);
|
|
1774
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1775
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1776
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1777
|
+
} // (hasParameter('pairingcode'))
|
|
1778
|
+
|
|
1779
|
+
if (hasParameter('unpairall')) {
|
|
1780
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1781
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1782
|
+
for (const nodeId of nodeIds) {
|
|
1783
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1784
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1785
|
+
}
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (hasParameter('discover')) {
|
|
1790
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1791
|
+
// console.log(discover);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1795
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1800
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1801
|
+
for (const nodeId of nodeIds) {
|
|
1802
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1803
|
+
|
|
1804
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1805
|
+
autoSubscribe: false,
|
|
1806
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1807
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1808
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1809
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1810
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1811
|
+
switch (info) {
|
|
1812
|
+
case NodeStateInformation.Connected:
|
|
1813
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1814
|
+
break;
|
|
1815
|
+
case NodeStateInformation.Disconnected:
|
|
1816
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1817
|
+
break;
|
|
1818
|
+
case NodeStateInformation.Reconnecting:
|
|
1819
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1820
|
+
break;
|
|
1821
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1822
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1823
|
+
break;
|
|
1824
|
+
case NodeStateInformation.StructureChanged:
|
|
1825
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1826
|
+
break;
|
|
1827
|
+
case NodeStateInformation.Decommissioned:
|
|
1828
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1829
|
+
break;
|
|
1830
|
+
default:
|
|
1831
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1832
|
+
break;
|
|
1833
|
+
}
|
|
1834
|
+
},
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
node.logStructure();
|
|
1838
|
+
|
|
1839
|
+
// Get the interaction client
|
|
1840
|
+
this.log.info('Getting the interaction client');
|
|
1841
|
+
const interactionClient = await node.getInteractionClient();
|
|
1842
|
+
let cluster;
|
|
1843
|
+
let attributes;
|
|
1844
|
+
|
|
1845
|
+
// Log BasicInformationCluster
|
|
1846
|
+
cluster = BasicInformationCluster;
|
|
1847
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1848
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1849
|
+
});
|
|
1850
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1851
|
+
attributes.forEach((attribute) => {
|
|
1852
|
+
this.log.info(
|
|
1853
|
+
`- 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}`,
|
|
1854
|
+
);
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
// Log PowerSourceCluster
|
|
1858
|
+
cluster = PowerSourceCluster;
|
|
1859
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1860
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1861
|
+
});
|
|
1862
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1863
|
+
attributes.forEach((attribute) => {
|
|
1864
|
+
this.log.info(
|
|
1865
|
+
`- 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}`,
|
|
1866
|
+
);
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
// Log ThreadNetworkDiagnostics
|
|
1870
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1871
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1872
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1873
|
+
});
|
|
1874
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1875
|
+
attributes.forEach((attribute) => {
|
|
1876
|
+
this.log.info(
|
|
1877
|
+
`- 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}`,
|
|
1878
|
+
);
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
// Log SwitchCluster
|
|
1882
|
+
cluster = SwitchCluster;
|
|
1883
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1884
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1885
|
+
});
|
|
1886
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1887
|
+
attributes.forEach((attribute) => {
|
|
1888
|
+
this.log.info(
|
|
1889
|
+
`- 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}`,
|
|
1890
|
+
);
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1894
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1895
|
+
ignoreInitialTriggers: false,
|
|
1896
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1897
|
+
this.log.info(
|
|
1898
|
+
`***${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}`,
|
|
1899
|
+
),
|
|
1900
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1901
|
+
this.log.info(
|
|
1902
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1903
|
+
);
|
|
1904
|
+
},
|
|
1905
|
+
});
|
|
1906
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1907
|
+
}
|
|
1908
|
+
*/
|
|
1368
1909
|
}
|
|
1910
|
+
/** */
|
|
1911
|
+
/** Matter.js methods */
|
|
1912
|
+
/** */
|
|
1913
|
+
/**
|
|
1914
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1915
|
+
*
|
|
1916
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1917
|
+
*/
|
|
1369
1918
|
async startMatterStorage() {
|
|
1919
|
+
// Setup Matter storage
|
|
1370
1920
|
this.log.info(`Starting matter node storage...`);
|
|
1371
1921
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1372
1922
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1375,8 +1925,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1375
1925
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1376
1926
|
this.matterbridgeInformation.matterbridgeSerialNumber = await this.matterbridgeContext.get('serialNumber', '');
|
|
1377
1927
|
this.log.info('Matter node storage started');
|
|
1928
|
+
// Backup matter storage since it is created/opened correctly
|
|
1378
1929
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, this.matterStorageName), path.join(this.matterbridgeDirectory, this.matterStorageName + '.backup'));
|
|
1379
1930
|
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1933
|
+
*
|
|
1934
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1935
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1936
|
+
* @private
|
|
1937
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1938
|
+
*/
|
|
1380
1939
|
async backupMatterStorage(storageName, backupName) {
|
|
1381
1940
|
this.log.info('Creating matter node storage backup...');
|
|
1382
1941
|
try {
|
|
@@ -1387,6 +1946,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1387
1946
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1388
1947
|
}
|
|
1389
1948
|
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Stops the matter storage.
|
|
1951
|
+
*
|
|
1952
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1953
|
+
*/
|
|
1390
1954
|
async stopMatterStorage() {
|
|
1391
1955
|
this.log.info('Closing matter node storage...');
|
|
1392
1956
|
await this.matterStorageManager?.close();
|
|
@@ -1395,6 +1959,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1395
1959
|
this.matterbridgeContext = undefined;
|
|
1396
1960
|
this.log.info('Matter node storage closed');
|
|
1397
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Creates a server node storage context.
|
|
1964
|
+
*
|
|
1965
|
+
* @param {string} storeId - The storeId.
|
|
1966
|
+
* @param {string} deviceName - The name of the device.
|
|
1967
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1968
|
+
* @param {number} vendorId - The vendor ID.
|
|
1969
|
+
* @param {string} vendorName - The vendor name.
|
|
1970
|
+
* @param {number} productId - The product ID.
|
|
1971
|
+
* @param {string} productName - The product name.
|
|
1972
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1973
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1974
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1975
|
+
*/
|
|
1398
1976
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1399
1977
|
const { randomBytes } = await import('node:crypto');
|
|
1400
1978
|
if (!this.matterStorageService)
|
|
@@ -1434,6 +2012,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1434
2012
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1435
2013
|
return storageContext;
|
|
1436
2014
|
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Creates a server node.
|
|
2017
|
+
*
|
|
2018
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2019
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2020
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2021
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2022
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2023
|
+
*/
|
|
1437
2024
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1438
2025
|
const storeId = await storageContext.get('storeId');
|
|
1439
2026
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1443,24 +2030,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1443
2030
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1444
2031
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1445
2032
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2033
|
+
/**
|
|
2034
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2035
|
+
*/
|
|
1446
2036
|
const serverNode = await ServerNode.create({
|
|
2037
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1447
2038
|
id: storeId,
|
|
2039
|
+
// Provide Network relevant configuration like the port
|
|
2040
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1448
2041
|
network: {
|
|
1449
2042
|
listeningAddressIpv4: this.ipv4address,
|
|
1450
2043
|
listeningAddressIpv6: this.ipv6address,
|
|
1451
2044
|
port,
|
|
1452
2045
|
},
|
|
2046
|
+
// Provide the certificate for the device
|
|
1453
2047
|
operationalCredentials: {
|
|
1454
2048
|
certification: this.certification,
|
|
1455
2049
|
},
|
|
2050
|
+
// Provide Commissioning relevant settings
|
|
2051
|
+
// Optional for development/testing purposes
|
|
1456
2052
|
commissioning: {
|
|
1457
2053
|
passcode,
|
|
1458
2054
|
discriminator,
|
|
1459
2055
|
},
|
|
2056
|
+
// Provide Node announcement settings
|
|
2057
|
+
// Optional: If Ommitted some development defaults are used
|
|
1460
2058
|
productDescription: {
|
|
1461
2059
|
name: await storageContext.get('deviceName'),
|
|
1462
2060
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1463
2061
|
},
|
|
2062
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2063
|
+
// Optional: If Omitted some development defaults are used
|
|
1464
2064
|
basicInformation: {
|
|
1465
2065
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1466
2066
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1477,14 +2077,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1477
2077
|
reachable: true,
|
|
1478
2078
|
},
|
|
1479
2079
|
});
|
|
2080
|
+
/**
|
|
2081
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2082
|
+
* This means: It is added to the first fabric.
|
|
2083
|
+
*/
|
|
1480
2084
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1481
2085
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1482
2086
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1483
2087
|
});
|
|
2088
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1484
2089
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1485
2090
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1486
2091
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1487
2092
|
});
|
|
2093
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1488
2094
|
serverNode.lifecycle.online.on(async () => {
|
|
1489
2095
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1490
2096
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1492,11 +2098,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1492
2098
|
const { qrPairingCode, manualPairingCode } = serverNode.state.commissioning.pairingCodes;
|
|
1493
2099
|
this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
|
|
1494
2100
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2101
|
+
// Set a timeout to show that advertising stops after 15 minutes if not commissioned
|
|
1495
2102
|
this.startEndAdvertiseTimer(serverNode);
|
|
1496
2103
|
this.advertisingNodes.set(storeId, Date.now());
|
|
1497
2104
|
}
|
|
1498
2105
|
else {
|
|
2106
|
+
// istanbul ignore next
|
|
1499
2107
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2108
|
+
// istanbul ignore next
|
|
1500
2109
|
this.advertisingNodes.delete(storeId);
|
|
1501
2110
|
}
|
|
1502
2111
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
@@ -1505,23 +2114,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1505
2114
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1506
2115
|
this.emit('online', storeId);
|
|
1507
2116
|
});
|
|
2117
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1508
2118
|
serverNode.lifecycle.offline.on(() => {
|
|
1509
2119
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1510
|
-
this.matterbridgeInformation.matterbridgeEndAdvertise = true;
|
|
2120
|
+
this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
|
|
1511
2121
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1512
2122
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1513
2123
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1514
2124
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1515
2125
|
this.emit('offline', storeId);
|
|
1516
2126
|
});
|
|
2127
|
+
/**
|
|
2128
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2129
|
+
* information is needed.
|
|
2130
|
+
*/
|
|
1517
2131
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1518
2132
|
let action = '';
|
|
1519
2133
|
switch (fabricAction) {
|
|
1520
2134
|
case FabricAction.Added:
|
|
1521
|
-
this.advertisingNodes.delete(storeId);
|
|
2135
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1522
2136
|
clearTimeout(this.endAdvertiseTimeout);
|
|
1523
2137
|
this.endAdvertiseTimeout = undefined;
|
|
1524
|
-
this.matterbridgeInformation.matterbridgeEndAdvertise = true;
|
|
2138
|
+
this.matterbridgeInformation.matterbridgeEndAdvertise = true; // Set the end advertise flag to true, so the frontend won't show the QR code anymore
|
|
1525
2139
|
action = 'added';
|
|
1526
2140
|
break;
|
|
1527
2141
|
case FabricAction.Removed:
|
|
@@ -1535,16 +2149,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1535
2149
|
this.frontend.wssSendRefreshRequired('fabrics');
|
|
1536
2150
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1537
2151
|
});
|
|
2152
|
+
/**
|
|
2153
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2154
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2155
|
+
*/
|
|
1538
2156
|
serverNode.events.sessions.opened.on((session) => {
|
|
1539
2157
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1540
2158
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1541
2159
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1542
2160
|
});
|
|
2161
|
+
/**
|
|
2162
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2163
|
+
*/
|
|
1543
2164
|
serverNode.events.sessions.closed.on((session) => {
|
|
1544
2165
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1545
2166
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
1546
2167
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1547
2168
|
});
|
|
2169
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1548
2170
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1549
2171
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1550
2172
|
this.frontend.wssSendRefreshRequired('sessions');
|
|
@@ -1553,6 +2175,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1553
2175
|
this.log.info(`Created server node for ${storeId}`);
|
|
1554
2176
|
return serverNode;
|
|
1555
2177
|
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2180
|
+
*
|
|
2181
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2182
|
+
*/
|
|
1556
2183
|
startEndAdvertiseTimer(matterServerNode) {
|
|
1557
2184
|
if (this.endAdvertiseTimeout) {
|
|
1558
2185
|
this.log.debug(`Clear ${matterServerNode.id} server node end advertise timer`);
|
|
@@ -1572,6 +2199,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1572
2199
|
this.log.notice(`Advertising stopped.`);
|
|
1573
2200
|
}, 15 * 60 * 1000).unref();
|
|
1574
2201
|
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Starts the 15 minutes timer to advice that advertising for the specified server node is ended.
|
|
2204
|
+
*
|
|
2205
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2206
|
+
* @returns {ApiDevicesMatter} The sanitized data of the server node.
|
|
2207
|
+
*/
|
|
1575
2208
|
getServerNodeData(serverNode) {
|
|
1576
2209
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1577
2210
|
return {
|
|
@@ -1587,12 +2220,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1587
2220
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1588
2221
|
};
|
|
1589
2222
|
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Starts the specified server node.
|
|
2225
|
+
*
|
|
2226
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2227
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2228
|
+
*/
|
|
1590
2229
|
async startServerNode(matterServerNode) {
|
|
1591
2230
|
if (!matterServerNode)
|
|
1592
2231
|
return;
|
|
1593
2232
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1594
2233
|
await matterServerNode.start();
|
|
1595
2234
|
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Stops the specified server node.
|
|
2237
|
+
*
|
|
2238
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2239
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2240
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2241
|
+
*/
|
|
1596
2242
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1597
2243
|
if (!matterServerNode)
|
|
1598
2244
|
return;
|
|
@@ -1605,6 +2251,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1605
2251
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1606
2252
|
}
|
|
1607
2253
|
}
|
|
2254
|
+
/**
|
|
2255
|
+
* Advertises the specified server node.
|
|
2256
|
+
*
|
|
2257
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2258
|
+
* @returns {Promise<{ qrPairingCode: string, manualPairingCode: string } | undefined>} A promise that resolves to the pairing codes if the server node is advertised, or undefined if not.
|
|
2259
|
+
*/
|
|
1608
2260
|
async advertiseServerNode(matterServerNode) {
|
|
1609
2261
|
if (matterServerNode) {
|
|
1610
2262
|
await matterServerNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
|
|
@@ -1614,6 +2266,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1614
2266
|
return { qrPairingCode, manualPairingCode };
|
|
1615
2267
|
}
|
|
1616
2268
|
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Stop advertise the specified server node.
|
|
2271
|
+
*
|
|
2272
|
+
* @param {ServerNode} [matterServerNode] - The server node to advertise.
|
|
2273
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped advertising.
|
|
2274
|
+
*/
|
|
1617
2275
|
async stopAdvertiseServerNode(matterServerNode) {
|
|
1618
2276
|
if (matterServerNode && matterServerNode.lifecycle.isOnline) {
|
|
1619
2277
|
await matterServerNode.env.get(DeviceCommissioner)?.endCommissioning();
|
|
@@ -1621,13 +2279,27 @@ export class Matterbridge extends EventEmitter {
|
|
|
1621
2279
|
this.log.notice(`Stopped advertising for ${matterServerNode.id}`);
|
|
1622
2280
|
}
|
|
1623
2281
|
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Creates an aggregator node with the specified storage context.
|
|
2284
|
+
*
|
|
2285
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2286
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2287
|
+
*/
|
|
1624
2288
|
async createAggregatorNode(storageContext) {
|
|
1625
2289
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1626
2290
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1627
2291
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1628
2292
|
return aggregatorNode;
|
|
1629
2293
|
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2296
|
+
*
|
|
2297
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2298
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2299
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2300
|
+
*/
|
|
1630
2301
|
async addBridgedEndpoint(pluginName, device) {
|
|
2302
|
+
// Check if the plugin is registered
|
|
1631
2303
|
const plugin = this.plugins.get(pluginName);
|
|
1632
2304
|
if (!plugin) {
|
|
1633
2305
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1647,6 +2319,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1647
2319
|
}
|
|
1648
2320
|
else if (this.bridgeMode === 'bridge') {
|
|
1649
2321
|
if (device.mode === 'matter') {
|
|
2322
|
+
// Register and add the device to the matterbridge server node
|
|
1650
2323
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1651
2324
|
if (!this.serverNode) {
|
|
1652
2325
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1663,6 +2336,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1663
2336
|
}
|
|
1664
2337
|
}
|
|
1665
2338
|
else {
|
|
2339
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1666
2340
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1667
2341
|
if (!this.aggregatorNode) {
|
|
1668
2342
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1680,6 +2354,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1680
2354
|
}
|
|
1681
2355
|
}
|
|
1682
2356
|
else if (this.bridgeMode === 'childbridge') {
|
|
2357
|
+
// Register and add the device to the plugin server node
|
|
1683
2358
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1684
2359
|
try {
|
|
1685
2360
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1703,10 +2378,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1703
2378
|
return;
|
|
1704
2379
|
}
|
|
1705
2380
|
}
|
|
2381
|
+
// Register and add the device to the plugin aggregator node
|
|
1706
2382
|
if (plugin.type === 'DynamicPlatform') {
|
|
1707
2383
|
try {
|
|
1708
2384
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1709
2385
|
await this.createDynamicPlugin(plugin);
|
|
2386
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1710
2387
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1711
2388
|
if (!plugin.aggregatorNode) {
|
|
1712
2389
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1729,17 +2406,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1729
2406
|
plugin.registeredDevices++;
|
|
1730
2407
|
if (plugin.addedDevices !== undefined)
|
|
1731
2408
|
plugin.addedDevices++;
|
|
2409
|
+
// Add the device to the DeviceManager
|
|
1732
2410
|
this.devices.set(device);
|
|
2411
|
+
// Subscribe to the reachable$Changed event
|
|
1733
2412
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1734
2413
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1735
2414
|
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2417
|
+
*
|
|
2418
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2419
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2420
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2421
|
+
*/
|
|
1736
2422
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1737
2423
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2424
|
+
// Check if the plugin is registered
|
|
1738
2425
|
const plugin = this.plugins.get(pluginName);
|
|
1739
2426
|
if (!plugin) {
|
|
1740
2427
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1741
2428
|
return;
|
|
1742
2429
|
}
|
|
2430
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1743
2431
|
if (this.bridgeMode === 'bridge') {
|
|
1744
2432
|
if (!this.aggregatorNode) {
|
|
1745
2433
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1754,6 +2442,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1754
2442
|
}
|
|
1755
2443
|
else if (this.bridgeMode === 'childbridge') {
|
|
1756
2444
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2445
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1757
2446
|
}
|
|
1758
2447
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1759
2448
|
if (!plugin.aggregatorNode) {
|
|
@@ -1768,8 +2457,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1768
2457
|
if (plugin.addedDevices !== undefined)
|
|
1769
2458
|
plugin.addedDevices--;
|
|
1770
2459
|
}
|
|
2460
|
+
// Remove the device from the DeviceManager
|
|
1771
2461
|
this.devices.remove(device);
|
|
1772
2462
|
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2465
|
+
*
|
|
2466
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2467
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2468
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2469
|
+
*
|
|
2470
|
+
* @remarks
|
|
2471
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2472
|
+
* It also applies a delay between each removal if specified.
|
|
2473
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2474
|
+
*/
|
|
1773
2475
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1774
2476
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1775
2477
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1780,6 +2482,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1780
2482
|
if (delay > 0)
|
|
1781
2483
|
await wait(2000);
|
|
1782
2484
|
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2487
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2488
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2489
|
+
*
|
|
2490
|
+
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2491
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2492
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2493
|
+
*/
|
|
1783
2494
|
async subscribeAttributeChanged(plugin, device) {
|
|
1784
2495
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
1785
2496
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
@@ -1795,6 +2506,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1795
2506
|
});
|
|
1796
2507
|
}
|
|
1797
2508
|
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2511
|
+
*
|
|
2512
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2513
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2514
|
+
*/
|
|
1798
2515
|
sanitizeFabricInformations(fabricInfo) {
|
|
1799
2516
|
return fabricInfo.map((info) => {
|
|
1800
2517
|
return {
|
|
@@ -1808,6 +2525,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1808
2525
|
};
|
|
1809
2526
|
});
|
|
1810
2527
|
}
|
|
2528
|
+
/**
|
|
2529
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2530
|
+
*
|
|
2531
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2532
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2533
|
+
*/
|
|
1811
2534
|
sanitizeSessionInformation(sessions) {
|
|
1812
2535
|
return sessions
|
|
1813
2536
|
.filter((session) => session.isPeerActive)
|
|
@@ -1834,7 +2557,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1834
2557
|
};
|
|
1835
2558
|
});
|
|
1836
2559
|
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2562
|
+
*
|
|
2563
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2564
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2565
|
+
*/
|
|
2566
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1837
2567
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2568
|
+
/*
|
|
2569
|
+
for (const child of aggregatorNode.parts) {
|
|
2570
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2571
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2572
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2573
|
+
}
|
|
2574
|
+
*/
|
|
1838
2575
|
}
|
|
1839
2576
|
getVendorIdName = (vendorId) => {
|
|
1840
2577
|
if (!vendorId)
|
|
@@ -1874,10 +2611,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1874
2611
|
case 0x1488:
|
|
1875
2612
|
vendorName = '(ShortcutLabsFlic)';
|
|
1876
2613
|
break;
|
|
1877
|
-
case 65521:
|
|
2614
|
+
case 65521: // 0xFFF1
|
|
1878
2615
|
vendorName = '(MatterTest)';
|
|
1879
2616
|
break;
|
|
1880
2617
|
}
|
|
1881
2618
|
return vendorName;
|
|
1882
2619
|
};
|
|
1883
2620
|
}
|
|
2621
|
+
//# sourceMappingURL=matterbridge.js.map
|