matterbridge 3.4.0-dev-20251126-5087664 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/broadcastServer.d.ts +115 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +93 -1
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +838 -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/defaultConfigSchema.d.ts +28 -0
- package/dist/defaultConfigSchema.d.ts.map +1 -0
- package/dist/defaultConfigSchema.js +24 -0
- package/dist/defaultConfigSchema.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 +303 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +352 -13
- 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 +473 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +787 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +41 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +38 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +2404 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +698 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +635 -14
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +41 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +38 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1507 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1444 -53
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +787 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +483 -20
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgeEndpointTypes.d.ts +166 -0
- package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
- package/dist/matterbridgeEndpointTypes.js +25 -0
- package/dist/matterbridgeEndpointTypes.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +524 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +439 -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 +371 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +340 -5
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +174 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +168 -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 +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +69 -0
- 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 +44 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +41 -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/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,19 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class Matterbridge.
|
|
3
|
+
*
|
|
4
|
+
* @file matterbridge.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2023-12-29
|
|
7
|
+
* @version 1.6.0
|
|
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';
|
|
@@ -28,19 +57,27 @@ import { bridge } from './matterbridgeDeviceTypes.js';
|
|
|
28
57
|
import { Frontend } from './frontend.js';
|
|
29
58
|
import { addVirtualDevices } from './helpers.js';
|
|
30
59
|
import { BroadcastServer } from './broadcastServer.js';
|
|
60
|
+
/**
|
|
61
|
+
* Represents the Matterbridge application.
|
|
62
|
+
*/
|
|
31
63
|
export class Matterbridge extends EventEmitter {
|
|
64
|
+
/** Matterbridge system information */
|
|
32
65
|
systemInformation = {
|
|
66
|
+
// Network properties
|
|
33
67
|
interfaceName: '',
|
|
34
68
|
macAddress: '',
|
|
35
69
|
ipv4Address: '',
|
|
36
70
|
ipv6Address: '',
|
|
71
|
+
// Node.js properties
|
|
37
72
|
nodeVersion: '',
|
|
73
|
+
// Fixed system properties
|
|
38
74
|
hostname: '',
|
|
39
75
|
user: '',
|
|
40
76
|
osType: '',
|
|
41
77
|
osRelease: '',
|
|
42
78
|
osPlatform: '',
|
|
43
79
|
osArch: '',
|
|
80
|
+
// Cpu and memory properties
|
|
44
81
|
totalMemory: '',
|
|
45
82
|
freeMemory: '',
|
|
46
83
|
systemUptime: '',
|
|
@@ -51,6 +88,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
51
88
|
heapTotal: '',
|
|
52
89
|
heapUsed: '',
|
|
53
90
|
};
|
|
91
|
+
// Matterbridge settings
|
|
54
92
|
homeDirectory = '';
|
|
55
93
|
rootDirectory = '';
|
|
56
94
|
matterbridgeDirectory = '';
|
|
@@ -65,12 +103,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
65
103
|
restartMode = '';
|
|
66
104
|
virtualMode = 'outlet';
|
|
67
105
|
profile = getParameter('profile');
|
|
68
|
-
|
|
106
|
+
/** Matterbridge logger */
|
|
107
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
108
|
+
/** Matterbridge logger level */
|
|
69
109
|
logLevel = this.log.logLevel;
|
|
110
|
+
/** Whether to log to a file */
|
|
70
111
|
fileLogger = false;
|
|
71
|
-
|
|
112
|
+
/** Matter logger */
|
|
113
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: hasParameter('debug') ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
114
|
+
/** Matter logger level */
|
|
72
115
|
matterLogLevel = this.matterLog.logLevel;
|
|
116
|
+
/** Whether to log Matter to a file */
|
|
73
117
|
matterFileLogger = false;
|
|
118
|
+
// Frontend settings
|
|
74
119
|
readOnly = hasParameter('readonly') || hasParameter('shelly');
|
|
75
120
|
shellyBoard = hasParameter('shelly');
|
|
76
121
|
shellySysUpdate = false;
|
|
@@ -78,12 +123,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
78
123
|
restartRequired = false;
|
|
79
124
|
fixedRestartRequired = false;
|
|
80
125
|
updateRequired = false;
|
|
126
|
+
// Managers
|
|
81
127
|
plugins = new PluginManager(this);
|
|
82
128
|
devices = new DeviceManager();
|
|
129
|
+
// Frontend
|
|
83
130
|
frontend = new Frontend(this);
|
|
131
|
+
/** Matterbridge node storage manager created in the directory 'storage' in matterbridgeDirectory */
|
|
84
132
|
nodeStorage;
|
|
133
|
+
/** Matterbridge node context created with name 'matterbridge' */
|
|
85
134
|
nodeContext;
|
|
135
|
+
/** The main instance of the Matterbridge class (singleton) */
|
|
86
136
|
static instance;
|
|
137
|
+
// Instance properties
|
|
87
138
|
shutdown = false;
|
|
88
139
|
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
89
140
|
hasCleanupStarted = false;
|
|
@@ -98,19 +149,32 @@ export class Matterbridge extends EventEmitter {
|
|
|
98
149
|
sigtermHandler;
|
|
99
150
|
exceptionHandler;
|
|
100
151
|
rejectionHandler;
|
|
152
|
+
/** Matter environment default */
|
|
101
153
|
environment = Environment.default;
|
|
154
|
+
/** Matter storage service from environment default */
|
|
102
155
|
matterStorageService;
|
|
156
|
+
/** Matter storage manager created with name 'Matterbridge' */
|
|
103
157
|
matterStorageManager;
|
|
158
|
+
/** Matter matterbridge storage context created in the storage manager with name 'persist' */
|
|
104
159
|
matterbridgeContext;
|
|
105
160
|
controllerContext;
|
|
161
|
+
/** Matter mdns interface e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
|
|
106
162
|
mdnsInterface;
|
|
163
|
+
/** Matter listeningAddressIpv4 address */
|
|
107
164
|
ipv4Address;
|
|
165
|
+
/** Matter listeningAddressIpv6 address */
|
|
108
166
|
ipv6Address;
|
|
109
|
-
port
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
167
|
+
/** Matter commissioning port */
|
|
168
|
+
port; // first server node port
|
|
169
|
+
/** Matter commissioning passcode */
|
|
170
|
+
passcode; // first server node passcode
|
|
171
|
+
/** Matter commissioning discriminator */
|
|
172
|
+
discriminator; // first server node discriminator
|
|
173
|
+
/** Matter device certification */
|
|
174
|
+
certification; // device certification
|
|
175
|
+
/** Matter server node in bridge mode */
|
|
113
176
|
serverNode;
|
|
177
|
+
/** Matter aggregator node in bridge mode */
|
|
114
178
|
aggregatorNode;
|
|
115
179
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
116
180
|
aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
|
|
@@ -119,10 +183,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
119
183
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
120
184
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
121
185
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
186
|
+
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
122
187
|
advertisingNodes = new Map();
|
|
188
|
+
/** Broadcast server */
|
|
123
189
|
server;
|
|
124
190
|
debug = hasParameter('debug') || hasParameter('verbose');
|
|
125
191
|
verbose = hasParameter('verbose');
|
|
192
|
+
/** We load asyncronously so is private */
|
|
126
193
|
constructor() {
|
|
127
194
|
super();
|
|
128
195
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
@@ -162,8 +229,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
162
229
|
}
|
|
163
230
|
}
|
|
164
231
|
}
|
|
232
|
+
//* ************************************************************************************************************************************ */
|
|
233
|
+
// loadInstance() and cleanup() methods */
|
|
234
|
+
//* ************************************************************************************************************************************ */
|
|
235
|
+
/**
|
|
236
|
+
* Loads an instance of the Matterbridge class.
|
|
237
|
+
* If an instance already exists, return that instance.
|
|
238
|
+
*
|
|
239
|
+
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
240
|
+
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
241
|
+
*/
|
|
165
242
|
static async loadInstance(initialize = false) {
|
|
166
243
|
if (!Matterbridge.instance) {
|
|
244
|
+
// eslint-disable-next-line no-console
|
|
167
245
|
if (hasParameter('debug'))
|
|
168
246
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
169
247
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -172,56 +250,84 @@ export class Matterbridge extends EventEmitter {
|
|
|
172
250
|
}
|
|
173
251
|
return Matterbridge.instance;
|
|
174
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Initializes the Matterbridge application.
|
|
255
|
+
*
|
|
256
|
+
* @remarks
|
|
257
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
258
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
259
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
260
|
+
*
|
|
261
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
262
|
+
*/
|
|
175
263
|
async initialize() {
|
|
264
|
+
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
265
|
+
// Emit the initialize_started event
|
|
176
266
|
this.emit('initialize_started');
|
|
267
|
+
// Set the restart mode
|
|
177
268
|
if (hasParameter('service'))
|
|
178
269
|
this.restartMode = 'service';
|
|
179
270
|
if (hasParameter('docker'))
|
|
180
271
|
this.restartMode = 'docker';
|
|
272
|
+
// Set the matterbridge home directory
|
|
181
273
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
182
274
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
275
|
+
// Set the matterbridge directory
|
|
183
276
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
184
277
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
185
278
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
186
279
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
280
|
+
// Set the matterbridge plugin directory
|
|
187
281
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
188
282
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
283
|
+
// Set the matterbridge cert directory
|
|
189
284
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
190
285
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
286
|
+
// Set the matterbridge root directory
|
|
191
287
|
const { fileURLToPath } = await import('node:url');
|
|
192
288
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
193
|
-
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
289
|
+
this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
|
|
290
|
+
// Setup the matter environment with default values
|
|
194
291
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
195
292
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
196
293
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
197
294
|
this.environment.vars.set('runtime.signals', false);
|
|
198
295
|
this.environment.vars.set('runtime.exitcode', false);
|
|
296
|
+
// Register process handlers
|
|
199
297
|
this.registerProcessHandlers();
|
|
298
|
+
// Initialize nodeStorage and nodeContext
|
|
200
299
|
try {
|
|
201
300
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
202
301
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
203
302
|
this.log.debug('Creating node storage context for matterbridge');
|
|
204
303
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
304
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
305
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
205
306
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
206
307
|
for (const key of keys) {
|
|
207
308
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
309
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
208
310
|
await this.nodeStorage?.storage.get(key);
|
|
209
311
|
}
|
|
210
312
|
const storages = await this.nodeStorage.getStorageNames();
|
|
211
313
|
for (const storage of storages) {
|
|
212
314
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
213
315
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
316
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
214
318
|
const keys = (await nodeContext?.storage.keys());
|
|
215
319
|
keys.forEach(async (key) => {
|
|
216
320
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
217
321
|
await nodeContext?.get(key);
|
|
218
322
|
});
|
|
219
323
|
}
|
|
324
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
220
325
|
this.log.debug('Creating node storage backup...');
|
|
221
326
|
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
222
327
|
this.log.debug('Created node storage backup');
|
|
223
328
|
}
|
|
224
329
|
catch (error) {
|
|
330
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
225
331
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
226
332
|
if (hasParameter('norestore')) {
|
|
227
333
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -235,14 +341,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
235
341
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
236
342
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
237
343
|
}
|
|
344
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
238
345
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
346
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
239
347
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
348
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
240
349
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
350
|
+
// Certificate management
|
|
241
351
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
242
352
|
try {
|
|
243
353
|
await fs.promises.access(pairingFilePath, fs.constants.R_OK);
|
|
244
354
|
const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
|
|
245
355
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
356
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
246
357
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
247
358
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
248
359
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -271,11 +382,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
271
382
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
272
383
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
273
384
|
}
|
|
385
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
274
386
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
275
387
|
this.passcode = pairingFileJson.passcode;
|
|
276
388
|
this.discriminator = pairingFileJson.discriminator;
|
|
277
389
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
278
390
|
}
|
|
391
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
279
392
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
280
393
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
281
394
|
this.certification = {
|
|
@@ -290,41 +403,44 @@ export class Matterbridge extends EventEmitter {
|
|
|
290
403
|
catch (error) {
|
|
291
404
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
292
405
|
}
|
|
406
|
+
// Store the passcode, discriminator and port in the node context
|
|
293
407
|
await this.nodeContext.set('matterport', this.port);
|
|
294
408
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
295
409
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
296
410
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
411
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
297
412
|
if (hasParameter('logger')) {
|
|
298
413
|
const level = getParameter('logger');
|
|
299
414
|
if (level === 'debug') {
|
|
300
|
-
this.log.logLevel = "debug"
|
|
415
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
301
416
|
}
|
|
302
417
|
else if (level === 'info') {
|
|
303
|
-
this.log.logLevel = "info"
|
|
418
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
304
419
|
}
|
|
305
420
|
else if (level === 'notice') {
|
|
306
|
-
this.log.logLevel = "notice"
|
|
421
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
307
422
|
}
|
|
308
423
|
else if (level === 'warn') {
|
|
309
|
-
this.log.logLevel = "warn"
|
|
424
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
310
425
|
}
|
|
311
426
|
else if (level === 'error') {
|
|
312
|
-
this.log.logLevel = "error"
|
|
427
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
313
428
|
}
|
|
314
429
|
else if (level === 'fatal') {
|
|
315
|
-
this.log.logLevel = "fatal"
|
|
430
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
316
431
|
}
|
|
317
432
|
else {
|
|
318
433
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
319
|
-
this.log.logLevel = "info"
|
|
434
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
320
435
|
}
|
|
321
436
|
}
|
|
322
437
|
else {
|
|
323
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
438
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
324
439
|
}
|
|
325
440
|
this.logLevel = this.log.logLevel;
|
|
326
441
|
this.frontend.logLevel = this.log.logLevel;
|
|
327
442
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
443
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
328
444
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
329
445
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
330
446
|
this.fileLogger = true;
|
|
@@ -333,6 +449,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
333
449
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
334
450
|
if (this.profile !== undefined)
|
|
335
451
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
452
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
336
453
|
if (hasParameter('matterlogger')) {
|
|
337
454
|
const level = getParameter('matterlogger');
|
|
338
455
|
if (level === 'debug') {
|
|
@@ -363,11 +480,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
363
480
|
}
|
|
364
481
|
Logger.format = MatterLogFormat.ANSI;
|
|
365
482
|
this.matterLogLevel = MatterLogLevel.names[Logger.level];
|
|
483
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
366
484
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
367
485
|
this.matterFileLogger = true;
|
|
368
486
|
}
|
|
369
487
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
370
488
|
this.log.debug(`Matter logLevel: ${this.matterLogLevel} fileLoger: ${this.matterFileLogger}.`);
|
|
489
|
+
// Log network interfaces
|
|
371
490
|
const networkInterfaces = os.networkInterfaces();
|
|
372
491
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
373
492
|
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
@@ -380,6 +499,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
380
499
|
});
|
|
381
500
|
}
|
|
382
501
|
}
|
|
502
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
383
503
|
if (hasParameter('mdnsinterface')) {
|
|
384
504
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
385
505
|
}
|
|
@@ -388,6 +508,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
388
508
|
if (this.mdnsInterface === '')
|
|
389
509
|
this.mdnsInterface = undefined;
|
|
390
510
|
}
|
|
511
|
+
// Validate mdnsInterface
|
|
391
512
|
if (this.mdnsInterface) {
|
|
392
513
|
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
393
514
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
@@ -400,6 +521,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
400
521
|
}
|
|
401
522
|
if (this.mdnsInterface)
|
|
402
523
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
524
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
403
525
|
if (hasParameter('ipv4address')) {
|
|
404
526
|
this.ipv4Address = getParameter('ipv4address');
|
|
405
527
|
}
|
|
@@ -408,6 +530,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
408
530
|
if (this.ipv4Address === '')
|
|
409
531
|
this.ipv4Address = undefined;
|
|
410
532
|
}
|
|
533
|
+
// Validate ipv4address
|
|
411
534
|
if (this.ipv4Address) {
|
|
412
535
|
let isValid = false;
|
|
413
536
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -423,6 +546,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
423
546
|
await this.nodeContext.remove('matteripv4address');
|
|
424
547
|
}
|
|
425
548
|
}
|
|
549
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
426
550
|
if (hasParameter('ipv6address')) {
|
|
427
551
|
this.ipv6Address = getParameter('ipv6address');
|
|
428
552
|
}
|
|
@@ -431,6 +555,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
431
555
|
if (this.ipv6Address === '')
|
|
432
556
|
this.ipv6Address = undefined;
|
|
433
557
|
}
|
|
558
|
+
// Validate ipv6address
|
|
434
559
|
if (this.ipv6Address) {
|
|
435
560
|
let isValid = false;
|
|
436
561
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -439,6 +564,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
439
564
|
isValid = true;
|
|
440
565
|
break;
|
|
441
566
|
}
|
|
567
|
+
/* istanbul ignore next */
|
|
442
568
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
443
569
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
444
570
|
isValid = true;
|
|
@@ -451,6 +577,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
451
577
|
await this.nodeContext.remove('matteripv6address');
|
|
452
578
|
}
|
|
453
579
|
}
|
|
580
|
+
// Initialize the virtual mode
|
|
454
581
|
if (hasParameter('novirtual')) {
|
|
455
582
|
this.virtualMode = 'disabled';
|
|
456
583
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -459,10 +586,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
459
586
|
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
460
587
|
}
|
|
461
588
|
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
589
|
+
// Initialize PluginManager
|
|
462
590
|
this.plugins.logLevel = this.log.logLevel;
|
|
463
591
|
await this.plugins.loadFromStorage();
|
|
592
|
+
// Initialize DeviceManager
|
|
464
593
|
this.devices.logLevel = this.log.logLevel;
|
|
594
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
465
595
|
for (const plugin of this.plugins) {
|
|
596
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
597
|
+
// We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
|
|
466
598
|
if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
467
599
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
|
|
468
600
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -492,6 +624,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
492
624
|
await plugin.nodeContext.set('description', plugin.description);
|
|
493
625
|
await plugin.nodeContext.set('author', plugin.author);
|
|
494
626
|
}
|
|
627
|
+
// Log system info and create .matterbridge directory
|
|
495
628
|
await this.logNodeAndSystemInfo();
|
|
496
629
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
497
630
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -499,6 +632,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
499
632
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
500
633
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
501
634
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
635
|
+
// Check node version and throw error
|
|
502
636
|
const minNodeVersion = 20;
|
|
503
637
|
const nodeVersion = process.versions.node;
|
|
504
638
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -506,10 +640,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
506
640
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
507
641
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
508
642
|
}
|
|
643
|
+
// Parse command line
|
|
509
644
|
await this.parseCommandLine();
|
|
645
|
+
// Emit the initialize_completed event
|
|
510
646
|
this.emit('initialize_completed');
|
|
511
647
|
this.initialized = true;
|
|
512
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
651
|
+
*
|
|
652
|
+
* @private
|
|
653
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
654
|
+
*/
|
|
513
655
|
async parseCommandLine() {
|
|
514
656
|
if (hasParameter('list')) {
|
|
515
657
|
this.log.info(`│ Registered plugins (${this.plugins.length})`);
|
|
@@ -525,6 +667,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
525
667
|
}
|
|
526
668
|
index++;
|
|
527
669
|
}
|
|
670
|
+
/*
|
|
671
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
672
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
673
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
674
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
675
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
676
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
677
|
+
} else {
|
|
678
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
679
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
*/
|
|
528
683
|
this.shutdown = true;
|
|
529
684
|
return;
|
|
530
685
|
}
|
|
@@ -574,8 +729,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
574
729
|
this.shutdown = true;
|
|
575
730
|
return;
|
|
576
731
|
}
|
|
732
|
+
// Initialize frontend
|
|
577
733
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
578
734
|
await this.frontend.start(getIntParameter('frontend'));
|
|
735
|
+
// Start the matter storage and create the matterbridge context
|
|
579
736
|
try {
|
|
580
737
|
await this.startMatterStorage();
|
|
581
738
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -589,18 +746,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
589
746
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
590
747
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
591
748
|
}
|
|
749
|
+
// Clear the matterbridge context if the reset parameter is set (bridge mode)
|
|
592
750
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
593
751
|
this.initialized = true;
|
|
594
752
|
await this.shutdownProcessAndReset();
|
|
595
753
|
this.shutdown = true;
|
|
596
754
|
return;
|
|
597
755
|
}
|
|
756
|
+
// Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
|
|
598
757
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
599
758
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
600
759
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
601
760
|
if (plugin) {
|
|
602
761
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
603
762
|
if (!matterStorageManager) {
|
|
763
|
+
/* istanbul ignore next */
|
|
604
764
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
605
765
|
}
|
|
606
766
|
else {
|
|
@@ -619,35 +779,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
619
779
|
this.shutdown = true;
|
|
620
780
|
return;
|
|
621
781
|
}
|
|
782
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
622
783
|
clearTimeout(this.checkUpdateTimeout);
|
|
623
784
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
624
785
|
const { checkUpdates } = await import('./update.js');
|
|
625
786
|
checkUpdates(this);
|
|
626
787
|
}, 30 * 1000).unref();
|
|
788
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
627
789
|
clearInterval(this.checkUpdateInterval);
|
|
628
790
|
this.checkUpdateInterval = setInterval(async () => {
|
|
629
791
|
const { checkUpdates } = await import('./update.js');
|
|
630
792
|
checkUpdates(this);
|
|
631
793
|
}, 12 * 60 * 60 * 1000).unref();
|
|
794
|
+
// Start the matterbridge in mode test
|
|
632
795
|
if (hasParameter('test')) {
|
|
633
796
|
this.bridgeMode = 'bridge';
|
|
634
797
|
return;
|
|
635
798
|
}
|
|
799
|
+
// Start the matterbridge in mode controller
|
|
636
800
|
if (hasParameter('controller')) {
|
|
637
801
|
this.bridgeMode = 'controller';
|
|
638
802
|
await this.startController();
|
|
639
803
|
return;
|
|
640
804
|
}
|
|
805
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
641
806
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
642
807
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
643
808
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
644
809
|
}
|
|
810
|
+
// Start matterbridge in bridge mode
|
|
645
811
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
646
812
|
this.bridgeMode = 'bridge';
|
|
647
813
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
648
814
|
await this.startBridge();
|
|
649
815
|
return;
|
|
650
816
|
}
|
|
817
|
+
// Start matterbridge in childbridge mode
|
|
651
818
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
652
819
|
this.bridgeMode = 'childbridge';
|
|
653
820
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -655,10 +822,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
655
822
|
return;
|
|
656
823
|
}
|
|
657
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Asynchronously loads and starts the registered plugins.
|
|
827
|
+
*
|
|
828
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
829
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
830
|
+
*
|
|
831
|
+
* @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving. Defaults to false.
|
|
832
|
+
* @param {boolean} [start] - If true, the method will start the plugins after loading them. Defaults to true.
|
|
833
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
834
|
+
*/
|
|
658
835
|
async startPlugins(wait = false, start = true) {
|
|
836
|
+
// Check, load and start the plugins
|
|
659
837
|
for (const plugin of this.plugins) {
|
|
660
838
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
661
839
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
840
|
+
// Check if the plugin is available
|
|
662
841
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
663
842
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
664
843
|
plugin.enabled = false;
|
|
@@ -678,10 +857,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
678
857
|
if (wait)
|
|
679
858
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
680
859
|
else
|
|
681
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
860
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
|
|
682
861
|
}
|
|
683
862
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
684
863
|
}
|
|
864
|
+
/**
|
|
865
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
866
|
+
* - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
|
|
867
|
+
* - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
|
|
868
|
+
* - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
|
|
869
|
+
*/
|
|
685
870
|
registerProcessHandlers() {
|
|
686
871
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
687
872
|
process.removeAllListeners('uncaughtException');
|
|
@@ -708,6 +893,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
708
893
|
};
|
|
709
894
|
process.on('SIGTERM', this.sigtermHandler);
|
|
710
895
|
}
|
|
896
|
+
/**
|
|
897
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
898
|
+
*/
|
|
711
899
|
deregisterProcessHandlers() {
|
|
712
900
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
713
901
|
if (this.exceptionHandler)
|
|
@@ -724,13 +912,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
724
912
|
process.off('SIGTERM', this.sigtermHandler);
|
|
725
913
|
this.sigtermHandler = undefined;
|
|
726
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Logs the node and system information.
|
|
917
|
+
*/
|
|
727
918
|
async logNodeAndSystemInfo() {
|
|
919
|
+
// IP address information
|
|
728
920
|
const networkInterfaces = os.networkInterfaces();
|
|
729
921
|
this.systemInformation.interfaceName = '';
|
|
730
922
|
this.systemInformation.ipv4Address = '';
|
|
731
923
|
this.systemInformation.ipv6Address = '';
|
|
732
924
|
this.systemInformation.macAddress = '';
|
|
733
925
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
926
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
734
927
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
735
928
|
continue;
|
|
736
929
|
if (!interfaceDetails) {
|
|
@@ -756,16 +949,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
756
949
|
break;
|
|
757
950
|
}
|
|
758
951
|
}
|
|
952
|
+
// Node information
|
|
759
953
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
760
954
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
761
955
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
762
956
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
957
|
+
// Host system information
|
|
763
958
|
this.systemInformation.hostname = os.hostname();
|
|
764
959
|
this.systemInformation.user = os.userInfo().username;
|
|
765
|
-
this.systemInformation.osType = os.type();
|
|
766
|
-
this.systemInformation.osRelease = os.release();
|
|
767
|
-
this.systemInformation.osPlatform = os.platform();
|
|
768
|
-
this.systemInformation.osArch = os.arch();
|
|
960
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
961
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
962
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
963
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
769
964
|
this.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
770
965
|
this.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
771
966
|
this.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -775,6 +970,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
775
970
|
this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
776
971
|
this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
777
972
|
this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
973
|
+
// Log the system information
|
|
778
974
|
this.log.debug('Host System Information:');
|
|
779
975
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
780
976
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -794,14 +990,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
794
990
|
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
795
991
|
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
796
992
|
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
993
|
+
// Log directories
|
|
797
994
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
798
995
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
799
996
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
800
997
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
801
998
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
999
|
+
// Global node_modules directory
|
|
802
1000
|
if (this.nodeContext)
|
|
803
1001
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
804
1002
|
if (this.globalModulesDirectory === '') {
|
|
1003
|
+
// First run of Matterbridge so the node storage is empty
|
|
805
1004
|
this.log.debug(`Getting global node_modules directory...`);
|
|
806
1005
|
try {
|
|
807
1006
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -814,6 +1013,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
814
1013
|
}
|
|
815
1014
|
}
|
|
816
1015
|
else {
|
|
1016
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
817
1017
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
818
1018
|
try {
|
|
819
1019
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -825,25 +1025,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
825
1025
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
826
1026
|
}
|
|
827
1027
|
}
|
|
1028
|
+
// Matterbridge version
|
|
828
1029
|
this.log.debug(`Reading matterbridge package.json...`);
|
|
829
1030
|
const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
830
1031
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
831
1032
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1033
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
832
1034
|
if (this.nodeContext)
|
|
833
1035
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
834
1036
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1037
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
835
1038
|
if (this.nodeContext)
|
|
836
1039
|
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
837
1040
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1041
|
+
// Frontend version
|
|
838
1042
|
this.log.debug(`Reading frontend package.json...`);
|
|
839
1043
|
const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
840
1044
|
this.frontendVersion = frontendPackageJson.version;
|
|
841
1045
|
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1046
|
+
// Current working directory
|
|
842
1047
|
const currentDir = process.cwd();
|
|
843
1048
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1049
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
844
1050
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
845
1051
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
846
1052
|
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
1055
|
+
*
|
|
1056
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
1057
|
+
* @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
|
|
1058
|
+
*/
|
|
847
1059
|
async setLogLevel(logLevel) {
|
|
848
1060
|
this.logLevel = logLevel;
|
|
849
1061
|
this.log.logLevel = logLevel;
|
|
@@ -854,58 +1066,87 @@ export class Matterbridge extends EventEmitter {
|
|
|
854
1066
|
for (const plugin of this.plugins) {
|
|
855
1067
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
856
1068
|
continue;
|
|
857
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
|
|
858
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1069
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
|
|
1070
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
|
|
1071
|
+
}
|
|
1072
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1073
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1074
|
+
if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1075
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1076
|
+
if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1077
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
865
1078
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
866
1079
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
867
1080
|
return logLevel;
|
|
868
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Get the current logger logLevel.
|
|
1084
|
+
*
|
|
1085
|
+
* @returns {LogLevel} The current logger logLevel.
|
|
1086
|
+
*/
|
|
869
1087
|
getLogLevel() {
|
|
870
1088
|
return this.log.logLevel;
|
|
871
1089
|
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1092
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1093
|
+
*
|
|
1094
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1095
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1096
|
+
*/
|
|
872
1097
|
createDestinationMatterLogger(fileLogger) {
|
|
873
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1098
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
874
1099
|
if (fileLogger) {
|
|
875
1100
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
876
1101
|
}
|
|
877
1102
|
return (text, message) => {
|
|
1103
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
878
1104
|
const logger = text.slice(44, 44 + 20).trim();
|
|
879
1105
|
const msg = text.slice(65);
|
|
880
1106
|
this.matterLog.logName = logger;
|
|
881
1107
|
switch (message.level) {
|
|
882
1108
|
case MatterLogLevel.DEBUG:
|
|
883
|
-
this.matterLog.log("debug"
|
|
1109
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
884
1110
|
break;
|
|
885
1111
|
case MatterLogLevel.INFO:
|
|
886
|
-
this.matterLog.log("info"
|
|
1112
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
887
1113
|
break;
|
|
888
1114
|
case MatterLogLevel.NOTICE:
|
|
889
|
-
this.matterLog.log("notice"
|
|
1115
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
890
1116
|
break;
|
|
891
1117
|
case MatterLogLevel.WARN:
|
|
892
|
-
this.matterLog.log("warn"
|
|
1118
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
893
1119
|
break;
|
|
894
1120
|
case MatterLogLevel.ERROR:
|
|
895
|
-
this.matterLog.log("error"
|
|
1121
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
896
1122
|
break;
|
|
897
1123
|
case MatterLogLevel.FATAL:
|
|
898
|
-
this.matterLog.log("fatal"
|
|
1124
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
899
1125
|
break;
|
|
900
1126
|
}
|
|
901
1127
|
};
|
|
902
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1131
|
+
*
|
|
1132
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1133
|
+
*/
|
|
903
1134
|
async restartProcess() {
|
|
904
1135
|
await this.cleanup('restarting...', true);
|
|
905
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Shut down the process (/api/shutdown).
|
|
1139
|
+
*
|
|
1140
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1141
|
+
*/
|
|
906
1142
|
async shutdownProcess() {
|
|
907
1143
|
await this.cleanup('shutting down...', false);
|
|
908
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1147
|
+
*
|
|
1148
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1149
|
+
*/
|
|
909
1150
|
async updateProcess() {
|
|
910
1151
|
this.log.info('Updating matterbridge...');
|
|
911
1152
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
@@ -918,6 +1159,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
918
1159
|
this.frontend.wssSendRestartRequired();
|
|
919
1160
|
await this.cleanup('updating...', false);
|
|
920
1161
|
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1164
|
+
*
|
|
1165
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1166
|
+
*
|
|
1167
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1168
|
+
*/
|
|
921
1169
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
922
1170
|
this.log.info('Unregistering all devices and shutting down...');
|
|
923
1171
|
for (const plugin of this.plugins.array()) {
|
|
@@ -929,46 +1177,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
929
1177
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
930
1178
|
}
|
|
931
1179
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
932
|
-
await wait(timeout);
|
|
1180
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
933
1181
|
this.log.debug('Cleaning up and shutting down...');
|
|
934
1182
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
935
1183
|
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1186
|
+
*
|
|
1187
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1188
|
+
*/
|
|
936
1189
|
async shutdownProcessAndReset() {
|
|
937
1190
|
await this.cleanup('shutting down with reset...', false);
|
|
938
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1194
|
+
*
|
|
1195
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1196
|
+
*/
|
|
939
1197
|
async shutdownProcessAndFactoryReset() {
|
|
940
1198
|
await this.cleanup('shutting down with factory reset...', false);
|
|
941
1199
|
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Cleans up the Matterbridge instance.
|
|
1202
|
+
*
|
|
1203
|
+
* @param {string} message - The cleanup message.
|
|
1204
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1205
|
+
* @param {number} [pause] - The pause in ms to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1206
|
+
*
|
|
1207
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1208
|
+
*/
|
|
942
1209
|
async cleanup(message, restart = false, pause = 1000) {
|
|
943
1210
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
944
1211
|
this.emit('cleanup_started');
|
|
945
1212
|
this.hasCleanupStarted = true;
|
|
946
1213
|
this.log.info(message);
|
|
1214
|
+
// Clear the start matter interval
|
|
947
1215
|
if (this.startMatterInterval) {
|
|
948
1216
|
clearInterval(this.startMatterInterval);
|
|
949
1217
|
this.startMatterInterval = undefined;
|
|
950
1218
|
this.log.debug('Start matter interval cleared');
|
|
951
1219
|
}
|
|
1220
|
+
// Clear the check update timeout
|
|
952
1221
|
if (this.checkUpdateTimeout) {
|
|
953
1222
|
clearTimeout(this.checkUpdateTimeout);
|
|
954
1223
|
this.checkUpdateTimeout = undefined;
|
|
955
1224
|
this.log.debug('Check update timeout cleared');
|
|
956
1225
|
}
|
|
1226
|
+
// Clear the check update interval
|
|
957
1227
|
if (this.checkUpdateInterval) {
|
|
958
1228
|
clearInterval(this.checkUpdateInterval);
|
|
959
1229
|
this.checkUpdateInterval = undefined;
|
|
960
1230
|
this.log.debug('Check update interval cleared');
|
|
961
1231
|
}
|
|
1232
|
+
// Clear the configure timeout
|
|
962
1233
|
if (this.configureTimeout) {
|
|
963
1234
|
clearTimeout(this.configureTimeout);
|
|
964
1235
|
this.configureTimeout = undefined;
|
|
965
1236
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
966
1237
|
}
|
|
1238
|
+
// Clear the reachability timeout
|
|
967
1239
|
if (this.reachabilityTimeout) {
|
|
968
1240
|
clearTimeout(this.reachabilityTimeout);
|
|
969
1241
|
this.reachabilityTimeout = undefined;
|
|
970
1242
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
971
1243
|
}
|
|
1244
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
972
1245
|
for (const plugin of this.plugins) {
|
|
973
1246
|
if (!plugin.enabled || plugin.error)
|
|
974
1247
|
continue;
|
|
@@ -979,6 +1252,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
979
1252
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
980
1253
|
}
|
|
981
1254
|
}
|
|
1255
|
+
// Stop matter server nodes
|
|
982
1256
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
983
1257
|
if (pause > 0) {
|
|
984
1258
|
this.log.debug(`Waiting ${pause}ms for the MessageExchange to finish...`);
|
|
@@ -1005,6 +1279,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1005
1279
|
}
|
|
1006
1280
|
}
|
|
1007
1281
|
this.log.notice('Stopped matter server nodes');
|
|
1282
|
+
// Matter commisioning reset
|
|
1008
1283
|
if (message === 'shutting down with reset...') {
|
|
1009
1284
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1010
1285
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1014,6 +1289,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1014
1289
|
await this.matterbridgeContext?.clearAll();
|
|
1015
1290
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1016
1291
|
}
|
|
1292
|
+
// Unregister all devices
|
|
1017
1293
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1018
1294
|
if (this.bridgeMode === 'bridge') {
|
|
1019
1295
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1031,16 +1307,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1031
1307
|
}
|
|
1032
1308
|
this.log.info('Matter storage reset done!');
|
|
1033
1309
|
}
|
|
1310
|
+
// Stop matter storage
|
|
1034
1311
|
await this.stopMatterStorage();
|
|
1312
|
+
// Stop the frontend
|
|
1035
1313
|
await this.frontend.stop();
|
|
1036
1314
|
this.frontend.destroy();
|
|
1315
|
+
// Close PluginManager and DeviceManager
|
|
1037
1316
|
this.plugins.destroy();
|
|
1038
1317
|
this.devices.destroy();
|
|
1318
|
+
// Stop thread messaging server
|
|
1039
1319
|
this.server.close();
|
|
1320
|
+
// Close the matterbridge node storage and context
|
|
1040
1321
|
if (this.nodeStorage && this.nodeContext) {
|
|
1322
|
+
/*
|
|
1323
|
+
TODO: Implement serialization of registered devices
|
|
1324
|
+
this.log.info('Saving registered devices...');
|
|
1325
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1326
|
+
this.devices.forEach(async (device) => {
|
|
1327
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1328
|
+
this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1329
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1330
|
+
});
|
|
1331
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1332
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1333
|
+
*/
|
|
1334
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1041
1335
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1042
1336
|
await this.nodeContext.close();
|
|
1043
1337
|
this.nodeContext = undefined;
|
|
1338
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1044
1339
|
for (const plugin of this.plugins) {
|
|
1045
1340
|
if (plugin.nodeContext) {
|
|
1046
1341
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1057,8 +1352,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1057
1352
|
}
|
|
1058
1353
|
this.plugins.clear();
|
|
1059
1354
|
this.devices.clear();
|
|
1355
|
+
// Factory reset
|
|
1060
1356
|
if (message === 'shutting down with factory reset...') {
|
|
1061
1357
|
try {
|
|
1358
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1062
1359
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1063
1360
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1064
1361
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1067,11 +1364,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1067
1364
|
await fs.promises.rm(backup, { recursive: true });
|
|
1068
1365
|
}
|
|
1069
1366
|
catch (error) {
|
|
1367
|
+
// istanbul ignore next if
|
|
1070
1368
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1071
1369
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1072
1370
|
}
|
|
1073
1371
|
}
|
|
1074
1372
|
try {
|
|
1373
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1075
1374
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1076
1375
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1077
1376
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1080,18 +1379,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1080
1379
|
await fs.promises.rm(backup, { recursive: true });
|
|
1081
1380
|
}
|
|
1082
1381
|
catch (error) {
|
|
1382
|
+
// istanbul ignore next if
|
|
1083
1383
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1084
1384
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1085
1385
|
}
|
|
1086
1386
|
}
|
|
1087
1387
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1088
1388
|
}
|
|
1389
|
+
// Deregisters the process handlers
|
|
1089
1390
|
this.deregisterProcessHandlers();
|
|
1090
1391
|
if (restart) {
|
|
1091
1392
|
if (message === 'updating...') {
|
|
1092
1393
|
this.log.info('Cleanup completed. Updating...');
|
|
1093
1394
|
Matterbridge.instance = undefined;
|
|
1094
|
-
this.emit('update');
|
|
1395
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1095
1396
|
}
|
|
1096
1397
|
else if (message === 'restarting...') {
|
|
1097
1398
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1120,7 +1421,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1120
1421
|
this.log.debug('Cleanup already started...');
|
|
1121
1422
|
}
|
|
1122
1423
|
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Starts the Matterbridge in bridge mode.
|
|
1426
|
+
*
|
|
1427
|
+
* @private
|
|
1428
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1429
|
+
*/
|
|
1123
1430
|
async startBridge() {
|
|
1431
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1124
1432
|
if (!this.matterStorageManager)
|
|
1125
1433
|
throw new Error('No storage manager initialized');
|
|
1126
1434
|
if (!this.matterbridgeContext)
|
|
@@ -1159,13 +1467,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1159
1467
|
clearInterval(this.startMatterInterval);
|
|
1160
1468
|
this.startMatterInterval = undefined;
|
|
1161
1469
|
this.log.debug('Cleared startMatterInterval interval in bridge mode');
|
|
1162
|
-
|
|
1470
|
+
// Start the Matter server node
|
|
1471
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1472
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1163
1473
|
for (const device of this.devices.array()) {
|
|
1164
1474
|
if (device.mode === 'server' && device.serverNode) {
|
|
1165
1475
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1166
|
-
this.startServerNode(device.serverNode);
|
|
1476
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1167
1477
|
}
|
|
1168
1478
|
}
|
|
1479
|
+
// Configure the plugins
|
|
1169
1480
|
this.configureTimeout = setTimeout(async () => {
|
|
1170
1481
|
for (const plugin of this.plugins.array()) {
|
|
1171
1482
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1183,28 +1494,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1183
1494
|
}
|
|
1184
1495
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1185
1496
|
}, 30 * 1000).unref();
|
|
1497
|
+
// Setting reachability to true
|
|
1186
1498
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1187
1499
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1188
1500
|
if (this.aggregatorNode)
|
|
1189
1501
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1190
1502
|
}, 60 * 1000).unref();
|
|
1503
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1191
1504
|
this.emit('bridge_started');
|
|
1192
1505
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1193
1506
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1194
1507
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1195
1508
|
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1196
1509
|
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1512
|
+
*
|
|
1513
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1514
|
+
*
|
|
1515
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1516
|
+
*/
|
|
1197
1517
|
async startChildbridge(delay = 1000) {
|
|
1198
1518
|
if (!this.matterStorageManager)
|
|
1199
1519
|
throw new Error('No storage manager initialized');
|
|
1520
|
+
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1200
1521
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1201
1522
|
await this.startPlugins(true, false);
|
|
1523
|
+
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1202
1524
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1203
1525
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1204
1526
|
if (plugin.type === 'DynamicPlatform')
|
|
1205
1527
|
await this.createDynamicPlugin(plugin);
|
|
1206
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1528
|
+
this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
|
|
1207
1529
|
}
|
|
1530
|
+
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1208
1531
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1209
1532
|
let failCount = 0;
|
|
1210
1533
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1238,8 +1561,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1238
1561
|
clearInterval(this.startMatterInterval);
|
|
1239
1562
|
this.startMatterInterval = undefined;
|
|
1240
1563
|
if (delay > 0)
|
|
1241
|
-
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay);
|
|
1564
|
+
await wait(Number(process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS']) || delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1242
1565
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1566
|
+
// Configure the plugins
|
|
1243
1567
|
this.configureTimeout = setTimeout(async () => {
|
|
1244
1568
|
for (const plugin of this.plugins.array()) {
|
|
1245
1569
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1264,6 +1588,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1264
1588
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1265
1589
|
continue;
|
|
1266
1590
|
}
|
|
1591
|
+
// istanbul ignore next if cause is just a safety check
|
|
1267
1592
|
if (!plugin.serverNode) {
|
|
1268
1593
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1269
1594
|
continue;
|
|
@@ -1276,28 +1601,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1276
1601
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1277
1602
|
continue;
|
|
1278
1603
|
}
|
|
1279
|
-
|
|
1604
|
+
// Start the Matter server node
|
|
1605
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1606
|
+
// Setting reachability to true
|
|
1280
1607
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1281
1608
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1282
1609
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1283
1610
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1284
1611
|
}, 60 * 1000).unref();
|
|
1285
1612
|
}
|
|
1613
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1286
1614
|
for (const device of this.devices.array()) {
|
|
1287
1615
|
if (device.mode === 'server' && device.serverNode) {
|
|
1288
1616
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1289
|
-
this.startServerNode(device.serverNode);
|
|
1617
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1290
1618
|
}
|
|
1291
1619
|
}
|
|
1620
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1292
1621
|
this.emit('childbridge_started');
|
|
1293
1622
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1294
1623
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1295
1624
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1296
1625
|
}, Number(process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS']) || this.startMatterIntervalMs);
|
|
1297
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Starts the Matterbridge controller.
|
|
1629
|
+
*
|
|
1630
|
+
* @private
|
|
1631
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1632
|
+
*/
|
|
1298
1633
|
async startController() {
|
|
1634
|
+
/*
|
|
1635
|
+
if (!this.matterStorageManager) {
|
|
1636
|
+
this.log.error('No storage manager initialized');
|
|
1637
|
+
await this.cleanup('No storage manager initialized');
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1641
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1642
|
+
if (!this.controllerContext) {
|
|
1643
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1644
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1649
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1650
|
+
this.log.info('Creating matter commissioning controller');
|
|
1651
|
+
this.commissioningController = new CommissioningController({
|
|
1652
|
+
autoConnect: false,
|
|
1653
|
+
});
|
|
1654
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1655
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1656
|
+
|
|
1657
|
+
this.log.info('Starting matter server');
|
|
1658
|
+
await this.matterServer.start();
|
|
1659
|
+
this.log.info('Matter server started');
|
|
1660
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1661
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1662
|
+
regulatoryCountryCode: 'XX',
|
|
1663
|
+
};
|
|
1664
|
+
const commissioningController = new CommissioningController({
|
|
1665
|
+
environment: {
|
|
1666
|
+
environment,
|
|
1667
|
+
id: uniqueId,
|
|
1668
|
+
},
|
|
1669
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1670
|
+
adminFabricLabel,
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
if (hasParameter('pairingcode')) {
|
|
1674
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1675
|
+
const pairingCode = getParameter('pairingcode');
|
|
1676
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1677
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1678
|
+
|
|
1679
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1680
|
+
if (pairingCode !== undefined) {
|
|
1681
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1682
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1683
|
+
longDiscriminator = undefined;
|
|
1684
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1685
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1686
|
+
} else {
|
|
1687
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1688
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1689
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1690
|
+
}
|
|
1691
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1692
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const options = {
|
|
1696
|
+
commissioning: commissioningOptions,
|
|
1697
|
+
discovery: {
|
|
1698
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1699
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1700
|
+
},
|
|
1701
|
+
passcode: setupPin,
|
|
1702
|
+
} as NodeCommissioningOptions;
|
|
1703
|
+
this.log.info('Commissioning with options:', options);
|
|
1704
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1705
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1706
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1707
|
+
} // (hasParameter('pairingcode'))
|
|
1708
|
+
|
|
1709
|
+
if (hasParameter('unpairall')) {
|
|
1710
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1711
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1712
|
+
for (const nodeId of nodeIds) {
|
|
1713
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1714
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1715
|
+
}
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (hasParameter('discover')) {
|
|
1720
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1721
|
+
// console.log(discover);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1725
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1730
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1731
|
+
for (const nodeId of nodeIds) {
|
|
1732
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1733
|
+
|
|
1734
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1735
|
+
autoSubscribe: false,
|
|
1736
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1737
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1738
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1739
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1740
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1741
|
+
switch (info) {
|
|
1742
|
+
case NodeStateInformation.Connected:
|
|
1743
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1744
|
+
break;
|
|
1745
|
+
case NodeStateInformation.Disconnected:
|
|
1746
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1747
|
+
break;
|
|
1748
|
+
case NodeStateInformation.Reconnecting:
|
|
1749
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1750
|
+
break;
|
|
1751
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1752
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1753
|
+
break;
|
|
1754
|
+
case NodeStateInformation.StructureChanged:
|
|
1755
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1756
|
+
break;
|
|
1757
|
+
case NodeStateInformation.Decommissioned:
|
|
1758
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1759
|
+
break;
|
|
1760
|
+
default:
|
|
1761
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1762
|
+
break;
|
|
1763
|
+
}
|
|
1764
|
+
},
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
node.logStructure();
|
|
1768
|
+
|
|
1769
|
+
// Get the interaction client
|
|
1770
|
+
this.log.info('Getting the interaction client');
|
|
1771
|
+
const interactionClient = await node.getInteractionClient();
|
|
1772
|
+
let cluster;
|
|
1773
|
+
let attributes;
|
|
1774
|
+
|
|
1775
|
+
// Log BasicInformationCluster
|
|
1776
|
+
cluster = BasicInformationCluster;
|
|
1777
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1778
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1779
|
+
});
|
|
1780
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1781
|
+
attributes.forEach((attribute) => {
|
|
1782
|
+
this.log.info(
|
|
1783
|
+
`- 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}`,
|
|
1784
|
+
);
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
// Log PowerSourceCluster
|
|
1788
|
+
cluster = PowerSourceCluster;
|
|
1789
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1790
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1791
|
+
});
|
|
1792
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1793
|
+
attributes.forEach((attribute) => {
|
|
1794
|
+
this.log.info(
|
|
1795
|
+
`- 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}`,
|
|
1796
|
+
);
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1799
|
+
// Log ThreadNetworkDiagnostics
|
|
1800
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1801
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1802
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1803
|
+
});
|
|
1804
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1805
|
+
attributes.forEach((attribute) => {
|
|
1806
|
+
this.log.info(
|
|
1807
|
+
`- 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}`,
|
|
1808
|
+
);
|
|
1809
|
+
});
|
|
1810
|
+
|
|
1811
|
+
// Log SwitchCluster
|
|
1812
|
+
cluster = SwitchCluster;
|
|
1813
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1814
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1815
|
+
});
|
|
1816
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1817
|
+
attributes.forEach((attribute) => {
|
|
1818
|
+
this.log.info(
|
|
1819
|
+
`- 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}`,
|
|
1820
|
+
);
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1824
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1825
|
+
ignoreInitialTriggers: false,
|
|
1826
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1827
|
+
this.log.info(
|
|
1828
|
+
`***${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}`,
|
|
1829
|
+
),
|
|
1830
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1831
|
+
this.log.info(
|
|
1832
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1833
|
+
);
|
|
1834
|
+
},
|
|
1835
|
+
});
|
|
1836
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1837
|
+
}
|
|
1838
|
+
*/
|
|
1299
1839
|
}
|
|
1840
|
+
/** */
|
|
1841
|
+
/** Matter.js methods */
|
|
1842
|
+
/** */
|
|
1843
|
+
/**
|
|
1844
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1845
|
+
*
|
|
1846
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1847
|
+
*/
|
|
1300
1848
|
async startMatterStorage() {
|
|
1849
|
+
// Setup Matter storage
|
|
1301
1850
|
this.log.info(`Starting matter node storage...`);
|
|
1302
1851
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1303
1852
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1305,8 +1854,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1305
1854
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1306
1855
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1307
1856
|
this.log.info('Matter node storage started');
|
|
1857
|
+
// Backup matter storage since it is created/opened correctly
|
|
1308
1858
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1309
1859
|
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1862
|
+
*
|
|
1863
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1864
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1865
|
+
* @private
|
|
1866
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1867
|
+
*/
|
|
1310
1868
|
async backupMatterStorage(storageName, backupName) {
|
|
1311
1869
|
this.log.info('Creating matter node storage backup...');
|
|
1312
1870
|
try {
|
|
@@ -1317,6 +1875,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1317
1875
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1318
1876
|
}
|
|
1319
1877
|
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Stops the matter storage.
|
|
1880
|
+
*
|
|
1881
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1882
|
+
*/
|
|
1320
1883
|
async stopMatterStorage() {
|
|
1321
1884
|
this.log.info('Closing matter node storage...');
|
|
1322
1885
|
await this.matterStorageManager?.close();
|
|
@@ -1325,6 +1888,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1325
1888
|
this.matterbridgeContext = undefined;
|
|
1326
1889
|
this.log.info('Matter node storage closed');
|
|
1327
1890
|
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Creates a server node storage context.
|
|
1893
|
+
*
|
|
1894
|
+
* @param {string} storeId - The storeId.
|
|
1895
|
+
* @param {string} deviceName - The name of the device.
|
|
1896
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1897
|
+
* @param {number} vendorId - The vendor ID.
|
|
1898
|
+
* @param {string} vendorName - The vendor name.
|
|
1899
|
+
* @param {number} productId - The product ID.
|
|
1900
|
+
* @param {string} productName - The product name.
|
|
1901
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1902
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1903
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1904
|
+
*/
|
|
1328
1905
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1329
1906
|
const { randomBytes } = await import('node:crypto');
|
|
1330
1907
|
if (!this.matterStorageService)
|
|
@@ -1364,6 +1941,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1364
1941
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1365
1942
|
return storageContext;
|
|
1366
1943
|
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Creates a server node.
|
|
1946
|
+
*
|
|
1947
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1948
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
1949
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
1950
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
1951
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1952
|
+
*/
|
|
1367
1953
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1368
1954
|
const storeId = await storageContext.get('storeId');
|
|
1369
1955
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1373,25 +1959,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1373
1959
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1374
1960
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1375
1961
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1962
|
+
/**
|
|
1963
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1964
|
+
*/
|
|
1376
1965
|
const serverNode = await ServerNode.create({
|
|
1966
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1377
1967
|
id: storeId,
|
|
1968
|
+
// Environment to run the server node in
|
|
1378
1969
|
environment: this.environment,
|
|
1970
|
+
// Provide Network relevant configuration like the port
|
|
1379
1971
|
network: {
|
|
1380
1972
|
listeningAddressIpv4: this.ipv4Address,
|
|
1381
1973
|
listeningAddressIpv6: this.ipv6Address,
|
|
1382
1974
|
port,
|
|
1383
1975
|
},
|
|
1976
|
+
// Provide the certificate for the device
|
|
1384
1977
|
operationalCredentials: {
|
|
1385
1978
|
certification: this.certification,
|
|
1386
1979
|
},
|
|
1980
|
+
// Provide Commissioning relevant settings
|
|
1387
1981
|
commissioning: {
|
|
1388
1982
|
passcode,
|
|
1389
1983
|
discriminator,
|
|
1390
1984
|
},
|
|
1985
|
+
// Provide Node announcement settings
|
|
1391
1986
|
productDescription: {
|
|
1392
1987
|
name: await storageContext.get('deviceName'),
|
|
1393
1988
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1394
1989
|
},
|
|
1990
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1395
1991
|
basicInformation: {
|
|
1396
1992
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1397
1993
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1408,17 +2004,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1408
2004
|
reachable: true,
|
|
1409
2005
|
},
|
|
1410
2006
|
});
|
|
2007
|
+
/**
|
|
2008
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2009
|
+
* This means: It is added to the first fabric.
|
|
2010
|
+
*/
|
|
1411
2011
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1412
2012
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1413
2013
|
this.advertisingNodes.delete(storeId);
|
|
1414
2014
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1415
2015
|
});
|
|
2016
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1416
2017
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1417
2018
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1418
2019
|
this.advertisingNodes.delete(storeId);
|
|
1419
2020
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1420
2021
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1421
2022
|
});
|
|
2023
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1422
2024
|
serverNode.lifecycle.online.on(async () => {
|
|
1423
2025
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1424
2026
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1429,13 +2031,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1429
2031
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1430
2032
|
}
|
|
1431
2033
|
else {
|
|
2034
|
+
// istanbul ignore next
|
|
1432
2035
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2036
|
+
// istanbul ignore next
|
|
1433
2037
|
this.advertisingNodes.delete(storeId);
|
|
1434
2038
|
}
|
|
1435
2039
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1436
2040
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1437
2041
|
this.emit('online', storeId);
|
|
1438
2042
|
});
|
|
2043
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1439
2044
|
serverNode.lifecycle.offline.on(() => {
|
|
1440
2045
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1441
2046
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1443,11 +2048,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1443
2048
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1444
2049
|
this.emit('offline', storeId);
|
|
1445
2050
|
});
|
|
2051
|
+
/**
|
|
2052
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2053
|
+
* information is needed.
|
|
2054
|
+
*/
|
|
1446
2055
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1447
2056
|
let action = '';
|
|
1448
2057
|
switch (fabricAction) {
|
|
1449
2058
|
case FabricAction.Added:
|
|
1450
|
-
this.advertisingNodes.delete(storeId);
|
|
2059
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1451
2060
|
action = 'added';
|
|
1452
2061
|
break;
|
|
1453
2062
|
case FabricAction.Removed:
|
|
@@ -1460,14 +2069,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1460
2069
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1461
2070
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1462
2071
|
});
|
|
2072
|
+
/**
|
|
2073
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2074
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2075
|
+
*/
|
|
1463
2076
|
serverNode.events.sessions.opened.on((session) => {
|
|
1464
2077
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1465
2078
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1466
2079
|
});
|
|
2080
|
+
/**
|
|
2081
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2082
|
+
*/
|
|
1467
2083
|
serverNode.events.sessions.closed.on((session) => {
|
|
1468
2084
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1469
2085
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1470
2086
|
});
|
|
2087
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1471
2088
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1472
2089
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1473
2090
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1475,6 +2092,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1475
2092
|
this.log.info(`Created server node for ${storeId}`);
|
|
1476
2093
|
return serverNode;
|
|
1477
2094
|
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Gets the matter sanitized data of the specified server node.
|
|
2097
|
+
*
|
|
2098
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2099
|
+
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2100
|
+
*/
|
|
1478
2101
|
getServerNodeData(serverNode) {
|
|
1479
2102
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1480
2103
|
return {
|
|
@@ -1491,12 +2114,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1491
2114
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1492
2115
|
};
|
|
1493
2116
|
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Starts the specified server node.
|
|
2119
|
+
*
|
|
2120
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2121
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2122
|
+
*/
|
|
1494
2123
|
async startServerNode(matterServerNode) {
|
|
1495
2124
|
if (!matterServerNode)
|
|
1496
2125
|
return;
|
|
1497
2126
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1498
2127
|
await matterServerNode.start();
|
|
1499
2128
|
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Stops the specified server node.
|
|
2131
|
+
*
|
|
2132
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2133
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2134
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2135
|
+
*/
|
|
1500
2136
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1501
2137
|
if (!matterServerNode)
|
|
1502
2138
|
return;
|
|
@@ -1509,12 +2145,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1509
2145
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1510
2146
|
}
|
|
1511
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Creates an aggregator node with the specified storage context.
|
|
2150
|
+
*
|
|
2151
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2152
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2153
|
+
*/
|
|
1512
2154
|
async createAggregatorNode(storageContext) {
|
|
1513
2155
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1514
2156
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1515
2157
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1516
2158
|
return aggregatorNode;
|
|
1517
2159
|
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2162
|
+
*
|
|
2163
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2164
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2165
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2166
|
+
*/
|
|
1518
2167
|
async createAccessoryPlugin(plugin, device) {
|
|
1519
2168
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1520
2169
|
plugin.locked = true;
|
|
@@ -1526,6 +2175,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1526
2175
|
await plugin.serverNode.add(device);
|
|
1527
2176
|
}
|
|
1528
2177
|
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2180
|
+
*
|
|
2181
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2182
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2183
|
+
*/
|
|
1529
2184
|
async createDynamicPlugin(plugin) {
|
|
1530
2185
|
if (!plugin.locked) {
|
|
1531
2186
|
plugin.locked = true;
|
|
@@ -1538,6 +2193,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1538
2193
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1539
2194
|
}
|
|
1540
2195
|
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Creates and configures the server node for a single not bridged device.
|
|
2198
|
+
*
|
|
2199
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2200
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2201
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2202
|
+
*/
|
|
1541
2203
|
async createDeviceServerNode(plugin, device) {
|
|
1542
2204
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1543
2205
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1548,7 +2210,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1548
2210
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1549
2211
|
}
|
|
1550
2212
|
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2215
|
+
*
|
|
2216
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2217
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2218
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2219
|
+
*/
|
|
1551
2220
|
async addBridgedEndpoint(pluginName, device) {
|
|
2221
|
+
// Check if the plugin is registered
|
|
1552
2222
|
const plugin = this.plugins.get(pluginName);
|
|
1553
2223
|
if (!plugin) {
|
|
1554
2224
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1568,6 +2238,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1568
2238
|
}
|
|
1569
2239
|
else if (this.bridgeMode === 'bridge') {
|
|
1570
2240
|
if (device.mode === 'matter') {
|
|
2241
|
+
// Register and add the device to the matterbridge server node
|
|
1571
2242
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1572
2243
|
if (!this.serverNode) {
|
|
1573
2244
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1584,6 +2255,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1584
2255
|
}
|
|
1585
2256
|
}
|
|
1586
2257
|
else {
|
|
2258
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1587
2259
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1588
2260
|
if (!this.aggregatorNode) {
|
|
1589
2261
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1601,6 +2273,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1601
2273
|
}
|
|
1602
2274
|
}
|
|
1603
2275
|
else if (this.bridgeMode === 'childbridge') {
|
|
2276
|
+
// Register and add the device to the plugin server node
|
|
1604
2277
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1605
2278
|
try {
|
|
1606
2279
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1624,10 +2297,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1624
2297
|
return;
|
|
1625
2298
|
}
|
|
1626
2299
|
}
|
|
2300
|
+
// Register and add the device to the plugin aggregator node
|
|
1627
2301
|
if (plugin.type === 'DynamicPlatform') {
|
|
1628
2302
|
try {
|
|
1629
2303
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1630
2304
|
await this.createDynamicPlugin(plugin);
|
|
2305
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1631
2306
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1632
2307
|
if (!plugin.aggregatorNode) {
|
|
1633
2308
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1648,17 +2323,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1648
2323
|
}
|
|
1649
2324
|
if (plugin.registeredDevices !== undefined)
|
|
1650
2325
|
plugin.registeredDevices++;
|
|
2326
|
+
// Add the device to the DeviceManager
|
|
1651
2327
|
this.devices.set(device);
|
|
2328
|
+
// Subscribe to the attributes changed event
|
|
1652
2329
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1653
2330
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1654
2331
|
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2334
|
+
*
|
|
2335
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2336
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2337
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2338
|
+
*/
|
|
1655
2339
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1656
2340
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2341
|
+
// Check if the plugin is registered
|
|
1657
2342
|
const plugin = this.plugins.get(pluginName);
|
|
1658
2343
|
if (!plugin) {
|
|
1659
2344
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1660
2345
|
return;
|
|
1661
2346
|
}
|
|
2347
|
+
// Unregister and remove the device from the matterbridge aggregator node
|
|
1662
2348
|
if (this.bridgeMode === 'bridge') {
|
|
1663
2349
|
if (!this.aggregatorNode) {
|
|
1664
2350
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1671,8 +2357,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1671
2357
|
}
|
|
1672
2358
|
else if (this.bridgeMode === 'childbridge') {
|
|
1673
2359
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2360
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1674
2361
|
}
|
|
1675
2362
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2363
|
+
// Unregister and remove the device from the plugin aggregator node
|
|
1676
2364
|
if (!plugin.aggregatorNode) {
|
|
1677
2365
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
1678
2366
|
return;
|
|
@@ -1683,8 +2371,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1683
2371
|
if (plugin.registeredDevices !== undefined)
|
|
1684
2372
|
plugin.registeredDevices--;
|
|
1685
2373
|
}
|
|
2374
|
+
// Remove the device from the DeviceManager
|
|
1686
2375
|
this.devices.remove(device);
|
|
1687
2376
|
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2379
|
+
*
|
|
2380
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2381
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2382
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2383
|
+
*
|
|
2384
|
+
* @remarks
|
|
2385
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2386
|
+
* It also applies a delay between each removal if specified.
|
|
2387
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2388
|
+
*/
|
|
1688
2389
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1689
2390
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1690
2391
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1695,13 +2396,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1695
2396
|
if (delay > 0)
|
|
1696
2397
|
await wait(2000);
|
|
1697
2398
|
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2401
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2402
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2403
|
+
*
|
|
2404
|
+
* @param {Plugin} plugin - The plugin associated with the device.
|
|
2405
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2406
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2407
|
+
*/
|
|
1698
2408
|
async subscribeAttributeChanged(plugin, device) {
|
|
1699
2409
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
1700
2410
|
return;
|
|
1701
2411
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2412
|
+
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
1702
2413
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1703
2414
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1704
2415
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2416
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1705
2417
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
1706
2418
|
});
|
|
1707
2419
|
}
|
|
@@ -1751,6 +2463,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1751
2463
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1752
2464
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1753
2465
|
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}`);
|
|
2466
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1754
2467
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
1755
2468
|
});
|
|
1756
2469
|
}
|
|
@@ -1759,12 +2472,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1759
2472
|
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...`);
|
|
1760
2473
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1761
2474
|
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}`);
|
|
2475
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1762
2476
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1763
2477
|
});
|
|
1764
2478
|
}
|
|
1765
2479
|
}
|
|
1766
2480
|
}
|
|
1767
2481
|
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2484
|
+
*
|
|
2485
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2486
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2487
|
+
*/
|
|
1768
2488
|
sanitizeFabricInformations(fabricInfo) {
|
|
1769
2489
|
return fabricInfo.map((info) => {
|
|
1770
2490
|
return {
|
|
@@ -1778,6 +2498,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1778
2498
|
};
|
|
1779
2499
|
});
|
|
1780
2500
|
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2503
|
+
*
|
|
2504
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2505
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2506
|
+
*/
|
|
1781
2507
|
sanitizeSessionInformation(sessions) {
|
|
1782
2508
|
return sessions
|
|
1783
2509
|
.filter((session) => session.isPeerActive)
|
|
@@ -1804,7 +2530,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1804
2530
|
};
|
|
1805
2531
|
});
|
|
1806
2532
|
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2535
|
+
*
|
|
2536
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2537
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2538
|
+
*/
|
|
2539
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1807
2540
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2541
|
+
/*
|
|
2542
|
+
for (const child of aggregatorNode.parts) {
|
|
2543
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2544
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2545
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2546
|
+
}
|
|
2547
|
+
*/
|
|
1808
2548
|
}
|
|
1809
2549
|
getVendorIdName = (vendorId) => {
|
|
1810
2550
|
if (!vendorId)
|
|
@@ -1844,10 +2584,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1844
2584
|
case 0x1488:
|
|
1845
2585
|
vendorName = '(ShortcutLabsFlic)';
|
|
1846
2586
|
break;
|
|
1847
|
-
case 65521:
|
|
2587
|
+
case 65521: // 0xFFF1
|
|
1848
2588
|
vendorName = '(MatterTest)';
|
|
1849
2589
|
break;
|
|
1850
2590
|
}
|
|
1851
2591
|
return vendorName;
|
|
1852
2592
|
};
|
|
1853
2593
|
}
|
|
2594
|
+
//# sourceMappingURL=matterbridge.js.map
|