matterbridge 3.4.4 → 3.4.5-dev-20251222-5853b56
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 +14 -0
- package/README.md +17 -2
- package/dist/broadcastServer.js +0 -117
- package/dist/broadcastServerTypes.js +0 -24
- package/dist/cli.js +1 -97
- package/dist/cliEmitter.js +0 -37
- package/dist/cliHistory.js +0 -38
- package/dist/clusters/export.js +0 -2
- package/dist/deviceManager.js +1 -113
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.js +0 -56
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +0 -43
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.js +3 -24
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/coap.js +13 -126
- package/dist/dgram/dgram.js +2 -114
- package/dist/dgram/mb_coap.js +3 -41
- package/dist/dgram/mb_mdns.js +15 -80
- package/dist/dgram/mdns.js +137 -299
- package/dist/dgram/multicast.js +1 -62
- package/dist/dgram/unicast.js +0 -54
- package/dist/frontend.js +38 -485
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +0 -53
- package/dist/index.js +0 -25
- package/dist/jestutils/export.js +0 -1
- package/dist/jestutils/jestHelpers.js +14 -371
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -3
- package/dist/matter/types.js +0 -3
- package/dist/matterNode.js +8 -369
- package/dist/matterbridge.js +46 -824
- package/dist/matterbridgeAccessoryPlatform.js +0 -38
- package/dist/matterbridgeBehaviors.js +5 -68
- package/dist/matterbridgeDeviceTypes.js +14 -635
- package/dist/matterbridgeDynamicPlatform.js +0 -38
- package/dist/matterbridgeEndpoint.js +53 -1457
- package/dist/matterbridgeEndpointHelpers.js +20 -483
- package/dist/matterbridgeEndpointTypes.js +0 -25
- package/dist/matterbridgePlatform.js +1 -451
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +5 -341
- package/dist/shelly.js +7 -178
- package/dist/storage/export.js +0 -1
- package/dist/update.js +1 -93
- package/dist/utils/colorUtils.js +2 -97
- package/dist/utils/commandLine.js +0 -60
- package/dist/utils/copyDirectory.js +0 -37
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/error.js +0 -42
- package/dist/utils/export.js +0 -1
- package/dist/utils/format.js +0 -49
- package/dist/utils/hex.js +0 -124
- package/dist/utils/inspector.js +1 -69
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/network.js +5 -96
- package/dist/utils/spawn.js +1 -71
- package/dist/utils/tracker.js +1 -64
- package/dist/utils/wait.js +8 -60
- package/dist/workerGlobalPrefix.js +5 -37
- package/dist/workerTypes.js +0 -24
- package/dist/workers.js +4 -68
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -2
- package/dist/broadcastServer.d.ts +0 -144
- package/dist/broadcastServer.d.ts.map +0 -1
- package/dist/broadcastServer.js.map +0 -1
- package/dist/broadcastServerTypes.d.ts +0 -841
- package/dist/broadcastServerTypes.d.ts.map +0 -1
- package/dist/broadcastServerTypes.js.map +0 -1
- package/dist/cli.d.ts +0 -30
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts +0 -50
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/cliHistory.d.ts +0 -48
- package/dist/cliHistory.d.ts.map +0 -1
- package/dist/cliHistory.js.map +0 -1
- package/dist/clusters/export.d.ts +0 -2
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -135
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts +0 -98
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts +0 -48
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/cooktop.d.ts +0 -61
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts +0 -71
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts +0 -76
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts +0 -17
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts +0 -46
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts +0 -47
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts +0 -67
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts +0 -81
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts +0 -168
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts +0 -105
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts +0 -118
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts +0 -40
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts +0 -87
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts +0 -166
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts +0 -111
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/coap.d.ts +0 -205
- package/dist/dgram/coap.d.ts.map +0 -1
- package/dist/dgram/coap.js.map +0 -1
- package/dist/dgram/dgram.d.ts +0 -141
- package/dist/dgram/dgram.d.ts.map +0 -1
- package/dist/dgram/dgram.js.map +0 -1
- package/dist/dgram/mb_coap.d.ts +0 -24
- package/dist/dgram/mb_coap.d.ts.map +0 -1
- package/dist/dgram/mb_coap.js.map +0 -1
- package/dist/dgram/mb_mdns.d.ts +0 -24
- package/dist/dgram/mb_mdns.d.ts.map +0 -1
- package/dist/dgram/mb_mdns.js.map +0 -1
- package/dist/dgram/mdns.d.ts +0 -290
- package/dist/dgram/mdns.d.ts.map +0 -1
- package/dist/dgram/mdns.js.map +0 -1
- package/dist/dgram/multicast.d.ts +0 -67
- package/dist/dgram/multicast.d.ts.map +0 -1
- package/dist/dgram/multicast.js.map +0 -1
- package/dist/dgram/unicast.d.ts +0 -56
- package/dist/dgram/unicast.d.ts.map +0 -1
- package/dist/dgram/unicast.js.map +0 -1
- package/dist/frontend.d.ts +0 -245
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts +0 -529
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/helpers.d.ts +0 -48
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -34
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/jestutils/export.d.ts +0 -2
- package/dist/jestutils/export.d.ts.map +0 -1
- package/dist/jestutils/export.js.map +0 -1
- package/dist/jestutils/jestHelpers.d.ts +0 -345
- package/dist/jestutils/jestHelpers.d.ts.map +0 -1
- package/dist/jestutils/jestHelpers.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterNode.d.ts +0 -342
- package/dist/matterNode.d.ts.map +0 -1
- package/dist/matterNode.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -505
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -41
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -2404
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -698
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -41
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1507
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -787
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgeEndpointTypes.d.ts +0 -166
- package/dist/matterbridgeEndpointTypes.d.ts.map +0 -1
- package/dist/matterbridgeEndpointTypes.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -539
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -252
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -372
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts +0 -181
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -84
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -101
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -66
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -35
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createDirectory.d.ts +0 -34
- package/dist/utils/createDirectory.d.ts.map +0 -1
- package/dist/utils/createDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -39
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -32
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -54
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/error.d.ts +0 -45
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/error.js.map +0 -1
- package/dist/utils/export.d.ts +0 -13
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/format.d.ts +0 -53
- package/dist/utils/format.d.ts.map +0 -1
- package/dist/utils/format.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -89
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/inspector.d.ts +0 -87
- package/dist/utils/inspector.d.ts.map +0 -1
- package/dist/utils/inspector.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -103
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/network.d.ts +0 -111
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -33
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/tracker.d.ts +0 -108
- package/dist/utils/tracker.d.ts.map +0 -1
- package/dist/utils/tracker.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -54
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
- package/dist/workerGlobalPrefix.d.ts +0 -25
- package/dist/workerGlobalPrefix.d.ts.map +0 -1
- package/dist/workerGlobalPrefix.js.map +0 -1
- package/dist/workerTypes.d.ts +0 -52
- package/dist/workerTypes.d.ts.map +0 -1
- package/dist/workerTypes.js.map +0 -1
- package/dist/workers.d.ts +0 -69
- package/dist/workers.d.ts.map +0 -1
- package/dist/workers.js.map +0 -1
- package/marked.ps1 +0 -25
package/dist/matterbridge.js
CHANGED
|
@@ -1,48 +1,19 @@
|
|
|
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.2
|
|
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
|
|
25
1
|
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
26
2
|
console.log('\u001B[32mMatterbridge loaded.\u001B[40;0m');
|
|
27
|
-
// Node.js modules
|
|
28
3
|
import os from 'node:os';
|
|
29
4
|
import path from 'node:path';
|
|
30
5
|
import fs from 'node:fs';
|
|
31
6
|
import EventEmitter from 'node:events';
|
|
32
7
|
import { inspect } from 'node:util';
|
|
33
|
-
// AnsiLogger module
|
|
34
8
|
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
|
|
36
9
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
37
|
-
|
|
38
|
-
import '@matter/nodejs'; // Set up Node.js environment for matter.js
|
|
10
|
+
import '@matter/nodejs';
|
|
39
11
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, UINT32_MAX, UINT16_MAX, Crypto, Environment, StorageService } from '@matter/general';
|
|
40
12
|
import { FabricAction, PaseClient } from '@matter/protocol';
|
|
41
13
|
import { Endpoint, ServerNode } from '@matter/node';
|
|
42
14
|
import { DeviceTypeId, VendorId } from '@matter/types/datatype';
|
|
43
15
|
import { AggregatorEndpoint } from '@matter/node/endpoints';
|
|
44
16
|
import { BasicInformationServer } from '@matter/node/behaviors/basic-information';
|
|
45
|
-
// Matterbridge
|
|
46
17
|
import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
|
|
47
18
|
import { copyDirectory } from './utils/copyDirectory.js';
|
|
48
19
|
import { createDirectory } from './utils/createDirectory.js';
|
|
@@ -56,27 +27,19 @@ import { bridge } from './matterbridgeDeviceTypes.js';
|
|
|
56
27
|
import { Frontend } from './frontend.js';
|
|
57
28
|
import { addVirtualDevice, addVirtualDevices } from './helpers.js';
|
|
58
29
|
import { BroadcastServer } from './broadcastServer.js';
|
|
59
|
-
/**
|
|
60
|
-
* Represents the Matterbridge application.
|
|
61
|
-
*/
|
|
62
30
|
export class Matterbridge extends EventEmitter {
|
|
63
|
-
/** Matterbridge system information */
|
|
64
31
|
systemInformation = {
|
|
65
|
-
// Network properties
|
|
66
32
|
interfaceName: '',
|
|
67
33
|
macAddress: '',
|
|
68
34
|
ipv4Address: '',
|
|
69
35
|
ipv6Address: '',
|
|
70
|
-
// Node.js properties
|
|
71
36
|
nodeVersion: '',
|
|
72
|
-
// Fixed system properties
|
|
73
37
|
hostname: '',
|
|
74
38
|
user: '',
|
|
75
39
|
osType: '',
|
|
76
40
|
osRelease: '',
|
|
77
41
|
osPlatform: '',
|
|
78
42
|
osArch: '',
|
|
79
|
-
// Cpu and memory properties
|
|
80
43
|
totalMemory: '',
|
|
81
44
|
freeMemory: '',
|
|
82
45
|
systemUptime: '',
|
|
@@ -87,66 +50,39 @@ export class Matterbridge extends EventEmitter {
|
|
|
87
50
|
heapTotal: '',
|
|
88
51
|
heapUsed: '',
|
|
89
52
|
};
|
|
90
|
-
// Matterbridge settings
|
|
91
|
-
/** It indicates the home directory of the Matterbridge application. The home directory is the base directory where Matterbridge creates the matterbridge directories (os.homedir() if not overridden). */
|
|
92
53
|
homeDirectory = '';
|
|
93
|
-
/** It indicates the root directory of the Matterbridge application. The root directory is the directory where Matterbridge is executed. */
|
|
94
54
|
rootDirectory = '';
|
|
95
|
-
/** It indicates where the directory .matterbridge is located. */
|
|
96
55
|
matterbridgeDirectory = '';
|
|
97
|
-
/** It indicates where the directory Matterbridge is located. */
|
|
98
56
|
matterbridgePluginDirectory = '';
|
|
99
|
-
/** It indicates where the directory .mattercert is located. */
|
|
100
57
|
matterbridgeCertDirectory = '';
|
|
101
|
-
/** It indicates the global modules directory for npm. */
|
|
102
58
|
globalModulesDirectory = '';
|
|
103
59
|
matterbridgeVersion = '';
|
|
104
60
|
matterbridgeLatestVersion = '';
|
|
105
61
|
matterbridgeDevVersion = '';
|
|
106
62
|
frontendVersion = '';
|
|
107
|
-
/** It indicates the mode of the Matterbridge instance. It can be 'bridge', 'childbridge', 'controller' or ''. */
|
|
108
63
|
bridgeMode = '';
|
|
109
|
-
/** It indicates the restart mode of the Matterbridge instance. It can be 'service', 'docker' or ''. */
|
|
110
64
|
restartMode = '';
|
|
111
|
-
/** It indicates whether virtual mode is enabled and its type. The virtual mode control the creation of "Update matterbridge" and "Restart matterbridge" endpoints. */
|
|
112
65
|
virtualMode = 'outlet';
|
|
113
|
-
/** It indicates the Matterbridge profile in use. */
|
|
114
66
|
profile = getParameter('profile');
|
|
115
|
-
|
|
116
|
-
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
117
|
-
/** Matterbridge logger level */
|
|
67
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
118
68
|
logLevel = this.log.logLevel;
|
|
119
|
-
/** Whether to log to a file */
|
|
120
69
|
fileLogger = false;
|
|
121
|
-
|
|
122
|
-
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
123
|
-
/** Matter logger level */
|
|
70
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
124
71
|
matterLogLevel = this.matterLog.logLevel;
|
|
125
|
-
/** Whether to log Matter to a file */
|
|
126
72
|
matterFileLogger = false;
|
|
127
|
-
// Frontend settings
|
|
128
73
|
readOnly = hasParameter('readonly') || hasParameter('shelly');
|
|
129
74
|
shellyBoard = hasParameter('shelly');
|
|
130
75
|
shellySysUpdate = false;
|
|
131
76
|
shellyMainUpdate = false;
|
|
132
|
-
/** It indicates whether a restart is required. It can be unset in childbridge mode by restarting the plugin that triggered the restart. */
|
|
133
77
|
restartRequired = false;
|
|
134
|
-
/** It indicates whether a fixed restart is required. It cannot be unset once set. */
|
|
135
78
|
fixedRestartRequired = false;
|
|
136
|
-
/** It indicates whether an update is available. */
|
|
137
79
|
updateRequired = false;
|
|
138
|
-
// Managers
|
|
139
80
|
plugins = new PluginManager(this);
|
|
140
81
|
devices = new DeviceManager();
|
|
141
|
-
// Frontend
|
|
142
82
|
frontend = new Frontend(this);
|
|
143
|
-
/** Matterbridge node storage manager created in the directory 'storage' in matterbridgeDirectory */
|
|
144
83
|
nodeStorage;
|
|
145
|
-
/** Matterbridge node context created with name 'matterbridge' */
|
|
146
84
|
nodeContext;
|
|
147
|
-
/** The main instance of the Matterbridge class (singleton) */
|
|
148
85
|
static instance;
|
|
149
|
-
// Instance properties
|
|
150
86
|
shutdown = false;
|
|
151
87
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
152
88
|
hasCleanupStarted = false;
|
|
@@ -161,32 +97,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
161
97
|
sigtermHandler;
|
|
162
98
|
exceptionHandler;
|
|
163
99
|
rejectionHandler;
|
|
164
|
-
/** Matter environment default */
|
|
165
100
|
environment = Environment.default;
|
|
166
|
-
/** Matter storage service from environment default */
|
|
167
101
|
matterStorageService;
|
|
168
|
-
/** Matter storage manager created with name 'Matterbridge' */
|
|
169
102
|
matterStorageManager;
|
|
170
|
-
/** Matter matterbridge storage context created in the storage manager with name 'persist' */
|
|
171
103
|
matterbridgeContext;
|
|
172
104
|
controllerContext;
|
|
173
|
-
/** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
|
|
174
105
|
mdnsInterface;
|
|
175
|
-
/** Matter listeningAddressIpv4 address */
|
|
176
106
|
ipv4Address;
|
|
177
|
-
/** Matter listeningAddressIpv6 address */
|
|
178
107
|
ipv6Address;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
/** Matter commissioning discriminator */
|
|
184
|
-
discriminator; // first server node discriminator
|
|
185
|
-
/** Matter device certification */
|
|
186
|
-
certification; // device certification
|
|
187
|
-
/** Matter server node in bridge mode */
|
|
108
|
+
port;
|
|
109
|
+
passcode;
|
|
110
|
+
discriminator;
|
|
111
|
+
certification;
|
|
188
112
|
serverNode;
|
|
189
|
-
/** Matter aggregator node in bridge mode */
|
|
190
113
|
aggregatorNode;
|
|
191
114
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
192
115
|
aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
|
|
@@ -195,12 +118,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
195
118
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
196
119
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
197
120
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
198
|
-
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
199
121
|
advertisingNodes = new Map();
|
|
200
|
-
/** Broadcast server */
|
|
201
122
|
server;
|
|
202
123
|
verbose = hasParameter('verbose');
|
|
203
|
-
/** We load asyncronously so is private */
|
|
204
124
|
constructor() {
|
|
205
125
|
super();
|
|
206
126
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
@@ -251,19 +171,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
251
171
|
}
|
|
252
172
|
}
|
|
253
173
|
}
|
|
254
|
-
//* ************************************************************************************************************************************ */
|
|
255
|
-
// loadInstance() and cleanup() methods */
|
|
256
|
-
//* ************************************************************************************************************************************ */
|
|
257
|
-
/**
|
|
258
|
-
* Loads an instance of the Matterbridge class.
|
|
259
|
-
* If an instance already exists, return that instance.
|
|
260
|
-
*
|
|
261
|
-
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
262
|
-
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
263
|
-
*/
|
|
264
174
|
static async loadInstance(initialize = false) {
|
|
265
175
|
if (!Matterbridge.instance) {
|
|
266
|
-
// eslint-disable-next-line no-console
|
|
267
176
|
if (hasParameter('debug'))
|
|
268
177
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
269
178
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -272,84 +181,56 @@ export class Matterbridge extends EventEmitter {
|
|
|
272
181
|
}
|
|
273
182
|
return Matterbridge.instance;
|
|
274
183
|
}
|
|
275
|
-
/**
|
|
276
|
-
* Initializes the Matterbridge application.
|
|
277
|
-
*
|
|
278
|
-
* @remarks
|
|
279
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
280
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
281
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
282
|
-
*
|
|
283
|
-
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
284
|
-
*/
|
|
285
184
|
async initialize() {
|
|
286
|
-
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
287
|
-
// Emit the initialize_started event
|
|
288
185
|
this.emit('initialize_started');
|
|
289
|
-
// Set the restart mode
|
|
290
186
|
if (hasParameter('service'))
|
|
291
187
|
this.restartMode = 'service';
|
|
292
188
|
if (hasParameter('docker'))
|
|
293
189
|
this.restartMode = 'docker';
|
|
294
|
-
// Set the matterbridge home directory
|
|
295
190
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
296
191
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
297
|
-
// Set the matterbridge directory
|
|
298
192
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
299
193
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
300
194
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
301
195
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
302
|
-
// Set the matterbridge plugin directory
|
|
303
196
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
304
197
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
305
|
-
// Set the matterbridge cert directory
|
|
306
198
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
307
199
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
308
|
-
// Set the matterbridge root directory
|
|
309
200
|
const { fileURLToPath } = await import('node:url');
|
|
310
201
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
311
|
-
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
312
|
-
// Setup the matter environment with default values
|
|
202
|
+
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
313
203
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
314
204
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
315
205
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
316
206
|
this.environment.vars.set('runtime.signals', false);
|
|
317
207
|
this.environment.vars.set('runtime.exitcode', false);
|
|
318
|
-
// Register process handlers
|
|
319
208
|
this.registerProcessHandlers();
|
|
320
|
-
// Initialize nodeStorage and nodeContext
|
|
321
209
|
try {
|
|
322
210
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
323
211
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
324
212
|
this.log.debug('Creating node storage context for matterbridge');
|
|
325
213
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
326
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
327
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
328
214
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
329
215
|
for (const key of keys) {
|
|
330
216
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
331
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
332
217
|
await this.nodeStorage?.storage.get(key);
|
|
333
218
|
}
|
|
334
219
|
const storages = await this.nodeStorage.getStorageNames();
|
|
335
220
|
for (const storage of storages) {
|
|
336
221
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
337
222
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
338
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
339
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
340
223
|
const keys = (await nodeContext?.storage.keys());
|
|
341
224
|
keys.forEach(async (key) => {
|
|
342
225
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
343
226
|
await nodeContext?.get(key);
|
|
344
227
|
});
|
|
345
228
|
}
|
|
346
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
347
229
|
this.log.debug('Creating node storage backup...');
|
|
348
230
|
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
349
231
|
this.log.debug('Created node storage backup');
|
|
350
232
|
}
|
|
351
233
|
catch (error) {
|
|
352
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
353
234
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
354
235
|
if (hasParameter('norestore')) {
|
|
355
236
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -363,19 +244,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
363
244
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
364
245
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
365
246
|
}
|
|
366
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
367
247
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
368
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
369
248
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
370
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
371
249
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
372
|
-
// Certificate management
|
|
373
250
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
374
251
|
try {
|
|
375
252
|
await fs.promises.access(pairingFilePath, fs.constants.R_OK);
|
|
376
253
|
const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
|
|
377
254
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
378
|
-
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
379
255
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
380
256
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
381
257
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -404,13 +280,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
404
280
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
405
281
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
406
282
|
}
|
|
407
|
-
// Override the passcode and discriminator if they are present in the pairing file
|
|
408
283
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
409
284
|
this.passcode = pairingFileJson.passcode;
|
|
410
285
|
this.discriminator = pairingFileJson.discriminator;
|
|
411
286
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
412
287
|
}
|
|
413
|
-
// Set the certification for matter.js if it is present in the pairing file
|
|
414
288
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
415
289
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
416
290
|
this.certification = {
|
|
@@ -425,44 +299,41 @@ export class Matterbridge extends EventEmitter {
|
|
|
425
299
|
catch (error) {
|
|
426
300
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
427
301
|
}
|
|
428
|
-
// Store the passcode, discriminator and port in the node context
|
|
429
302
|
await this.nodeContext.set('matterport', this.port);
|
|
430
303
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
431
304
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
432
305
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
433
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
434
306
|
if (hasParameter('logger')) {
|
|
435
307
|
const level = getParameter('logger');
|
|
436
308
|
if (level === 'debug') {
|
|
437
|
-
this.log.logLevel = "debug"
|
|
309
|
+
this.log.logLevel = "debug";
|
|
438
310
|
}
|
|
439
311
|
else if (level === 'info') {
|
|
440
|
-
this.log.logLevel = "info"
|
|
312
|
+
this.log.logLevel = "info";
|
|
441
313
|
}
|
|
442
314
|
else if (level === 'notice') {
|
|
443
|
-
this.log.logLevel = "notice"
|
|
315
|
+
this.log.logLevel = "notice";
|
|
444
316
|
}
|
|
445
317
|
else if (level === 'warn') {
|
|
446
|
-
this.log.logLevel = "warn"
|
|
318
|
+
this.log.logLevel = "warn";
|
|
447
319
|
}
|
|
448
320
|
else if (level === 'error') {
|
|
449
|
-
this.log.logLevel = "error"
|
|
321
|
+
this.log.logLevel = "error";
|
|
450
322
|
}
|
|
451
323
|
else if (level === 'fatal') {
|
|
452
|
-
this.log.logLevel = "fatal"
|
|
324
|
+
this.log.logLevel = "fatal";
|
|
453
325
|
}
|
|
454
326
|
else {
|
|
455
327
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
456
|
-
this.log.logLevel = "info"
|
|
328
|
+
this.log.logLevel = "info";
|
|
457
329
|
}
|
|
458
330
|
}
|
|
459
331
|
else {
|
|
460
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice"
|
|
332
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
461
333
|
}
|
|
462
334
|
this.logLevel = this.log.logLevel;
|
|
463
335
|
this.frontend.logLevel = this.log.logLevel;
|
|
464
336
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
465
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
466
337
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
467
338
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
468
339
|
this.fileLogger = true;
|
|
@@ -471,7 +342,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
471
342
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
472
343
|
if (this.profile !== undefined)
|
|
473
344
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
474
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
475
345
|
if (hasParameter('matterlogger')) {
|
|
476
346
|
const level = getParameter('matterlogger');
|
|
477
347
|
if (level === 'debug') {
|
|
@@ -502,13 +372,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
502
372
|
}
|
|
503
373
|
Logger.format = MatterLogFormat.ANSI;
|
|
504
374
|
this.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
505
|
-
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
506
375
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
507
376
|
this.matterFileLogger = true;
|
|
508
377
|
}
|
|
509
378
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
510
379
|
this.log.debug(`Matter logLevel: ${this.matterLogLevel} fileLoger: ${this.matterFileLogger}.`);
|
|
511
|
-
// Log network interfaces
|
|
512
380
|
const networkInterfaces = os.networkInterfaces();
|
|
513
381
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
514
382
|
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
@@ -521,7 +389,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
521
389
|
});
|
|
522
390
|
}
|
|
523
391
|
}
|
|
524
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
525
392
|
if (hasParameter('mdnsinterface')) {
|
|
526
393
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
527
394
|
}
|
|
@@ -530,7 +397,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
530
397
|
if (this.mdnsInterface === '')
|
|
531
398
|
this.mdnsInterface = undefined;
|
|
532
399
|
}
|
|
533
|
-
// Validate mdnsInterface
|
|
534
400
|
if (this.mdnsInterface) {
|
|
535
401
|
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
536
402
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
@@ -543,7 +409,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
543
409
|
}
|
|
544
410
|
if (this.mdnsInterface)
|
|
545
411
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
546
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
547
412
|
if (hasParameter('ipv4address')) {
|
|
548
413
|
this.ipv4Address = getParameter('ipv4address');
|
|
549
414
|
}
|
|
@@ -552,7 +417,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
552
417
|
if (this.ipv4Address === '')
|
|
553
418
|
this.ipv4Address = undefined;
|
|
554
419
|
}
|
|
555
|
-
// Validate ipv4address
|
|
556
420
|
if (this.ipv4Address) {
|
|
557
421
|
let isValid = false;
|
|
558
422
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -568,7 +432,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
568
432
|
await this.nodeContext.remove('matteripv4address');
|
|
569
433
|
}
|
|
570
434
|
}
|
|
571
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
572
435
|
if (hasParameter('ipv6address')) {
|
|
573
436
|
this.ipv6Address = getParameter('ipv6address');
|
|
574
437
|
}
|
|
@@ -577,7 +440,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
577
440
|
if (this.ipv6Address === '')
|
|
578
441
|
this.ipv6Address = undefined;
|
|
579
442
|
}
|
|
580
|
-
// Validate ipv6address
|
|
581
443
|
if (this.ipv6Address) {
|
|
582
444
|
let isValid = false;
|
|
583
445
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -586,7 +448,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
586
448
|
isValid = true;
|
|
587
449
|
break;
|
|
588
450
|
}
|
|
589
|
-
/* istanbul ignore next */
|
|
590
451
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
591
452
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
592
453
|
isValid = true;
|
|
@@ -599,7 +460,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
599
460
|
await this.nodeContext.remove('matteripv6address');
|
|
600
461
|
}
|
|
601
462
|
}
|
|
602
|
-
// Initialize the virtual mode
|
|
603
463
|
if (hasParameter('novirtual')) {
|
|
604
464
|
this.virtualMode = 'disabled';
|
|
605
465
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -608,15 +468,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
608
468
|
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
609
469
|
}
|
|
610
470
|
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
611
|
-
// Initialize PluginManager
|
|
612
471
|
this.plugins.logLevel = this.log.logLevel;
|
|
613
472
|
await this.plugins.loadFromStorage();
|
|
614
|
-
// Initialize DeviceManager
|
|
615
473
|
this.devices.logLevel = this.log.logLevel;
|
|
616
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
617
474
|
for (const plugin of this.plugins) {
|
|
618
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
619
|
-
// We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
|
|
620
475
|
if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
621
476
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
|
|
622
477
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -646,7 +501,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
646
501
|
await plugin.nodeContext.set('description', plugin.description);
|
|
647
502
|
await plugin.nodeContext.set('author', plugin.author);
|
|
648
503
|
}
|
|
649
|
-
// Log system info and create .matterbridge directory
|
|
650
504
|
await this.logNodeAndSystemInfo();
|
|
651
505
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
652
506
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -654,7 +508,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
654
508
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
655
509
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
656
510
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
657
|
-
// Check node version and throw error
|
|
658
511
|
const minNodeVersion = 20;
|
|
659
512
|
const nodeVersion = process.versions.node;
|
|
660
513
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -662,18 +515,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
662
515
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
663
516
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
664
517
|
}
|
|
665
|
-
// Parse command line
|
|
666
518
|
await this.parseCommandLine();
|
|
667
|
-
// Emit the initialize_completed event
|
|
668
519
|
this.emit('initialize_completed');
|
|
669
520
|
this.initialized = true;
|
|
670
521
|
}
|
|
671
|
-
/**
|
|
672
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
673
|
-
*
|
|
674
|
-
* @private
|
|
675
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
676
|
-
*/
|
|
677
522
|
async parseCommandLine() {
|
|
678
523
|
if (hasParameter('list')) {
|
|
679
524
|
this.log.info(`│ Registered plugins (${this.plugins.length})`);
|
|
@@ -689,19 +534,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
689
534
|
}
|
|
690
535
|
index++;
|
|
691
536
|
}
|
|
692
|
-
/*
|
|
693
|
-
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
694
|
-
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
695
|
-
serializedRegisteredDevices?.forEach((device, index) => {
|
|
696
|
-
if (index !== serializedRegisteredDevices.length - 1) {
|
|
697
|
-
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
698
|
-
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
699
|
-
} else {
|
|
700
|
-
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
701
|
-
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
*/
|
|
705
537
|
this.shutdown = true;
|
|
706
538
|
return;
|
|
707
539
|
}
|
|
@@ -751,10 +583,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
751
583
|
this.shutdown = true;
|
|
752
584
|
return;
|
|
753
585
|
}
|
|
754
|
-
// Initialize frontend
|
|
755
586
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
756
587
|
await this.frontend.start(getIntParameter('frontend'));
|
|
757
|
-
// Start the matter storage and create the matterbridge context
|
|
758
588
|
try {
|
|
759
589
|
await this.startMatterStorage();
|
|
760
590
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -768,21 +598,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
768
598
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
769
599
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
770
600
|
}
|
|
771
|
-
// Clear the matterbridge context if the reset parameter is set (bridge mode)
|
|
772
601
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
773
602
|
this.initialized = true;
|
|
774
603
|
await this.shutdownProcessAndReset();
|
|
775
604
|
this.shutdown = true;
|
|
776
605
|
return;
|
|
777
606
|
}
|
|
778
|
-
// Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
|
|
779
607
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
780
608
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
781
609
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
782
610
|
if (plugin) {
|
|
783
611
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
784
612
|
if (!matterStorageManager) {
|
|
785
|
-
/* istanbul ignore next */
|
|
786
613
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
787
614
|
}
|
|
788
615
|
else {
|
|
@@ -801,56 +628,47 @@ export class Matterbridge extends EventEmitter {
|
|
|
801
628
|
this.shutdown = true;
|
|
802
629
|
return;
|
|
803
630
|
}
|
|
804
|
-
// Check in 5 minutes the latest and dev versions of matterbridge and the plugins
|
|
805
631
|
clearTimeout(this.checkUpdateTimeout);
|
|
806
632
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
807
633
|
const { checkUpdates } = await import('./update.js');
|
|
808
634
|
checkUpdates(this);
|
|
809
635
|
}, 300 * 1000).unref();
|
|
810
|
-
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
811
636
|
clearInterval(this.checkUpdateInterval);
|
|
812
637
|
this.checkUpdateInterval = setInterval(async () => {
|
|
813
638
|
const { checkUpdates } = await import('./update.js');
|
|
814
639
|
checkUpdates(this);
|
|
815
640
|
}, 12 * 60 * 60 * 1000).unref();
|
|
816
|
-
// Start the matterbridge in mode test
|
|
817
641
|
if (hasParameter('test')) {
|
|
818
642
|
this.bridgeMode = 'bridge';
|
|
819
643
|
return;
|
|
820
644
|
}
|
|
821
|
-
// Start the matterbridge in mode controller
|
|
822
645
|
if (hasParameter('controller')) {
|
|
823
646
|
this.bridgeMode = 'controller';
|
|
824
647
|
await this.startController();
|
|
825
648
|
return;
|
|
826
649
|
}
|
|
827
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
828
650
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
829
651
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
830
652
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
831
653
|
}
|
|
832
|
-
// Wait delay if specified (default 2 minutes) and the system uptime is less than 5 minutes. It solves race conditions on system startup.
|
|
833
654
|
if (hasParameter('delay') && os.uptime() <= 60 * 5) {
|
|
834
655
|
const { wait } = await import('./utils/wait.js');
|
|
835
656
|
const delay = getIntParameter('delay') || 120;
|
|
836
657
|
this.log.warn('Delay switch found with system uptime less then 5 minutes. Waiting for ' + delay + ' seconds before starting matterbridge...');
|
|
837
658
|
await wait(delay * 1000, 'Race condition delay', true);
|
|
838
659
|
}
|
|
839
|
-
// Wait delay if specified (default 2 minutes). It solves race conditions on docker compose startup.
|
|
840
660
|
if (hasParameter('fixed_delay')) {
|
|
841
661
|
const { wait } = await import('./utils/wait.js');
|
|
842
662
|
const delay = getIntParameter('fixed_delay') || 120;
|
|
843
663
|
this.log.warn('Fixed delay switch found. Waiting for ' + delay + ' seconds before starting matterbridge...');
|
|
844
664
|
await wait(delay * 1000, 'Fixed race condition delay', true);
|
|
845
665
|
}
|
|
846
|
-
// Start matterbridge in bridge mode
|
|
847
666
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
848
667
|
this.bridgeMode = 'bridge';
|
|
849
668
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
850
669
|
await this.startBridge();
|
|
851
670
|
return;
|
|
852
671
|
}
|
|
853
|
-
// Start matterbridge in childbridge mode
|
|
854
672
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
855
673
|
this.bridgeMode = 'childbridge';
|
|
856
674
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -858,22 +676,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
858
676
|
return;
|
|
859
677
|
}
|
|
860
678
|
}
|
|
861
|
-
/**
|
|
862
|
-
* Asynchronously loads and starts the registered plugins.
|
|
863
|
-
*
|
|
864
|
-
* This method is responsible for initializing and starting all enabled plugins.
|
|
865
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
866
|
-
*
|
|
867
|
-
* @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving. Defaults to false.
|
|
868
|
-
* @param {boolean} [start] - If true, the method will start the plugins after loading them. Defaults to true.
|
|
869
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
870
|
-
*/
|
|
871
679
|
async startPlugins(wait = false, start = true) {
|
|
872
|
-
// Check, load and start the plugins
|
|
873
680
|
for (const plugin of this.plugins) {
|
|
874
681
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
875
682
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
876
|
-
// Check if the plugin is available
|
|
877
683
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
878
684
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
879
685
|
plugin.enabled = false;
|
|
@@ -893,16 +699,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
893
699
|
if (wait)
|
|
894
700
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
895
701
|
else
|
|
896
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
702
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
897
703
|
}
|
|
898
704
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
899
705
|
}
|
|
900
|
-
/**
|
|
901
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
902
|
-
* - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
|
|
903
|
-
* - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
|
|
904
|
-
* - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
|
|
905
|
-
*/
|
|
906
706
|
registerProcessHandlers() {
|
|
907
707
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
908
708
|
process.removeAllListeners('uncaughtException');
|
|
@@ -929,9 +729,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
929
729
|
};
|
|
930
730
|
process.on('SIGTERM', this.sigtermHandler);
|
|
931
731
|
}
|
|
932
|
-
/**
|
|
933
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
934
|
-
*/
|
|
935
732
|
deregisterProcessHandlers() {
|
|
936
733
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
937
734
|
if (this.exceptionHandler)
|
|
@@ -948,18 +745,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
948
745
|
process.off('SIGTERM', this.sigtermHandler);
|
|
949
746
|
this.sigtermHandler = undefined;
|
|
950
747
|
}
|
|
951
|
-
/**
|
|
952
|
-
* Logs the node and system information.
|
|
953
|
-
*/
|
|
954
748
|
async logNodeAndSystemInfo() {
|
|
955
|
-
// IP address information
|
|
956
749
|
const networkInterfaces = os.networkInterfaces();
|
|
957
750
|
this.systemInformation.interfaceName = '';
|
|
958
751
|
this.systemInformation.ipv4Address = '';
|
|
959
752
|
this.systemInformation.ipv6Address = '';
|
|
960
753
|
this.systemInformation.macAddress = '';
|
|
961
754
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
962
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
963
755
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
964
756
|
continue;
|
|
965
757
|
if (!interfaceDetails) {
|
|
@@ -985,18 +777,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
985
777
|
break;
|
|
986
778
|
}
|
|
987
779
|
}
|
|
988
|
-
// Node information
|
|
989
780
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
990
781
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
991
782
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
992
783
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
993
|
-
// Host system information
|
|
994
784
|
this.systemInformation.hostname = os.hostname();
|
|
995
785
|
this.systemInformation.user = os.userInfo().username;
|
|
996
|
-
this.systemInformation.osType = os.type();
|
|
997
|
-
this.systemInformation.osRelease = os.release();
|
|
998
|
-
this.systemInformation.osPlatform = os.platform();
|
|
999
|
-
this.systemInformation.osArch = os.arch();
|
|
786
|
+
this.systemInformation.osType = os.type();
|
|
787
|
+
this.systemInformation.osRelease = os.release();
|
|
788
|
+
this.systemInformation.osPlatform = os.platform();
|
|
789
|
+
this.systemInformation.osArch = os.arch();
|
|
1000
790
|
this.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
1001
791
|
this.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
1002
792
|
this.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -1006,7 +796,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1006
796
|
this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
1007
797
|
this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
1008
798
|
this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
1009
|
-
// Log the system information
|
|
1010
799
|
this.log.debug('Host System Information:');
|
|
1011
800
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
1012
801
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -1026,17 +815,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1026
815
|
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
1027
816
|
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
1028
817
|
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
1029
|
-
// Log directories
|
|
1030
818
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1031
819
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1032
820
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1033
821
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1034
822
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1035
|
-
// Global node_modules directory
|
|
1036
823
|
if (this.nodeContext)
|
|
1037
824
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1038
825
|
if (this.globalModulesDirectory === '') {
|
|
1039
|
-
// First run of Matterbridge so the node storage is empty
|
|
1040
826
|
this.log.debug(`Getting global node_modules directory...`);
|
|
1041
827
|
try {
|
|
1042
828
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -1049,42 +835,29 @@ export class Matterbridge extends EventEmitter {
|
|
|
1049
835
|
}
|
|
1050
836
|
}
|
|
1051
837
|
else {
|
|
1052
|
-
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
1053
838
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1054
839
|
const { createESMWorker } = await import('./workers.js');
|
|
1055
840
|
createESMWorker('NpmGlobalPrefix', path.join(this.rootDirectory, 'dist/workerGlobalPrefix.js'));
|
|
1056
841
|
}
|
|
1057
|
-
// Matterbridge version
|
|
1058
842
|
this.log.debug(`Reading matterbridge package.json...`);
|
|
1059
843
|
const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1060
844
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
1061
845
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1062
|
-
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
1063
846
|
if (this.nodeContext)
|
|
1064
847
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1065
848
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1066
|
-
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
1067
849
|
if (this.nodeContext)
|
|
1068
850
|
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
1069
851
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1070
|
-
// Frontend version
|
|
1071
852
|
this.log.debug(`Reading frontend package.json...`);
|
|
1072
853
|
const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
1073
854
|
this.frontendVersion = frontendPackageJson.version;
|
|
1074
855
|
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1075
|
-
// Current working directory
|
|
1076
856
|
const currentDir = process.cwd();
|
|
1077
857
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1078
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1079
858
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1080
859
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1081
860
|
}
|
|
1082
|
-
/**
|
|
1083
|
-
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
1084
|
-
*
|
|
1085
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
1086
|
-
* @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
|
|
1087
|
-
*/
|
|
1088
861
|
async setLogLevel(logLevel) {
|
|
1089
862
|
this.logLevel = logLevel;
|
|
1090
863
|
this.log.logLevel = logLevel;
|
|
@@ -1098,87 +871,58 @@ export class Matterbridge extends EventEmitter {
|
|
|
1098
871
|
continue;
|
|
1099
872
|
if (plugin.platform.config.debug === true)
|
|
1100
873
|
pluginDebug = true;
|
|
1101
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug"
|
|
1102
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug"
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
874
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
|
|
875
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
|
|
876
|
+
}
|
|
877
|
+
let callbackLogLevel = "notice";
|
|
878
|
+
if (logLevel === "info" || Logger.level === MatterLogLevel.INFO)
|
|
879
|
+
callbackLogLevel = "info";
|
|
880
|
+
if (logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG || pluginDebug)
|
|
881
|
+
callbackLogLevel = "debug";
|
|
1110
882
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
1111
883
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
1112
884
|
return logLevel;
|
|
1113
885
|
}
|
|
1114
|
-
/**
|
|
1115
|
-
* Get the current logger logLevel.
|
|
1116
|
-
*
|
|
1117
|
-
* @returns {LogLevel} The current logger logLevel.
|
|
1118
|
-
*/
|
|
1119
886
|
getLogLevel() {
|
|
1120
887
|
return this.log.logLevel;
|
|
1121
888
|
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1124
|
-
* It also logs to file (matter.log) if fileLogger is true.
|
|
1125
|
-
*
|
|
1126
|
-
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1127
|
-
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1128
|
-
*/
|
|
1129
889
|
createDestinationMatterLogger(fileLogger) {
|
|
1130
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
890
|
+
this.matterLog.logNameColor = '\x1b[34m';
|
|
1131
891
|
if (fileLogger) {
|
|
1132
892
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
1133
893
|
}
|
|
1134
894
|
return (text, message) => {
|
|
1135
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1136
895
|
const logger = text.slice(44, 44 + 20).trim();
|
|
1137
896
|
const msg = text.slice(65);
|
|
1138
897
|
this.matterLog.logName = logger;
|
|
1139
898
|
switch (message.level) {
|
|
1140
899
|
case MatterLogLevel.DEBUG:
|
|
1141
|
-
this.matterLog.log("debug"
|
|
900
|
+
this.matterLog.log("debug", msg);
|
|
1142
901
|
break;
|
|
1143
902
|
case MatterLogLevel.INFO:
|
|
1144
|
-
this.matterLog.log("info"
|
|
903
|
+
this.matterLog.log("info", msg);
|
|
1145
904
|
break;
|
|
1146
905
|
case MatterLogLevel.NOTICE:
|
|
1147
|
-
this.matterLog.log("notice"
|
|
906
|
+
this.matterLog.log("notice", msg);
|
|
1148
907
|
break;
|
|
1149
908
|
case MatterLogLevel.WARN:
|
|
1150
|
-
this.matterLog.log("warn"
|
|
909
|
+
this.matterLog.log("warn", msg);
|
|
1151
910
|
break;
|
|
1152
911
|
case MatterLogLevel.ERROR:
|
|
1153
|
-
this.matterLog.log("error"
|
|
912
|
+
this.matterLog.log("error", msg);
|
|
1154
913
|
break;
|
|
1155
914
|
case MatterLogLevel.FATAL:
|
|
1156
|
-
this.matterLog.log("fatal"
|
|
915
|
+
this.matterLog.log("fatal", msg);
|
|
1157
916
|
break;
|
|
1158
917
|
}
|
|
1159
918
|
};
|
|
1160
919
|
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1163
|
-
*
|
|
1164
|
-
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1165
|
-
*/
|
|
1166
920
|
async restartProcess() {
|
|
1167
921
|
await this.cleanup('restarting...', true);
|
|
1168
922
|
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Shut down the process (/api/shutdown).
|
|
1171
|
-
*
|
|
1172
|
-
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1173
|
-
*/
|
|
1174
923
|
async shutdownProcess() {
|
|
1175
924
|
await this.cleanup('shutting down...', false);
|
|
1176
925
|
}
|
|
1177
|
-
/**
|
|
1178
|
-
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1179
|
-
*
|
|
1180
|
-
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1181
|
-
*/
|
|
1182
926
|
async updateProcess() {
|
|
1183
927
|
this.log.info('Updating matterbridge...');
|
|
1184
928
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -1191,13 +935,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1191
935
|
this.frontend.wssSendRestartRequired();
|
|
1192
936
|
await this.cleanup('updating...', false);
|
|
1193
937
|
}
|
|
1194
|
-
/**
|
|
1195
|
-
* Unregister all devices and shut down the process (/api/unregister).
|
|
1196
|
-
*
|
|
1197
|
-
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1198
|
-
*
|
|
1199
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1200
|
-
*/
|
|
1201
938
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
1202
939
|
const { wait } = await import('./utils/wait.js');
|
|
1203
940
|
this.log.info('Unregistering all devices and shutting down...');
|
|
@@ -1210,71 +947,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
1210
947
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
1211
948
|
}
|
|
1212
949
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1213
|
-
await wait(timeout);
|
|
950
|
+
await wait(timeout);
|
|
1214
951
|
this.log.debug('Cleaning up and shutting down...');
|
|
1215
952
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
1216
953
|
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Reset commissioning and shut down the process (/api/reset).
|
|
1219
|
-
*
|
|
1220
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1221
|
-
*/
|
|
1222
954
|
async shutdownProcessAndReset() {
|
|
1223
955
|
await this.cleanup('shutting down with reset...', false);
|
|
1224
956
|
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Factory reset and shut down the process (/api/factory-reset).
|
|
1227
|
-
*
|
|
1228
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1229
|
-
*/
|
|
1230
957
|
async shutdownProcessAndFactoryReset() {
|
|
1231
958
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1232
959
|
}
|
|
1233
|
-
/**
|
|
1234
|
-
* Cleans up the Matterbridge instance.
|
|
1235
|
-
*
|
|
1236
|
-
* @param {string} message - The cleanup message.
|
|
1237
|
-
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1238
|
-
* @param {number} [pause] - The pause in ms to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1239
|
-
*
|
|
1240
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1241
|
-
*/
|
|
1242
960
|
async cleanup(message, restart = false, pause = 1000) {
|
|
1243
961
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1244
962
|
this.emit('cleanup_started');
|
|
1245
963
|
this.hasCleanupStarted = true;
|
|
1246
964
|
this.log.info(message);
|
|
1247
|
-
// Clear the start matter interval
|
|
1248
965
|
if (this.startMatterInterval) {
|
|
1249
966
|
clearInterval(this.startMatterInterval);
|
|
1250
967
|
this.startMatterInterval = undefined;
|
|
1251
968
|
this.log.debug('Start matter interval cleared');
|
|
1252
969
|
}
|
|
1253
|
-
// Clear the check update timeout
|
|
1254
970
|
if (this.checkUpdateTimeout) {
|
|
1255
971
|
clearTimeout(this.checkUpdateTimeout);
|
|
1256
972
|
this.checkUpdateTimeout = undefined;
|
|
1257
973
|
this.log.debug('Check update timeout cleared');
|
|
1258
974
|
}
|
|
1259
|
-
// Clear the check update interval
|
|
1260
975
|
if (this.checkUpdateInterval) {
|
|
1261
976
|
clearInterval(this.checkUpdateInterval);
|
|
1262
977
|
this.checkUpdateInterval = undefined;
|
|
1263
978
|
this.log.debug('Check update interval cleared');
|
|
1264
979
|
}
|
|
1265
|
-
// Clear the configure timeout
|
|
1266
980
|
if (this.configureTimeout) {
|
|
1267
981
|
clearTimeout(this.configureTimeout);
|
|
1268
982
|
this.configureTimeout = undefined;
|
|
1269
983
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1270
984
|
}
|
|
1271
|
-
// Clear the reachability timeout
|
|
1272
985
|
if (this.reachabilityTimeout) {
|
|
1273
986
|
clearTimeout(this.reachabilityTimeout);
|
|
1274
987
|
this.reachabilityTimeout = undefined;
|
|
1275
988
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1276
989
|
}
|
|
1277
|
-
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1278
990
|
for (const plugin of this.plugins) {
|
|
1279
991
|
if (!plugin.enabled || plugin.error)
|
|
1280
992
|
continue;
|
|
@@ -1285,7 +997,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1285
997
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1286
998
|
}
|
|
1287
999
|
}
|
|
1288
|
-
// Stop matter server nodes
|
|
1289
1000
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1290
1001
|
if (pause > 0) {
|
|
1291
1002
|
const { wait } = await import('./utils/wait.js');
|
|
@@ -1313,7 +1024,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1313
1024
|
}
|
|
1314
1025
|
}
|
|
1315
1026
|
this.log.notice('Stopped matter server nodes');
|
|
1316
|
-
// Matter commisioning reset
|
|
1317
1027
|
if (message === 'shutting down with reset...') {
|
|
1318
1028
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1319
1029
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1323,7 +1033,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1323
1033
|
await this.matterbridgeContext?.clearAll();
|
|
1324
1034
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1325
1035
|
}
|
|
1326
|
-
// Unregister all devices
|
|
1327
1036
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1328
1037
|
if (this.bridgeMode === 'bridge') {
|
|
1329
1038
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1341,35 +1050,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1341
1050
|
}
|
|
1342
1051
|
this.log.info('Matter storage reset done!');
|
|
1343
1052
|
}
|
|
1344
|
-
// Stop matter storage
|
|
1345
1053
|
await this.stopMatterStorage();
|
|
1346
|
-
// Stop the frontend
|
|
1347
1054
|
await this.frontend.stop();
|
|
1348
1055
|
this.frontend.destroy();
|
|
1349
|
-
// Close PluginManager and DeviceManager
|
|
1350
1056
|
this.plugins.destroy();
|
|
1351
1057
|
this.devices.destroy();
|
|
1352
|
-
// Stop thread messaging server
|
|
1353
1058
|
this.server.close();
|
|
1354
|
-
// Close the matterbridge node storage and context
|
|
1355
1059
|
if (this.nodeStorage && this.nodeContext) {
|
|
1356
|
-
/*
|
|
1357
|
-
TODO: Implement serialization of registered devices
|
|
1358
|
-
this.log.info('Saving registered devices...');
|
|
1359
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1360
|
-
this.devices.forEach(async (device) => {
|
|
1361
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1362
|
-
this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1363
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1364
|
-
});
|
|
1365
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1366
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1367
|
-
*/
|
|
1368
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1369
1060
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1370
1061
|
await this.nodeContext.close();
|
|
1371
1062
|
this.nodeContext = undefined;
|
|
1372
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1373
1063
|
for (const plugin of this.plugins) {
|
|
1374
1064
|
if (plugin.nodeContext) {
|
|
1375
1065
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1386,10 +1076,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1386
1076
|
}
|
|
1387
1077
|
this.plugins.clear();
|
|
1388
1078
|
this.devices.clear();
|
|
1389
|
-
// Factory reset
|
|
1390
1079
|
if (message === 'shutting down with factory reset...') {
|
|
1391
1080
|
try {
|
|
1392
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1393
1081
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1394
1082
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1395
1083
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1398,13 +1086,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1398
1086
|
await fs.promises.rm(backup, { recursive: true });
|
|
1399
1087
|
}
|
|
1400
1088
|
catch (error) {
|
|
1401
|
-
// istanbul ignore next if
|
|
1402
1089
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1403
1090
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1404
1091
|
}
|
|
1405
1092
|
}
|
|
1406
1093
|
try {
|
|
1407
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1408
1094
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1409
1095
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1410
1096
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1413,20 +1099,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1413
1099
|
await fs.promises.rm(backup, { recursive: true });
|
|
1414
1100
|
}
|
|
1415
1101
|
catch (error) {
|
|
1416
|
-
// istanbul ignore next if
|
|
1417
1102
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1418
1103
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1419
1104
|
}
|
|
1420
1105
|
}
|
|
1421
1106
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1422
1107
|
}
|
|
1423
|
-
// Deregisters the process handlers
|
|
1424
1108
|
this.deregisterProcessHandlers();
|
|
1425
1109
|
if (restart) {
|
|
1426
1110
|
if (message === 'updating...') {
|
|
1427
1111
|
this.log.info('Cleanup completed. Updating...');
|
|
1428
1112
|
Matterbridge.instance = undefined;
|
|
1429
|
-
this.emit('update');
|
|
1113
|
+
this.emit('update');
|
|
1430
1114
|
}
|
|
1431
1115
|
else if (message === 'restarting...') {
|
|
1432
1116
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1455,14 +1139,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1455
1139
|
this.log.debug('Cleanup already started...');
|
|
1456
1140
|
}
|
|
1457
1141
|
}
|
|
1458
|
-
/**
|
|
1459
|
-
* Starts the Matterbridge in bridge mode.
|
|
1460
|
-
*
|
|
1461
|
-
* @private
|
|
1462
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1463
|
-
*/
|
|
1464
1142
|
async startBridge() {
|
|
1465
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1466
1143
|
if (!this.matterStorageManager)
|
|
1467
1144
|
throw new Error('No storage manager initialized');
|
|
1468
1145
|
if (!this.matterbridgeContext)
|
|
@@ -1476,7 +1153,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1476
1153
|
this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
|
|
1477
1154
|
let failCount = 0;
|
|
1478
1155
|
this.startMatterInterval = setInterval(async () => {
|
|
1479
|
-
// istanbul ignore if cause is just a logging statement
|
|
1480
1156
|
if (failCount && failCount % 10 === 0) {
|
|
1481
1157
|
this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
|
|
1482
1158
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
@@ -1510,16 +1186,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1510
1186
|
clearInterval(this.startMatterInterval);
|
|
1511
1187
|
this.startMatterInterval = undefined;
|
|
1512
1188
|
this.log.debug('Cleared startMatterInterval interval in bridge mode');
|
|
1513
|
-
|
|
1514
|
-
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1515
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1189
|
+
this.startServerNode(this.serverNode);
|
|
1516
1190
|
for (const device of this.devices.array()) {
|
|
1517
1191
|
if (device.mode === 'server' && device.serverNode) {
|
|
1518
1192
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1519
|
-
this.startServerNode(device.serverNode);
|
|
1193
|
+
this.startServerNode(device.serverNode);
|
|
1520
1194
|
}
|
|
1521
1195
|
}
|
|
1522
|
-
// Configure the plugins
|
|
1523
1196
|
this.configureTimeout = setTimeout(async () => {
|
|
1524
1197
|
for (const plugin of this.plugins.array()) {
|
|
1525
1198
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1537,13 +1210,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1537
1210
|
}
|
|
1538
1211
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1539
1212
|
}, 30 * 1000).unref();
|
|
1540
|
-
// Setting reachability to true
|
|
1541
1213
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1542
1214
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1543
1215
|
if (this.aggregatorNode)
|
|
1544
1216
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1545
1217
|
}, 60 * 1000).unref();
|
|
1546
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1547
1218
|
this.emit('bridge_started');
|
|
1548
1219
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1549
1220
|
this.frontend.wssSendRefreshRequired('settings');
|
|
@@ -1551,33 +1222,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1551
1222
|
this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
|
|
1552
1223
|
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1553
1224
|
}
|
|
1554
|
-
/**
|
|
1555
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1556
|
-
*
|
|
1557
|
-
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1558
|
-
*
|
|
1559
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1560
|
-
*/
|
|
1561
1225
|
async startChildbridge(delay = 1000) {
|
|
1562
1226
|
if (!this.matterStorageManager)
|
|
1563
1227
|
throw new Error('No storage manager initialized');
|
|
1564
1228
|
const { wait } = await import('./utils/wait.js');
|
|
1565
|
-
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1566
1229
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1567
1230
|
await this.startPlugins(true, false);
|
|
1568
|
-
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1569
1231
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1570
1232
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1571
1233
|
if (plugin.type === 'DynamicPlatform')
|
|
1572
1234
|
await this.createDynamicPlugin(plugin);
|
|
1573
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1235
|
+
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1574
1236
|
}
|
|
1575
|
-
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1576
1237
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1577
1238
|
this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
|
|
1578
1239
|
let failCount = 0;
|
|
1579
1240
|
this.startMatterInterval = setInterval(async () => {
|
|
1580
|
-
// istanbul ignore if cause is just a logging statement
|
|
1581
1241
|
if (failCount && failCount % 10 === 0) {
|
|
1582
1242
|
this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
|
|
1583
1243
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
@@ -1615,9 +1275,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1615
1275
|
clearInterval(this.startMatterInterval);
|
|
1616
1276
|
this.startMatterInterval = undefined;
|
|
1617
1277
|
if (delay > 0)
|
|
1618
|
-
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
|
|
1278
|
+
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
|
|
1619
1279
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1620
|
-
// Configure the plugins
|
|
1621
1280
|
this.configureTimeout = setTimeout(async () => {
|
|
1622
1281
|
for (const plugin of this.plugins.array()) {
|
|
1623
1282
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1642,7 +1301,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1642
1301
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1643
1302
|
continue;
|
|
1644
1303
|
}
|
|
1645
|
-
// istanbul ignore next if cause is just a safety check
|
|
1646
1304
|
if (!plugin.serverNode) {
|
|
1647
1305
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1648
1306
|
continue;
|
|
@@ -1655,23 +1313,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1655
1313
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1656
1314
|
continue;
|
|
1657
1315
|
}
|
|
1658
|
-
|
|
1659
|
-
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1660
|
-
// Setting reachability to true
|
|
1316
|
+
this.startServerNode(plugin.serverNode);
|
|
1661
1317
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1662
1318
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1663
1319
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1664
1320
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1665
1321
|
}, 60 * 1000).unref();
|
|
1666
1322
|
}
|
|
1667
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1668
1323
|
for (const device of this.devices.array()) {
|
|
1669
1324
|
if (device.mode === 'server' && device.serverNode) {
|
|
1670
1325
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1671
|
-
this.startServerNode(device.serverNode);
|
|
1326
|
+
this.startServerNode(device.serverNode);
|
|
1672
1327
|
}
|
|
1673
1328
|
}
|
|
1674
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1675
1329
|
this.emit('childbridge_started');
|
|
1676
1330
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1677
1331
|
this.frontend.wssSendRefreshRequired('settings');
|
|
@@ -1679,229 +1333,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1679
1333
|
this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
|
|
1680
1334
|
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1681
1335
|
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Starts the Matterbridge controller.
|
|
1684
|
-
*
|
|
1685
|
-
* @private
|
|
1686
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1687
|
-
*/
|
|
1688
1336
|
async startController() {
|
|
1689
|
-
/*
|
|
1690
|
-
if (!this.matterStorageManager) {
|
|
1691
|
-
this.log.error('No storage manager initialized');
|
|
1692
|
-
await this.cleanup('No storage manager initialized');
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
this.log.info('Creating context: mattercontrollerContext');
|
|
1696
|
-
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1697
|
-
if (!this.controllerContext) {
|
|
1698
|
-
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1699
|
-
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1700
|
-
return;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1704
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1705
|
-
this.log.info('Creating matter commissioning controller');
|
|
1706
|
-
this.commissioningController = new CommissioningController({
|
|
1707
|
-
autoConnect: false,
|
|
1708
|
-
});
|
|
1709
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1710
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1711
|
-
|
|
1712
|
-
this.log.info('Starting matter server');
|
|
1713
|
-
await this.matterServer.start();
|
|
1714
|
-
this.log.info('Matter server started');
|
|
1715
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1716
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1717
|
-
regulatoryCountryCode: 'XX',
|
|
1718
|
-
};
|
|
1719
|
-
const commissioningController = new CommissioningController({
|
|
1720
|
-
environment: {
|
|
1721
|
-
environment,
|
|
1722
|
-
id: uniqueId,
|
|
1723
|
-
},
|
|
1724
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1725
|
-
adminFabricLabel,
|
|
1726
|
-
});
|
|
1727
|
-
|
|
1728
|
-
if (hasParameter('pairingcode')) {
|
|
1729
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1730
|
-
const pairingCode = getParameter('pairingcode');
|
|
1731
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1732
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1733
|
-
|
|
1734
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1735
|
-
if (pairingCode !== undefined) {
|
|
1736
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1737
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1738
|
-
longDiscriminator = undefined;
|
|
1739
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1740
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1741
|
-
} else {
|
|
1742
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1743
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1744
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1745
|
-
}
|
|
1746
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1747
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
const options = {
|
|
1751
|
-
commissioning: commissioningOptions,
|
|
1752
|
-
discovery: {
|
|
1753
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1754
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1755
|
-
},
|
|
1756
|
-
passcode: setupPin,
|
|
1757
|
-
} as NodeCommissioningOptions;
|
|
1758
|
-
this.log.info('Commissioning with options:', options);
|
|
1759
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1760
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1761
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1762
|
-
} // (hasParameter('pairingcode'))
|
|
1763
|
-
|
|
1764
|
-
if (hasParameter('unpairall')) {
|
|
1765
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1766
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1767
|
-
for (const nodeId of nodeIds) {
|
|
1768
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1769
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1770
|
-
}
|
|
1771
|
-
return;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
if (hasParameter('discover')) {
|
|
1775
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1776
|
-
// console.log(discover);
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1780
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1781
|
-
return;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1785
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1786
|
-
for (const nodeId of nodeIds) {
|
|
1787
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1788
|
-
|
|
1789
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1790
|
-
autoSubscribe: false,
|
|
1791
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1792
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1793
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1794
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1795
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1796
|
-
switch (info) {
|
|
1797
|
-
case NodeStateInformation.Connected:
|
|
1798
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1799
|
-
break;
|
|
1800
|
-
case NodeStateInformation.Disconnected:
|
|
1801
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1802
|
-
break;
|
|
1803
|
-
case NodeStateInformation.Reconnecting:
|
|
1804
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1805
|
-
break;
|
|
1806
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1807
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1808
|
-
break;
|
|
1809
|
-
case NodeStateInformation.StructureChanged:
|
|
1810
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1811
|
-
break;
|
|
1812
|
-
case NodeStateInformation.Decommissioned:
|
|
1813
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1814
|
-
break;
|
|
1815
|
-
default:
|
|
1816
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1817
|
-
break;
|
|
1818
|
-
}
|
|
1819
|
-
},
|
|
1820
|
-
});
|
|
1821
|
-
|
|
1822
|
-
node.logStructure();
|
|
1823
|
-
|
|
1824
|
-
// Get the interaction client
|
|
1825
|
-
this.log.info('Getting the interaction client');
|
|
1826
|
-
const interactionClient = await node.getInteractionClient();
|
|
1827
|
-
let cluster;
|
|
1828
|
-
let attributes;
|
|
1829
|
-
|
|
1830
|
-
// Log BasicInformationCluster
|
|
1831
|
-
cluster = BasicInformationCluster;
|
|
1832
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1833
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1834
|
-
});
|
|
1835
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1836
|
-
attributes.forEach((attribute) => {
|
|
1837
|
-
this.log.info(
|
|
1838
|
-
`- 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}`,
|
|
1839
|
-
);
|
|
1840
|
-
});
|
|
1841
|
-
|
|
1842
|
-
// Log PowerSourceCluster
|
|
1843
|
-
cluster = PowerSourceCluster;
|
|
1844
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1845
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1846
|
-
});
|
|
1847
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1848
|
-
attributes.forEach((attribute) => {
|
|
1849
|
-
this.log.info(
|
|
1850
|
-
`- 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}`,
|
|
1851
|
-
);
|
|
1852
|
-
});
|
|
1853
|
-
|
|
1854
|
-
// Log ThreadNetworkDiagnostics
|
|
1855
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1856
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1857
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1858
|
-
});
|
|
1859
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1860
|
-
attributes.forEach((attribute) => {
|
|
1861
|
-
this.log.info(
|
|
1862
|
-
`- 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}`,
|
|
1863
|
-
);
|
|
1864
|
-
});
|
|
1865
|
-
|
|
1866
|
-
// Log SwitchCluster
|
|
1867
|
-
cluster = SwitchCluster;
|
|
1868
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1869
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1870
|
-
});
|
|
1871
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1872
|
-
attributes.forEach((attribute) => {
|
|
1873
|
-
this.log.info(
|
|
1874
|
-
`- 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}`,
|
|
1875
|
-
);
|
|
1876
|
-
});
|
|
1877
|
-
|
|
1878
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1879
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1880
|
-
ignoreInitialTriggers: false,
|
|
1881
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1882
|
-
this.log.info(
|
|
1883
|
-
`***${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}`,
|
|
1884
|
-
),
|
|
1885
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1886
|
-
this.log.info(
|
|
1887
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1888
|
-
);
|
|
1889
|
-
},
|
|
1890
|
-
});
|
|
1891
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1892
|
-
}
|
|
1893
|
-
*/
|
|
1894
1337
|
}
|
|
1895
|
-
/** */
|
|
1896
|
-
/** Matter.js methods */
|
|
1897
|
-
/** */
|
|
1898
|
-
/**
|
|
1899
|
-
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1900
|
-
*
|
|
1901
|
-
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1902
|
-
*/
|
|
1903
1338
|
async startMatterStorage() {
|
|
1904
|
-
// Setup Matter storage
|
|
1905
1339
|
this.log.info(`Starting matter node storage...`);
|
|
1906
1340
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1907
1341
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1909,17 +1343,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1909
1343
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1910
1344
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1911
1345
|
this.log.info('Matter node storage started');
|
|
1912
|
-
// Backup matter storage since it is created/opened correctly
|
|
1913
1346
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1914
1347
|
}
|
|
1915
|
-
/**
|
|
1916
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1917
|
-
*
|
|
1918
|
-
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1919
|
-
* @param {string} backupName - The name of the backup directory to be created.
|
|
1920
|
-
* @private
|
|
1921
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1922
|
-
*/
|
|
1923
1348
|
async backupMatterStorage(storageName, backupName) {
|
|
1924
1349
|
this.log.info('Creating matter node storage backup...');
|
|
1925
1350
|
try {
|
|
@@ -1930,11 +1355,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1930
1355
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1931
1356
|
}
|
|
1932
1357
|
}
|
|
1933
|
-
/**
|
|
1934
|
-
* Stops the matter storage.
|
|
1935
|
-
*
|
|
1936
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1937
|
-
*/
|
|
1938
1358
|
async stopMatterStorage() {
|
|
1939
1359
|
this.log.info('Closing matter node storage...');
|
|
1940
1360
|
await this.matterStorageManager?.close();
|
|
@@ -1943,20 +1363,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1943
1363
|
this.matterbridgeContext = undefined;
|
|
1944
1364
|
this.log.info('Matter node storage closed');
|
|
1945
1365
|
}
|
|
1946
|
-
/**
|
|
1947
|
-
* Creates a server node storage context.
|
|
1948
|
-
*
|
|
1949
|
-
* @param {string} storeId - The storeId.
|
|
1950
|
-
* @param {string} deviceName - The name of the device.
|
|
1951
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1952
|
-
* @param {number} vendorId - The vendor ID.
|
|
1953
|
-
* @param {string} vendorName - The vendor name.
|
|
1954
|
-
* @param {number} productId - The product ID.
|
|
1955
|
-
* @param {string} productName - The product name.
|
|
1956
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1957
|
-
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1958
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1959
|
-
*/
|
|
1960
1366
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1961
1367
|
const { randomBytes } = await import('node:crypto');
|
|
1962
1368
|
if (!this.matterStorageService)
|
|
@@ -1996,15 +1402,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1996
1402
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1997
1403
|
return storageContext;
|
|
1998
1404
|
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Creates a server node.
|
|
2001
|
-
*
|
|
2002
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2003
|
-
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2004
|
-
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2005
|
-
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2006
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2007
|
-
*/
|
|
2008
1405
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
2009
1406
|
const storeId = await storageContext.get('storeId');
|
|
2010
1407
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -2014,35 +1411,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
2014
1411
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
2015
1412
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
2016
1413
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2017
|
-
/**
|
|
2018
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2019
|
-
*/
|
|
2020
1414
|
const serverNode = await ServerNode.create({
|
|
2021
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
2022
1415
|
id: storeId,
|
|
2023
|
-
// Environment to run the server node in
|
|
2024
1416
|
environment: this.environment,
|
|
2025
|
-
// Provide Network relevant configuration like the port
|
|
2026
1417
|
network: {
|
|
2027
1418
|
listeningAddressIpv4: this.ipv4Address,
|
|
2028
1419
|
listeningAddressIpv6: this.ipv6Address,
|
|
2029
1420
|
port,
|
|
2030
1421
|
},
|
|
2031
|
-
// Provide the certificate for the device
|
|
2032
1422
|
operationalCredentials: {
|
|
2033
1423
|
certification: this.certification,
|
|
2034
1424
|
},
|
|
2035
|
-
// Provide Commissioning relevant settings
|
|
2036
1425
|
commissioning: {
|
|
2037
1426
|
passcode,
|
|
2038
1427
|
discriminator,
|
|
2039
1428
|
},
|
|
2040
|
-
// Provide Node announcement settings
|
|
2041
1429
|
productDescription: {
|
|
2042
1430
|
name: await storageContext.get('deviceName'),
|
|
2043
1431
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
2044
1432
|
},
|
|
2045
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2046
1433
|
basicInformation: {
|
|
2047
1434
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2048
1435
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2059,23 +1446,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2059
1446
|
reachable: true,
|
|
2060
1447
|
},
|
|
2061
1448
|
});
|
|
2062
|
-
/**
|
|
2063
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2064
|
-
* This means: It is added to the first fabric.
|
|
2065
|
-
*/
|
|
2066
1449
|
serverNode.lifecycle.commissioned.on(() => {
|
|
2067
1450
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
2068
1451
|
this.advertisingNodes.delete(storeId);
|
|
2069
1452
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2070
1453
|
});
|
|
2071
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2072
1454
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
2073
1455
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
2074
1456
|
this.advertisingNodes.delete(storeId);
|
|
2075
1457
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2076
1458
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2077
1459
|
});
|
|
2078
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2079
1460
|
serverNode.lifecycle.online.on(async () => {
|
|
2080
1461
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2081
1462
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2086,16 +1467,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2086
1467
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2087
1468
|
}
|
|
2088
1469
|
else {
|
|
2089
|
-
// istanbul ignore next
|
|
2090
1470
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2091
|
-
// istanbul ignore next
|
|
2092
1471
|
this.advertisingNodes.delete(storeId);
|
|
2093
1472
|
}
|
|
2094
1473
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2095
1474
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2096
1475
|
this.emit('online', storeId);
|
|
2097
1476
|
});
|
|
2098
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2099
1477
|
serverNode.lifecycle.offline.on(() => {
|
|
2100
1478
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2101
1479
|
this.advertisingNodes.delete(storeId);
|
|
@@ -2103,15 +1481,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2103
1481
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2104
1482
|
this.emit('offline', storeId);
|
|
2105
1483
|
});
|
|
2106
|
-
/**
|
|
2107
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2108
|
-
* information is needed.
|
|
2109
|
-
*/
|
|
2110
1484
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2111
1485
|
let action = '';
|
|
2112
1486
|
switch (fabricAction) {
|
|
2113
1487
|
case FabricAction.Added:
|
|
2114
|
-
this.advertisingNodes.delete(storeId);
|
|
1488
|
+
this.advertisingNodes.delete(storeId);
|
|
2115
1489
|
action = 'added';
|
|
2116
1490
|
break;
|
|
2117
1491
|
case FabricAction.Removed:
|
|
@@ -2124,22 +1498,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2124
1498
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
2125
1499
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2126
1500
|
});
|
|
2127
|
-
/**
|
|
2128
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2129
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2130
|
-
*/
|
|
2131
1501
|
serverNode.events.sessions.opened.on((session) => {
|
|
2132
1502
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2133
1503
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2134
1504
|
});
|
|
2135
|
-
/**
|
|
2136
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2137
|
-
*/
|
|
2138
1505
|
serverNode.events.sessions.closed.on((session) => {
|
|
2139
1506
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2140
1507
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2141
1508
|
});
|
|
2142
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2143
1509
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2144
1510
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2145
1511
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -2147,12 +1513,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2147
1513
|
this.log.info(`Created server node for ${storeId}`);
|
|
2148
1514
|
return serverNode;
|
|
2149
1515
|
}
|
|
2150
|
-
/**
|
|
2151
|
-
* Gets the matter sanitized data of the specified server node.
|
|
2152
|
-
*
|
|
2153
|
-
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2154
|
-
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2155
|
-
*/
|
|
2156
1516
|
getServerNodeData(serverNode) {
|
|
2157
1517
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
2158
1518
|
return {
|
|
@@ -2169,25 +1529,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2169
1529
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
2170
1530
|
};
|
|
2171
1531
|
}
|
|
2172
|
-
/**
|
|
2173
|
-
* Starts the specified server node.
|
|
2174
|
-
*
|
|
2175
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2176
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2177
|
-
*/
|
|
2178
1532
|
async startServerNode(matterServerNode) {
|
|
2179
1533
|
if (!matterServerNode)
|
|
2180
1534
|
return;
|
|
2181
1535
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2182
1536
|
await matterServerNode.start();
|
|
2183
1537
|
}
|
|
2184
|
-
/**
|
|
2185
|
-
* Stops the specified server node.
|
|
2186
|
-
*
|
|
2187
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2188
|
-
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2189
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2190
|
-
*/
|
|
2191
1538
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
2192
1539
|
const { withTimeout } = await import('./utils/wait.js');
|
|
2193
1540
|
if (!matterServerNode)
|
|
@@ -2201,25 +1548,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2201
1548
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2202
1549
|
}
|
|
2203
1550
|
}
|
|
2204
|
-
/**
|
|
2205
|
-
* Creates an aggregator node with the specified storage context.
|
|
2206
|
-
*
|
|
2207
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2208
|
-
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2209
|
-
*/
|
|
2210
1551
|
async createAggregatorNode(storageContext) {
|
|
2211
1552
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2212
1553
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2213
1554
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2214
1555
|
return aggregatorNode;
|
|
2215
1556
|
}
|
|
2216
|
-
/**
|
|
2217
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2218
|
-
*
|
|
2219
|
-
* @param {Plugin} plugin - The plugin to configure.
|
|
2220
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2221
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2222
|
-
*/
|
|
2223
1557
|
async createAccessoryPlugin(plugin, device) {
|
|
2224
1558
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
2225
1559
|
plugin.locked = true;
|
|
@@ -2231,12 +1565,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2231
1565
|
await plugin.serverNode.add(device);
|
|
2232
1566
|
}
|
|
2233
1567
|
}
|
|
2234
|
-
/**
|
|
2235
|
-
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2236
|
-
*
|
|
2237
|
-
* @param {Plugin} plugin - The plugin to configure.
|
|
2238
|
-
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2239
|
-
*/
|
|
2240
1568
|
async createDynamicPlugin(plugin) {
|
|
2241
1569
|
if (!plugin.locked) {
|
|
2242
1570
|
plugin.locked = true;
|
|
@@ -2249,13 +1577,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2249
1577
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
2250
1578
|
}
|
|
2251
1579
|
}
|
|
2252
|
-
/**
|
|
2253
|
-
* Creates and configures the server node for a single not bridged device.
|
|
2254
|
-
*
|
|
2255
|
-
* @param {Plugin} plugin - The plugin to configure.
|
|
2256
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2257
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2258
|
-
*/
|
|
2259
1580
|
async createDeviceServerNode(plugin, device) {
|
|
2260
1581
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
2261
1582
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -2266,16 +1587,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2266
1587
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
2267
1588
|
}
|
|
2268
1589
|
}
|
|
2269
|
-
/**
|
|
2270
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2271
|
-
*
|
|
2272
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2273
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2274
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2275
|
-
*/
|
|
2276
1590
|
async addBridgedEndpoint(pluginName, device) {
|
|
2277
1591
|
const { waiter } = await import('./utils/wait.js');
|
|
2278
|
-
// Check if the plugin is registered
|
|
2279
1592
|
const plugin = this.plugins.get(pluginName);
|
|
2280
1593
|
if (!plugin) {
|
|
2281
1594
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -2295,7 +1608,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2295
1608
|
}
|
|
2296
1609
|
else if (this.bridgeMode === 'bridge') {
|
|
2297
1610
|
if (device.mode === 'matter') {
|
|
2298
|
-
// Register and add the device to the matterbridge server node
|
|
2299
1611
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
2300
1612
|
if (!this.serverNode) {
|
|
2301
1613
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -2312,7 +1624,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2312
1624
|
}
|
|
2313
1625
|
}
|
|
2314
1626
|
else {
|
|
2315
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2316
1627
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2317
1628
|
if (!this.aggregatorNode) {
|
|
2318
1629
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2330,7 +1641,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2330
1641
|
}
|
|
2331
1642
|
}
|
|
2332
1643
|
else if (this.bridgeMode === 'childbridge') {
|
|
2333
|
-
// Register and add the device to the plugin server node
|
|
2334
1644
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2335
1645
|
try {
|
|
2336
1646
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2354,12 +1664,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2354
1664
|
return;
|
|
2355
1665
|
}
|
|
2356
1666
|
}
|
|
2357
|
-
// Register and add the device to the plugin aggregator node
|
|
2358
1667
|
if (plugin.type === 'DynamicPlatform') {
|
|
2359
1668
|
try {
|
|
2360
1669
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2361
1670
|
await this.createDynamicPlugin(plugin);
|
|
2362
|
-
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
2363
1671
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2364
1672
|
if (!plugin.aggregatorNode) {
|
|
2365
1673
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2380,28 +1688,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2380
1688
|
}
|
|
2381
1689
|
if (plugin.registeredDevices !== undefined)
|
|
2382
1690
|
plugin.registeredDevices++;
|
|
2383
|
-
// Add the device to the DeviceManager
|
|
2384
1691
|
this.devices.set(device);
|
|
2385
|
-
// Subscribe to the attributes changed event
|
|
2386
1692
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2387
1693
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
2388
1694
|
}
|
|
2389
|
-
/**
|
|
2390
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2391
|
-
*
|
|
2392
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2393
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2394
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2395
|
-
*/
|
|
2396
1695
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2397
1696
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2398
|
-
// Check if the plugin is registered
|
|
2399
1697
|
const plugin = this.plugins.get(pluginName);
|
|
2400
1698
|
if (!plugin) {
|
|
2401
1699
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2402
1700
|
return;
|
|
2403
1701
|
}
|
|
2404
|
-
// Unregister and remove the device from the matterbridge aggregator node
|
|
2405
1702
|
if (this.bridgeMode === 'bridge') {
|
|
2406
1703
|
if (!this.aggregatorNode) {
|
|
2407
1704
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2414,10 +1711,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2414
1711
|
}
|
|
2415
1712
|
else if (this.bridgeMode === 'childbridge') {
|
|
2416
1713
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2417
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2418
1714
|
}
|
|
2419
1715
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2420
|
-
// Unregister and remove the device from the plugin aggregator node
|
|
2421
1716
|
if (!plugin.aggregatorNode) {
|
|
2422
1717
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
2423
1718
|
return;
|
|
@@ -2428,21 +1723,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2428
1723
|
if (plugin.registeredDevices !== undefined)
|
|
2429
1724
|
plugin.registeredDevices--;
|
|
2430
1725
|
}
|
|
2431
|
-
// Remove the device from the DeviceManager
|
|
2432
1726
|
this.devices.remove(device);
|
|
2433
1727
|
}
|
|
2434
|
-
/**
|
|
2435
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2436
|
-
*
|
|
2437
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2438
|
-
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2439
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2440
|
-
*
|
|
2441
|
-
* @remarks
|
|
2442
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2443
|
-
* It also applies a delay between each removal if specified.
|
|
2444
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2445
|
-
*/
|
|
2446
1728
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2447
1729
|
const { wait } = await import('./utils/wait.js');
|
|
2448
1730
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
@@ -2454,28 +1736,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2454
1736
|
if (delay > 0)
|
|
2455
1737
|
await wait(2000);
|
|
2456
1738
|
}
|
|
2457
|
-
/**
|
|
2458
|
-
* Registers a virtual device.
|
|
2459
|
-
* Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
|
|
2460
|
-
*
|
|
2461
|
-
* The virtual device is created as an instance of `Endpoint` with the provided device type.
|
|
2462
|
-
* When the virtual device is turned on, the provided callback function is executed.
|
|
2463
|
-
* The onOff state of the virtual device always reverts to false when the device is turned on.
|
|
2464
|
-
*
|
|
2465
|
-
* @param { string } pluginName - The name of the plugin to register the virtual device under.
|
|
2466
|
-
* @param { string } name - The name of the virtual device.
|
|
2467
|
-
* @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
|
|
2468
|
-
* @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
|
|
2469
|
-
*
|
|
2470
|
-
* @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
|
|
2471
|
-
*
|
|
2472
|
-
* @remarks
|
|
2473
|
-
* The virtual devices don't show up in the device list of the frontend.
|
|
2474
|
-
* Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
|
|
2475
|
-
*/
|
|
2476
1739
|
async addVirtualEndpoint(pluginName, name, type, callback) {
|
|
2477
1740
|
this.log.debug(`Adding virtual endpoint ${plg}${pluginName}${db}:${dev}${name}${db}...`);
|
|
2478
|
-
// Check if the plugin is registered
|
|
2479
1741
|
const plugin = this.plugins.get(pluginName);
|
|
2480
1742
|
if (!plugin) {
|
|
2481
1743
|
this.log.error(`Error adding virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
@@ -2502,24 +1764,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2502
1764
|
this.log.error(`Virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er} not created. Virtual endpoints are only supported in bridge mode and childbridge mode with a DynamicPlatform.`);
|
|
2503
1765
|
return false;
|
|
2504
1766
|
}
|
|
2505
|
-
/**
|
|
2506
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2507
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2508
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2509
|
-
*
|
|
2510
|
-
* @param {Plugin} plugin - The plugin associated with the device.
|
|
2511
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2512
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2513
|
-
*/
|
|
2514
1767
|
async subscribeAttributeChanged(plugin, device) {
|
|
2515
1768
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
2516
1769
|
return;
|
|
2517
1770
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2518
|
-
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
2519
1771
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2520
1772
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
2521
1773
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2522
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2523
1774
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
2524
1775
|
});
|
|
2525
1776
|
}
|
|
@@ -2569,7 +1820,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2569
1820
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
2570
1821
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
2571
1822
|
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}`);
|
|
2572
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2573
1823
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
2574
1824
|
});
|
|
2575
1825
|
}
|
|
@@ -2578,19 +1828,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2578
1828
|
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...`);
|
|
2579
1829
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
2580
1830
|
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}`);
|
|
2581
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2582
1831
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
2583
1832
|
});
|
|
2584
1833
|
}
|
|
2585
1834
|
}
|
|
2586
1835
|
}
|
|
2587
1836
|
}
|
|
2588
|
-
/**
|
|
2589
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2590
|
-
*
|
|
2591
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2592
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2593
|
-
*/
|
|
2594
1837
|
sanitizeFabricInformations(fabricInfo) {
|
|
2595
1838
|
return fabricInfo.map((info) => {
|
|
2596
1839
|
return {
|
|
@@ -2604,12 +1847,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2604
1847
|
};
|
|
2605
1848
|
});
|
|
2606
1849
|
}
|
|
2607
|
-
/**
|
|
2608
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2609
|
-
*
|
|
2610
|
-
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2611
|
-
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2612
|
-
*/
|
|
2613
1850
|
sanitizeSessionInformation(sessions) {
|
|
2614
1851
|
return sessions
|
|
2615
1852
|
.filter((session) => session.isPeerActive)
|
|
@@ -2636,21 +1873,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2636
1873
|
};
|
|
2637
1874
|
});
|
|
2638
1875
|
}
|
|
2639
|
-
/**
|
|
2640
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2641
|
-
*
|
|
2642
|
-
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2643
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2644
|
-
*/
|
|
2645
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2646
1876
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2647
|
-
/*
|
|
2648
|
-
for (const child of aggregatorNode.parts) {
|
|
2649
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2650
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2651
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2652
|
-
}
|
|
2653
|
-
*/
|
|
2654
1877
|
}
|
|
2655
1878
|
getVendorIdName = (vendorId) => {
|
|
2656
1879
|
if (!vendorId)
|
|
@@ -2690,11 +1913,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2690
1913
|
case 0x1488:
|
|
2691
1914
|
vendorName = '(ShortcutLabsFlic)';
|
|
2692
1915
|
break;
|
|
2693
|
-
case 65521:
|
|
1916
|
+
case 65521:
|
|
2694
1917
|
vendorName = '(MatterTest)';
|
|
2695
1918
|
break;
|
|
2696
1919
|
}
|
|
2697
1920
|
return vendorName;
|
|
2698
1921
|
};
|
|
2699
1922
|
}
|
|
2700
|
-
//# sourceMappingURL=matterbridge.js.map
|