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