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