matterbridge 3.5.0-dev-20260119-f9ea00e → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -117
- package/dist/broadcastServer.d.ts +115 -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 +43 -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 +24 -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 +36 -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 +42 -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 +1 -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 +108 -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 +75 -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 +43 -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 +55 -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 +55 -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 +57 -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 +1 -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 +41 -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 +43 -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 +58 -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 +64 -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 +77 -1
- 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 +82 -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 +100 -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 +83 -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 +36 -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 +79 -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 +21 -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 +74 -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 +171 -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 +99 -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 +23 -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 +23 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -24
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +187 -4
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +371 -139
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +49 -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 +53 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +60 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +187 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +498 -37
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +57 -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 +43 -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 +23 -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 +1 -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 +255 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +372 -14
- package/dist/jestutils/jestHelpers.js.map +1 -0
- package/dist/logger/export.d.ts +1 -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 +1 -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 +1 -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 +1 -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 +1 -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 +1 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +1 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterNode.d.ts +258 -0
- package/dist/matterNode.d.ts.map +1 -0
- package/dist/matterNode.js +359 -8
- package/dist/matterNode.js.map +1 -0
- package/dist/matterbridge.d.ts +362 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +842 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +36 -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 +24 -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 +649 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +673 -6
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +36 -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 +1332 -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 +425 -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 +70 -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 +425 -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 +46 -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 +305 -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 +157 -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 +1 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +93 -1
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +77 -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 +60 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +60 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -0
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +32 -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 +38 -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 +31 -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 +53 -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 +42 -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 +1 -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 +49 -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 +85 -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 +63 -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 +93 -0
- package/dist/utils/isValid.d.ts.map +1 -0
- package/dist/utils/isValid.js +93 -0
- package/dist/utils/isValid.js.map +1 -0
- package/dist/utils/network.d.ts +116 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +126 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +32 -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 +56 -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 +51 -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 +24 -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 +25 -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 +61 -0
- package/dist/workers.d.ts.map +1 -0
- package/dist/workers.js +68 -4
- package/dist/workers.js.map +1 -0
- package/frontend/build/assets/index.js +4 -4
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +5 -35
- package/package.json +7 -7
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, { unlinkSync } 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 { 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,7 +948,20 @@ 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
|
+
*
|
|
954
|
+
* @remarks
|
|
955
|
+
* This method retrieves and logs various details about the host system, including:
|
|
956
|
+
* - IP address information (IPv4, IPv6, MAC address)
|
|
957
|
+
* - Node.js version
|
|
958
|
+
* - Hostname and user information
|
|
959
|
+
* - Operating system details (type, release, platform, architecture)
|
|
960
|
+
* - Memory usage statistics
|
|
961
|
+
* - Uptime information for both the system and the process
|
|
962
|
+
*/
|
|
748
963
|
async logNodeAndSystemInfo() {
|
|
964
|
+
// IP address information
|
|
749
965
|
const excludedInterfaceNamePattern = /(tailscale|wireguard|openvpn|zerotier|hamachi|\bwg\d+\b|\btun\d+\b|\btap\d+\b|\butun\d+\b|docker|podman|\bveth[a-z0-9]*\b|\bbr-[a-z0-9]+\b|cni|kube|flannel|calico|virbr\d*\b|vmware|vmnet\d*\b|virtualbox|vboxnet\d*\b|teredo|isatap)/i;
|
|
750
966
|
const networkInterfaces = os.networkInterfaces();
|
|
751
967
|
this.systemInformation.interfaceName = '';
|
|
@@ -779,16 +995,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
779
995
|
break;
|
|
780
996
|
}
|
|
781
997
|
}
|
|
998
|
+
// Node information
|
|
782
999
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
783
1000
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
784
1001
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
785
1002
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1003
|
+
// Host system information
|
|
786
1004
|
this.systemInformation.hostname = os.hostname();
|
|
787
1005
|
this.systemInformation.user = os.userInfo().username;
|
|
788
|
-
this.systemInformation.osType = os.type();
|
|
789
|
-
this.systemInformation.osRelease = os.release();
|
|
790
|
-
this.systemInformation.osPlatform = os.platform();
|
|
791
|
-
this.systemInformation.osArch = os.arch();
|
|
1006
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1007
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1008
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1009
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
792
1010
|
this.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
793
1011
|
this.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
794
1012
|
this.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -798,6 +1016,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
798
1016
|
this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
799
1017
|
this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
800
1018
|
this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
1019
|
+
// Log the system information
|
|
801
1020
|
this.log.debug('Host System Information:');
|
|
802
1021
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
803
1022
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -817,14 +1036,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
817
1036
|
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
818
1037
|
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
819
1038
|
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
1039
|
+
// Log directories
|
|
820
1040
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
821
1041
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
822
1042
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
823
1043
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
824
1044
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1045
|
+
// Global node_modules directory
|
|
825
1046
|
if (this.nodeContext)
|
|
826
1047
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
827
1048
|
if (this.globalModulesDirectory === '') {
|
|
1049
|
+
// First run of Matterbridge so the node storage is empty
|
|
828
1050
|
this.log.debug(`Getting global node_modules directory...`);
|
|
829
1051
|
try {
|
|
830
1052
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -837,29 +1059,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
837
1059
|
}
|
|
838
1060
|
}
|
|
839
1061
|
else {
|
|
1062
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
840
1063
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
841
1064
|
const { createESMWorker } = await import('./workers.js');
|
|
842
1065
|
createESMWorker('NpmGlobalPrefix', path.join(this.rootDirectory, 'dist/workerGlobalPrefix.js'));
|
|
843
1066
|
}
|
|
1067
|
+
// Matterbridge version
|
|
844
1068
|
this.log.debug(`Reading matterbridge package.json...`);
|
|
845
1069
|
const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
846
1070
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
847
1071
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1072
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
848
1073
|
if (this.nodeContext)
|
|
849
1074
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
850
1075
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1076
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
851
1077
|
if (this.nodeContext)
|
|
852
1078
|
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
853
1079
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1080
|
+
// Frontend version
|
|
854
1081
|
this.log.debug(`Reading frontend package.json...`);
|
|
855
1082
|
const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
856
1083
|
this.frontendVersion = frontendPackageJson.version;
|
|
857
1084
|
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1085
|
+
// Current working directory
|
|
858
1086
|
const currentDir = process.cwd();
|
|
859
1087
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1088
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
860
1089
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
861
1090
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
862
1091
|
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
1094
|
+
*
|
|
1095
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
1096
|
+
* @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
|
|
1097
|
+
*/
|
|
863
1098
|
async setLogLevel(logLevel) {
|
|
864
1099
|
this.logLevel = logLevel;
|
|
865
1100
|
this.log.logLevel = logLevel;
|
|
@@ -873,58 +1108,87 @@ export class Matterbridge extends EventEmitter {
|
|
|
873
1108
|
continue;
|
|
874
1109
|
if (plugin.platform.config.debug === true)
|
|
875
1110
|
pluginDebug = true;
|
|
876
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
|
|
877
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1111
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
|
|
1112
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
|
|
1113
|
+
}
|
|
1114
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1115
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1116
|
+
if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1117
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1118
|
+
if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG || pluginDebug)
|
|
1119
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
884
1120
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
885
1121
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
886
1122
|
return logLevel;
|
|
887
1123
|
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Get the current logger logLevel.
|
|
1126
|
+
*
|
|
1127
|
+
* @returns {LogLevel} The current logger logLevel.
|
|
1128
|
+
*/
|
|
888
1129
|
getLogLevel() {
|
|
889
1130
|
return this.log.logLevel;
|
|
890
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1134
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1135
|
+
*
|
|
1136
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1137
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1138
|
+
*/
|
|
891
1139
|
createDestinationMatterLogger(fileLogger) {
|
|
892
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1140
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
893
1141
|
if (fileLogger) {
|
|
894
1142
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
895
1143
|
}
|
|
896
1144
|
return (text, message) => {
|
|
1145
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
897
1146
|
const logger = text.slice(44, 44 + 20).trim();
|
|
898
1147
|
const msg = text.slice(65);
|
|
899
1148
|
this.matterLog.logName = logger;
|
|
900
1149
|
switch (message.level) {
|
|
901
1150
|
case MatterLogLevel.DEBUG:
|
|
902
|
-
this.matterLog.log("debug"
|
|
1151
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
903
1152
|
break;
|
|
904
1153
|
case MatterLogLevel.INFO:
|
|
905
|
-
this.matterLog.log("info"
|
|
1154
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
906
1155
|
break;
|
|
907
1156
|
case MatterLogLevel.NOTICE:
|
|
908
|
-
this.matterLog.log("notice"
|
|
1157
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
909
1158
|
break;
|
|
910
1159
|
case MatterLogLevel.WARN:
|
|
911
|
-
this.matterLog.log("warn"
|
|
1160
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
912
1161
|
break;
|
|
913
1162
|
case MatterLogLevel.ERROR:
|
|
914
|
-
this.matterLog.log("error"
|
|
1163
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
915
1164
|
break;
|
|
916
1165
|
case MatterLogLevel.FATAL:
|
|
917
|
-
this.matterLog.log("fatal"
|
|
1166
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
918
1167
|
break;
|
|
919
1168
|
}
|
|
920
1169
|
};
|
|
921
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1173
|
+
*
|
|
1174
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1175
|
+
*/
|
|
922
1176
|
async restartProcess() {
|
|
923
1177
|
await this.cleanup('restarting...', true);
|
|
924
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Shut down the process (/api/shutdown).
|
|
1181
|
+
*
|
|
1182
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1183
|
+
*/
|
|
925
1184
|
async shutdownProcess() {
|
|
926
1185
|
await this.cleanup('shutting down...', false);
|
|
927
1186
|
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1189
|
+
*
|
|
1190
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1191
|
+
*/
|
|
928
1192
|
async updateProcess() {
|
|
929
1193
|
this.log.info('Updating matterbridge...');
|
|
930
1194
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -937,6 +1201,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
937
1201
|
this.frontend.wssSendRestartRequired();
|
|
938
1202
|
await this.cleanup('updating...', false);
|
|
939
1203
|
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1206
|
+
*
|
|
1207
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1208
|
+
*
|
|
1209
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1210
|
+
*/
|
|
940
1211
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
941
1212
|
const { wait } = await import('./utils/wait.js');
|
|
942
1213
|
this.log.info('Unregistering all devices and shutting down...');
|
|
@@ -949,46 +1220,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
949
1220
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
950
1221
|
}
|
|
951
1222
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
952
|
-
await wait(timeout);
|
|
1223
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
953
1224
|
this.log.debug('Cleaning up and shutting down...');
|
|
954
1225
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
955
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1229
|
+
*
|
|
1230
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1231
|
+
*/
|
|
956
1232
|
async shutdownProcessAndReset() {
|
|
957
1233
|
await this.cleanup('shutting down with reset...', false);
|
|
958
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1237
|
+
*
|
|
1238
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1239
|
+
*/
|
|
959
1240
|
async shutdownProcessAndFactoryReset() {
|
|
960
1241
|
await this.cleanup('shutting down with factory reset...', false);
|
|
961
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Cleans up the Matterbridge instance.
|
|
1245
|
+
*
|
|
1246
|
+
* @param {string} message - The cleanup message.
|
|
1247
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1248
|
+
* @param {number} [pause] - The pause in ms to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1249
|
+
*
|
|
1250
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1251
|
+
*/
|
|
962
1252
|
async cleanup(message, restart = false, pause = 1000) {
|
|
963
1253
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
964
1254
|
this.emit('cleanup_started');
|
|
965
1255
|
this.hasCleanupStarted = true;
|
|
966
1256
|
this.log.info(message);
|
|
1257
|
+
// Clear the start matter interval
|
|
967
1258
|
if (this.startMatterInterval) {
|
|
968
1259
|
clearInterval(this.startMatterInterval);
|
|
969
1260
|
this.startMatterInterval = undefined;
|
|
970
1261
|
this.log.debug('Start matter interval cleared');
|
|
971
1262
|
}
|
|
1263
|
+
// Clear the check update timeout
|
|
972
1264
|
if (this.checkUpdateTimeout) {
|
|
973
1265
|
clearTimeout(this.checkUpdateTimeout);
|
|
974
1266
|
this.checkUpdateTimeout = undefined;
|
|
975
1267
|
this.log.debug('Check update timeout cleared');
|
|
976
1268
|
}
|
|
1269
|
+
// Clear the check update interval
|
|
977
1270
|
if (this.checkUpdateInterval) {
|
|
978
1271
|
clearInterval(this.checkUpdateInterval);
|
|
979
1272
|
this.checkUpdateInterval = undefined;
|
|
980
1273
|
this.log.debug('Check update interval cleared');
|
|
981
1274
|
}
|
|
1275
|
+
// Clear the configure timeout
|
|
982
1276
|
if (this.configureTimeout) {
|
|
983
1277
|
clearTimeout(this.configureTimeout);
|
|
984
1278
|
this.configureTimeout = undefined;
|
|
985
1279
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
986
1280
|
}
|
|
1281
|
+
// Clear the reachability timeout
|
|
987
1282
|
if (this.reachabilityTimeout) {
|
|
988
1283
|
clearTimeout(this.reachabilityTimeout);
|
|
989
1284
|
this.reachabilityTimeout = undefined;
|
|
990
1285
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
991
1286
|
}
|
|
1287
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
992
1288
|
for (const plugin of this.plugins) {
|
|
993
1289
|
if (!plugin.enabled || plugin.error)
|
|
994
1290
|
continue;
|
|
@@ -999,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
999
1295
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1000
1296
|
}
|
|
1001
1297
|
}
|
|
1298
|
+
// Stop matter server nodes
|
|
1002
1299
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1003
1300
|
if (pause > 0) {
|
|
1004
1301
|
const { wait } = await import('./utils/wait.js');
|
|
@@ -1026,6 +1323,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1026
1323
|
}
|
|
1027
1324
|
}
|
|
1028
1325
|
this.log.notice('Stopped matter server nodes');
|
|
1326
|
+
// Matter commisioning reset
|
|
1029
1327
|
if (message === 'shutting down with reset...') {
|
|
1030
1328
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1031
1329
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1035,6 +1333,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1035
1333
|
await this.matterbridgeContext?.clearAll();
|
|
1036
1334
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1037
1335
|
}
|
|
1336
|
+
// Unregister all devices
|
|
1038
1337
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1039
1338
|
if (this.bridgeMode === 'bridge') {
|
|
1040
1339
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1052,7 +1351,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1052
1351
|
}
|
|
1053
1352
|
this.log.info('Matter storage reset done!');
|
|
1054
1353
|
}
|
|
1354
|
+
// Stop matter storage
|
|
1055
1355
|
await this.stopMatterStorage();
|
|
1356
|
+
/**
|
|
1357
|
+
* Unlink a file safely, ignoring errors.
|
|
1358
|
+
*
|
|
1359
|
+
* @param {string} path - The path to the file to unlink.
|
|
1360
|
+
* @param {AnsiLogger} log - The logger to use for logging.
|
|
1361
|
+
*/
|
|
1056
1362
|
function unlinkSafe(path, log) {
|
|
1057
1363
|
try {
|
|
1058
1364
|
log.debug(`Removing ${path}...`);
|
|
@@ -1060,12 +1366,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1060
1366
|
log.debug(`Removed ${path}`);
|
|
1061
1367
|
}
|
|
1062
1368
|
catch {
|
|
1369
|
+
// Ignore errors if the file does not exist
|
|
1063
1370
|
}
|
|
1064
1371
|
}
|
|
1372
|
+
// Remove the resumption records for Matterbridge (bridge mode)
|
|
1065
1373
|
this.log.debug(`Cleaning matter storage context for ${GREEN}Matterbridge${db}...`);
|
|
1066
1374
|
unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, 'Matterbridge', 'sessions.resumptionRecords'), this.log);
|
|
1067
1375
|
unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, 'Matterbridge', 'root.subscriptions.subscriptions'), this.log);
|
|
1068
1376
|
for (const plugin of this.plugins.array()) {
|
|
1377
|
+
// Remove the resumption records for the plugins (childbridge mode)
|
|
1069
1378
|
this.log.debug(`Cleaning matter storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
1070
1379
|
unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, plugin.name, 'sessions.resumptionRecords'), this.log);
|
|
1071
1380
|
unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, plugin.name, 'root.subscriptions.subscriptions'), this.log);
|
|
@@ -1073,19 +1382,38 @@ export class Matterbridge extends EventEmitter {
|
|
|
1073
1382
|
for (const device of this.devices.array().filter((d) => d.mode === 'server')) {
|
|
1074
1383
|
if (!device.deviceName)
|
|
1075
1384
|
continue;
|
|
1385
|
+
// Remove the resumption records for the server mode devices
|
|
1076
1386
|
this.log.debug(`Cleaning matter storage context for server node device ${dev}${device.deviceName}${db}...`);
|
|
1077
1387
|
unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, device.deviceName.replace(/[ .]/g, ''), 'sessions.resumptionRecords'), this.log);
|
|
1078
1388
|
unlinkSafe(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME, device.deviceName.replace(/[ .]/g, ''), 'root.subscriptions.subscriptions'), this.log);
|
|
1079
1389
|
}
|
|
1390
|
+
// Stop the frontend
|
|
1080
1391
|
await this.frontend.stop();
|
|
1081
1392
|
this.frontend.destroy();
|
|
1393
|
+
// Close PluginManager and DeviceManager
|
|
1082
1394
|
this.plugins.destroy();
|
|
1083
1395
|
this.devices.destroy();
|
|
1396
|
+
// Stop thread messaging server
|
|
1084
1397
|
this.server.close();
|
|
1398
|
+
// Close the matterbridge node storage and context
|
|
1085
1399
|
if (this.nodeStorage && this.nodeContext) {
|
|
1400
|
+
/*
|
|
1401
|
+
TODO: Implement serialization of registered devices
|
|
1402
|
+
this.log.info('Saving registered devices...');
|
|
1403
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1404
|
+
this.devices.forEach(async (device) => {
|
|
1405
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1406
|
+
this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1407
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1408
|
+
});
|
|
1409
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1410
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1411
|
+
*/
|
|
1412
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1086
1413
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1087
1414
|
await this.nodeContext.close();
|
|
1088
1415
|
this.nodeContext = undefined;
|
|
1416
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1089
1417
|
for (const plugin of this.plugins) {
|
|
1090
1418
|
if (plugin.nodeContext) {
|
|
1091
1419
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1102,8 +1430,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1102
1430
|
}
|
|
1103
1431
|
this.plugins.clear();
|
|
1104
1432
|
this.devices.clear();
|
|
1433
|
+
// Factory reset
|
|
1105
1434
|
if (message === 'shutting down with factory reset...') {
|
|
1106
1435
|
try {
|
|
1436
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1107
1437
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1108
1438
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1109
1439
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1112,11 +1442,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1112
1442
|
await fs.promises.rm(backup, { recursive: true });
|
|
1113
1443
|
}
|
|
1114
1444
|
catch (error) {
|
|
1445
|
+
// istanbul ignore next if
|
|
1115
1446
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1116
1447
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1117
1448
|
}
|
|
1118
1449
|
}
|
|
1119
1450
|
try {
|
|
1451
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1120
1452
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1121
1453
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1122
1454
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1125,18 +1457,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1125
1457
|
await fs.promises.rm(backup, { recursive: true });
|
|
1126
1458
|
}
|
|
1127
1459
|
catch (error) {
|
|
1460
|
+
// istanbul ignore next if
|
|
1128
1461
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1129
1462
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1130
1463
|
}
|
|
1131
1464
|
}
|
|
1132
1465
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1133
1466
|
}
|
|
1467
|
+
// Deregisters the process handlers
|
|
1134
1468
|
this.deregisterProcessHandlers();
|
|
1135
1469
|
if (restart) {
|
|
1136
1470
|
if (message === 'updating...') {
|
|
1137
1471
|
this.log.info('Cleanup completed. Updating...');
|
|
1138
1472
|
Matterbridge.instance = undefined;
|
|
1139
|
-
this.emit('update');
|
|
1473
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1140
1474
|
}
|
|
1141
1475
|
else if (message === 'restarting...') {
|
|
1142
1476
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1165,7 +1499,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1165
1499
|
this.log.debug('Cleanup already started...');
|
|
1166
1500
|
}
|
|
1167
1501
|
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Starts the Matterbridge in bridge mode.
|
|
1504
|
+
*
|
|
1505
|
+
* @private
|
|
1506
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1507
|
+
*/
|
|
1168
1508
|
async startBridge() {
|
|
1509
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1169
1510
|
if (!this.matterStorageManager)
|
|
1170
1511
|
throw new Error('No storage manager initialized');
|
|
1171
1512
|
if (!this.matterbridgeContext)
|
|
@@ -1179,6 +1520,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1179
1520
|
this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
|
|
1180
1521
|
let failCount = 0;
|
|
1181
1522
|
this.startMatterInterval = setInterval(async () => {
|
|
1523
|
+
// istanbul ignore if cause is just a logging statement
|
|
1182
1524
|
if (failCount && failCount % 10 === 0) {
|
|
1183
1525
|
this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
|
|
1184
1526
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
@@ -1212,13 +1554,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1212
1554
|
clearInterval(this.startMatterInterval);
|
|
1213
1555
|
this.startMatterInterval = undefined;
|
|
1214
1556
|
this.log.debug('Cleared startMatterInterval interval in bridge mode');
|
|
1215
|
-
|
|
1557
|
+
// Start the Matter server node
|
|
1558
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1559
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1216
1560
|
for (const device of this.devices.array()) {
|
|
1217
1561
|
if (device.mode === 'server' && device.serverNode) {
|
|
1218
1562
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1219
|
-
this.startServerNode(device.serverNode);
|
|
1563
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1220
1564
|
}
|
|
1221
1565
|
}
|
|
1566
|
+
// Configure the plugins
|
|
1222
1567
|
this.configureTimeout = setTimeout(async () => {
|
|
1223
1568
|
for (const plugin of this.plugins.array()) {
|
|
1224
1569
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1236,11 +1581,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1236
1581
|
}
|
|
1237
1582
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1238
1583
|
}, 30 * 1000).unref();
|
|
1584
|
+
// Setting reachability to true
|
|
1239
1585
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1240
1586
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1241
1587
|
if (this.aggregatorNode)
|
|
1242
1588
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1243
1589
|
}, 60 * 1000).unref();
|
|
1590
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1244
1591
|
this.emit('bridge_started');
|
|
1245
1592
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1246
1593
|
this.frontend.wssSendRefreshRequired('settings');
|
|
@@ -1248,22 +1595,33 @@ export class Matterbridge extends EventEmitter {
|
|
|
1248
1595
|
this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
|
|
1249
1596
|
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1250
1597
|
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1600
|
+
*
|
|
1601
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1602
|
+
*
|
|
1603
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1604
|
+
*/
|
|
1251
1605
|
async startChildbridge(delay = 1000) {
|
|
1252
1606
|
if (!this.matterStorageManager)
|
|
1253
1607
|
throw new Error('No storage manager initialized');
|
|
1254
1608
|
const { wait } = await import('./utils/wait.js');
|
|
1609
|
+
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1255
1610
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1256
1611
|
await this.startPlugins(true, false);
|
|
1612
|
+
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1257
1613
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1258
1614
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1259
1615
|
if (plugin.type === 'DynamicPlatform')
|
|
1260
1616
|
await this.createDynamicPlugin(plugin);
|
|
1261
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1617
|
+
this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
|
|
1262
1618
|
}
|
|
1619
|
+
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1263
1620
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1264
1621
|
this.frontend.wssSendSnackbarMessage(`The bridge is starting...`, 0, 'info');
|
|
1265
1622
|
let failCount = 0;
|
|
1266
1623
|
this.startMatterInterval = setInterval(async () => {
|
|
1624
|
+
// istanbul ignore if cause is just a logging statement
|
|
1267
1625
|
if (failCount && failCount % 10 === 0) {
|
|
1268
1626
|
this.frontend.wssSendSnackbarMessage(`The bridge is still starting...`, 10, 'info');
|
|
1269
1627
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
@@ -1301,8 +1659,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1301
1659
|
clearInterval(this.startMatterInterval);
|
|
1302
1660
|
this.startMatterInterval = undefined;
|
|
1303
1661
|
if (delay > 0)
|
|
1304
|
-
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
|
|
1662
|
+
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1305
1663
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1664
|
+
// Configure the plugins
|
|
1306
1665
|
this.configureTimeout = setTimeout(async () => {
|
|
1307
1666
|
for (const plugin of this.plugins.array()) {
|
|
1308
1667
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1327,6 +1686,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1327
1686
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1328
1687
|
continue;
|
|
1329
1688
|
}
|
|
1689
|
+
// istanbul ignore next if cause is just a safety check
|
|
1330
1690
|
if (!plugin.serverNode) {
|
|
1331
1691
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1332
1692
|
continue;
|
|
@@ -1339,19 +1699,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1339
1699
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1340
1700
|
continue;
|
|
1341
1701
|
}
|
|
1342
|
-
|
|
1702
|
+
// Start the Matter server node
|
|
1703
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1704
|
+
// Setting reachability to true
|
|
1343
1705
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1344
1706
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1345
1707
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1346
1708
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1347
1709
|
}, 60 * 1000).unref();
|
|
1348
1710
|
}
|
|
1711
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1349
1712
|
for (const device of this.devices.array()) {
|
|
1350
1713
|
if (device.mode === 'server' && device.serverNode) {
|
|
1351
1714
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1352
|
-
this.startServerNode(device.serverNode);
|
|
1715
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1353
1716
|
}
|
|
1354
1717
|
}
|
|
1718
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1355
1719
|
this.emit('childbridge_started');
|
|
1356
1720
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1357
1721
|
this.frontend.wssSendRefreshRequired('settings');
|
|
@@ -1359,9 +1723,229 @@ export class Matterbridge extends EventEmitter {
|
|
|
1359
1723
|
this.frontend.wssSendCloseSnackbarMessage(`The bridge is starting...`);
|
|
1360
1724
|
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1361
1725
|
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Starts the Matterbridge controller.
|
|
1728
|
+
*
|
|
1729
|
+
* @private
|
|
1730
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1731
|
+
*/
|
|
1362
1732
|
async startController() {
|
|
1733
|
+
/*
|
|
1734
|
+
if (!this.matterStorageManager) {
|
|
1735
|
+
this.log.error('No storage manager initialized');
|
|
1736
|
+
await this.cleanup('No storage manager initialized');
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1740
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1741
|
+
if (!this.controllerContext) {
|
|
1742
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1743
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1748
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1749
|
+
this.log.info('Creating matter commissioning controller');
|
|
1750
|
+
this.commissioningController = new CommissioningController({
|
|
1751
|
+
autoConnect: false,
|
|
1752
|
+
});
|
|
1753
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1754
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1755
|
+
|
|
1756
|
+
this.log.info('Starting matter server');
|
|
1757
|
+
await this.matterServer.start();
|
|
1758
|
+
this.log.info('Matter server started');
|
|
1759
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1760
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1761
|
+
regulatoryCountryCode: 'XX',
|
|
1762
|
+
};
|
|
1763
|
+
const commissioningController = new CommissioningController({
|
|
1764
|
+
environment: {
|
|
1765
|
+
environment,
|
|
1766
|
+
id: uniqueId,
|
|
1767
|
+
},
|
|
1768
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1769
|
+
adminFabricLabel,
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
if (hasParameter('pairingcode')) {
|
|
1773
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1774
|
+
const pairingCode = getParameter('pairingcode');
|
|
1775
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1776
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1777
|
+
|
|
1778
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1779
|
+
if (pairingCode !== undefined) {
|
|
1780
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1781
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1782
|
+
longDiscriminator = undefined;
|
|
1783
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1784
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1785
|
+
} else {
|
|
1786
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1787
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1788
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1789
|
+
}
|
|
1790
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1791
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
const options = {
|
|
1795
|
+
commissioning: commissioningOptions,
|
|
1796
|
+
discovery: {
|
|
1797
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1798
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1799
|
+
},
|
|
1800
|
+
passcode: setupPin,
|
|
1801
|
+
} as NodeCommissioningOptions;
|
|
1802
|
+
this.log.info('Commissioning with options:', options);
|
|
1803
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1804
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1805
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1806
|
+
} // (hasParameter('pairingcode'))
|
|
1807
|
+
|
|
1808
|
+
if (hasParameter('unpairall')) {
|
|
1809
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1810
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1811
|
+
for (const nodeId of nodeIds) {
|
|
1812
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1813
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1814
|
+
}
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
if (hasParameter('discover')) {
|
|
1819
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1820
|
+
// console.log(discover);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1824
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1829
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1830
|
+
for (const nodeId of nodeIds) {
|
|
1831
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1832
|
+
|
|
1833
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1834
|
+
autoSubscribe: false,
|
|
1835
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1836
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1837
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1838
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1839
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1840
|
+
switch (info) {
|
|
1841
|
+
case NodeStateInformation.Connected:
|
|
1842
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1843
|
+
break;
|
|
1844
|
+
case NodeStateInformation.Disconnected:
|
|
1845
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1846
|
+
break;
|
|
1847
|
+
case NodeStateInformation.Reconnecting:
|
|
1848
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1849
|
+
break;
|
|
1850
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1851
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1852
|
+
break;
|
|
1853
|
+
case NodeStateInformation.StructureChanged:
|
|
1854
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1855
|
+
break;
|
|
1856
|
+
case NodeStateInformation.Decommissioned:
|
|
1857
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1858
|
+
break;
|
|
1859
|
+
default:
|
|
1860
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1861
|
+
break;
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1866
|
+
node.logStructure();
|
|
1867
|
+
|
|
1868
|
+
// Get the interaction client
|
|
1869
|
+
this.log.info('Getting the interaction client');
|
|
1870
|
+
const interactionClient = await node.getInteractionClient();
|
|
1871
|
+
let cluster;
|
|
1872
|
+
let attributes;
|
|
1873
|
+
|
|
1874
|
+
// Log BasicInformationCluster
|
|
1875
|
+
cluster = BasicInformationCluster;
|
|
1876
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1877
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1878
|
+
});
|
|
1879
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1880
|
+
attributes.forEach((attribute) => {
|
|
1881
|
+
this.log.info(
|
|
1882
|
+
`- 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}`,
|
|
1883
|
+
);
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
// Log PowerSourceCluster
|
|
1887
|
+
cluster = PowerSourceCluster;
|
|
1888
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1889
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1890
|
+
});
|
|
1891
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1892
|
+
attributes.forEach((attribute) => {
|
|
1893
|
+
this.log.info(
|
|
1894
|
+
`- 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}`,
|
|
1895
|
+
);
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
// Log ThreadNetworkDiagnostics
|
|
1899
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1900
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1901
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1902
|
+
});
|
|
1903
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1904
|
+
attributes.forEach((attribute) => {
|
|
1905
|
+
this.log.info(
|
|
1906
|
+
`- 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}`,
|
|
1907
|
+
);
|
|
1908
|
+
});
|
|
1909
|
+
|
|
1910
|
+
// Log SwitchCluster
|
|
1911
|
+
cluster = SwitchCluster;
|
|
1912
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1913
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1914
|
+
});
|
|
1915
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1916
|
+
attributes.forEach((attribute) => {
|
|
1917
|
+
this.log.info(
|
|
1918
|
+
`- 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}`,
|
|
1919
|
+
);
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1923
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1924
|
+
ignoreInitialTriggers: false,
|
|
1925
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1926
|
+
this.log.info(
|
|
1927
|
+
`***${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}`,
|
|
1928
|
+
),
|
|
1929
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1930
|
+
this.log.info(
|
|
1931
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1932
|
+
);
|
|
1933
|
+
},
|
|
1934
|
+
});
|
|
1935
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1936
|
+
}
|
|
1937
|
+
*/
|
|
1363
1938
|
}
|
|
1939
|
+
/** */
|
|
1940
|
+
/** Matter.js methods */
|
|
1941
|
+
/** */
|
|
1942
|
+
/**
|
|
1943
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1944
|
+
*
|
|
1945
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1946
|
+
*/
|
|
1364
1947
|
async startMatterStorage() {
|
|
1948
|
+
// Setup Matter storage
|
|
1365
1949
|
this.log.info(`Starting matter node storage...`);
|
|
1366
1950
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1367
1951
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1369,8 +1953,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1369
1953
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1370
1954
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1371
1955
|
this.log.info('Matter node storage started');
|
|
1956
|
+
// Backup matter storage since it is created/opened correctly
|
|
1372
1957
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1373
1958
|
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1961
|
+
*
|
|
1962
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1963
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1964
|
+
* @private
|
|
1965
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1966
|
+
*/
|
|
1374
1967
|
async backupMatterStorage(storageName, backupName) {
|
|
1375
1968
|
this.log.info('Creating matter node storage backup...');
|
|
1376
1969
|
try {
|
|
@@ -1381,6 +1974,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1381
1974
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1382
1975
|
}
|
|
1383
1976
|
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Stops the matter storage.
|
|
1979
|
+
*
|
|
1980
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1981
|
+
*/
|
|
1384
1982
|
async stopMatterStorage() {
|
|
1385
1983
|
this.log.info('Closing matter node storage...');
|
|
1386
1984
|
await this.matterStorageManager?.close();
|
|
@@ -1389,6 +1987,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1987
|
this.matterbridgeContext = undefined;
|
|
1390
1988
|
this.log.info('Matter node storage closed');
|
|
1391
1989
|
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Creates a server node storage context.
|
|
1992
|
+
*
|
|
1993
|
+
* @param {string} storeId - The storeId.
|
|
1994
|
+
* @param {string} deviceName - The name of the device.
|
|
1995
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1996
|
+
* @param {number} vendorId - The vendor ID.
|
|
1997
|
+
* @param {string} vendorName - The vendor name.
|
|
1998
|
+
* @param {number} productId - The product ID.
|
|
1999
|
+
* @param {string} productName - The product name.
|
|
2000
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
2001
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
2002
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
2003
|
+
*/
|
|
1392
2004
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1393
2005
|
const { randomBytes } = await import('node:crypto');
|
|
1394
2006
|
if (!this.matterStorageService)
|
|
@@ -1428,6 +2040,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1428
2040
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1429
2041
|
return storageContext;
|
|
1430
2042
|
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Creates a server node.
|
|
2045
|
+
*
|
|
2046
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
2047
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
2048
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
2049
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
2050
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
2051
|
+
*/
|
|
1431
2052
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1432
2053
|
const storeId = await storageContext.get('storeId');
|
|
1433
2054
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1437,25 +2058,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1437
2058
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1438
2059
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1439
2060
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2061
|
+
/**
|
|
2062
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2063
|
+
*/
|
|
1440
2064
|
const serverNode = await ServerNode.create({
|
|
2065
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1441
2066
|
id: storeId,
|
|
2067
|
+
// Environment to run the server node in
|
|
1442
2068
|
environment: this.environment,
|
|
2069
|
+
// Provide Network relevant configuration like the port
|
|
1443
2070
|
network: {
|
|
1444
2071
|
listeningAddressIpv4: this.ipv4Address,
|
|
1445
2072
|
listeningAddressIpv6: this.ipv6Address,
|
|
1446
2073
|
port,
|
|
1447
2074
|
},
|
|
2075
|
+
// Provide the certificate for the device
|
|
1448
2076
|
operationalCredentials: {
|
|
1449
2077
|
certification: this.certification,
|
|
1450
2078
|
},
|
|
2079
|
+
// Provide Commissioning relevant settings
|
|
1451
2080
|
commissioning: {
|
|
1452
2081
|
passcode,
|
|
1453
2082
|
discriminator,
|
|
1454
2083
|
},
|
|
2084
|
+
// Provide Node announcement settings
|
|
1455
2085
|
productDescription: {
|
|
1456
2086
|
name: await storageContext.get('deviceName'),
|
|
1457
2087
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1458
2088
|
},
|
|
2089
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1459
2090
|
basicInformation: {
|
|
1460
2091
|
nodeLabel: await storageContext.get('nodeLabel'),
|
|
1461
2092
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
@@ -1472,17 +2103,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1472
2103
|
reachable: true,
|
|
1473
2104
|
},
|
|
1474
2105
|
});
|
|
2106
|
+
/**
|
|
2107
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2108
|
+
* This means: It is added to the first fabric.
|
|
2109
|
+
*/
|
|
1475
2110
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1476
2111
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1477
2112
|
this.advertisingNodes.delete(storeId);
|
|
1478
2113
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1479
2114
|
});
|
|
2115
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1480
2116
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1481
2117
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1482
2118
|
this.advertisingNodes.delete(storeId);
|
|
1483
2119
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1484
2120
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1485
2121
|
});
|
|
2122
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1486
2123
|
serverNode.lifecycle.online.on(async () => {
|
|
1487
2124
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1488
2125
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1493,13 +2130,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1493
2130
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1494
2131
|
}
|
|
1495
2132
|
else {
|
|
2133
|
+
// istanbul ignore next
|
|
1496
2134
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2135
|
+
// istanbul ignore next
|
|
1497
2136
|
this.advertisingNodes.delete(storeId);
|
|
1498
2137
|
}
|
|
1499
2138
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1500
2139
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1501
2140
|
this.emit('online', storeId);
|
|
1502
2141
|
});
|
|
2142
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1503
2143
|
serverNode.lifecycle.offline.on(() => {
|
|
1504
2144
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1505
2145
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1507,11 +2147,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1507
2147
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1508
2148
|
this.emit('offline', storeId);
|
|
1509
2149
|
});
|
|
2150
|
+
/**
|
|
2151
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2152
|
+
* information is needed.
|
|
2153
|
+
*/
|
|
1510
2154
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1511
2155
|
let action = '';
|
|
1512
2156
|
switch (fabricAction) {
|
|
1513
2157
|
case 'added':
|
|
1514
|
-
this.advertisingNodes.delete(storeId);
|
|
2158
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1515
2159
|
action = 'added';
|
|
1516
2160
|
break;
|
|
1517
2161
|
case 'deleted':
|
|
@@ -1524,14 +2168,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1524
2168
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1525
2169
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1526
2170
|
});
|
|
2171
|
+
/**
|
|
2172
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2173
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2174
|
+
*/
|
|
1527
2175
|
serverNode.events.sessions.opened.on((session) => {
|
|
1528
2176
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1529
2177
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1530
2178
|
});
|
|
2179
|
+
/**
|
|
2180
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2181
|
+
*/
|
|
1531
2182
|
serverNode.events.sessions.closed.on((session) => {
|
|
1532
2183
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1533
2184
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1534
2185
|
});
|
|
2186
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1535
2187
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1536
2188
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1537
2189
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1539,6 +2191,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1539
2191
|
this.log.info(`Created server node for ${storeId}`);
|
|
1540
2192
|
return serverNode;
|
|
1541
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Gets the matter sanitized data of the specified server node.
|
|
2196
|
+
*
|
|
2197
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2198
|
+
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2199
|
+
*/
|
|
1542
2200
|
getServerNodeData(serverNode) {
|
|
1543
2201
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1544
2202
|
return {
|
|
@@ -1555,12 +2213,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1555
2213
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1556
2214
|
};
|
|
1557
2215
|
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Starts the specified server node.
|
|
2218
|
+
*
|
|
2219
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2220
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2221
|
+
*/
|
|
1558
2222
|
async startServerNode(matterServerNode) {
|
|
1559
2223
|
if (!matterServerNode)
|
|
1560
2224
|
return;
|
|
1561
2225
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1562
2226
|
await matterServerNode.start();
|
|
1563
2227
|
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Stops the specified server node.
|
|
2230
|
+
*
|
|
2231
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2232
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 10 seconds.
|
|
2233
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2234
|
+
*/
|
|
1564
2235
|
async stopServerNode(matterServerNode, timeout = 10000) {
|
|
1565
2236
|
const { withTimeout } = await import('./utils/wait.js');
|
|
1566
2237
|
if (!matterServerNode)
|
|
@@ -1574,12 +2245,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1574
2245
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1575
2246
|
}
|
|
1576
2247
|
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Creates an aggregator node with the specified storage context.
|
|
2250
|
+
*
|
|
2251
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2252
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2253
|
+
*/
|
|
1577
2254
|
async createAggregatorNode(storageContext) {
|
|
1578
2255
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1579
2256
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1580
2257
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1581
2258
|
return aggregatorNode;
|
|
1582
2259
|
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2262
|
+
*
|
|
2263
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2264
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2265
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2266
|
+
*/
|
|
1583
2267
|
async createAccessoryPlugin(plugin, device) {
|
|
1584
2268
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1585
2269
|
plugin.locked = true;
|
|
@@ -1591,6 +2275,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1591
2275
|
await plugin.serverNode.add(device);
|
|
1592
2276
|
}
|
|
1593
2277
|
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2280
|
+
*
|
|
2281
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2282
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2283
|
+
*/
|
|
1594
2284
|
async createDynamicPlugin(plugin) {
|
|
1595
2285
|
if (!plugin.locked) {
|
|
1596
2286
|
plugin.locked = true;
|
|
@@ -1603,6 +2293,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1603
2293
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1604
2294
|
}
|
|
1605
2295
|
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Creates and configures the server node for a single not bridged device.
|
|
2298
|
+
*
|
|
2299
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2300
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2301
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2302
|
+
*/
|
|
1606
2303
|
async createDeviceServerNode(plugin, device) {
|
|
1607
2304
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1608
2305
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1613,8 +2310,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1613
2310
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1614
2311
|
}
|
|
1615
2312
|
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2315
|
+
*
|
|
2316
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2317
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2318
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2319
|
+
*/
|
|
1616
2320
|
async addBridgedEndpoint(pluginName, device) {
|
|
1617
2321
|
const { waiter } = await import('./utils/wait.js');
|
|
2322
|
+
// Check if the plugin is registered
|
|
1618
2323
|
const plugin = this.plugins.get(pluginName);
|
|
1619
2324
|
if (!plugin) {
|
|
1620
2325
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1634,6 +2339,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1634
2339
|
}
|
|
1635
2340
|
else if (this.bridgeMode === 'bridge') {
|
|
1636
2341
|
if (device.mode === 'matter') {
|
|
2342
|
+
// Register and add the device to the matterbridge server node
|
|
1637
2343
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1638
2344
|
if (!this.serverNode) {
|
|
1639
2345
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1650,6 +2356,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1650
2356
|
}
|
|
1651
2357
|
}
|
|
1652
2358
|
else {
|
|
2359
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1653
2360
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1654
2361
|
if (!this.aggregatorNode) {
|
|
1655
2362
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1667,6 +2374,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1667
2374
|
}
|
|
1668
2375
|
}
|
|
1669
2376
|
else if (this.bridgeMode === 'childbridge') {
|
|
2377
|
+
// Register and add the device to the plugin server node
|
|
1670
2378
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1671
2379
|
try {
|
|
1672
2380
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1690,10 +2398,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1690
2398
|
return;
|
|
1691
2399
|
}
|
|
1692
2400
|
}
|
|
2401
|
+
// Register and add the device to the plugin aggregator node
|
|
1693
2402
|
if (plugin.type === 'DynamicPlatform') {
|
|
1694
2403
|
try {
|
|
1695
2404
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1696
2405
|
await this.createDynamicPlugin(plugin);
|
|
2406
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1697
2407
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1698
2408
|
if (!plugin.aggregatorNode) {
|
|
1699
2409
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1714,12 +2424,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1714
2424
|
}
|
|
1715
2425
|
if (plugin.registeredDevices !== undefined)
|
|
1716
2426
|
plugin.registeredDevices++;
|
|
2427
|
+
// Add the device to the DeviceManager
|
|
1717
2428
|
this.devices.set(device);
|
|
2429
|
+
// Subscribe to the attributes changed event
|
|
1718
2430
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1719
2431
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1720
2432
|
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2435
|
+
*
|
|
2436
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2437
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2438
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2439
|
+
*/
|
|
1721
2440
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1722
2441
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2442
|
+
// Check if the plugin is registered
|
|
1723
2443
|
const plugin = this.plugins.get(pluginName);
|
|
1724
2444
|
if (!plugin) {
|
|
1725
2445
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
@@ -1729,6 +2449,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1729
2449
|
this.log.info(`Removed mode server bridged endpoint(${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1730
2450
|
}
|
|
1731
2451
|
else if (this.bridgeMode === 'bridge') {
|
|
2452
|
+
// Unregister and remove the device from the matterbridge aggregator node
|
|
1732
2453
|
if (!this.aggregatorNode) {
|
|
1733
2454
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1734
2455
|
return;
|
|
@@ -1740,8 +2461,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1740
2461
|
}
|
|
1741
2462
|
else if (this.bridgeMode === 'childbridge') {
|
|
1742
2463
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2464
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1743
2465
|
}
|
|
1744
2466
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2467
|
+
// Unregister and remove the device from the plugin aggregator node
|
|
1745
2468
|
if (!plugin.aggregatorNode) {
|
|
1746
2469
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1747
2470
|
return;
|
|
@@ -1752,8 +2475,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1752
2475
|
if (plugin.registeredDevices !== undefined)
|
|
1753
2476
|
plugin.registeredDevices--;
|
|
1754
2477
|
}
|
|
2478
|
+
// Remove the device from the DeviceManager
|
|
1755
2479
|
this.devices.remove(device);
|
|
1756
2480
|
}
|
|
2481
|
+
/**
|
|
2482
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2483
|
+
*
|
|
2484
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2485
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2486
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2487
|
+
*
|
|
2488
|
+
* @remarks
|
|
2489
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2490
|
+
* It also applies a delay between each removal if specified.
|
|
2491
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2492
|
+
*/
|
|
1757
2493
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1758
2494
|
const { wait } = await import('./utils/wait.js');
|
|
1759
2495
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
@@ -1765,8 +2501,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1765
2501
|
if (delay > 0)
|
|
1766
2502
|
await wait(2000);
|
|
1767
2503
|
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Registers a virtual device.
|
|
2506
|
+
* Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
|
|
2507
|
+
*
|
|
2508
|
+
* The virtual device is created as an instance of `Endpoint` with the provided device type.
|
|
2509
|
+
* When the virtual device is turned on, the provided callback function is executed.
|
|
2510
|
+
* The onOff state of the virtual device always reverts to false when the device is turned on.
|
|
2511
|
+
*
|
|
2512
|
+
* @param { string } pluginName - The name of the plugin to register the virtual device under.
|
|
2513
|
+
* @param { string } name - The name of the virtual device.
|
|
2514
|
+
* @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
|
|
2515
|
+
* @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
|
|
2516
|
+
*
|
|
2517
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
|
|
2518
|
+
*
|
|
2519
|
+
* @remarks
|
|
2520
|
+
* The virtual devices don't show up in the device list of the frontend.
|
|
2521
|
+
* Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
|
|
2522
|
+
*/
|
|
1768
2523
|
async addVirtualEndpoint(pluginName, name, type, callback) {
|
|
1769
2524
|
this.log.debug(`Adding virtual endpoint ${plg}${pluginName}${db}:${dev}${name}${db}...`);
|
|
2525
|
+
// Check if the plugin is registered
|
|
1770
2526
|
const plugin = this.plugins.get(pluginName);
|
|
1771
2527
|
if (!plugin) {
|
|
1772
2528
|
this.log.error(`Error adding virtual endpoint ${dev}${name}${er} for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
@@ -1793,13 +2549,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1793
2549
|
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.`);
|
|
1794
2550
|
return false;
|
|
1795
2551
|
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2554
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2555
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2556
|
+
*
|
|
2557
|
+
* @param {Plugin} plugin - The plugin associated with the device.
|
|
2558
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2559
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2560
|
+
*/
|
|
1796
2561
|
async subscribeAttributeChanged(plugin, device) {
|
|
1797
2562
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
1798
2563
|
return;
|
|
1799
2564
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2565
|
+
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
1800
2566
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1801
2567
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1802
2568
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2569
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1803
2570
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
1804
2571
|
});
|
|
1805
2572
|
}
|
|
@@ -1849,6 +2616,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1849
2616
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1850
2617
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1851
2618
|
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}`);
|
|
2619
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1852
2620
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
1853
2621
|
});
|
|
1854
2622
|
}
|
|
@@ -1857,12 +2625,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1857
2625
|
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...`);
|
|
1858
2626
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1859
2627
|
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}`);
|
|
2628
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1860
2629
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1861
2630
|
});
|
|
1862
2631
|
}
|
|
1863
2632
|
}
|
|
1864
2633
|
}
|
|
1865
2634
|
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2637
|
+
*
|
|
2638
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2639
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2640
|
+
*/
|
|
1866
2641
|
sanitizeFabricInformations(fabricInfo) {
|
|
1867
2642
|
return fabricInfo.map((info) => {
|
|
1868
2643
|
return {
|
|
@@ -1876,6 +2651,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1876
2651
|
};
|
|
1877
2652
|
});
|
|
1878
2653
|
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2656
|
+
*
|
|
2657
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2658
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2659
|
+
*/
|
|
1879
2660
|
sanitizeSessionInformation(sessions) {
|
|
1880
2661
|
return sessions
|
|
1881
2662
|
.filter((session) => session.isPeerActive)
|
|
@@ -1902,7 +2683,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1902
2683
|
};
|
|
1903
2684
|
});
|
|
1904
2685
|
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2688
|
+
*
|
|
2689
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2690
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2691
|
+
*/
|
|
2692
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1905
2693
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2694
|
+
/*
|
|
2695
|
+
for (const child of aggregatorNode.parts) {
|
|
2696
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2697
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2698
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2699
|
+
}
|
|
2700
|
+
*/
|
|
1906
2701
|
}
|
|
1907
2702
|
getVendorIdName = (vendorId) => {
|
|
1908
2703
|
if (!vendorId)
|
|
@@ -1942,10 +2737,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1942
2737
|
case 0x1488:
|
|
1943
2738
|
vendorName = '(ShortcutLabsFlic)';
|
|
1944
2739
|
break;
|
|
1945
|
-
case 65521:
|
|
2740
|
+
case 65521: // 0xFFF1
|
|
1946
2741
|
vendorName = '(MatterTest)';
|
|
1947
2742
|
break;
|
|
1948
2743
|
}
|
|
1949
2744
|
return vendorName;
|
|
1950
2745
|
};
|
|
1951
2746
|
}
|
|
2747
|
+
//# sourceMappingURL=matterbridge.js.map
|