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