matterbridge 3.3.7-dev-20251109-a306ab9 → 3.3.7
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 +42 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +24 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +238 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +451 -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 +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.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 +478 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +828 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +42 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +37 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +2404 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +770 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +638 -17
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +42 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +37 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1556 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1408 -52
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +758 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +464 -19
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +402 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +341 -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/jestHelpers.d.ts +139 -0
- package/dist/utils/jestHelpers.d.ts.map +1 -0
- package/dist/utils/jestHelpers.js +153 -3
- package/dist/utils/jestHelpers.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,62 +247,131 @@ export class Matterbridge extends EventEmitter {
|
|
|
171
247
|
}
|
|
172
248
|
return Matterbridge.instance;
|
|
173
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Call cleanup() and dispose MdnsService. Will be removed since matter.js 0.15.6 dispose MdnsService.
|
|
252
|
+
*
|
|
253
|
+
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
254
|
+
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
255
|
+
*
|
|
256
|
+
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
257
|
+
*/
|
|
174
258
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
175
259
|
this.log.info(`Destroy instance...`);
|
|
260
|
+
// Save server nodes to close
|
|
261
|
+
/*
|
|
262
|
+
const servers: ServerNode<ServerNode.RootEndpoint>[] = [];
|
|
263
|
+
if (this.bridgeMode === 'bridge') {
|
|
264
|
+
if (this.serverNode) servers.push(this.serverNode);
|
|
265
|
+
}
|
|
266
|
+
if (this.bridgeMode === 'childbridge' && this.plugins !== undefined) {
|
|
267
|
+
for (const plugin of this.plugins.array()) {
|
|
268
|
+
if (plugin.serverNode) servers.push(plugin.serverNode);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (this.devices !== undefined) {
|
|
272
|
+
for (const device of this.devices.array()) {
|
|
273
|
+
if (device.mode === 'server' && device.serverNode) servers.push(device.serverNode);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
*/
|
|
277
|
+
// Let any already‐queued microtasks run first
|
|
278
|
+
// await Promise.resolve();
|
|
279
|
+
// Wait for the cleanup to finish
|
|
280
|
+
// await wait(pause, 'destroyInstance start', true);
|
|
281
|
+
// Cleanup
|
|
176
282
|
await this.cleanup('destroying instance...', false, timeout);
|
|
283
|
+
// Close servers mdns service
|
|
284
|
+
/*
|
|
285
|
+
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
286
|
+
for (const server of servers) {
|
|
287
|
+
// await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
288
|
+
this.log.info(`Closed ${server.id} MdnsService`);
|
|
289
|
+
}
|
|
290
|
+
*/
|
|
291
|
+
// Let any already‐queued microtasks run first
|
|
292
|
+
// await Promise.resolve();
|
|
293
|
+
// Wait for the cleanup to finish
|
|
177
294
|
if (pause)
|
|
178
295
|
await wait(pause, 'destroyInstance stop', true);
|
|
179
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Initializes the Matterbridge application.
|
|
299
|
+
*
|
|
300
|
+
* @remarks
|
|
301
|
+
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
302
|
+
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
303
|
+
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
304
|
+
*
|
|
305
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
306
|
+
*/
|
|
180
307
|
async initialize() {
|
|
308
|
+
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
309
|
+
// Emit the initialize_started event
|
|
181
310
|
this.emit('initialize_started');
|
|
311
|
+
// Set the restart mode
|
|
182
312
|
if (hasParameter('service'))
|
|
183
313
|
this.restartMode = 'service';
|
|
184
314
|
if (hasParameter('docker'))
|
|
185
315
|
this.restartMode = 'docker';
|
|
316
|
+
// Set the matterbridge home directory
|
|
186
317
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
187
318
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
319
|
+
// Set the matterbridge directory
|
|
188
320
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
189
321
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
190
322
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
191
323
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
324
|
+
// Set the matterbridge plugin directory
|
|
192
325
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
193
326
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
327
|
+
// Set the matterbridge cert directory
|
|
194
328
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
195
329
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
330
|
+
// Set the matterbridge root directory
|
|
196
331
|
const { fileURLToPath } = await import('node:url');
|
|
197
332
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
198
|
-
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
333
|
+
this.rootDirectory = path.resolve(currentFileDirectory, '../'); // Adjust the path for dist directory
|
|
334
|
+
// Setup the matter environment with default values
|
|
199
335
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
200
336
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
201
337
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
202
338
|
this.environment.vars.set('runtime.signals', false);
|
|
203
339
|
this.environment.vars.set('runtime.exitcode', false);
|
|
340
|
+
// Register process handlers
|
|
204
341
|
this.registerProcessHandlers();
|
|
342
|
+
// Initialize nodeStorage and nodeContext
|
|
205
343
|
try {
|
|
206
344
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
207
345
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
208
346
|
this.log.debug('Creating node storage context for matterbridge');
|
|
209
347
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
348
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
349
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
210
350
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
211
351
|
for (const key of keys) {
|
|
212
352
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
354
|
await this.nodeStorage?.storage.get(key);
|
|
214
355
|
}
|
|
215
356
|
const storages = await this.nodeStorage.getStorageNames();
|
|
216
357
|
for (const storage of storages) {
|
|
217
358
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
218
359
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
360
|
+
// TODO: Remove this code when node-persist-manager is updated
|
|
361
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
362
|
const keys = (await nodeContext?.storage.keys());
|
|
220
363
|
keys.forEach(async (key) => {
|
|
221
364
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
222
365
|
await nodeContext?.get(key);
|
|
223
366
|
});
|
|
224
367
|
}
|
|
368
|
+
// Creating a backup of the node storage since it is not corrupted
|
|
225
369
|
this.log.debug('Creating node storage backup...');
|
|
226
370
|
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
227
371
|
this.log.debug('Created node storage backup');
|
|
228
372
|
}
|
|
229
373
|
catch (error) {
|
|
374
|
+
// Restoring the backup of the node storage since it is corrupted
|
|
230
375
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
231
376
|
if (hasParameter('norestore')) {
|
|
232
377
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -240,14 +385,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
240
385
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
241
386
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
242
387
|
}
|
|
388
|
+
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
243
389
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
390
|
+
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
244
391
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
392
|
+
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
245
393
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
394
|
+
// Certificate management
|
|
246
395
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
247
396
|
try {
|
|
248
397
|
await fs.promises.access(pairingFilePath, fs.constants.R_OK);
|
|
249
398
|
const pairingFileContent = await fs.promises.readFile(pairingFilePath, 'utf8');
|
|
250
399
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
400
|
+
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
251
401
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
252
402
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
253
403
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -276,11 +426,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
276
426
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
277
427
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
278
428
|
}
|
|
429
|
+
// Override the passcode and discriminator if they are present in the pairing file
|
|
279
430
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
280
431
|
this.passcode = pairingFileJson.passcode;
|
|
281
432
|
this.discriminator = pairingFileJson.discriminator;
|
|
282
433
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
283
434
|
}
|
|
435
|
+
// Set the certification for matter.js if it is present in the pairing file
|
|
284
436
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
285
437
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
286
438
|
this.certification = {
|
|
@@ -295,40 +447,43 @@ export class Matterbridge extends EventEmitter {
|
|
|
295
447
|
catch (error) {
|
|
296
448
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
297
449
|
}
|
|
450
|
+
// Store the passcode, discriminator and port in the node context
|
|
298
451
|
await this.nodeContext.set('matterport', this.port);
|
|
299
452
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
300
453
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
301
454
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
455
|
+
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
302
456
|
if (hasParameter('logger')) {
|
|
303
457
|
const level = getParameter('logger');
|
|
304
458
|
if (level === 'debug') {
|
|
305
|
-
this.log.logLevel = "debug"
|
|
459
|
+
this.log.logLevel = "debug" /* LogLevel.DEBUG */;
|
|
306
460
|
}
|
|
307
461
|
else if (level === 'info') {
|
|
308
|
-
this.log.logLevel = "info"
|
|
462
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
309
463
|
}
|
|
310
464
|
else if (level === 'notice') {
|
|
311
|
-
this.log.logLevel = "notice"
|
|
465
|
+
this.log.logLevel = "notice" /* LogLevel.NOTICE */;
|
|
312
466
|
}
|
|
313
467
|
else if (level === 'warn') {
|
|
314
|
-
this.log.logLevel = "warn"
|
|
468
|
+
this.log.logLevel = "warn" /* LogLevel.WARN */;
|
|
315
469
|
}
|
|
316
470
|
else if (level === 'error') {
|
|
317
|
-
this.log.logLevel = "error"
|
|
471
|
+
this.log.logLevel = "error" /* LogLevel.ERROR */;
|
|
318
472
|
}
|
|
319
473
|
else if (level === 'fatal') {
|
|
320
|
-
this.log.logLevel = "fatal"
|
|
474
|
+
this.log.logLevel = "fatal" /* LogLevel.FATAL */;
|
|
321
475
|
}
|
|
322
476
|
else {
|
|
323
477
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
324
|
-
this.log.logLevel = "info"
|
|
478
|
+
this.log.logLevel = "info" /* LogLevel.INFO */;
|
|
325
479
|
}
|
|
326
480
|
}
|
|
327
481
|
else {
|
|
328
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
482
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" /* LogLevel.NOTICE */ : "info" /* LogLevel.INFO */);
|
|
329
483
|
}
|
|
330
484
|
this.frontend.logLevel = this.log.logLevel;
|
|
331
485
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
486
|
+
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
332
487
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
333
488
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
334
489
|
this.fileLogger = true;
|
|
@@ -337,6 +492,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
337
492
|
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
338
493
|
if (this.profile !== undefined)
|
|
339
494
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
495
|
+
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
340
496
|
if (hasParameter('matterlogger')) {
|
|
341
497
|
const level = getParameter('matterlogger');
|
|
342
498
|
if (level === 'debug') {
|
|
@@ -366,11 +522,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
366
522
|
Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
367
523
|
}
|
|
368
524
|
Logger.format = MatterLogFormat.ANSI;
|
|
525
|
+
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
369
526
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
370
527
|
this.matterFileLogger = true;
|
|
371
528
|
}
|
|
372
529
|
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
373
530
|
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
|
|
531
|
+
// Log network interfaces
|
|
374
532
|
const networkInterfaces = os.networkInterfaces();
|
|
375
533
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
376
534
|
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
@@ -383,6 +541,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
383
541
|
});
|
|
384
542
|
}
|
|
385
543
|
}
|
|
544
|
+
// Set the interface to use for matter server node mdnsInterface
|
|
386
545
|
if (hasParameter('mdnsinterface')) {
|
|
387
546
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
388
547
|
}
|
|
@@ -391,6 +550,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
391
550
|
if (this.mdnsInterface === '')
|
|
392
551
|
this.mdnsInterface = undefined;
|
|
393
552
|
}
|
|
553
|
+
// Validate mdnsInterface
|
|
394
554
|
if (this.mdnsInterface) {
|
|
395
555
|
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
396
556
|
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
@@ -403,6 +563,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
403
563
|
}
|
|
404
564
|
if (this.mdnsInterface)
|
|
405
565
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
566
|
+
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
406
567
|
if (hasParameter('ipv4address')) {
|
|
407
568
|
this.ipv4Address = getParameter('ipv4address');
|
|
408
569
|
}
|
|
@@ -411,6 +572,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
411
572
|
if (this.ipv4Address === '')
|
|
412
573
|
this.ipv4Address = undefined;
|
|
413
574
|
}
|
|
575
|
+
// Validate ipv4address
|
|
414
576
|
if (this.ipv4Address) {
|
|
415
577
|
let isValid = false;
|
|
416
578
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -426,6 +588,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
426
588
|
await this.nodeContext.remove('matteripv4address');
|
|
427
589
|
}
|
|
428
590
|
}
|
|
591
|
+
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
429
592
|
if (hasParameter('ipv6address')) {
|
|
430
593
|
this.ipv6Address = getParameter('ipv6address');
|
|
431
594
|
}
|
|
@@ -434,6 +597,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
434
597
|
if (this.ipv6Address === '')
|
|
435
598
|
this.ipv6Address = undefined;
|
|
436
599
|
}
|
|
600
|
+
// Validate ipv6address
|
|
437
601
|
if (this.ipv6Address) {
|
|
438
602
|
let isValid = false;
|
|
439
603
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
@@ -442,6 +606,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
442
606
|
isValid = true;
|
|
443
607
|
break;
|
|
444
608
|
}
|
|
609
|
+
/* istanbul ignore next */
|
|
445
610
|
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
446
611
|
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
447
612
|
isValid = true;
|
|
@@ -454,6 +619,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
454
619
|
await this.nodeContext.remove('matteripv6address');
|
|
455
620
|
}
|
|
456
621
|
}
|
|
622
|
+
// Initialize the virtual mode
|
|
457
623
|
if (hasParameter('novirtual')) {
|
|
458
624
|
this.virtualMode = 'disabled';
|
|
459
625
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
@@ -462,10 +628,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
462
628
|
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
463
629
|
}
|
|
464
630
|
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
631
|
+
// Initialize PluginManager
|
|
465
632
|
this.plugins.logLevel = this.log.logLevel;
|
|
466
633
|
await this.plugins.loadFromStorage();
|
|
634
|
+
// Initialize DeviceManager
|
|
467
635
|
this.devices.logLevel = this.log.logLevel;
|
|
636
|
+
// Get the plugins from node storage and create the plugins node storage contexts
|
|
468
637
|
for (const plugin of this.plugins) {
|
|
638
|
+
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
639
|
+
// We don't do this when the add and other shutdown parameters are set because we shut down the process after adding the plugin
|
|
469
640
|
if (!fs.existsSync(plugin.path) && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
470
641
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
|
|
471
642
|
try {
|
|
@@ -496,6 +667,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
496
667
|
await plugin.nodeContext.set('description', plugin.description);
|
|
497
668
|
await plugin.nodeContext.set('author', plugin.author);
|
|
498
669
|
}
|
|
670
|
+
// Log system info and create .matterbridge directory
|
|
499
671
|
await this.logNodeAndSystemInfo();
|
|
500
672
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
501
673
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -503,6 +675,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
503
675
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
504
676
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
505
677
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
678
|
+
// Check node version and throw error
|
|
506
679
|
const minNodeVersion = 20;
|
|
507
680
|
const nodeVersion = process.versions.node;
|
|
508
681
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -510,10 +683,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
510
683
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
511
684
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
512
685
|
}
|
|
686
|
+
// Parse command line
|
|
513
687
|
await this.parseCommandLine();
|
|
688
|
+
// Emit the initialize_completed event
|
|
514
689
|
this.emit('initialize_completed');
|
|
515
690
|
this.initialized = true;
|
|
516
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* Parses the command line arguments and performs the corresponding actions.
|
|
694
|
+
*
|
|
695
|
+
* @private
|
|
696
|
+
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
697
|
+
*/
|
|
517
698
|
async parseCommandLine() {
|
|
518
699
|
if (hasParameter('list')) {
|
|
519
700
|
this.log.info(`│ Registered plugins (${this.plugins.length})`);
|
|
@@ -529,6 +710,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
529
710
|
}
|
|
530
711
|
index++;
|
|
531
712
|
}
|
|
713
|
+
/*
|
|
714
|
+
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
715
|
+
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
716
|
+
serializedRegisteredDevices?.forEach((device, index) => {
|
|
717
|
+
if (index !== serializedRegisteredDevices.length - 1) {
|
|
718
|
+
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
719
|
+
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
720
|
+
} else {
|
|
721
|
+
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
722
|
+
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
*/
|
|
532
726
|
this.shutdown = true;
|
|
533
727
|
return;
|
|
534
728
|
}
|
|
@@ -578,8 +772,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
578
772
|
this.shutdown = true;
|
|
579
773
|
return;
|
|
580
774
|
}
|
|
775
|
+
// Initialize frontend
|
|
581
776
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
582
777
|
await this.frontend.start(getIntParameter('frontend'));
|
|
778
|
+
// Start the matter storage and create the matterbridge context
|
|
583
779
|
try {
|
|
584
780
|
await this.startMatterStorage();
|
|
585
781
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -593,18 +789,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
593
789
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
594
790
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
595
791
|
}
|
|
792
|
+
// Clear the matterbridge context if the reset parameter is set (bridge mode)
|
|
596
793
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
597
794
|
this.initialized = true;
|
|
598
795
|
await this.shutdownProcessAndReset();
|
|
599
796
|
this.shutdown = true;
|
|
600
797
|
return;
|
|
601
798
|
}
|
|
799
|
+
// Clear matterbridge plugin context if the reset parameter is set (childbridge mode)
|
|
602
800
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
603
801
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
604
802
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
605
803
|
if (plugin) {
|
|
606
804
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
607
805
|
if (!matterStorageManager) {
|
|
806
|
+
/* istanbul ignore next */
|
|
608
807
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
609
808
|
}
|
|
610
809
|
else {
|
|
@@ -623,35 +822,42 @@ export class Matterbridge extends EventEmitter {
|
|
|
623
822
|
this.shutdown = true;
|
|
624
823
|
return;
|
|
625
824
|
}
|
|
825
|
+
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
626
826
|
clearTimeout(this.checkUpdateTimeout);
|
|
627
827
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
628
828
|
const { checkUpdates } = await import('./update.js');
|
|
629
829
|
checkUpdates(this);
|
|
630
830
|
}, 30 * 1000).unref();
|
|
831
|
+
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
631
832
|
clearInterval(this.checkUpdateInterval);
|
|
632
833
|
this.checkUpdateInterval = setInterval(async () => {
|
|
633
834
|
const { checkUpdates } = await import('./update.js');
|
|
634
835
|
checkUpdates(this);
|
|
635
836
|
}, 12 * 60 * 60 * 1000).unref();
|
|
837
|
+
// Start the matterbridge in mode test
|
|
636
838
|
if (hasParameter('test')) {
|
|
637
839
|
this.bridgeMode = 'bridge';
|
|
638
840
|
return;
|
|
639
841
|
}
|
|
842
|
+
// Start the matterbridge in mode controller
|
|
640
843
|
if (hasParameter('controller')) {
|
|
641
844
|
this.bridgeMode = 'controller';
|
|
642
845
|
await this.startController();
|
|
643
846
|
return;
|
|
644
847
|
}
|
|
848
|
+
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
645
849
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
646
850
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
647
851
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
648
852
|
}
|
|
853
|
+
// Start matterbridge in bridge mode
|
|
649
854
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
650
855
|
this.bridgeMode = 'bridge';
|
|
651
856
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
652
857
|
await this.startBridge();
|
|
653
858
|
return;
|
|
654
859
|
}
|
|
860
|
+
// Start matterbridge in childbridge mode
|
|
655
861
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
656
862
|
this.bridgeMode = 'childbridge';
|
|
657
863
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -659,10 +865,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
659
865
|
return;
|
|
660
866
|
}
|
|
661
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Asynchronously loads and starts the registered plugins.
|
|
870
|
+
*
|
|
871
|
+
* This method is responsible for initializing and starting all enabled plugins.
|
|
872
|
+
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
873
|
+
*
|
|
874
|
+
* @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
|
|
875
|
+
* @param {boolean} [start] - If true, the method will start the plugins after loading them.
|
|
876
|
+
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
877
|
+
*/
|
|
662
878
|
async startPlugins(wait = false, start = true) {
|
|
879
|
+
// Check, load and start the plugins
|
|
663
880
|
for (const plugin of this.plugins) {
|
|
664
881
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
665
882
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
883
|
+
// Check if the plugin is available
|
|
666
884
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
667
885
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
668
886
|
plugin.enabled = false;
|
|
@@ -682,10 +900,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
682
900
|
if (wait)
|
|
683
901
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
684
902
|
else
|
|
685
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
903
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting'); // No await do it asyncronously
|
|
686
904
|
}
|
|
687
905
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
688
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
909
|
+
* - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
|
|
910
|
+
* - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
|
|
911
|
+
* - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
|
|
912
|
+
*/
|
|
689
913
|
registerProcessHandlers() {
|
|
690
914
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
691
915
|
process.removeAllListeners('uncaughtException');
|
|
@@ -712,6 +936,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
712
936
|
};
|
|
713
937
|
process.on('SIGTERM', this.sigtermHandler);
|
|
714
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
941
|
+
*/
|
|
715
942
|
deregisterProcessHandlers() {
|
|
716
943
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
717
944
|
if (this.exceptionHandler)
|
|
@@ -728,13 +955,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
728
955
|
process.off('SIGTERM', this.sigtermHandler);
|
|
729
956
|
this.sigtermHandler = undefined;
|
|
730
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Logs the node and system information.
|
|
960
|
+
*/
|
|
731
961
|
async logNodeAndSystemInfo() {
|
|
962
|
+
// IP address information
|
|
732
963
|
const networkInterfaces = os.networkInterfaces();
|
|
733
964
|
this.systemInformation.interfaceName = '';
|
|
734
965
|
this.systemInformation.ipv4Address = '';
|
|
735
966
|
this.systemInformation.ipv6Address = '';
|
|
736
967
|
this.systemInformation.macAddress = '';
|
|
737
968
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
969
|
+
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
738
970
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
739
971
|
continue;
|
|
740
972
|
if (!interfaceDetails) {
|
|
@@ -760,16 +992,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
760
992
|
break;
|
|
761
993
|
}
|
|
762
994
|
}
|
|
995
|
+
// Node information
|
|
763
996
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
764
997
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
765
998
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
766
999
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1000
|
+
// Host system information
|
|
767
1001
|
this.systemInformation.hostname = os.hostname();
|
|
768
1002
|
this.systemInformation.user = os.userInfo().username;
|
|
769
|
-
this.systemInformation.osType = os.type();
|
|
770
|
-
this.systemInformation.osRelease = os.release();
|
|
771
|
-
this.systemInformation.osPlatform = os.platform();
|
|
772
|
-
this.systemInformation.osArch = os.arch();
|
|
1003
|
+
this.systemInformation.osType = os.type(); // "Windows_NT", "Darwin", etc.
|
|
1004
|
+
this.systemInformation.osRelease = os.release(); // Kernel version
|
|
1005
|
+
this.systemInformation.osPlatform = os.platform(); // "win32", "linux", "darwin", etc.
|
|
1006
|
+
this.systemInformation.osArch = os.arch(); // "x64", "arm", etc.
|
|
773
1007
|
this.systemInformation.totalMemory = formatBytes(os.totalmem());
|
|
774
1008
|
this.systemInformation.freeMemory = formatBytes(os.freemem());
|
|
775
1009
|
this.systemInformation.systemUptime = formatUptime(os.uptime());
|
|
@@ -779,6 +1013,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
779
1013
|
this.systemInformation.rss = formatBytes(process.memoryUsage().rss);
|
|
780
1014
|
this.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
|
|
781
1015
|
this.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
|
|
1016
|
+
// Log the system information
|
|
782
1017
|
this.log.debug('Host System Information:');
|
|
783
1018
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
784
1019
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -798,14 +1033,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
798
1033
|
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
799
1034
|
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
800
1035
|
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
1036
|
+
// Log directories
|
|
801
1037
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
802
1038
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
803
1039
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
804
1040
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
805
1041
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1042
|
+
// Global node_modules directory
|
|
806
1043
|
if (this.nodeContext)
|
|
807
1044
|
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
808
1045
|
if (this.globalModulesDirectory === '') {
|
|
1046
|
+
// First run of Matterbridge so the node storage is empty
|
|
809
1047
|
this.log.debug(`Getting global node_modules directory...`);
|
|
810
1048
|
try {
|
|
811
1049
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -818,6 +1056,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
818
1056
|
}
|
|
819
1057
|
}
|
|
820
1058
|
else {
|
|
1059
|
+
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
821
1060
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
822
1061
|
try {
|
|
823
1062
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
@@ -829,25 +1068,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
829
1068
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
830
1069
|
}
|
|
831
1070
|
}
|
|
1071
|
+
// Matterbridge version
|
|
832
1072
|
this.log.debug(`Reading matterbridge package.json...`);
|
|
833
1073
|
const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
834
1074
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
835
1075
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1076
|
+
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
836
1077
|
if (this.nodeContext)
|
|
837
1078
|
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
838
1079
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1080
|
+
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
839
1081
|
if (this.nodeContext)
|
|
840
1082
|
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
841
1083
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1084
|
+
// Frontend version
|
|
842
1085
|
this.log.debug(`Reading frontend package.json...`);
|
|
843
1086
|
const frontendPackageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
844
1087
|
this.frontendVersion = frontendPackageJson.version;
|
|
845
1088
|
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1089
|
+
// Current working directory
|
|
846
1090
|
const currentDir = process.cwd();
|
|
847
1091
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1092
|
+
// Command line arguments (excluding 'node' and the script name)
|
|
848
1093
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
849
1094
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
850
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
1098
|
+
*
|
|
1099
|
+
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
1100
|
+
* @returns {Promise<LogLevel>} A promise that resolves when the logLevel has been set.
|
|
1101
|
+
*/
|
|
851
1102
|
async setLogLevel(logLevel) {
|
|
852
1103
|
this.log.logLevel = logLevel;
|
|
853
1104
|
this.frontend.logLevel = logLevel;
|
|
@@ -857,58 +1108,87 @@ export class Matterbridge extends EventEmitter {
|
|
|
857
1108
|
for (const plugin of this.plugins) {
|
|
858
1109
|
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
859
1110
|
continue;
|
|
860
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : logLevel;
|
|
861
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : logLevel);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1111
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel;
|
|
1112
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : logLevel);
|
|
1113
|
+
}
|
|
1114
|
+
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
1115
|
+
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
1116
|
+
if (logLevel === "info" /* LogLevel.INFO */ || Logger.level === MatterLogLevel.INFO)
|
|
1117
|
+
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
1118
|
+
if (logLevel === "debug" /* LogLevel.DEBUG */ || Logger.level === MatterLogLevel.DEBUG)
|
|
1119
|
+
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
868
1120
|
AnsiLogger.setGlobalCallbackLevel(callbackLogLevel);
|
|
869
1121
|
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
870
1122
|
return logLevel;
|
|
871
1123
|
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Get the current logger logLevel.
|
|
1126
|
+
*
|
|
1127
|
+
* @returns {LogLevel} The current logger logLevel.
|
|
1128
|
+
*/
|
|
872
1129
|
getLogLevel() {
|
|
873
1130
|
return this.log.logLevel;
|
|
874
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (for the frontend).
|
|
1134
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
1135
|
+
*
|
|
1136
|
+
* @param {boolean} fileLogger - Whether to log to file or not.
|
|
1137
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
1138
|
+
*/
|
|
875
1139
|
createDestinationMatterLogger(fileLogger) {
|
|
876
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
1140
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
877
1141
|
if (fileLogger) {
|
|
878
1142
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
879
1143
|
}
|
|
880
1144
|
return (text, message) => {
|
|
1145
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
881
1146
|
const logger = text.slice(44, 44 + 20).trim();
|
|
882
1147
|
const msg = text.slice(65);
|
|
883
1148
|
this.matterLog.logName = logger;
|
|
884
1149
|
switch (message.level) {
|
|
885
1150
|
case MatterLogLevel.DEBUG:
|
|
886
|
-
this.matterLog.log("debug"
|
|
1151
|
+
this.matterLog.log("debug" /* LogLevel.DEBUG */, msg);
|
|
887
1152
|
break;
|
|
888
1153
|
case MatterLogLevel.INFO:
|
|
889
|
-
this.matterLog.log("info"
|
|
1154
|
+
this.matterLog.log("info" /* LogLevel.INFO */, msg);
|
|
890
1155
|
break;
|
|
891
1156
|
case MatterLogLevel.NOTICE:
|
|
892
|
-
this.matterLog.log("notice"
|
|
1157
|
+
this.matterLog.log("notice" /* LogLevel.NOTICE */, msg);
|
|
893
1158
|
break;
|
|
894
1159
|
case MatterLogLevel.WARN:
|
|
895
|
-
this.matterLog.log("warn"
|
|
1160
|
+
this.matterLog.log("warn" /* LogLevel.WARN */, msg);
|
|
896
1161
|
break;
|
|
897
1162
|
case MatterLogLevel.ERROR:
|
|
898
|
-
this.matterLog.log("error"
|
|
1163
|
+
this.matterLog.log("error" /* LogLevel.ERROR */, msg);
|
|
899
1164
|
break;
|
|
900
1165
|
case MatterLogLevel.FATAL:
|
|
901
|
-
this.matterLog.log("fatal"
|
|
1166
|
+
this.matterLog.log("fatal" /* LogLevel.FATAL */, msg);
|
|
902
1167
|
break;
|
|
903
1168
|
}
|
|
904
1169
|
};
|
|
905
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1173
|
+
*
|
|
1174
|
+
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1175
|
+
*/
|
|
906
1176
|
async restartProcess() {
|
|
907
1177
|
await this.cleanup('restarting...', true);
|
|
908
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Shut down the process (/api/shutdown).
|
|
1181
|
+
*
|
|
1182
|
+
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1183
|
+
*/
|
|
909
1184
|
async shutdownProcess() {
|
|
910
1185
|
await this.cleanup('shutting down...', false);
|
|
911
1186
|
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1189
|
+
*
|
|
1190
|
+
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1191
|
+
*/
|
|
912
1192
|
async updateProcess() {
|
|
913
1193
|
this.log.info('Updating matterbridge...');
|
|
914
1194
|
try {
|
|
@@ -922,6 +1202,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
922
1202
|
this.frontend.wssSendRestartRequired();
|
|
923
1203
|
await this.cleanup('updating...', false);
|
|
924
1204
|
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Unregister all devices and shut down the process (/api/unregister).
|
|
1207
|
+
*
|
|
1208
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1209
|
+
*
|
|
1210
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1211
|
+
*/
|
|
925
1212
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
926
1213
|
this.log.info('Unregistering all devices and shutting down...');
|
|
927
1214
|
for (const plugin of this.plugins.array()) {
|
|
@@ -933,46 +1220,71 @@ export class Matterbridge extends EventEmitter {
|
|
|
933
1220
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
934
1221
|
}
|
|
935
1222
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
936
|
-
await wait(timeout);
|
|
1223
|
+
await wait(timeout); // Wait for MessageExchange to finish
|
|
937
1224
|
this.log.debug('Cleaning up and shutting down...');
|
|
938
1225
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
939
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Reset commissioning and shut down the process (/api/reset).
|
|
1229
|
+
*
|
|
1230
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1231
|
+
*/
|
|
940
1232
|
async shutdownProcessAndReset() {
|
|
941
1233
|
await this.cleanup('shutting down with reset...', false);
|
|
942
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Factory reset and shut down the process (/api/factory-reset).
|
|
1237
|
+
*
|
|
1238
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1239
|
+
*/
|
|
943
1240
|
async shutdownProcessAndFactoryReset() {
|
|
944
1241
|
await this.cleanup('shutting down with factory reset...', false);
|
|
945
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Cleans up the Matterbridge instance.
|
|
1245
|
+
*
|
|
1246
|
+
* @param {string} message - The cleanup message.
|
|
1247
|
+
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1248
|
+
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1249
|
+
*
|
|
1250
|
+
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1251
|
+
*/
|
|
946
1252
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
947
1253
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
948
1254
|
this.emit('cleanup_started');
|
|
949
1255
|
this.hasCleanupStarted = true;
|
|
950
1256
|
this.log.info(message);
|
|
1257
|
+
// Clear the start matter interval
|
|
951
1258
|
if (this.startMatterInterval) {
|
|
952
1259
|
clearInterval(this.startMatterInterval);
|
|
953
1260
|
this.startMatterInterval = undefined;
|
|
954
1261
|
this.log.debug('Start matter interval cleared');
|
|
955
1262
|
}
|
|
1263
|
+
// Clear the check update timeout
|
|
956
1264
|
if (this.checkUpdateTimeout) {
|
|
957
1265
|
clearTimeout(this.checkUpdateTimeout);
|
|
958
1266
|
this.checkUpdateTimeout = undefined;
|
|
959
1267
|
this.log.debug('Check update timeout cleared');
|
|
960
1268
|
}
|
|
1269
|
+
// Clear the check update interval
|
|
961
1270
|
if (this.checkUpdateInterval) {
|
|
962
1271
|
clearInterval(this.checkUpdateInterval);
|
|
963
1272
|
this.checkUpdateInterval = undefined;
|
|
964
1273
|
this.log.debug('Check update interval cleared');
|
|
965
1274
|
}
|
|
1275
|
+
// Clear the configure timeout
|
|
966
1276
|
if (this.configureTimeout) {
|
|
967
1277
|
clearTimeout(this.configureTimeout);
|
|
968
1278
|
this.configureTimeout = undefined;
|
|
969
1279
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
970
1280
|
}
|
|
1281
|
+
// Clear the reachability timeout
|
|
971
1282
|
if (this.reachabilityTimeout) {
|
|
972
1283
|
clearTimeout(this.reachabilityTimeout);
|
|
973
1284
|
this.reachabilityTimeout = undefined;
|
|
974
1285
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
975
1286
|
}
|
|
1287
|
+
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
976
1288
|
for (const plugin of this.plugins) {
|
|
977
1289
|
if (!plugin.enabled || plugin.error)
|
|
978
1290
|
continue;
|
|
@@ -983,6 +1295,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
983
1295
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
984
1296
|
}
|
|
985
1297
|
}
|
|
1298
|
+
// Stop matter server nodes
|
|
986
1299
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
987
1300
|
if (timeout > 0) {
|
|
988
1301
|
this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
|
|
@@ -1009,6 +1322,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1009
1322
|
}
|
|
1010
1323
|
}
|
|
1011
1324
|
this.log.notice('Stopped matter server nodes');
|
|
1325
|
+
// Matter commisioning reset
|
|
1012
1326
|
if (message === 'shutting down with reset...') {
|
|
1013
1327
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1014
1328
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1018,6 +1332,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1018
1332
|
await this.matterbridgeContext?.clearAll();
|
|
1019
1333
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1020
1334
|
}
|
|
1335
|
+
// Unregister all devices
|
|
1021
1336
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1022
1337
|
if (this.bridgeMode === 'bridge') {
|
|
1023
1338
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1035,16 +1350,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
1035
1350
|
}
|
|
1036
1351
|
this.log.info('Matter storage reset done!');
|
|
1037
1352
|
}
|
|
1353
|
+
// Stop matter storage
|
|
1038
1354
|
await this.stopMatterStorage();
|
|
1355
|
+
// Stop the frontend
|
|
1039
1356
|
await this.frontend.stop();
|
|
1040
1357
|
this.frontend.destroy();
|
|
1358
|
+
// Close PluginManager and DeviceManager
|
|
1041
1359
|
this.plugins.destroy();
|
|
1042
1360
|
this.devices.destroy();
|
|
1361
|
+
// Stop thread messaging server
|
|
1043
1362
|
this.server.close();
|
|
1363
|
+
// Close the matterbridge node storage and context
|
|
1044
1364
|
if (this.nodeStorage && this.nodeContext) {
|
|
1365
|
+
/*
|
|
1366
|
+
TODO: Implement serialization of registered devices
|
|
1367
|
+
this.log.info('Saving registered devices...');
|
|
1368
|
+
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1369
|
+
this.devices.forEach(async (device) => {
|
|
1370
|
+
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1371
|
+
this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1372
|
+
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1373
|
+
});
|
|
1374
|
+
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1375
|
+
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1376
|
+
*/
|
|
1377
|
+
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1045
1378
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1046
1379
|
await this.nodeContext.close();
|
|
1047
1380
|
this.nodeContext = undefined;
|
|
1381
|
+
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1048
1382
|
for (const plugin of this.plugins) {
|
|
1049
1383
|
if (plugin.nodeContext) {
|
|
1050
1384
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1061,8 +1395,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1061
1395
|
}
|
|
1062
1396
|
this.plugins.clear();
|
|
1063
1397
|
this.devices.clear();
|
|
1398
|
+
// Factory reset
|
|
1064
1399
|
if (message === 'shutting down with factory reset...') {
|
|
1065
1400
|
try {
|
|
1401
|
+
// Delete matter storage directory with its subdirectories and backup
|
|
1066
1402
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1067
1403
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1068
1404
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1071,11 +1407,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1071
1407
|
await fs.promises.rm(backup, { recursive: true });
|
|
1072
1408
|
}
|
|
1073
1409
|
catch (error) {
|
|
1410
|
+
// istanbul ignore next if
|
|
1074
1411
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1075
1412
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1076
1413
|
}
|
|
1077
1414
|
}
|
|
1078
1415
|
try {
|
|
1416
|
+
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1079
1417
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1080
1418
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1081
1419
|
await fs.promises.rm(dir, { recursive: true });
|
|
@@ -1084,18 +1422,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1084
1422
|
await fs.promises.rm(backup, { recursive: true });
|
|
1085
1423
|
}
|
|
1086
1424
|
catch (error) {
|
|
1425
|
+
// istanbul ignore next if
|
|
1087
1426
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1088
1427
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1089
1428
|
}
|
|
1090
1429
|
}
|
|
1091
1430
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1092
1431
|
}
|
|
1432
|
+
// Deregisters the process handlers
|
|
1093
1433
|
this.deregisterProcessHandlers();
|
|
1094
1434
|
if (restart) {
|
|
1095
1435
|
if (message === 'updating...') {
|
|
1096
1436
|
this.log.info('Cleanup completed. Updating...');
|
|
1097
1437
|
Matterbridge.instance = undefined;
|
|
1098
|
-
this.emit('update');
|
|
1438
|
+
this.emit('update'); // Restart the process but the update has been done before. TODO move all updates to the cli
|
|
1099
1439
|
}
|
|
1100
1440
|
else if (message === 'restarting...') {
|
|
1101
1441
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1124,7 +1464,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
1124
1464
|
this.log.debug('Cleanup already started...');
|
|
1125
1465
|
}
|
|
1126
1466
|
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Starts the Matterbridge in bridge mode.
|
|
1469
|
+
*
|
|
1470
|
+
* @private
|
|
1471
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1472
|
+
*/
|
|
1127
1473
|
async startBridge() {
|
|
1474
|
+
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1128
1475
|
if (!this.matterStorageManager)
|
|
1129
1476
|
throw new Error('No storage manager initialized');
|
|
1130
1477
|
if (!this.matterbridgeContext)
|
|
@@ -1163,13 +1510,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1163
1510
|
clearInterval(this.startMatterInterval);
|
|
1164
1511
|
this.startMatterInterval = undefined;
|
|
1165
1512
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1166
|
-
|
|
1513
|
+
// Start the Matter server node
|
|
1514
|
+
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1515
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1167
1516
|
for (const device of this.devices.array()) {
|
|
1168
1517
|
if (device.mode === 'server' && device.serverNode) {
|
|
1169
1518
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1170
|
-
this.startServerNode(device.serverNode);
|
|
1519
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1171
1520
|
}
|
|
1172
1521
|
}
|
|
1522
|
+
// Configure the plugins
|
|
1173
1523
|
this.configureTimeout = setTimeout(async () => {
|
|
1174
1524
|
for (const plugin of this.plugins.array()) {
|
|
1175
1525
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1187,28 +1537,40 @@ export class Matterbridge extends EventEmitter {
|
|
|
1187
1537
|
}
|
|
1188
1538
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1189
1539
|
}, 30 * 1000).unref();
|
|
1540
|
+
// Setting reachability to true
|
|
1190
1541
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1191
1542
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1192
1543
|
if (this.aggregatorNode)
|
|
1193
1544
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1194
1545
|
}, 60 * 1000).unref();
|
|
1546
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1195
1547
|
this.emit('bridge_started');
|
|
1196
1548
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1197
1549
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1198
1550
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1199
1551
|
}, this.startMatterIntervalMs);
|
|
1200
1552
|
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Starts the Matterbridge in childbridge mode.
|
|
1555
|
+
*
|
|
1556
|
+
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1557
|
+
*
|
|
1558
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1559
|
+
*/
|
|
1201
1560
|
async startChildbridge(delay = 1000) {
|
|
1202
1561
|
if (!this.matterStorageManager)
|
|
1203
1562
|
throw new Error('No storage manager initialized');
|
|
1563
|
+
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1204
1564
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1205
1565
|
await this.startPlugins(true, false);
|
|
1566
|
+
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1206
1567
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1207
1568
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1208
1569
|
if (plugin.type === 'DynamicPlatform')
|
|
1209
1570
|
await this.createDynamicPlugin(plugin);
|
|
1210
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1571
|
+
this.plugins.start(plugin, 'Matterbridge is starting'); // Start the plugin in the background
|
|
1211
1572
|
}
|
|
1573
|
+
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1212
1574
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1213
1575
|
let failCount = 0;
|
|
1214
1576
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1242,8 +1604,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1242
1604
|
clearInterval(this.startMatterInterval);
|
|
1243
1605
|
this.startMatterInterval = undefined;
|
|
1244
1606
|
if (delay > 0)
|
|
1245
|
-
await wait(delay);
|
|
1607
|
+
await wait(delay); // Wait for the specified delay to ensure all plugins server nodes are ready
|
|
1246
1608
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1609
|
+
// Configure the plugins
|
|
1247
1610
|
this.configureTimeout = setTimeout(async () => {
|
|
1248
1611
|
for (const plugin of this.plugins.array()) {
|
|
1249
1612
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1268,6 +1631,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1268
1631
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1269
1632
|
continue;
|
|
1270
1633
|
}
|
|
1634
|
+
// istanbul ignore next if cause is just a safety check
|
|
1271
1635
|
if (!plugin.serverNode) {
|
|
1272
1636
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1273
1637
|
continue;
|
|
@@ -1280,28 +1644,252 @@ export class Matterbridge extends EventEmitter {
|
|
|
1280
1644
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1281
1645
|
continue;
|
|
1282
1646
|
}
|
|
1283
|
-
|
|
1647
|
+
// Start the Matter server node
|
|
1648
|
+
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1649
|
+
// Setting reachability to true
|
|
1284
1650
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1285
1651
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1286
1652
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1287
1653
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1288
1654
|
}, 60 * 1000).unref();
|
|
1289
1655
|
}
|
|
1656
|
+
// Start the Matter server node of single devices in mode 'server'
|
|
1290
1657
|
for (const device of this.devices.array()) {
|
|
1291
1658
|
if (device.mode === 'server' && device.serverNode) {
|
|
1292
1659
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1293
|
-
this.startServerNode(device.serverNode);
|
|
1660
|
+
this.startServerNode(device.serverNode); // We don't await this, because the server node is started in the background
|
|
1294
1661
|
}
|
|
1295
1662
|
}
|
|
1663
|
+
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1296
1664
|
this.emit('childbridge_started');
|
|
1297
1665
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1298
1666
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1299
1667
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1300
1668
|
}, this.startMatterIntervalMs);
|
|
1301
1669
|
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Starts the Matterbridge controller.
|
|
1672
|
+
*
|
|
1673
|
+
* @private
|
|
1674
|
+
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1675
|
+
*/
|
|
1302
1676
|
async startController() {
|
|
1677
|
+
/*
|
|
1678
|
+
if (!this.matterStorageManager) {
|
|
1679
|
+
this.log.error('No storage manager initialized');
|
|
1680
|
+
await this.cleanup('No storage manager initialized');
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
this.log.info('Creating context: mattercontrollerContext');
|
|
1684
|
+
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1685
|
+
if (!this.controllerContext) {
|
|
1686
|
+
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1687
|
+
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1692
|
+
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1693
|
+
this.log.info('Creating matter commissioning controller');
|
|
1694
|
+
this.commissioningController = new CommissioningController({
|
|
1695
|
+
autoConnect: false,
|
|
1696
|
+
});
|
|
1697
|
+
this.log.info('Adding matter commissioning controller to matter server');
|
|
1698
|
+
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1699
|
+
|
|
1700
|
+
this.log.info('Starting matter server');
|
|
1701
|
+
await this.matterServer.start();
|
|
1702
|
+
this.log.info('Matter server started');
|
|
1703
|
+
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1704
|
+
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1705
|
+
regulatoryCountryCode: 'XX',
|
|
1706
|
+
};
|
|
1707
|
+
const commissioningController = new CommissioningController({
|
|
1708
|
+
environment: {
|
|
1709
|
+
environment,
|
|
1710
|
+
id: uniqueId,
|
|
1711
|
+
},
|
|
1712
|
+
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1713
|
+
adminFabricLabel,
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
if (hasParameter('pairingcode')) {
|
|
1717
|
+
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1718
|
+
const pairingCode = getParameter('pairingcode');
|
|
1719
|
+
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1720
|
+
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1721
|
+
|
|
1722
|
+
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1723
|
+
if (pairingCode !== undefined) {
|
|
1724
|
+
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1725
|
+
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1726
|
+
longDiscriminator = undefined;
|
|
1727
|
+
setupPin = pairingCodeCodec.passcode;
|
|
1728
|
+
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1729
|
+
} else {
|
|
1730
|
+
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1731
|
+
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1732
|
+
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1733
|
+
}
|
|
1734
|
+
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1735
|
+
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
const options = {
|
|
1739
|
+
commissioning: commissioningOptions,
|
|
1740
|
+
discovery: {
|
|
1741
|
+
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1742
|
+
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1743
|
+
},
|
|
1744
|
+
passcode: setupPin,
|
|
1745
|
+
} as NodeCommissioningOptions;
|
|
1746
|
+
this.log.info('Commissioning with options:', options);
|
|
1747
|
+
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1748
|
+
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1749
|
+
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1750
|
+
} // (hasParameter('pairingcode'))
|
|
1751
|
+
|
|
1752
|
+
if (hasParameter('unpairall')) {
|
|
1753
|
+
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1754
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1755
|
+
for (const nodeId of nodeIds) {
|
|
1756
|
+
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1757
|
+
await this.commissioningController.removeNode(nodeId);
|
|
1758
|
+
}
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
if (hasParameter('discover')) {
|
|
1763
|
+
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1764
|
+
// console.log(discover);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
if (!this.commissioningController.isCommissioned()) {
|
|
1768
|
+
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1773
|
+
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1774
|
+
for (const nodeId of nodeIds) {
|
|
1775
|
+
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1776
|
+
|
|
1777
|
+
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1778
|
+
autoSubscribe: false,
|
|
1779
|
+
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1780
|
+
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1781
|
+
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1782
|
+
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1783
|
+
stateInformationCallback: (peerNodeId, info) => {
|
|
1784
|
+
switch (info) {
|
|
1785
|
+
case NodeStateInformation.Connected:
|
|
1786
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1787
|
+
break;
|
|
1788
|
+
case NodeStateInformation.Disconnected:
|
|
1789
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1790
|
+
break;
|
|
1791
|
+
case NodeStateInformation.Reconnecting:
|
|
1792
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1793
|
+
break;
|
|
1794
|
+
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1795
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1796
|
+
break;
|
|
1797
|
+
case NodeStateInformation.StructureChanged:
|
|
1798
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1799
|
+
break;
|
|
1800
|
+
case NodeStateInformation.Decommissioned:
|
|
1801
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1802
|
+
break;
|
|
1803
|
+
default:
|
|
1804
|
+
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
},
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
node.logStructure();
|
|
1811
|
+
|
|
1812
|
+
// Get the interaction client
|
|
1813
|
+
this.log.info('Getting the interaction client');
|
|
1814
|
+
const interactionClient = await node.getInteractionClient();
|
|
1815
|
+
let cluster;
|
|
1816
|
+
let attributes;
|
|
1817
|
+
|
|
1818
|
+
// Log BasicInformationCluster
|
|
1819
|
+
cluster = BasicInformationCluster;
|
|
1820
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1821
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1822
|
+
});
|
|
1823
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1824
|
+
attributes.forEach((attribute) => {
|
|
1825
|
+
this.log.info(
|
|
1826
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1827
|
+
);
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
// Log PowerSourceCluster
|
|
1831
|
+
cluster = PowerSourceCluster;
|
|
1832
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1833
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1834
|
+
});
|
|
1835
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1836
|
+
attributes.forEach((attribute) => {
|
|
1837
|
+
this.log.info(
|
|
1838
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1839
|
+
);
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
// Log ThreadNetworkDiagnostics
|
|
1843
|
+
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1844
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1845
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1846
|
+
});
|
|
1847
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1848
|
+
attributes.forEach((attribute) => {
|
|
1849
|
+
this.log.info(
|
|
1850
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1851
|
+
);
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
// Log SwitchCluster
|
|
1855
|
+
cluster = SwitchCluster;
|
|
1856
|
+
attributes = await interactionClient.getMultipleAttributes({
|
|
1857
|
+
attributes: [{ clusterId: cluster.id }],
|
|
1858
|
+
});
|
|
1859
|
+
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1860
|
+
attributes.forEach((attribute) => {
|
|
1861
|
+
this.log.info(
|
|
1862
|
+
`- endpoint ${or}${attribute.path.endpointId}${nf} cluster ${hk}${getClusterNameById(attribute.path.clusterId)}${nf} (${hk}0x${attribute.path.clusterId.toString(16)}${nf}) attribute ${zb}${attribute.path.attributeName}${nf} (${zb}0x${attribute.path.attributeId.toString(16)}${nf}): ${typeof attribute.value === 'object' ? stringify(attribute.value) : attribute.value}`,
|
|
1863
|
+
);
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1866
|
+
this.log.info('Subscribing to all attributes and events');
|
|
1867
|
+
await node.subscribeAllAttributesAndEvents({
|
|
1868
|
+
ignoreInitialTriggers: false,
|
|
1869
|
+
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1870
|
+
this.log.info(
|
|
1871
|
+
`***${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}`,
|
|
1872
|
+
),
|
|
1873
|
+
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1874
|
+
this.log.info(
|
|
1875
|
+
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1876
|
+
);
|
|
1877
|
+
},
|
|
1878
|
+
});
|
|
1879
|
+
this.log.info('Subscribed to all attributes and events');
|
|
1880
|
+
}
|
|
1881
|
+
*/
|
|
1303
1882
|
}
|
|
1883
|
+
/** */
|
|
1884
|
+
/** Matter.js methods */
|
|
1885
|
+
/** */
|
|
1886
|
+
/**
|
|
1887
|
+
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1888
|
+
*
|
|
1889
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1890
|
+
*/
|
|
1304
1891
|
async startMatterStorage() {
|
|
1892
|
+
// Setup Matter storage
|
|
1305
1893
|
this.log.info(`Starting matter node storage...`);
|
|
1306
1894
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1307
1895
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1309,8 +1897,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
1309
1897
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1310
1898
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1311
1899
|
this.log.info('Matter node storage started');
|
|
1900
|
+
// Backup matter storage since it is created/opened correctly
|
|
1312
1901
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1313
1902
|
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
1905
|
+
*
|
|
1906
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1907
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
1908
|
+
* @private
|
|
1909
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1910
|
+
*/
|
|
1314
1911
|
async backupMatterStorage(storageName, backupName) {
|
|
1315
1912
|
this.log.info('Creating matter node storage backup...');
|
|
1316
1913
|
try {
|
|
@@ -1321,6 +1918,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1321
1918
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1322
1919
|
}
|
|
1323
1920
|
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Stops the matter storage.
|
|
1923
|
+
*
|
|
1924
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1925
|
+
*/
|
|
1324
1926
|
async stopMatterStorage() {
|
|
1325
1927
|
this.log.info('Closing matter node storage...');
|
|
1326
1928
|
await this.matterStorageManager?.close();
|
|
@@ -1329,6 +1931,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
1329
1931
|
this.matterbridgeContext = undefined;
|
|
1330
1932
|
this.log.info('Matter node storage closed');
|
|
1331
1933
|
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Creates a server node storage context.
|
|
1936
|
+
*
|
|
1937
|
+
* @param {string} storeId - The storeId.
|
|
1938
|
+
* @param {string} deviceName - The name of the device.
|
|
1939
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1940
|
+
* @param {number} vendorId - The vendor ID.
|
|
1941
|
+
* @param {string} vendorName - The vendor name.
|
|
1942
|
+
* @param {number} productId - The product ID.
|
|
1943
|
+
* @param {string} productName - The product name.
|
|
1944
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1945
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1946
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1947
|
+
*/
|
|
1332
1948
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1333
1949
|
const { randomBytes } = await import('node:crypto');
|
|
1334
1950
|
if (!this.matterStorageService)
|
|
@@ -1368,6 +1984,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1368
1984
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1369
1985
|
return storageContext;
|
|
1370
1986
|
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Creates a server node.
|
|
1989
|
+
*
|
|
1990
|
+
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1991
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
1992
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
1993
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
1994
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1995
|
+
*/
|
|
1371
1996
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1372
1997
|
const storeId = await storageContext.get('storeId');
|
|
1373
1998
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1377,24 +2002,37 @@ export class Matterbridge extends EventEmitter {
|
|
|
1377
2002
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1378
2003
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1379
2004
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
2005
|
+
/**
|
|
2006
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
2007
|
+
*/
|
|
1380
2008
|
const serverNode = await ServerNode.create({
|
|
2009
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1381
2010
|
id: storeId,
|
|
2011
|
+
// Provide Network relevant configuration like the port
|
|
2012
|
+
// Optional when operating only one device on a host, Default port is 5540
|
|
1382
2013
|
network: {
|
|
1383
2014
|
listeningAddressIpv4: this.ipv4Address,
|
|
1384
2015
|
listeningAddressIpv6: this.ipv6Address,
|
|
1385
2016
|
port,
|
|
1386
2017
|
},
|
|
2018
|
+
// Provide the certificate for the device
|
|
1387
2019
|
operationalCredentials: {
|
|
1388
2020
|
certification: this.certification,
|
|
1389
2021
|
},
|
|
2022
|
+
// Provide Commissioning relevant settings
|
|
2023
|
+
// Optional for development/testing purposes
|
|
1390
2024
|
commissioning: {
|
|
1391
2025
|
passcode,
|
|
1392
2026
|
discriminator,
|
|
1393
2027
|
},
|
|
2028
|
+
// Provide Node announcement settings
|
|
2029
|
+
// Optional: If Ommitted some development defaults are used
|
|
1394
2030
|
productDescription: {
|
|
1395
2031
|
name: await storageContext.get('deviceName'),
|
|
1396
2032
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1397
2033
|
},
|
|
2034
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
2035
|
+
// Optional: If Omitted some development defaults are used
|
|
1398
2036
|
basicInformation: {
|
|
1399
2037
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
1400
2038
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -1411,17 +2049,23 @@ export class Matterbridge extends EventEmitter {
|
|
|
1411
2049
|
reachable: true,
|
|
1412
2050
|
},
|
|
1413
2051
|
});
|
|
2052
|
+
/**
|
|
2053
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
2054
|
+
* This means: It is added to the first fabric.
|
|
2055
|
+
*/
|
|
1414
2056
|
serverNode.lifecycle.commissioned.on(() => {
|
|
1415
2057
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
1416
2058
|
this.advertisingNodes.delete(storeId);
|
|
1417
2059
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1418
2060
|
});
|
|
2061
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
1419
2062
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
1420
2063
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
1421
2064
|
this.advertisingNodes.delete(storeId);
|
|
1422
2065
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1423
2066
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1424
2067
|
});
|
|
2068
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
1425
2069
|
serverNode.lifecycle.online.on(async () => {
|
|
1426
2070
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1427
2071
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -1432,13 +2076,16 @@ export class Matterbridge extends EventEmitter {
|
|
|
1432
2076
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
1433
2077
|
}
|
|
1434
2078
|
else {
|
|
2079
|
+
// istanbul ignore next
|
|
1435
2080
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2081
|
+
// istanbul ignore next
|
|
1436
2082
|
this.advertisingNodes.delete(storeId);
|
|
1437
2083
|
}
|
|
1438
2084
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1439
2085
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
1440
2086
|
this.emit('online', storeId);
|
|
1441
2087
|
});
|
|
2088
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
1442
2089
|
serverNode.lifecycle.offline.on(() => {
|
|
1443
2090
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
1444
2091
|
this.advertisingNodes.delete(storeId);
|
|
@@ -1446,11 +2093,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1446
2093
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
1447
2094
|
this.emit('offline', storeId);
|
|
1448
2095
|
});
|
|
2096
|
+
/**
|
|
2097
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2098
|
+
* information is needed.
|
|
2099
|
+
*/
|
|
1449
2100
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
1450
2101
|
let action = '';
|
|
1451
2102
|
switch (fabricAction) {
|
|
1452
2103
|
case FabricAction.Added:
|
|
1453
|
-
this.advertisingNodes.delete(storeId);
|
|
2104
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
1454
2105
|
action = 'added';
|
|
1455
2106
|
break;
|
|
1456
2107
|
case FabricAction.Removed:
|
|
@@ -1463,14 +2114,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1463
2114
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
1464
2115
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1465
2116
|
});
|
|
2117
|
+
/**
|
|
2118
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
2119
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2120
|
+
*/
|
|
1466
2121
|
serverNode.events.sessions.opened.on((session) => {
|
|
1467
2122
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1468
2123
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1469
2124
|
});
|
|
2125
|
+
/**
|
|
2126
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2127
|
+
*/
|
|
1470
2128
|
serverNode.events.sessions.closed.on((session) => {
|
|
1471
2129
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1472
2130
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
1473
2131
|
});
|
|
2132
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
1474
2133
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
1475
2134
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
1476
2135
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -1478,6 +2137,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1478
2137
|
this.log.info(`Created server node for ${storeId}`);
|
|
1479
2138
|
return serverNode;
|
|
1480
2139
|
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Gets the matter sanitized data of the specified server node.
|
|
2142
|
+
*
|
|
2143
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2144
|
+
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2145
|
+
*/
|
|
1481
2146
|
getServerNodeData(serverNode) {
|
|
1482
2147
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
1483
2148
|
return {
|
|
@@ -1494,12 +2159,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1494
2159
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
1495
2160
|
};
|
|
1496
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Starts the specified server node.
|
|
2164
|
+
*
|
|
2165
|
+
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2166
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2167
|
+
*/
|
|
1497
2168
|
async startServerNode(matterServerNode) {
|
|
1498
2169
|
if (!matterServerNode)
|
|
1499
2170
|
return;
|
|
1500
2171
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
1501
2172
|
await matterServerNode.start();
|
|
1502
2173
|
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Stops the specified server node.
|
|
2176
|
+
*
|
|
2177
|
+
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2178
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2179
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2180
|
+
*/
|
|
1503
2181
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
1504
2182
|
if (!matterServerNode)
|
|
1505
2183
|
return;
|
|
@@ -1512,12 +2190,25 @@ export class Matterbridge extends EventEmitter {
|
|
|
1512
2190
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
1513
2191
|
}
|
|
1514
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Creates an aggregator node with the specified storage context.
|
|
2195
|
+
*
|
|
2196
|
+
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2197
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2198
|
+
*/
|
|
1515
2199
|
async createAggregatorNode(storageContext) {
|
|
1516
2200
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
1517
2201
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
1518
2202
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
1519
2203
|
return aggregatorNode;
|
|
1520
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2207
|
+
*
|
|
2208
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2209
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2210
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2211
|
+
*/
|
|
1521
2212
|
async createAccessoryPlugin(plugin, device) {
|
|
1522
2213
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
1523
2214
|
plugin.locked = true;
|
|
@@ -1529,6 +2220,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1529
2220
|
await plugin.serverNode.add(device);
|
|
1530
2221
|
}
|
|
1531
2222
|
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2225
|
+
*
|
|
2226
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2227
|
+
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2228
|
+
*/
|
|
1532
2229
|
async createDynamicPlugin(plugin) {
|
|
1533
2230
|
if (!plugin.locked) {
|
|
1534
2231
|
plugin.locked = true;
|
|
@@ -1541,6 +2238,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1541
2238
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
1542
2239
|
}
|
|
1543
2240
|
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Creates and configures the server node for a single not bridged device.
|
|
2243
|
+
*
|
|
2244
|
+
* @param {Plugin} plugin - The plugin to configure.
|
|
2245
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2246
|
+
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2247
|
+
*/
|
|
1544
2248
|
async createDeviceServerNode(plugin, device) {
|
|
1545
2249
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
1546
2250
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -1551,7 +2255,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1551
2255
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
1552
2256
|
}
|
|
1553
2257
|
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2260
|
+
*
|
|
2261
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2262
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2263
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2264
|
+
*/
|
|
1554
2265
|
async addBridgedEndpoint(pluginName, device) {
|
|
2266
|
+
// Check if the plugin is registered
|
|
1555
2267
|
const plugin = this.plugins.get(pluginName);
|
|
1556
2268
|
if (!plugin) {
|
|
1557
2269
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -1571,6 +2283,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1571
2283
|
}
|
|
1572
2284
|
else if (this.bridgeMode === 'bridge') {
|
|
1573
2285
|
if (device.mode === 'matter') {
|
|
2286
|
+
// Register and add the device to the matterbridge server node
|
|
1574
2287
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
1575
2288
|
if (!this.serverNode) {
|
|
1576
2289
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -1587,6 +2300,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1587
2300
|
}
|
|
1588
2301
|
}
|
|
1589
2302
|
else {
|
|
2303
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1590
2304
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
1591
2305
|
if (!this.aggregatorNode) {
|
|
1592
2306
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -1604,6 +2318,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1604
2318
|
}
|
|
1605
2319
|
}
|
|
1606
2320
|
else if (this.bridgeMode === 'childbridge') {
|
|
2321
|
+
// Register and add the device to the plugin server node
|
|
1607
2322
|
if (plugin.type === 'AccessoryPlatform') {
|
|
1608
2323
|
try {
|
|
1609
2324
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -1627,10 +2342,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1627
2342
|
return;
|
|
1628
2343
|
}
|
|
1629
2344
|
}
|
|
2345
|
+
// Register and add the device to the plugin aggregator node
|
|
1630
2346
|
if (plugin.type === 'DynamicPlatform') {
|
|
1631
2347
|
try {
|
|
1632
2348
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
1633
2349
|
await this.createDynamicPlugin(plugin);
|
|
2350
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
1634
2351
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
1635
2352
|
if (!plugin.aggregatorNode) {
|
|
1636
2353
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -1651,17 +2368,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1651
2368
|
}
|
|
1652
2369
|
if (plugin.registeredDevices !== undefined)
|
|
1653
2370
|
plugin.registeredDevices++;
|
|
2371
|
+
// Add the device to the DeviceManager
|
|
1654
2372
|
this.devices.set(device);
|
|
2373
|
+
// Subscribe to the attributes changed event
|
|
1655
2374
|
await this.subscribeAttributeChanged(plugin, device);
|
|
1656
2375
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
1657
2376
|
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2379
|
+
*
|
|
2380
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2381
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2382
|
+
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2383
|
+
*/
|
|
1658
2384
|
async removeBridgedEndpoint(pluginName, device) {
|
|
1659
2385
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2386
|
+
// Check if the plugin is registered
|
|
1660
2387
|
const plugin = this.plugins.get(pluginName);
|
|
1661
2388
|
if (!plugin) {
|
|
1662
2389
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
1663
2390
|
return;
|
|
1664
2391
|
}
|
|
2392
|
+
// Register and add the device to the matterbridge aggregator node
|
|
1665
2393
|
if (this.bridgeMode === 'bridge') {
|
|
1666
2394
|
if (!this.aggregatorNode) {
|
|
1667
2395
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -1674,6 +2402,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1674
2402
|
}
|
|
1675
2403
|
else if (this.bridgeMode === 'childbridge') {
|
|
1676
2404
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2405
|
+
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
1677
2406
|
}
|
|
1678
2407
|
else if (plugin.type === 'DynamicPlatform') {
|
|
1679
2408
|
if (!plugin.aggregatorNode) {
|
|
@@ -1686,8 +2415,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1686
2415
|
if (plugin.registeredDevices !== undefined)
|
|
1687
2416
|
plugin.registeredDevices--;
|
|
1688
2417
|
}
|
|
2418
|
+
// Remove the device from the DeviceManager
|
|
1689
2419
|
this.devices.remove(device);
|
|
1690
2420
|
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
2423
|
+
*
|
|
2424
|
+
* @param {string} pluginName - The name of the plugin.
|
|
2425
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2426
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2427
|
+
*
|
|
2428
|
+
* @remarks
|
|
2429
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2430
|
+
* It also applies a delay between each removal if specified.
|
|
2431
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2432
|
+
*/
|
|
1691
2433
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
1692
2434
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
1693
2435
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -1698,13 +2440,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1698
2440
|
if (delay > 0)
|
|
1699
2441
|
await wait(2000);
|
|
1700
2442
|
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
2445
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2446
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2447
|
+
*
|
|
2448
|
+
* @param {Plugin} plugin - The plugin associated with the device.
|
|
2449
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2450
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2451
|
+
*/
|
|
1701
2452
|
async subscribeAttributeChanged(plugin, device) {
|
|
1702
2453
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
1703
2454
|
return;
|
|
1704
2455
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2456
|
+
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
1705
2457
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
1706
2458
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
1707
2459
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2460
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1708
2461
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
1709
2462
|
});
|
|
1710
2463
|
}
|
|
@@ -1754,6 +2507,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1754
2507
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
1755
2508
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1756
2509
|
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}`);
|
|
2510
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1757
2511
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
1758
2512
|
});
|
|
1759
2513
|
}
|
|
@@ -1762,12 +2516,19 @@ export class Matterbridge extends EventEmitter {
|
|
|
1762
2516
|
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...`);
|
|
1763
2517
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1764
2518
|
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}`);
|
|
2519
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1765
2520
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1766
2521
|
});
|
|
1767
2522
|
}
|
|
1768
2523
|
}
|
|
1769
2524
|
}
|
|
1770
2525
|
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2528
|
+
*
|
|
2529
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2530
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2531
|
+
*/
|
|
1771
2532
|
sanitizeFabricInformations(fabricInfo) {
|
|
1772
2533
|
return fabricInfo.map((info) => {
|
|
1773
2534
|
return {
|
|
@@ -1781,6 +2542,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
1781
2542
|
};
|
|
1782
2543
|
});
|
|
1783
2544
|
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2547
|
+
*
|
|
2548
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2549
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2550
|
+
*/
|
|
1784
2551
|
sanitizeSessionInformation(sessions) {
|
|
1785
2552
|
return sessions
|
|
1786
2553
|
.filter((session) => session.isPeerActive)
|
|
@@ -1807,7 +2574,21 @@ export class Matterbridge extends EventEmitter {
|
|
|
1807
2574
|
};
|
|
1808
2575
|
});
|
|
1809
2576
|
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2579
|
+
*
|
|
2580
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2581
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2582
|
+
*/
|
|
2583
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1810
2584
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2585
|
+
/*
|
|
2586
|
+
for (const child of aggregatorNode.parts) {
|
|
2587
|
+
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2588
|
+
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2589
|
+
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2590
|
+
}
|
|
2591
|
+
*/
|
|
1811
2592
|
}
|
|
1812
2593
|
getVendorIdName = (vendorId) => {
|
|
1813
2594
|
if (!vendorId)
|
|
@@ -1847,10 +2628,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1847
2628
|
case 0x1488:
|
|
1848
2629
|
vendorName = '(ShortcutLabsFlic)';
|
|
1849
2630
|
break;
|
|
1850
|
-
case 65521:
|
|
2631
|
+
case 65521: // 0xFFF1
|
|
1851
2632
|
vendorName = '(MatterTest)';
|
|
1852
2633
|
break;
|
|
1853
2634
|
}
|
|
1854
2635
|
return vendorName;
|
|
1855
2636
|
};
|
|
1856
2637
|
}
|
|
2638
|
+
//# sourceMappingURL=matterbridge.js.map
|