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