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