matterbridge 3.3.1-dev-20251012-b3546f8 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/dist/broadcastServer.d.ts +105 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +86 -1
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +717 -0
- package/dist/broadcastServerTypes.d.ts.map +1 -0
- package/dist/broadcastServerTypes.js +24 -0
- package/dist/broadcastServerTypes.js.map +1 -0
- package/dist/cli.d.ts +26 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +143 -5
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +50 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +37 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +70 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +46 -2
- package/dist/cliHistory.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 +117 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +124 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +60 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +55 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +75 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +25 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +234 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +402 -29
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +522 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.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 +25 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterbridge.d.ts +469 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +795 -45
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +36 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +1747 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +65 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +761 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +630 -17
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +36 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1534 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1398 -58
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +407 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +345 -12
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +402 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +341 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +209 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +26 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +353 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +325 -3
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +99 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +59 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +54 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +38 -1
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +13 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/hex.d.ts +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/jestHelpers.d.ts +137 -0
- package/dist/utils/jestHelpers.d.ts.map +1 -0
- package/dist/utils/jestHelpers.js +153 -3
- package/dist/utils/jestHelpers.js.map +1 -0
- package/dist/utils/network.d.ts +115 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +108 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +35 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +71 -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,16 +1,45 @@
|
|
|
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
|
+
// eslint-disable-next-line no-console
|
|
1
25
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
2
26
|
console.log('\u001B[32mMatterbridge loaded.\u001B[40;0m');
|
|
27
|
+
// Node.js modules
|
|
3
28
|
import os from 'node:os';
|
|
4
29
|
import path from 'node:path';
|
|
5
30
|
import { promises as fs } from 'node:fs';
|
|
6
31
|
import EventEmitter from 'node:events';
|
|
7
32
|
import { inspect } from 'node:util';
|
|
33
|
+
// AnsiLogger module
|
|
8
34
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE, or } from 'node-ansi-logger';
|
|
35
|
+
// NodeStorage module
|
|
9
36
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
37
|
+
// @matter
|
|
10
38
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
11
39
|
import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
12
40
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
13
41
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
42
|
+
// Matterbridge
|
|
14
43
|
import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
|
|
15
44
|
import { copyDirectory } from './utils/copyDirectory.js';
|
|
16
45
|
import { createDirectory } from './utils/createDirectory.js';
|
|
@@ -24,7 +53,11 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
|
24
53
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
25
54
|
import { Frontend } from './frontend.js';
|
|
26
55
|
import { addVirtualDevices } from './helpers.js';
|
|
56
|
+
/**
|
|
57
|
+
* Represents the Matterbridge application.
|
|
58
|
+
*/
|
|
27
59
|
export class Matterbridge extends EventEmitter {
|
|
60
|
+
/** Matterbridge system information */
|
|
28
61
|
systemInformation = {
|
|
29
62
|
interfaceName: '',
|
|
30
63
|
macAddress: '',
|
|
@@ -47,6 +80,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
47
80
|
heapTotal: '',
|
|
48
81
|
heapUsed: '',
|
|
49
82
|
};
|
|
83
|
+
// Matterbridge settings
|
|
50
84
|
homeDirectory = '';
|
|
51
85
|
rootDirectory = '';
|
|
52
86
|
matterbridgeDirectory = '';
|
|
@@ -61,10 +95,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
61
95
|
restartMode = '';
|
|
62
96
|
virtualMode = 'outlet';
|
|
63
97
|
profile = getParameter('profile');
|
|
64
|
-
|
|
98
|
+
/** Matterbridge logger */
|
|
99
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
100
|
+
/** Whether to log to a file */
|
|
65
101
|
fileLogger = false;
|
|
66
|
-
|
|
102
|
+
/** Matter logger */
|
|
103
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
104
|
+
/** Whether to log Matter to a file */
|
|
67
105
|
matterFileLogger = false;
|
|
106
|
+
// Frontend settings
|
|
68
107
|
readOnly = hasParameter('readonly') || hasParameter('shelly');
|
|
69
108
|
shellyBoard = hasParameter('shelly');
|
|
70
109
|
shellySysUpdate = false;
|
|
@@ -72,12 +111,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
72
111
|
restartRequired = false;
|
|
73
112
|
fixedRestartRequired = false;
|
|
74
113
|
updateRequired = false;
|
|
114
|
+
// Managers
|
|
75
115
|
plugins = new PluginManager(this);
|
|
76
116
|
devices = new DeviceManager();
|
|
117
|
+
// Frontend
|
|
77
118
|
frontend = new Frontend(this);
|
|
119
|
+
/** Matterbridge node storage manager created in matterbridgeDirectory with name 'storage' */
|
|
78
120
|
nodeStorage;
|
|
121
|
+
/** Matterbridge node context created with name 'matterbridge' */
|
|
79
122
|
nodeContext;
|
|
123
|
+
/** The main instance of the Matterbridge class (singleton) */
|
|
80
124
|
static instance;
|
|
125
|
+
// Instance properties
|
|
81
126
|
shutdown = false;
|
|
82
127
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
83
128
|
hasCleanupStarted = false;
|
|
@@ -92,19 +137,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
92
137
|
sigtermHandler;
|
|
93
138
|
exceptionHandler;
|
|
94
139
|
rejectionHandler;
|
|
140
|
+
/** Matter environment default */
|
|
95
141
|
environment = Environment.default;
|
|
142
|
+
/** Matter storage service from environment default */
|
|
96
143
|
matterStorageService;
|
|
144
|
+
/** Matter storage manager created with name 'Matterbridge' */
|
|
97
145
|
matterStorageManager;
|
|
146
|
+
/** Matter matterbridge storage context created in the storage manager with name 'persist' */
|
|
98
147
|
matterbridgeContext;
|
|
99
148
|
controllerContext;
|
|
149
|
+
/** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
|
|
100
150
|
mdnsInterface;
|
|
151
|
+
/** Matter listeningAddressIpv4 address */
|
|
101
152
|
ipv4Address;
|
|
153
|
+
/** Matter listeningAddressIpv6 address */
|
|
102
154
|
ipv6Address;
|
|
103
|
-
port
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
155
|
+
/** Matter commissioning port */
|
|
156
|
+
port; // first server node port
|
|
157
|
+
/** Matter commissioning passcode */
|
|
158
|
+
passcode; // first server node passcode
|
|
159
|
+
/** Matter commissioning discriminator */
|
|
160
|
+
discriminator; // first server node discriminator
|
|
161
|
+
/** Matter device certification */
|
|
162
|
+
certification; // device certification
|
|
163
|
+
/** Matter server node in bridge mode */
|
|
107
164
|
serverNode;
|
|
165
|
+
/** Matter aggregator node in bridge mode */
|
|
108
166
|
aggregatorNode;
|
|
109
167
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
110
168
|
aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
|
|
@@ -113,13 +171,26 @@ export class Matterbridge extends EventEmitter {
|
|
|
113
171
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
114
172
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
115
173
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
174
|
+
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
116
175
|
advertisingNodes = new Map();
|
|
176
|
+
/** We load asyncronously so is private */
|
|
117
177
|
constructor() {
|
|
118
178
|
super();
|
|
119
179
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
120
180
|
}
|
|
181
|
+
//* ************************************************************************************************************************************ */
|
|
182
|
+
// loadInstance() and cleanup() methods */
|
|
183
|
+
//* ************************************************************************************************************************************ */
|
|
184
|
+
/**
|
|
185
|
+
* Loads an instance of the Matterbridge class.
|
|
186
|
+
* If an instance already exists, return that instance.
|
|
187
|
+
*
|
|
188
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
189
|
+
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
190
|
+
*/
|
|
121
191
|
static async loadInstance(initialize = false) {
|
|
122
192
|
if (!Matterbridge.instance) {
|
|
193
|
+
// eslint-disable-next-line no-console
|
|
123
194
|
if (hasParameter('debug'))
|
|
124
195
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
125
196
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -128,8 +199,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
128
199
|
}
|
|
129
200
|
return Matterbridge.instance;
|
|
130
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Call cleanup() and dispose MdnsService.
|
|
204
|
+
*
|
|
205
|
+
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
206
|
+
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
207
|
+
*
|
|
208
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
209
|
+
*/
|
|
131
210
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
132
211
|
this.log.info(`Destroy instance...`);
|
|
212
|
+
// Save server nodes to close
|
|
133
213
|
const servers = [];
|
|
134
214
|
if (this.bridgeMode === 'bridge') {
|
|
135
215
|
if (this.serverNode)
|
|
@@ -147,67 +227,101 @@ export class Matterbridge extends EventEmitter {
|
|
|
147
227
|
servers.push(device.serverNode);
|
|
148
228
|
}
|
|
149
229
|
}
|
|
230
|
+
// Let any already‐queued microtasks run first
|
|
150
231
|
await Promise.resolve();
|
|
232
|
+
// Wait for the cleanup to finish
|
|
151
233
|
await wait(pause, 'destroyInstance start', true);
|
|
234
|
+
// Cleanup
|
|
152
235
|
await this.cleanup('destroying instance...', false, timeout);
|
|
236
|
+
// Close servers mdns service
|
|
153
237
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
154
238
|
for (const server of servers) {
|
|
155
239
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
156
240
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
157
241
|
}
|
|
242
|
+
// Let any already‐queued microtasks run first
|
|
158
243
|
await Promise.resolve();
|
|
244
|
+
// Wait for the cleanup to finish
|
|
159
245
|
await wait(pause, 'destroyInstance stop', true);
|
|
160
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Initializes the Matterbridge application.
|
|
249
|
+
*
|
|
250
|
+
* @remarks
|
|
251
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
252
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
253
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
254
|
+
*
|
|
255
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
256
|
+
*/
|
|
161
257
|
async initialize() {
|
|
258
|
+
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
259
|
+
// Emit the initialize_started event
|
|
162
260
|
this.emit('initialize_started');
|
|
261
|
+
// Set the restart mode
|
|
163
262
|
if (hasParameter('service'))
|
|
164
263
|
this.restartMode = 'service';
|
|
165
264
|
if (hasParameter('docker'))
|
|
166
265
|
this.restartMode = 'docker';
|
|
266
|
+
// Set the matterbridge home directory
|
|
167
267
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
168
268
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
269
|
+
// Set the matterbridge directory
|
|
169
270
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
170
271
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
171
272
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
172
273
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
274
|
+
// Set the matterbridge plugin directory
|
|
173
275
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
174
276
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
277
|
+
// Set the matterbridge cert directory
|
|
175
278
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
176
279
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
280
|
+
// Set the matterbridge root directory
|
|
177
281
|
const { fileURLToPath } = await import('node:url');
|
|
178
282
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
179
|
-
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
283
|
+
this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
|
|
284
|
+
// Setup the matter environment with default values
|
|
180
285
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
181
286
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
182
287
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
183
288
|
this.environment.vars.set('runtime.signals', false);
|
|
184
289
|
this.environment.vars.set('runtime.exitcode', false);
|
|
290
|
+
// Register process handlers
|
|
185
291
|
this.registerProcessHandlers();
|
|
292
|
+
// Initialize nodeStorage and nodeContext
|
|
186
293
|
try {
|
|
187
294
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
188
295
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
189
296
|
this.log.debug('Creating node storage context for matterbridge');
|
|
190
297
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
298
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
191
300
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
192
301
|
for (const key of keys) {
|
|
193
302
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
304
|
await this.nodeStorage?.storage.get(key);
|
|
195
305
|
}
|
|
196
306
|
const storages = await this.nodeStorage.getStorageNames();
|
|
197
307
|
for (const storage of storages) {
|
|
198
308
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
199
309
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
310
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
312
|
const keys = (await nodeContext?.storage.keys());
|
|
201
313
|
keys.forEach(async (key) => {
|
|
202
314
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
203
315
|
await nodeContext?.get(key);
|
|
204
316
|
});
|
|
205
317
|
}
|
|
318
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
206
319
|
this.log.debug('Creating node storage backup...');
|
|
207
320
|
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
208
321
|
this.log.debug('Created node storage backup');
|
|
209
322
|
}
|
|
210
323
|
catch (error) {
|
|
324
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
211
325
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
212
326
|
if (hasParameter('norestore')) {
|
|
213
327
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -221,14 +335,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
221
335
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
222
336
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
223
337
|
}
|
|
338
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
224
339
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
340
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
225
341
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
342
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
226
343
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
344
|
+
// Certificate management
|
|
227
345
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
228
346
|
try {
|
|
229
347
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
230
348
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
231
349
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
350
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
232
351
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
233
352
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
234
353
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -257,11 +376,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
257
376
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
258
377
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
259
378
|
}
|
|
379
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
260
380
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
261
381
|
this.passcode = pairingFileJson.passcode;
|
|
262
382
|
this.discriminator = pairingFileJson.discriminator;
|
|
263
383
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
264
384
|
}
|
|
385
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
265
386
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
266
387
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
267
388
|
this.certification = {
|
|
@@ -276,40 +397,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
276
397
|
catch (error) {
|
|
277
398
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
278
399
|
}
|
|
400
|
+
// Store the passcode, discriminator and port in the node context
|
|
279
401
|
await this.nodeContext.set('matterport', this.port);
|
|
280
402
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
281
403
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
282
404
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
405
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
283
406
|
if (hasParameter('logger')) {
|
|
284
407
|
const level = getParameter('logger');
|
|
285
408
|
if (level === 'debug') {
|
|
286
|
-
this.log.logLevel = "debug"
|
|
409
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
287
410
|
}
|
|
288
411
|
else if (level === 'info') {
|
|
289
|
-
this.log.logLevel = "info"
|
|
412
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
290
413
|
}
|
|
291
414
|
else if (level === 'notice') {
|
|
292
|
-
this.log.logLevel = "notice"
|
|
415
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
293
416
|
}
|
|
294
417
|
else if (level === 'warn') {
|
|
295
|
-
this.log.logLevel = "warn"
|
|
418
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
296
419
|
}
|
|
297
420
|
else if (level === 'error') {
|
|
298
|
-
this.log.logLevel = "error"
|
|
421
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
299
422
|
}
|
|
300
423
|
else if (level === 'fatal') {
|
|
301
|
-
this.log.logLevel = "fatal"
|
|
424
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
302
425
|
}
|
|
303
426
|
else {
|
|
304
427
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
305
|
-
this.log.logLevel = "info"
|
|
428
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
306
429
|
}
|
|
307
430
|
}
|
|
308
431
|
else {
|
|
309
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
432
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
310
433
|
}
|
|
311
434
|
this.frontend.logLevel = this.log.logLevel;
|
|
312
435
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
436
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
313
437
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
314
438
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
315
439
|
this.fileLogger = true;
|
|
@@ -318,6 +442,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
318
442
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
319
443
|
if (this.profile !== undefined)
|
|
320
444
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
445
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
321
446
|
if (hasParameter('matterlogger')) {
|
|
322
447
|
const level = getParameter('matterlogger');
|
|
323
448
|
if (level === 'debug') {
|
|
@@ -347,11 +472,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
347
472
|
Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
348
473
|
}
|
|
349
474
|
Logger.format = MatterLogFormat.ANSI;
|
|
475
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
350
476
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
351
477
|
this.matterFileLogger = true;
|
|
352
478
|
}
|
|
353
479
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
354
480
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
|
|
481
|
+
// Log network interfaces
|
|
355
482
|
const networkInterfaces = os.networkInterfaces();
|
|
356
483
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
357
484
|
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
@@ -364,6 +491,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
364
491
|
});
|
|
365
492
|
}
|
|
366
493
|
}
|
|
494
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
367
495
|
if (hasParameter('mdnsinterface')) {
|
|
368
496
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
369
497
|
}
|
|
@@ -372,6 +500,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
372
500
|
if (this.mdnsInterface === '')
|
|
373
501
|
this.mdnsInterface = undefined;
|
|
374
502
|
}
|
|
503
|
+
// Validate mdnsInterface
|
|
375
504
|
if (this.mdnsInterface) {
|
|
376
505
|
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
377
506
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
@@ -384,6 +513,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
384
513
|
}
|
|
385
514
|
if (this.mdnsInterface)
|
|
386
515
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
516
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
387
517
|
if (hasParameter('ipv4address')) {
|
|
388
518
|
this.ipv4Address = getParameter('ipv4address');
|
|
389
519
|
}
|
|
@@ -392,6 +522,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
392
522
|
if (this.ipv4Address === '')
|
|
393
523
|
this.ipv4Address = undefined;
|
|
394
524
|
}
|
|
525
|
+
// Validate ipv4address
|
|
395
526
|
if (this.ipv4Address) {
|
|
396
527
|
let isValid = false;
|
|
397
528
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -407,6 +538,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
407
538
|
await this.nodeContext.remove('matteripv4address');
|
|
408
539
|
}
|
|
409
540
|
}
|
|
541
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
410
542
|
if (hasParameter('ipv6address')) {
|
|
411
543
|
this.ipv6Address = getParameter('ipv6address');
|
|
412
544
|
}
|
|
@@ -415,6 +547,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
415
547
|
if (this.ipv6Address === '')
|
|
416
548
|
this.ipv6Address = undefined;
|
|
417
549
|
}
|
|
550
|
+
// Validate ipv6address
|
|
418
551
|
if (this.ipv6Address) {
|
|
419
552
|
let isValid = false;
|
|
420
553
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -423,6 +556,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
423
556
|
isValid = true;
|
|
424
557
|
break;
|
|
425
558
|
}
|
|
559
|
+
/* istanbul ignore next */
|
|
426
560
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
427
561
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
428
562
|
isValid = true;
|
|
@@ -435,6 +569,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
435
569
|
await this.nodeContext.remove('matteripv6address');
|
|
436
570
|
}
|
|
437
571
|
}
|
|
572
|
+
// Initialize the virtual mode
|
|
438
573
|
if (hasParameter('novirtual')) {
|
|
439
574
|
this.virtualMode = 'disabled';
|
|
440
575
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -443,12 +578,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
443
578
|
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
444
579
|
}
|
|
445
580
|
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
581
|
+
// Initialize PluginManager
|
|
446
582
|
this.plugins.logLevel = this.log.logLevel;
|
|
447
583
|
await this.plugins.loadFromStorage();
|
|
584
|
+
// Initialize DeviceManager
|
|
448
585
|
this.devices.logLevel = this.log.logLevel;
|
|
586
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
449
587
|
for (const plugin of this.plugins) {
|
|
450
588
|
const packageJson = await this.plugins.parse(plugin);
|
|
451
589
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
590
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
591
|
+
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
452
592
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
453
593
|
try {
|
|
454
594
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -471,6 +611,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
611
|
await plugin.nodeContext.set('description', plugin.description);
|
|
472
612
|
await plugin.nodeContext.set('author', plugin.author);
|
|
473
613
|
}
|
|
614
|
+
// Log system info and create .matterbridge directory
|
|
474
615
|
await this.logNodeAndSystemInfo();
|
|
475
616
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
476
617
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -478,6 +619,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
478
619
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
479
620
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
480
621
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
622
|
+
// Check node version and throw error
|
|
481
623
|
const minNodeVersion = 18;
|
|
482
624
|
const nodeVersion = process.versions.node;
|
|
483
625
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -485,10 +627,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
485
627
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
486
628
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
487
629
|
}
|
|
630
|
+
// Parse command line
|
|
488
631
|
await this.parseCommandLine();
|
|
632
|
+
// Emit the initialize_completed event
|
|
489
633
|
this.emit('initialize_completed');
|
|
490
634
|
this.initialized = true;
|
|
491
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
638
|
+
*
|
|
639
|
+
* @private
|
|
640
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
641
|
+
*/
|
|
492
642
|
async parseCommandLine() {
|
|
493
643
|
if (hasParameter('help')) {
|
|
494
644
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -550,6 +700,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
550
700
|
}
|
|
551
701
|
index++;
|
|
552
702
|
}
|
|
703
|
+
/*
|
|
704
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
705
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
706
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
707
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
708
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
709
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
710
|
+
} else {
|
|
711
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
712
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
*/
|
|
553
716
|
this.shutdown = true;
|
|
554
717
|
return;
|
|
555
718
|
}
|
|
@@ -599,8 +762,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
599
762
|
this.shutdown = true;
|
|
600
763
|
return;
|
|
601
764
|
}
|
|
765
|
+
// Initialize frontend
|
|
602
766
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
603
767
|
await this.frontend.start(getIntParameter('frontend'));
|
|
768
|
+
// Start the matter storage and create the matterbridge context
|
|
604
769
|
try {
|
|
605
770
|
await this.startMatterStorage();
|
|
606
771
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -616,18 +781,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
616
781
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
617
782
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
618
783
|
}
|
|
784
|
+
// Clear the matterbridge context if the reset parameter is set
|
|
619
785
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
620
786
|
this.initialized = true;
|
|
621
787
|
await this.shutdownProcessAndReset();
|
|
622
788
|
this.shutdown = true;
|
|
623
789
|
return;
|
|
624
790
|
}
|
|
791
|
+
// Clear matterbridge plugin context if the reset parameter is set
|
|
625
792
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
626
793
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
627
794
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
628
795
|
if (plugin) {
|
|
629
796
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
630
797
|
if (!matterStorageManager) {
|
|
798
|
+
/* istanbul ignore next */
|
|
631
799
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
632
800
|
}
|
|
633
801
|
else {
|
|
@@ -646,35 +814,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
646
814
|
this.shutdown = true;
|
|
647
815
|
return;
|
|
648
816
|
}
|
|
817
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
649
818
|
clearTimeout(this.checkUpdateTimeout);
|
|
650
819
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
651
820
|
const { checkUpdates } = await import('./update.js');
|
|
652
821
|
checkUpdates(this);
|
|
653
822
|
}, 30 * 1000).unref();
|
|
823
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
654
824
|
clearInterval(this.checkUpdateInterval);
|
|
655
825
|
this.checkUpdateInterval = setInterval(async () => {
|
|
656
826
|
const { checkUpdates } = await import('./update.js');
|
|
657
827
|
checkUpdates(this);
|
|
658
828
|
}, 12 * 60 * 60 * 1000).unref();
|
|
829
|
+
// Start the matterbridge in mode test
|
|
659
830
|
if (hasParameter('test')) {
|
|
660
831
|
this.bridgeMode = 'bridge';
|
|
661
832
|
return;
|
|
662
833
|
}
|
|
834
|
+
// Start the matterbridge in mode controller
|
|
663
835
|
if (hasParameter('controller')) {
|
|
664
836
|
this.bridgeMode = 'controller';
|
|
665
837
|
await this.startController();
|
|
666
838
|
return;
|
|
667
839
|
}
|
|
840
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
668
841
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
669
842
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
670
843
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
671
844
|
}
|
|
845
|
+
// Start matterbridge in bridge mode
|
|
672
846
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
673
847
|
this.bridgeMode = 'bridge';
|
|
674
848
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
675
849
|
await this.startBridge();
|
|
676
850
|
return;
|
|
677
851
|
}
|
|
852
|
+
// Start matterbridge in childbridge mode
|
|
678
853
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
679
854
|
this.bridgeMode = 'childbridge';
|
|
680
855
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -682,10 +857,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
682
857
|
return;
|
|
683
858
|
}
|
|
684
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Asynchronously loads and starts the registered plugins.
|
|
862
|
+
*
|
|
863
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
864
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
865
|
+
*
|
|
866
|
+
* @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
|
|
867
|
+
* @param {boolean} [start] - If true, the method will start the plugins after loading them.
|
|
868
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
869
|
+
*/
|
|
685
870
|
async startPlugins(wait = false, start = true) {
|
|
871
|
+
// Check, load and start the plugins
|
|
686
872
|
for (const plugin of this.plugins) {
|
|
687
873
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
688
874
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
875
|
+
// Check if the plugin is available
|
|
689
876
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
690
877
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
691
878
|
plugin.enabled = false;
|
|
@@ -705,10 +892,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
705
892
|
if (wait)
|
|
706
893
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
707
894
|
else
|
|
708
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
895
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
|
|
709
896
|
}
|
|
710
897
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
711
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
901
|
+
* - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
|
|
902
|
+
* - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
|
|
903
|
+
* - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
|
|
904
|
+
*/
|
|
712
905
|
registerProcessHandlers() {
|
|
713
906
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
714
907
|
process.removeAllListeners('uncaughtException');
|
|
@@ -735,6 +928,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
735
928
|
};
|
|
736
929
|
process.on('SIGTERM', this.sigtermHandler);
|
|
737
930
|
}
|
|
931
|
+
/**
|
|
932
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
933
|
+
*/
|
|
738
934
|
deregisterProcessHandlers() {
|
|
739
935
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
740
936
|
if (this.exceptionHandler)
|
|
@@ -751,13 +947,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
751
947
|
process.off('SIGTERM', this.sigtermHandler);
|
|
752
948
|
this.sigtermHandler = undefined;
|
|
753
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Logs the node and system information.
|
|
952
|
+
*/
|
|
754
953
|
async logNodeAndSystemInfo() {
|
|
954
|
+
// IP address information
|
|
755
955
|
const networkInterfaces = os.networkInterfaces();
|
|
756
956
|
this.systemInformation.interfaceName = '';
|
|
757
957
|
this.systemInformation.ipv4Address = '';
|
|
758
958
|
this.systemInformation.ipv6Address = '';
|
|
759
959
|
this.systemInformation.macAddress = '';
|
|
760
960
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
961
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
761
962
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
762
963
|
continue;
|
|
763
964
|
if (!interfaceDetails) {
|
|
@@ -783,16 +984,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
783
984
|
break;
|
|
784
985
|
}
|
|
785
986
|
}
|
|
987
|
+
// Node information
|
|
786
988
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
787
989
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
788
990
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
789
991
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
992
|
+
// Host system information
|
|
790
993
|
this.systemInformation.hostname = os.hostname();
|
|
791
994
|
this.systemInformation.user = os.userInfo().username;
|
|
792
|
-
this.systemInformation.osType = os.type();
|
|
793
|
-
this.systemInformation.osRelease = os.release();
|
|
794
|
-
this.systemInformation.osPlatform = os.platform();
|
|
795
|
-
this.systemInformation.osArch = os.arch();
|
|
995
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
996
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
997
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
998
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
796
999
|
this.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
797
1000
|
this.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
798
1001
|
this.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
@@ -801,6 +1004,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
1004
|
this.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
802
1005
|
this.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
803
1006
|
this.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1007
|
+
// Log the system information
|
|
804
1008
|
this.log.debug('Host System Information:');
|
|
805
1009
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
806
1010
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -820,14 +1024,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
820
1024
|
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
821
1025
|
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
822
1026
|
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
1027
|
+
// Log directories
|
|
823
1028
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
824
1029
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
825
1030
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
826
1031
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
827
1032
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1033
|
+
// Global node_modules directory
|
|
828
1034
|
if (this.nodeContext)
|
|
829
1035
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
830
1036
|
if (this.globalModulesDirectory === '') {
|
|
1037
|
+
// First run of Matterbridge so the node storage is empty
|
|
831
1038
|
this.log.debug(`Getting global node_modules directory...`);
|
|
832
1039
|
try {
|
|
833
1040
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -840,6 +1047,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
840
1047
|
}
|
|
841
1048
|
}
|
|
842
1049
|
else {
|
|
1050
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
843
1051
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
844
1052
|
try {
|
|
845
1053
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -851,25 +1059,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
851
1059
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
852
1060
|
}
|
|
853
1061
|
}
|
|
1062
|
+
// Matterbridge version
|
|
854
1063
|
this.log.debug(`Reading matterbridge package.json...`);
|
|
855
1064
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
856
1065
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
857
1066
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1067
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
858
1068
|
if (this.nodeContext)
|
|
859
1069
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
860
1070
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1071
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
861
1072
|
if (this.nodeContext)
|
|
862
1073
|
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
863
1074
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1075
|
+
// Frontend version
|
|
864
1076
|
this.log.debug(`Reading frontend package.json...`);
|
|
865
1077
|
const frontendPackageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
866
1078
|
this.frontendVersion = frontendPackageJson.version;
|
|
867
1079
|
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1080
|
+
// Current working directory
|
|
868
1081
|
const currentDir = process.cwd();
|
|
869
1082
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1083
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
870
1084
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
871
1085
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
872
1086
|
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
1089
|
+
*
|
|
1090
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
1091
|
+
* @returns {Promise<void>} A promise that resolves when the logLevel has been set.
|
|
1092
|
+
*/
|
|
873
1093
|
async setLogLevel(logLevel) {
|
|
874
1094
|
this.log.logLevel = logLevel;
|
|
875
1095
|
this.frontend.logLevel = logLevel;
|
|
@@ -879,57 +1099,86 @@ export class Matterbridge extends EventEmitter {
|
|
|
879
1099
|
for (const plugin of this.plugins) {
|
|
880
1100
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
881
1101
|
continue;
|
|
882
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
|
|
883
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1102
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
|
|
1103
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
|
|
1104
|
+
}
|
|
1105
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1106
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1107
|
+
if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1108
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1109
|
+
if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1110
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
890
1111
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
891
1112
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
892
1113
|
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Get the current logger logLevel.
|
|
1116
|
+
*
|
|
1117
|
+
* @returns {LogLevel} The current logger logLevel.
|
|
1118
|
+
*/
|
|
893
1119
|
getLogLevel() {
|
|
894
1120
|
return this.log.logLevel;
|
|
895
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1124
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1125
|
+
*
|
|
1126
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1127
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1128
|
+
*/
|
|
896
1129
|
createDestinationMatterLogger(fileLogger) {
|
|
897
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1130
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
898
1131
|
if (fileLogger) {
|
|
899
1132
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
900
1133
|
}
|
|
901
1134
|
return (text, message) => {
|
|
1135
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
902
1136
|
const logger = text.slice(44, 44 + 20).trim();
|
|
903
1137
|
const msg = text.slice(65);
|
|
904
1138
|
this.matterLog.logName = logger;
|
|
905
1139
|
switch (message.level) {
|
|
906
1140
|
case MatterLogLevel.DEBUG:
|
|
907
|
-
this.matterLog.log("debug"
|
|
1141
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
908
1142
|
break;
|
|
909
1143
|
case MatterLogLevel.INFO:
|
|
910
|
-
this.matterLog.log("info"
|
|
1144
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
911
1145
|
break;
|
|
912
1146
|
case MatterLogLevel.NOTICE:
|
|
913
|
-
this.matterLog.log("notice"
|
|
1147
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
914
1148
|
break;
|
|
915
1149
|
case MatterLogLevel.WARN:
|
|
916
|
-
this.matterLog.log("warn"
|
|
1150
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
917
1151
|
break;
|
|
918
1152
|
case MatterLogLevel.ERROR:
|
|
919
|
-
this.matterLog.log("error"
|
|
1153
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
920
1154
|
break;
|
|
921
1155
|
case MatterLogLevel.FATAL:
|
|
922
|
-
this.matterLog.log("fatal"
|
|
1156
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
923
1157
|
break;
|
|
924
1158
|
}
|
|
925
1159
|
};
|
|
926
1160
|
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1163
|
+
*
|
|
1164
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1165
|
+
*/
|
|
927
1166
|
async restartProcess() {
|
|
928
1167
|
await this.cleanup('restarting...', true);
|
|
929
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Shut down the process (/api/shutdown).
|
|
1171
|
+
*
|
|
1172
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1173
|
+
*/
|
|
930
1174
|
async shutdownProcess() {
|
|
931
1175
|
await this.cleanup('shutting down...', false);
|
|
932
1176
|
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1179
|
+
*
|
|
1180
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1181
|
+
*/
|
|
933
1182
|
async updateProcess() {
|
|
934
1183
|
this.log.info('Updating matterbridge...');
|
|
935
1184
|
try {
|
|
@@ -943,6 +1192,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
943
1192
|
this.frontend.wssSendRestartRequired();
|
|
944
1193
|
await this.cleanup('updating...', false);
|
|
945
1194
|
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1197
|
+
*
|
|
1198
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1199
|
+
*
|
|
1200
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1201
|
+
*/
|
|
946
1202
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
947
1203
|
this.log.info('Unregistering all devices and shutting down...');
|
|
948
1204
|
for (const plugin of this.plugins.array()) {
|
|
@@ -954,46 +1210,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
954
1210
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
955
1211
|
}
|
|
956
1212
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
957
|
-
await wait(timeout);
|
|
1213
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
958
1214
|
this.log.debug('Cleaning up and shutting down...');
|
|
959
1215
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
960
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1219
|
+
*
|
|
1220
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1221
|
+
*/
|
|
961
1222
|
async shutdownProcessAndReset() {
|
|
962
1223
|
await this.cleanup('shutting down with reset...', false);
|
|
963
1224
|
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1227
|
+
*
|
|
1228
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1229
|
+
*/
|
|
964
1230
|
async shutdownProcessAndFactoryReset() {
|
|
965
1231
|
await this.cleanup('shutting down with factory reset...', false);
|
|
966
1232
|
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Cleans up the Matterbridge instance.
|
|
1235
|
+
*
|
|
1236
|
+
* @param {string} message - The cleanup message.
|
|
1237
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1238
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1239
|
+
*
|
|
1240
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1241
|
+
*/
|
|
967
1242
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
968
1243
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
969
1244
|
this.emit('cleanup_started');
|
|
970
1245
|
this.hasCleanupStarted = true;
|
|
971
1246
|
this.log.info(message);
|
|
1247
|
+
// Clear the start matter interval
|
|
972
1248
|
if (this.startMatterInterval) {
|
|
973
1249
|
clearInterval(this.startMatterInterval);
|
|
974
1250
|
this.startMatterInterval = undefined;
|
|
975
1251
|
this.log.debug('Start matter interval cleared');
|
|
976
1252
|
}
|
|
1253
|
+
// Clear the check update timeout
|
|
977
1254
|
if (this.checkUpdateTimeout) {
|
|
978
1255
|
clearTimeout(this.checkUpdateTimeout);
|
|
979
1256
|
this.checkUpdateTimeout = undefined;
|
|
980
1257
|
this.log.debug('Check update timeout cleared');
|
|
981
1258
|
}
|
|
1259
|
+
// Clear the check update interval
|
|
982
1260
|
if (this.checkUpdateInterval) {
|
|
983
1261
|
clearInterval(this.checkUpdateInterval);
|
|
984
1262
|
this.checkUpdateInterval = undefined;
|
|
985
1263
|
this.log.debug('Check update interval cleared');
|
|
986
1264
|
}
|
|
1265
|
+
// Clear the configure timeout
|
|
987
1266
|
if (this.configureTimeout) {
|
|
988
1267
|
clearTimeout(this.configureTimeout);
|
|
989
1268
|
this.configureTimeout = undefined;
|
|
990
1269
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
991
1270
|
}
|
|
1271
|
+
// Clear the reachability timeout
|
|
992
1272
|
if (this.reachabilityTimeout) {
|
|
993
1273
|
clearTimeout(this.reachabilityTimeout);
|
|
994
1274
|
this.reachabilityTimeout = undefined;
|
|
995
1275
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
996
1276
|
}
|
|
1277
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
997
1278
|
for (const plugin of this.plugins) {
|
|
998
1279
|
if (!plugin.enabled || plugin.error)
|
|
999
1280
|
continue;
|
|
@@ -1004,6 +1285,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1004
1285
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1005
1286
|
}
|
|
1006
1287
|
}
|
|
1288
|
+
// Stop matter server nodes
|
|
1007
1289
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1008
1290
|
if (timeout > 0) {
|
|
1009
1291
|
this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
|
|
@@ -1030,6 +1312,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1030
1312
|
}
|
|
1031
1313
|
}
|
|
1032
1314
|
this.log.notice('Stopped matter server nodes');
|
|
1315
|
+
// Matter commisioning reset
|
|
1033
1316
|
if (message === 'shutting down with reset...') {
|
|
1034
1317
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1035
1318
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1039,6 +1322,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1039
1322
|
await this.matterbridgeContext?.clearAll();
|
|
1040
1323
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1041
1324
|
}
|
|
1325
|
+
// Unregister all devices
|
|
1042
1326
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1043
1327
|
if (this.bridgeMode === 'bridge') {
|
|
1044
1328
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1056,15 +1340,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1056
1340
|
}
|
|
1057
1341
|
this.log.info('Matter storage reset done!');
|
|
1058
1342
|
}
|
|
1343
|
+
// Stop matter storage
|
|
1059
1344
|
await this.stopMatterStorage();
|
|
1345
|
+
// Stop the frontend
|
|
1060
1346
|
await this.frontend.stop();
|
|
1061
1347
|
this.frontend.destroy();
|
|
1348
|
+
// Close PluginManager and DeviceManager
|
|
1062
1349
|
this.plugins.destroy();
|
|
1063
1350
|
this.devices.destroy();
|
|
1351
|
+
// Close the matterbridge node storage and context
|
|
1064
1352
|
if (this.nodeStorage && this.nodeContext) {
|
|
1353
|
+
/*
|
|
1354
|
+
TODO: Implement serialization of registered devices in edge mode
|
|
1355
|
+
this.log.info('Saving registered devices...');
|
|
1356
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1357
|
+
this.devices.forEach(async (device) => {
|
|
1358
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1359
|
+
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1360
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1361
|
+
});
|
|
1362
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1363
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1364
|
+
*/
|
|
1365
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1065
1366
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1066
1367
|
await this.nodeContext.close();
|
|
1067
1368
|
this.nodeContext = undefined;
|
|
1369
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1068
1370
|
for (const plugin of this.plugins) {
|
|
1069
1371
|
if (plugin.nodeContext) {
|
|
1070
1372
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1081,8 +1383,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1081
1383
|
}
|
|
1082
1384
|
this.plugins.clear();
|
|
1083
1385
|
this.devices.clear();
|
|
1386
|
+
// Factory reset
|
|
1084
1387
|
if (message === 'shutting down with factory reset...') {
|
|
1085
1388
|
try {
|
|
1389
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1086
1390
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1087
1391
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1088
1392
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1091,11 +1395,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1091
1395
|
await fs.rm(backup, { recursive: true });
|
|
1092
1396
|
}
|
|
1093
1397
|
catch (error) {
|
|
1398
|
+
// istanbul ignore next if
|
|
1094
1399
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1095
1400
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1096
1401
|
}
|
|
1097
1402
|
}
|
|
1098
1403
|
try {
|
|
1404
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1099
1405
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1100
1406
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1101
1407
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1104,18 +1410,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1104
1410
|
await fs.rm(backup, { recursive: true });
|
|
1105
1411
|
}
|
|
1106
1412
|
catch (error) {
|
|
1413
|
+
// istanbul ignore next if
|
|
1107
1414
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1108
1415
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1109
1416
|
}
|
|
1110
1417
|
}
|
|
1111
1418
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1112
1419
|
}
|
|
1420
|
+
// Deregisters the process handlers
|
|
1113
1421
|
this.deregisterProcessHandlers();
|
|
1114
1422
|
if (restart) {
|
|
1115
1423
|
if (message === 'updating...') {
|
|
1116
1424
|
this.log.info('Cleanup completed. Updating...');
|
|
1117
1425
|
Matterbridge.instance = undefined;
|
|
1118
|
-
this.emit('update');
|
|
1426
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1119
1427
|
}
|
|
1120
1428
|
else if (message === 'restarting...') {
|
|
1121
1429
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1139,7 +1447,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1139
1447
|
this.log.debug('Cleanup already started...');
|
|
1140
1448
|
}
|
|
1141
1449
|
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Starts the Matterbridge in bridge mode.
|
|
1452
|
+
*
|
|
1453
|
+
* @private
|
|
1454
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1455
|
+
*/
|
|
1142
1456
|
async startBridge() {
|
|
1457
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1143
1458
|
if (!this.matterStorageManager)
|
|
1144
1459
|
throw new Error('No storage manager initialized');
|
|
1145
1460
|
if (!this.matterbridgeContext)
|
|
@@ -1178,13 +1493,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1178
1493
|
clearInterval(this.startMatterInterval);
|
|
1179
1494
|
this.startMatterInterval = undefined;
|
|
1180
1495
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1181
|
-
|
|
1496
|
+
// Start the Matter server node
|
|
1497
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1498
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1182
1499
|
for (const device of this.devices.array()) {
|
|
1183
1500
|
if (device.mode === 'server' && device.serverNode) {
|
|
1184
1501
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1185
|
-
this.startServerNode(device.serverNode);
|
|
1502
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1186
1503
|
}
|
|
1187
1504
|
}
|
|
1505
|
+
// Configure the plugins
|
|
1188
1506
|
this.configureTimeout = setTimeout(async () => {
|
|
1189
1507
|
for (const plugin of this.plugins.array()) {
|
|
1190
1508
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1202,28 +1520,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1202
1520
|
}
|
|
1203
1521
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1204
1522
|
}, 30 * 1000).unref();
|
|
1523
|
+
// Setting reachability to true
|
|
1205
1524
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1206
1525
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1207
1526
|
if (this.aggregatorNode)
|
|
1208
1527
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1209
1528
|
}, 60 * 1000).unref();
|
|
1529
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1210
1530
|
this.emit('bridge_started');
|
|
1211
1531
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1212
1532
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1213
1533
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1214
1534
|
}, this.startMatterIntervalMs);
|
|
1215
1535
|
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1538
|
+
*
|
|
1539
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1540
|
+
*
|
|
1541
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1542
|
+
*/
|
|
1216
1543
|
async startChildbridge(delay = 1000) {
|
|
1217
1544
|
if (!this.matterStorageManager)
|
|
1218
1545
|
throw new Error('No storage manager initialized');
|
|
1546
|
+
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1219
1547
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1220
1548
|
await this.startPlugins(true, false);
|
|
1549
|
+
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1221
1550
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1222
1551
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1223
1552
|
if (plugin.type === 'DynamicPlatform')
|
|
1224
1553
|
await this.createDynamicPlugin(plugin);
|
|
1225
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1554
|
+
this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
|
|
1226
1555
|
}
|
|
1556
|
+
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1227
1557
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1228
1558
|
let failCount = 0;
|
|
1229
1559
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1257,8 +1587,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1257
1587
|
clearInterval(this.startMatterInterval);
|
|
1258
1588
|
this.startMatterInterval = undefined;
|
|
1259
1589
|
if (delay > 0)
|
|
1260
|
-
await wait(delay);
|
|
1590
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1261
1591
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1592
|
+
// Configure the plugins
|
|
1262
1593
|
this.configureTimeout = setTimeout(async () => {
|
|
1263
1594
|
for (const plugin of this.plugins.array()) {
|
|
1264
1595
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1283,6 +1614,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1283
1614
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1284
1615
|
continue;
|
|
1285
1616
|
}
|
|
1617
|
+
// istanbul ignore next if cause is just a safety check
|
|
1286
1618
|
if (!plugin.serverNode) {
|
|
1287
1619
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1288
1620
|
continue;
|
|
@@ -1295,28 +1627,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1295
1627
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1296
1628
|
continue;
|
|
1297
1629
|
}
|
|
1298
|
-
|
|
1630
|
+
// Start the Matter server node
|
|
1631
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1632
|
+
// Setting reachability to true
|
|
1299
1633
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1300
1634
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1301
1635
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1302
1636
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1303
1637
|
}, 60 * 1000).unref();
|
|
1304
1638
|
}
|
|
1639
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1305
1640
|
for (const device of this.devices.array()) {
|
|
1306
1641
|
if (device.mode === 'server' && device.serverNode) {
|
|
1307
1642
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1308
|
-
this.startServerNode(device.serverNode);
|
|
1643
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1309
1644
|
}
|
|
1310
1645
|
}
|
|
1646
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1311
1647
|
this.emit('childbridge_started');
|
|
1312
1648
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1313
1649
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1314
1650
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1315
1651
|
}, this.startMatterIntervalMs);
|
|
1316
1652
|
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Starts the Matterbridge controller.
|
|
1655
|
+
*
|
|
1656
|
+
* @private
|
|
1657
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1658
|
+
*/
|
|
1317
1659
|
async startController() {
|
|
1660
|
+
/*
|
|
1661
|
+
if (!this.matterStorageManager) {
|
|
1662
|
+
this.log.error('No storage manager initialized');
|
|
1663
|
+
await this.cleanup('No storage manager initialized');
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1667
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1668
|
+
if (!this.controllerContext) {
|
|
1669
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1670
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1675
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1676
|
+
this.log.info('Creating matter commissioning controller');
|
|
1677
|
+
this.commissioningController = new CommissioningController({
|
|
1678
|
+
autoConnect: false,
|
|
1679
|
+
});
|
|
1680
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1681
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1682
|
+
|
|
1683
|
+
this.log.info('Starting matter server');
|
|
1684
|
+
await this.matterServer.start();
|
|
1685
|
+
this.log.info('Matter server started');
|
|
1686
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1687
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1688
|
+
regulatoryCountryCode: 'XX',
|
|
1689
|
+
};
|
|
1690
|
+
const commissioningController = new CommissioningController({
|
|
1691
|
+
environment: {
|
|
1692
|
+
environment,
|
|
1693
|
+
id: uniqueId,
|
|
1694
|
+
},
|
|
1695
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1696
|
+
adminFabricLabel,
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
if (hasParameter('pairingcode')) {
|
|
1700
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1701
|
+
const pairingCode = getParameter('pairingcode');
|
|
1702
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1703
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1704
|
+
|
|
1705
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1706
|
+
if (pairingCode !== undefined) {
|
|
1707
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1708
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1709
|
+
longDiscriminator = undefined;
|
|
1710
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1711
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1712
|
+
} else {
|
|
1713
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1714
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1715
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1716
|
+
}
|
|
1717
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1718
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
const options = {
|
|
1722
|
+
commissioning: commissioningOptions,
|
|
1723
|
+
discovery: {
|
|
1724
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1725
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1726
|
+
},
|
|
1727
|
+
passcode: setupPin,
|
|
1728
|
+
} as NodeCommissioningOptions;
|
|
1729
|
+
this.log.info('Commissioning with options:', options);
|
|
1730
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1731
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1732
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1733
|
+
} // (hasParameter('pairingcode'))
|
|
1734
|
+
|
|
1735
|
+
if (hasParameter('unpairall')) {
|
|
1736
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1737
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1738
|
+
for (const nodeId of nodeIds) {
|
|
1739
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1740
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1741
|
+
}
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (hasParameter('discover')) {
|
|
1746
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1747
|
+
// console.log(discover);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1751
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1756
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1757
|
+
for (const nodeId of nodeIds) {
|
|
1758
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1759
|
+
|
|
1760
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1761
|
+
autoSubscribe: false,
|
|
1762
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1763
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1764
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1765
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1766
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1767
|
+
switch (info) {
|
|
1768
|
+
case NodeStateInformation.Connected:
|
|
1769
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1770
|
+
break;
|
|
1771
|
+
case NodeStateInformation.Disconnected:
|
|
1772
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1773
|
+
break;
|
|
1774
|
+
case NodeStateInformation.Reconnecting:
|
|
1775
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1776
|
+
break;
|
|
1777
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1778
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1779
|
+
break;
|
|
1780
|
+
case NodeStateInformation.StructureChanged:
|
|
1781
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1782
|
+
break;
|
|
1783
|
+
case NodeStateInformation.Decommissioned:
|
|
1784
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1785
|
+
break;
|
|
1786
|
+
default:
|
|
1787
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1788
|
+
break;
|
|
1789
|
+
}
|
|
1790
|
+
},
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
node.logStructure();
|
|
1794
|
+
|
|
1795
|
+
// Get the interaction client
|
|
1796
|
+
this.log.info('Getting the interaction client');
|
|
1797
|
+
const interactionClient = await node.getInteractionClient();
|
|
1798
|
+
let cluster;
|
|
1799
|
+
let attributes;
|
|
1800
|
+
|
|
1801
|
+
// Log BasicInformationCluster
|
|
1802
|
+
cluster = BasicInformationCluster;
|
|
1803
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1804
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1805
|
+
});
|
|
1806
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1807
|
+
attributes.forEach((attribute) => {
|
|
1808
|
+
this.log.info(
|
|
1809
|
+
`- 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}`,
|
|
1810
|
+
);
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
// Log PowerSourceCluster
|
|
1814
|
+
cluster = PowerSourceCluster;
|
|
1815
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1816
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1817
|
+
});
|
|
1818
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1819
|
+
attributes.forEach((attribute) => {
|
|
1820
|
+
this.log.info(
|
|
1821
|
+
`- 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}`,
|
|
1822
|
+
);
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
// Log ThreadNetworkDiagnostics
|
|
1826
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1827
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1828
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1829
|
+
});
|
|
1830
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1831
|
+
attributes.forEach((attribute) => {
|
|
1832
|
+
this.log.info(
|
|
1833
|
+
`- 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}`,
|
|
1834
|
+
);
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
// Log SwitchCluster
|
|
1838
|
+
cluster = SwitchCluster;
|
|
1839
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1840
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1841
|
+
});
|
|
1842
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1843
|
+
attributes.forEach((attribute) => {
|
|
1844
|
+
this.log.info(
|
|
1845
|
+
`- 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}`,
|
|
1846
|
+
);
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1850
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1851
|
+
ignoreInitialTriggers: false,
|
|
1852
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1853
|
+
this.log.info(
|
|
1854
|
+
`***${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}`,
|
|
1855
|
+
),
|
|
1856
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1857
|
+
this.log.info(
|
|
1858
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1859
|
+
);
|
|
1860
|
+
},
|
|
1861
|
+
});
|
|
1862
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1863
|
+
}
|
|
1864
|
+
*/
|
|
1318
1865
|
}
|
|
1866
|
+
/** */
|
|
1867
|
+
/** Matter.js methods */
|
|
1868
|
+
/** */
|
|
1869
|
+
/**
|
|
1870
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1871
|
+
*
|
|
1872
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1873
|
+
*/
|
|
1319
1874
|
async startMatterStorage() {
|
|
1875
|
+
// Setup Matter storage
|
|
1320
1876
|
this.log.info(`Starting matter node storage...`);
|
|
1321
1877
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1322
1878
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1324,8 +1880,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1324
1880
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1325
1881
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1326
1882
|
this.log.info('Matter node storage started');
|
|
1883
|
+
// Backup matter storage since it is created/opened correctly
|
|
1327
1884
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1328
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1888
|
+
*
|
|
1889
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1890
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1891
|
+
* @private
|
|
1892
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1893
|
+
*/
|
|
1329
1894
|
async backupMatterStorage(storageName, backupName) {
|
|
1330
1895
|
this.log.info('Creating matter node storage backup...');
|
|
1331
1896
|
try {
|
|
@@ -1336,6 +1901,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1336
1901
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1337
1902
|
}
|
|
1338
1903
|
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Stops the matter storage.
|
|
1906
|
+
*
|
|
1907
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1908
|
+
*/
|
|
1339
1909
|
async stopMatterStorage() {
|
|
1340
1910
|
this.log.info('Closing matter node storage...');
|
|
1341
1911
|
await this.matterStorageManager?.close();
|
|
@@ -1344,6 +1914,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1344
1914
|
this.matterbridgeContext = undefined;
|
|
1345
1915
|
this.log.info('Matter node storage closed');
|
|
1346
1916
|
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Creates a server node storage context.
|
|
1919
|
+
*
|
|
1920
|
+
* @param {string} storeId - The storeId.
|
|
1921
|
+
* @param {string} deviceName - The name of the device.
|
|
1922
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1923
|
+
* @param {number} vendorId - The vendor ID.
|
|
1924
|
+
* @param {string} vendorName - The vendor name.
|
|
1925
|
+
* @param {number} productId - The product ID.
|
|
1926
|
+
* @param {string} productName - The product name.
|
|
1927
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1928
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1929
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1930
|
+
*/
|
|
1347
1931
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1348
1932
|
const { randomBytes } = await import('node:crypto');
|
|
1349
1933
|
if (!this.matterStorageService)
|
|
@@ -1383,6 +1967,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1383
1967
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1384
1968
|
return storageContext;
|
|
1385
1969
|
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Creates a server node.
|
|
1972
|
+
*
|
|
1973
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1974
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
1975
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
1976
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
1977
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1978
|
+
*/
|
|
1386
1979
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1387
1980
|
const storeId = await storageContext.get('storeId');
|
|
1388
1981
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1392,24 +1985,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1392
1985
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1393
1986
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1394
1987
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1988
|
+
/**
|
|
1989
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1990
|
+
*/
|
|
1395
1991
|
const serverNode = await ServerNode.create({
|
|
1992
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1396
1993
|
id: storeId,
|
|
1994
|
+
// Provide Network relevant configuration like the port
|
|
1995
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1397
1996
|
network: {
|
|
1398
1997
|
listeningAddressIpv4: this.ipv4Address,
|
|
1399
1998
|
listeningAddressIpv6: this.ipv6Address,
|
|
1400
1999
|
port,
|
|
1401
2000
|
},
|
|
2001
|
+
// Provide the certificate for the device
|
|
1402
2002
|
operationalCredentials: {
|
|
1403
2003
|
certification: this.certification,
|
|
1404
2004
|
},
|
|
2005
|
+
// Provide Commissioning relevant settings
|
|
2006
|
+
// Optional for development/testing purposes
|
|
1405
2007
|
commissioning: {
|
|
1406
2008
|
passcode,
|
|
1407
2009
|
discriminator,
|
|
1408
2010
|
},
|
|
2011
|
+
// Provide Node announcement settings
|
|
2012
|
+
// Optional: If Ommitted some development defaults are used
|
|
1409
2013
|
productDescription: {
|
|
1410
2014
|
name: await storageContext.get('deviceName'),
|
|
1411
2015
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1412
2016
|
},
|
|
2017
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2018
|
+
// Optional: If Omitted some development defaults are used
|
|
1413
2019
|
basicInformation: {
|
|
1414
2020
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1415
2021
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1426,17 +2032,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1426
2032
|
reachable: true,
|
|
1427
2033
|
},
|
|
1428
2034
|
});
|
|
2035
|
+
/**
|
|
2036
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2037
|
+
* This means: It is added to the first fabric.
|
|
2038
|
+
*/
|
|
1429
2039
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1430
2040
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1431
2041
|
this.advertisingNodes.delete(storeId);
|
|
1432
2042
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1433
2043
|
});
|
|
2044
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1434
2045
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1435
2046
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1436
2047
|
this.advertisingNodes.delete(storeId);
|
|
1437
2048
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1438
2049
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1439
2050
|
});
|
|
2051
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1440
2052
|
serverNode.lifecycle.online.on(async () => {
|
|
1441
2053
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1442
2054
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1447,13 +2059,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1447
2059
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1448
2060
|
}
|
|
1449
2061
|
else {
|
|
2062
|
+
// istanbul ignore next
|
|
1450
2063
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2064
|
+
// istanbul ignore next
|
|
1451
2065
|
this.advertisingNodes.delete(storeId);
|
|
1452
2066
|
}
|
|
1453
2067
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1454
2068
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1455
2069
|
this.emit('online', storeId);
|
|
1456
2070
|
});
|
|
2071
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1457
2072
|
serverNode.lifecycle.offline.on(() => {
|
|
1458
2073
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1459
2074
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1461,11 +2076,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1461
2076
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1462
2077
|
this.emit('offline', storeId);
|
|
1463
2078
|
});
|
|
2079
|
+
/**
|
|
2080
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2081
|
+
* information is needed.
|
|
2082
|
+
*/
|
|
1464
2083
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1465
2084
|
let action = '';
|
|
1466
2085
|
switch (fabricAction) {
|
|
1467
2086
|
case FabricAction.Added:
|
|
1468
|
-
this.advertisingNodes.delete(storeId);
|
|
2087
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1469
2088
|
action = 'added';
|
|
1470
2089
|
break;
|
|
1471
2090
|
case FabricAction.Removed:
|
|
@@ -1478,14 +2097,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1478
2097
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1479
2098
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1480
2099
|
});
|
|
2100
|
+
/**
|
|
2101
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2102
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2103
|
+
*/
|
|
1481
2104
|
serverNode.events.sessions.opened.on((session) => {
|
|
1482
2105
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1483
2106
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1484
2107
|
});
|
|
2108
|
+
/**
|
|
2109
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2110
|
+
*/
|
|
1485
2111
|
serverNode.events.sessions.closed.on((session) => {
|
|
1486
2112
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1487
2113
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1488
2114
|
});
|
|
2115
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1489
2116
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1490
2117
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1491
2118
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1493,6 +2120,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1493
2120
|
this.log.info(`Created server node for ${storeId}`);
|
|
1494
2121
|
return serverNode;
|
|
1495
2122
|
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Gets the matter sanitized data of the specified server node.
|
|
2125
|
+
*
|
|
2126
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2127
|
+
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2128
|
+
*/
|
|
1496
2129
|
getServerNodeData(serverNode) {
|
|
1497
2130
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1498
2131
|
return {
|
|
@@ -1509,12 +2142,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1509
2142
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1510
2143
|
};
|
|
1511
2144
|
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Starts the specified server node.
|
|
2147
|
+
*
|
|
2148
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2149
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2150
|
+
*/
|
|
1512
2151
|
async startServerNode(matterServerNode) {
|
|
1513
2152
|
if (!matterServerNode)
|
|
1514
2153
|
return;
|
|
1515
2154
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1516
2155
|
await matterServerNode.start();
|
|
1517
2156
|
}
|
|
2157
|
+
/**
|
|
2158
|
+
* Stops the specified server node.
|
|
2159
|
+
*
|
|
2160
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2161
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2162
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2163
|
+
*/
|
|
1518
2164
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1519
2165
|
if (!matterServerNode)
|
|
1520
2166
|
return;
|
|
@@ -1527,12 +2173,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1527
2173
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1528
2174
|
}
|
|
1529
2175
|
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Creates an aggregator node with the specified storage context.
|
|
2178
|
+
*
|
|
2179
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2180
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2181
|
+
*/
|
|
1530
2182
|
async createAggregatorNode(storageContext) {
|
|
1531
2183
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1532
2184
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1533
2185
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1534
2186
|
return aggregatorNode;
|
|
1535
2187
|
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2190
|
+
*
|
|
2191
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2192
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2193
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2194
|
+
*/
|
|
1536
2195
|
async createAccessoryPlugin(plugin, device) {
|
|
1537
2196
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1538
2197
|
plugin.locked = true;
|
|
@@ -1544,6 +2203,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1544
2203
|
await plugin.serverNode.add(device);
|
|
1545
2204
|
}
|
|
1546
2205
|
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2208
|
+
*
|
|
2209
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2210
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2211
|
+
*/
|
|
1547
2212
|
async createDynamicPlugin(plugin) {
|
|
1548
2213
|
if (!plugin.locked) {
|
|
1549
2214
|
plugin.locked = true;
|
|
@@ -1556,6 +2221,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1556
2221
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1557
2222
|
}
|
|
1558
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Creates and configures the server node for a single not bridged device.
|
|
2226
|
+
*
|
|
2227
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2228
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2229
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2230
|
+
*/
|
|
1559
2231
|
async createDeviceServerNode(plugin, device) {
|
|
1560
2232
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1561
2233
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1566,7 +2238,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1566
2238
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1567
2239
|
}
|
|
1568
2240
|
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2243
|
+
*
|
|
2244
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2245
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2246
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2247
|
+
*/
|
|
1569
2248
|
async addBridgedEndpoint(pluginName, device) {
|
|
2249
|
+
// Check if the plugin is registered
|
|
1570
2250
|
const plugin = this.plugins.get(pluginName);
|
|
1571
2251
|
if (!plugin) {
|
|
1572
2252
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1586,6 +2266,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1586
2266
|
}
|
|
1587
2267
|
else if (this.bridgeMode === 'bridge') {
|
|
1588
2268
|
if (device.mode === 'matter') {
|
|
2269
|
+
// Register and add the device to the matterbridge server node
|
|
1589
2270
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1590
2271
|
if (!this.serverNode) {
|
|
1591
2272
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1602,6 +2283,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1602
2283
|
}
|
|
1603
2284
|
}
|
|
1604
2285
|
else {
|
|
2286
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1605
2287
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1606
2288
|
if (!this.aggregatorNode) {
|
|
1607
2289
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1619,6 +2301,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1619
2301
|
}
|
|
1620
2302
|
}
|
|
1621
2303
|
else if (this.bridgeMode === 'childbridge') {
|
|
2304
|
+
// Register and add the device to the plugin server node
|
|
1622
2305
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1623
2306
|
try {
|
|
1624
2307
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1642,10 +2325,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
2325
|
return;
|
|
1643
2326
|
}
|
|
1644
2327
|
}
|
|
2328
|
+
// Register and add the device to the plugin aggregator node
|
|
1645
2329
|
if (plugin.type === 'DynamicPlatform') {
|
|
1646
2330
|
try {
|
|
1647
2331
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1648
2332
|
await this.createDynamicPlugin(plugin);
|
|
2333
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1649
2334
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1650
2335
|
if (!plugin.aggregatorNode) {
|
|
1651
2336
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1666,17 +2351,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1666
2351
|
}
|
|
1667
2352
|
if (plugin.registeredDevices !== undefined)
|
|
1668
2353
|
plugin.registeredDevices++;
|
|
2354
|
+
// Add the device to the DeviceManager
|
|
1669
2355
|
this.devices.set(device);
|
|
2356
|
+
// Subscribe to the attributes changed event
|
|
1670
2357
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1671
2358
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1672
2359
|
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2362
|
+
*
|
|
2363
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2364
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2365
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2366
|
+
*/
|
|
1673
2367
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1674
2368
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2369
|
+
// Check if the plugin is registered
|
|
1675
2370
|
const plugin = this.plugins.get(pluginName);
|
|
1676
2371
|
if (!plugin) {
|
|
1677
2372
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1678
2373
|
return;
|
|
1679
2374
|
}
|
|
2375
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1680
2376
|
if (this.bridgeMode === 'bridge') {
|
|
1681
2377
|
if (!this.aggregatorNode) {
|
|
1682
2378
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1689,6 +2385,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1689
2385
|
}
|
|
1690
2386
|
else if (this.bridgeMode === 'childbridge') {
|
|
1691
2387
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2388
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1692
2389
|
}
|
|
1693
2390
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1694
2391
|
if (!plugin.aggregatorNode) {
|
|
@@ -1701,8 +2398,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1701
2398
|
if (plugin.registeredDevices !== undefined)
|
|
1702
2399
|
plugin.registeredDevices--;
|
|
1703
2400
|
}
|
|
2401
|
+
// Remove the device from the DeviceManager
|
|
1704
2402
|
this.devices.remove(device);
|
|
1705
2403
|
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2406
|
+
*
|
|
2407
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2408
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2409
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2410
|
+
*
|
|
2411
|
+
* @remarks
|
|
2412
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2413
|
+
* It also applies a delay between each removal if specified.
|
|
2414
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2415
|
+
*/
|
|
1706
2416
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1707
2417
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1708
2418
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1713,13 +2423,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1713
2423
|
if (delay > 0)
|
|
1714
2424
|
await wait(2000);
|
|
1715
2425
|
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2428
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2429
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2430
|
+
*
|
|
2431
|
+
* @param {Plugin} plugin - The plugin associated with the device.
|
|
2432
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2433
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2434
|
+
*/
|
|
1716
2435
|
async subscribeAttributeChanged(plugin, device) {
|
|
1717
2436
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
1718
2437
|
return;
|
|
1719
2438
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2439
|
+
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
1720
2440
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1721
2441
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1722
2442
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2443
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1723
2444
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
1724
2445
|
});
|
|
1725
2446
|
}
|
|
@@ -1767,6 +2488,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1767
2488
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1768
2489
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1769
2490
|
this.log.debug(`Bridged endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
|
|
2491
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1770
2492
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
1771
2493
|
});
|
|
1772
2494
|
}
|
|
@@ -1775,12 +2497,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1775
2497
|
this.log.debug(`Subscribing to child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1776
2498
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1777
2499
|
this.log.debug(`Bridged child endpoint ${or}${child.id}${db}:${or}${child.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changed to ${CYAN}${value}${db}`);
|
|
2500
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1778
2501
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1779
2502
|
});
|
|
1780
2503
|
}
|
|
1781
2504
|
}
|
|
1782
2505
|
}
|
|
1783
2506
|
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2509
|
+
*
|
|
2510
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2511
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2512
|
+
*/
|
|
1784
2513
|
sanitizeFabricInformations(fabricInfo) {
|
|
1785
2514
|
return fabricInfo.map((info) => {
|
|
1786
2515
|
return {
|
|
@@ -1794,6 +2523,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1794
2523
|
};
|
|
1795
2524
|
});
|
|
1796
2525
|
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2528
|
+
*
|
|
2529
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2530
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2531
|
+
*/
|
|
1797
2532
|
sanitizeSessionInformation(sessions) {
|
|
1798
2533
|
return sessions
|
|
1799
2534
|
.filter((session) => session.isPeerActive)
|
|
@@ -1820,7 +2555,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1820
2555
|
};
|
|
1821
2556
|
});
|
|
1822
2557
|
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2560
|
+
*
|
|
2561
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2562
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2563
|
+
*/
|
|
2564
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1823
2565
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2566
|
+
/*
|
|
2567
|
+
for (const child of aggregatorNode.parts) {
|
|
2568
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2569
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2570
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2571
|
+
}
|
|
2572
|
+
*/
|
|
1824
2573
|
}
|
|
1825
2574
|
getVendorIdName = (vendorId) => {
|
|
1826
2575
|
if (!vendorId)
|
|
@@ -1860,10 +2609,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1860
2609
|
case 0x1488:
|
|
1861
2610
|
vendorName = '(ShortcutLabsFlic)';
|
|
1862
2611
|
break;
|
|
1863
|
-
case 65521:
|
|
2612
|
+
case 65521: // 0xFFF1
|
|
1864
2613
|
vendorName = '(MatterTest)';
|
|
1865
2614
|
break;
|
|
1866
2615
|
}
|
|
1867
2616
|
return vendorName;
|
|
1868
2617
|
};
|
|
1869
2618
|
}
|
|
2619
|
+
//# sourceMappingURL=matterbridge.js.map
|