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