matterbridge 3.3.0 → 3.3.1-dev-20251008-e61b8db
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -3
- package/README.md +9 -1
- package/dist/broadcastServer.js +73 -0
- package/dist/broadcastServerTypes.js +1 -0
- package/dist/cli.js +38 -99
- package/dist/cliEmitter.js +0 -30
- package/dist/clusters/export.js +0 -2
- package/dist/defaultConfigSchema.js +0 -24
- package/dist/deviceManager.js +44 -98
- package/dist/devices/airConditioner.js +0 -57
- package/dist/devices/batteryStorage.js +1 -48
- package/dist/devices/cooktop.js +0 -55
- package/dist/devices/dishwasher.js +0 -57
- package/dist/devices/evse.js +10 -74
- package/dist/devices/export.js +0 -5
- package/dist/devices/extractorHood.js +0 -42
- package/dist/devices/heatPump.js +2 -50
- package/dist/devices/laundryDryer.js +3 -62
- package/dist/devices/laundryWasher.js +4 -70
- package/dist/devices/microwaveOven.js +5 -88
- package/dist/devices/oven.js +0 -85
- package/dist/devices/refrigerator.js +0 -102
- package/dist/devices/roboticVacuumCleaner.js +9 -100
- package/dist/devices/solarPower.js +0 -38
- package/dist/devices/speaker.js +0 -84
- package/dist/devices/temperatureControl.js +3 -25
- package/dist/devices/waterHeater.js +2 -82
- package/dist/dgram/coap.js +13 -126
- package/dist/dgram/dgram.js +2 -114
- package/dist/dgram/mb_coap.js +3 -41
- package/dist/dgram/mb_mdns.js +15 -80
- package/dist/dgram/mdns.js +137 -299
- package/dist/dgram/multicast.js +1 -62
- package/dist/dgram/unicast.js +0 -54
- package/dist/frontend.js +233 -634
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +4 -57
- package/dist/index.js +2 -41
- package/dist/logger/export.js +0 -1
- package/dist/matter/behaviors.js +0 -2
- package/dist/matter/clusters.js +0 -2
- package/dist/matter/devices.js +0 -2
- package/dist/matter/endpoints.js +0 -2
- package/dist/matter/export.js +0 -3
- package/dist/matter/types.js +0 -3
- package/dist/matterbridge.js +156 -885
- package/dist/matterbridgeAccessoryPlatform.js +0 -36
- package/dist/matterbridgeBehaviors.js +5 -65
- package/dist/matterbridgeDeviceTypes.js +17 -630
- package/dist/matterbridgeDynamicPlatform.js +0 -36
- package/dist/matterbridgeEndpoint.js +58 -1398
- package/dist/matterbridgeEndpointHelpers.js +12 -345
- package/dist/matterbridgePlatform.js +4 -341
- package/dist/matterbridgeTypes.js +0 -26
- package/dist/pluginManager.js +133 -254
- package/dist/shelly.js +11 -172
- package/dist/storage/export.js +0 -1
- package/dist/update.js +0 -71
- package/dist/utils/colorUtils.js +2 -97
- package/dist/utils/commandLine.js +0 -54
- package/dist/utils/copyDirectory.js +1 -38
- package/dist/utils/createDirectory.js +0 -33
- package/dist/utils/createZip.js +2 -47
- package/dist/utils/deepCopy.js +0 -39
- package/dist/utils/deepEqual.js +1 -72
- package/dist/utils/error.js +0 -41
- package/dist/utils/export.js +0 -1
- package/dist/utils/hex.js +0 -124
- package/dist/utils/isvalid.js +0 -101
- package/dist/utils/jestHelpers.js +3 -153
- package/dist/utils/network.js +76 -129
- package/dist/utils/spawn.js +5 -75
- package/dist/utils/wait.js +8 -60
- package/frontend/build/assets/index.js +4 -7
- package/frontend/build/assets/vendor_mui.js +1 -1
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +44 -44
- package/package.json +2 -3
- package/dist/cli.d.ts +0 -26
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/cliEmitter.d.ts +0 -34
- package/dist/cliEmitter.d.ts.map +0 -1
- package/dist/cliEmitter.js.map +0 -1
- package/dist/clusters/export.d.ts +0 -2
- package/dist/clusters/export.d.ts.map +0 -1
- package/dist/clusters/export.js.map +0 -1
- package/dist/defaultConfigSchema.d.ts +0 -28
- package/dist/defaultConfigSchema.d.ts.map +0 -1
- package/dist/defaultConfigSchema.js.map +0 -1
- package/dist/deviceManager.d.ts +0 -112
- package/dist/deviceManager.d.ts.map +0 -1
- package/dist/deviceManager.js.map +0 -1
- package/dist/devices/airConditioner.d.ts +0 -98
- package/dist/devices/airConditioner.d.ts.map +0 -1
- package/dist/devices/airConditioner.js.map +0 -1
- package/dist/devices/batteryStorage.d.ts +0 -48
- package/dist/devices/batteryStorage.d.ts.map +0 -1
- package/dist/devices/batteryStorage.js.map +0 -1
- package/dist/devices/cooktop.d.ts +0 -60
- package/dist/devices/cooktop.d.ts.map +0 -1
- package/dist/devices/cooktop.js.map +0 -1
- package/dist/devices/dishwasher.d.ts +0 -71
- package/dist/devices/dishwasher.d.ts.map +0 -1
- package/dist/devices/dishwasher.js.map +0 -1
- package/dist/devices/evse.d.ts +0 -75
- package/dist/devices/evse.d.ts.map +0 -1
- package/dist/devices/evse.js.map +0 -1
- package/dist/devices/export.d.ts +0 -17
- package/dist/devices/export.d.ts.map +0 -1
- package/dist/devices/export.js.map +0 -1
- package/dist/devices/extractorHood.d.ts +0 -46
- package/dist/devices/extractorHood.d.ts.map +0 -1
- package/dist/devices/extractorHood.js.map +0 -1
- package/dist/devices/heatPump.d.ts +0 -47
- package/dist/devices/heatPump.d.ts.map +0 -1
- package/dist/devices/heatPump.js.map +0 -1
- package/dist/devices/laundryDryer.d.ts +0 -67
- package/dist/devices/laundryDryer.d.ts.map +0 -1
- package/dist/devices/laundryDryer.js.map +0 -1
- package/dist/devices/laundryWasher.d.ts +0 -81
- package/dist/devices/laundryWasher.d.ts.map +0 -1
- package/dist/devices/laundryWasher.js.map +0 -1
- package/dist/devices/microwaveOven.d.ts +0 -168
- package/dist/devices/microwaveOven.d.ts.map +0 -1
- package/dist/devices/microwaveOven.js.map +0 -1
- package/dist/devices/oven.d.ts +0 -105
- package/dist/devices/oven.d.ts.map +0 -1
- package/dist/devices/oven.js.map +0 -1
- package/dist/devices/refrigerator.d.ts +0 -118
- package/dist/devices/refrigerator.d.ts.map +0 -1
- package/dist/devices/refrigerator.js.map +0 -1
- package/dist/devices/roboticVacuumCleaner.d.ts +0 -112
- package/dist/devices/roboticVacuumCleaner.d.ts.map +0 -1
- package/dist/devices/roboticVacuumCleaner.js.map +0 -1
- package/dist/devices/solarPower.d.ts +0 -40
- package/dist/devices/solarPower.d.ts.map +0 -1
- package/dist/devices/solarPower.js.map +0 -1
- package/dist/devices/speaker.d.ts +0 -87
- package/dist/devices/speaker.d.ts.map +0 -1
- package/dist/devices/speaker.js.map +0 -1
- package/dist/devices/temperatureControl.d.ts +0 -166
- package/dist/devices/temperatureControl.d.ts.map +0 -1
- package/dist/devices/temperatureControl.js.map +0 -1
- package/dist/devices/waterHeater.d.ts +0 -111
- package/dist/devices/waterHeater.d.ts.map +0 -1
- package/dist/devices/waterHeater.js.map +0 -1
- package/dist/dgram/coap.d.ts +0 -205
- package/dist/dgram/coap.d.ts.map +0 -1
- package/dist/dgram/coap.js.map +0 -1
- package/dist/dgram/dgram.d.ts +0 -141
- package/dist/dgram/dgram.d.ts.map +0 -1
- package/dist/dgram/dgram.js.map +0 -1
- package/dist/dgram/mb_coap.d.ts +0 -24
- package/dist/dgram/mb_coap.d.ts.map +0 -1
- package/dist/dgram/mb_coap.js.map +0 -1
- package/dist/dgram/mb_mdns.d.ts +0 -24
- package/dist/dgram/mb_mdns.d.ts.map +0 -1
- package/dist/dgram/mb_mdns.js.map +0 -1
- package/dist/dgram/mdns.d.ts +0 -290
- package/dist/dgram/mdns.d.ts.map +0 -1
- package/dist/dgram/mdns.js.map +0 -1
- package/dist/dgram/multicast.d.ts +0 -67
- package/dist/dgram/multicast.d.ts.map +0 -1
- package/dist/dgram/multicast.js.map +0 -1
- package/dist/dgram/unicast.d.ts +0 -56
- package/dist/dgram/unicast.d.ts.map +0 -1
- package/dist/dgram/unicast.js.map +0 -1
- package/dist/frontend.d.ts +0 -232
- package/dist/frontend.d.ts.map +0 -1
- package/dist/frontend.js.map +0 -1
- package/dist/frontendTypes.d.ts +0 -514
- package/dist/frontendTypes.d.ts.map +0 -1
- package/dist/frontendTypes.js.map +0 -1
- package/dist/globalMatterbridge.d.ts +0 -59
- package/dist/globalMatterbridge.d.ts.map +0 -1
- package/dist/globalMatterbridge.js +0 -70
- package/dist/globalMatterbridge.js.map +0 -1
- package/dist/helpers.d.ts +0 -48
- package/dist/helpers.d.ts.map +0 -1
- package/dist/helpers.js.map +0 -1
- package/dist/index.d.ts +0 -33
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger/export.d.ts +0 -2
- package/dist/logger/export.d.ts.map +0 -1
- package/dist/logger/export.js.map +0 -1
- package/dist/matter/behaviors.d.ts +0 -2
- package/dist/matter/behaviors.d.ts.map +0 -1
- package/dist/matter/behaviors.js.map +0 -1
- package/dist/matter/clusters.d.ts +0 -2
- package/dist/matter/clusters.d.ts.map +0 -1
- package/dist/matter/clusters.js.map +0 -1
- package/dist/matter/devices.d.ts +0 -2
- package/dist/matter/devices.d.ts.map +0 -1
- package/dist/matter/devices.js.map +0 -1
- package/dist/matter/endpoints.d.ts +0 -2
- package/dist/matter/endpoints.d.ts.map +0 -1
- package/dist/matter/endpoints.js.map +0 -1
- package/dist/matter/export.d.ts +0 -5
- package/dist/matter/export.d.ts.map +0 -1
- package/dist/matter/export.js.map +0 -1
- package/dist/matter/types.d.ts +0 -3
- package/dist/matter/types.d.ts.map +0 -1
- package/dist/matter/types.js.map +0 -1
- package/dist/matterbridge.d.ts +0 -430
- package/dist/matterbridge.d.ts.map +0 -1
- package/dist/matterbridge.js.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.d.ts +0 -42
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +0 -1
- package/dist/matterbridgeAccessoryPlatform.js.map +0 -1
- package/dist/matterbridgeBehaviors.d.ts +0 -1747
- package/dist/matterbridgeBehaviors.d.ts.map +0 -1
- package/dist/matterbridgeBehaviors.js.map +0 -1
- package/dist/matterbridgeDeviceTypes.d.ts +0 -761
- package/dist/matterbridgeDeviceTypes.d.ts.map +0 -1
- package/dist/matterbridgeDeviceTypes.js.map +0 -1
- package/dist/matterbridgeDynamicPlatform.d.ts +0 -42
- package/dist/matterbridgeDynamicPlatform.d.ts.map +0 -1
- package/dist/matterbridgeDynamicPlatform.js.map +0 -1
- package/dist/matterbridgeEndpoint.d.ts +0 -1534
- package/dist/matterbridgeEndpoint.d.ts.map +0 -1
- package/dist/matterbridgeEndpoint.js.map +0 -1
- package/dist/matterbridgeEndpointHelpers.d.ts +0 -407
- package/dist/matterbridgeEndpointHelpers.d.ts.map +0 -1
- package/dist/matterbridgeEndpointHelpers.js.map +0 -1
- package/dist/matterbridgePlatform.d.ts +0 -402
- package/dist/matterbridgePlatform.d.ts.map +0 -1
- package/dist/matterbridgePlatform.js.map +0 -1
- package/dist/matterbridgeTypes.d.ts +0 -201
- package/dist/matterbridgeTypes.d.ts.map +0 -1
- package/dist/matterbridgeTypes.js.map +0 -1
- package/dist/pluginManager.d.ts +0 -270
- package/dist/pluginManager.d.ts.map +0 -1
- package/dist/pluginManager.js.map +0 -1
- package/dist/shelly.d.ts +0 -174
- package/dist/shelly.d.ts.map +0 -1
- package/dist/shelly.js.map +0 -1
- package/dist/storage/export.d.ts +0 -2
- package/dist/storage/export.d.ts.map +0 -1
- package/dist/storage/export.js.map +0 -1
- package/dist/update.d.ts +0 -75
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js.map +0 -1
- package/dist/utils/colorUtils.d.ts +0 -99
- package/dist/utils/colorUtils.d.ts.map +0 -1
- package/dist/utils/colorUtils.js.map +0 -1
- package/dist/utils/commandLine.d.ts +0 -59
- package/dist/utils/commandLine.d.ts.map +0 -1
- package/dist/utils/commandLine.js.map +0 -1
- package/dist/utils/copyDirectory.d.ts +0 -33
- package/dist/utils/copyDirectory.d.ts.map +0 -1
- package/dist/utils/copyDirectory.js.map +0 -1
- package/dist/utils/createDirectory.d.ts +0 -34
- package/dist/utils/createDirectory.d.ts.map +0 -1
- package/dist/utils/createDirectory.js.map +0 -1
- package/dist/utils/createZip.d.ts +0 -39
- package/dist/utils/createZip.d.ts.map +0 -1
- package/dist/utils/createZip.js.map +0 -1
- package/dist/utils/deepCopy.d.ts +0 -32
- package/dist/utils/deepCopy.d.ts.map +0 -1
- package/dist/utils/deepCopy.js.map +0 -1
- package/dist/utils/deepEqual.d.ts +0 -54
- package/dist/utils/deepEqual.d.ts.map +0 -1
- package/dist/utils/deepEqual.js.map +0 -1
- package/dist/utils/error.d.ts +0 -44
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/error.js.map +0 -1
- package/dist/utils/export.d.ts +0 -13
- package/dist/utils/export.d.ts.map +0 -1
- package/dist/utils/export.js.map +0 -1
- package/dist/utils/hex.d.ts +0 -89
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/isvalid.d.ts +0 -103
- package/dist/utils/isvalid.d.ts.map +0 -1
- package/dist/utils/isvalid.js.map +0 -1
- package/dist/utils/jestHelpers.d.ts +0 -137
- package/dist/utils/jestHelpers.d.ts.map +0 -1
- package/dist/utils/jestHelpers.js.map +0 -1
- package/dist/utils/network.d.ts +0 -84
- package/dist/utils/network.d.ts.map +0 -1
- package/dist/utils/network.js.map +0 -1
- package/dist/utils/spawn.d.ts +0 -34
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/wait.d.ts +0 -54
- package/dist/utils/wait.d.ts.map +0 -1
- package/dist/utils/wait.js.map +0 -1
package/dist/matterbridge.js
CHANGED
|
@@ -1,54 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
// Node.js modules
|
|
1
|
+
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
2
|
+
console.log('\u001B[32mMatterbridge loaded.\u001B[40;0m');
|
|
25
3
|
import os from 'node:os';
|
|
26
4
|
import path from 'node:path';
|
|
27
5
|
import { promises as fs } from 'node:fs';
|
|
28
6
|
import EventEmitter from 'node:events';
|
|
29
7
|
import { inspect } from 'node:util';
|
|
30
|
-
// AnsiLogger module
|
|
31
8
|
import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, debugStringify, BRIGHT, RESET, er, nf, rs, wr, RED, GREEN, zb, CYAN, nt, BLUE, or } from 'node-ansi-logger';
|
|
32
|
-
// NodeStorage module
|
|
33
9
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
-
// @matter
|
|
35
10
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
36
11
|
import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
37
12
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
38
13
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
39
|
-
|
|
40
|
-
import {
|
|
14
|
+
import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
|
|
15
|
+
import { copyDirectory } from './utils/copyDirectory.js';
|
|
16
|
+
import { createDirectory } from './utils/createDirectory.js';
|
|
17
|
+
import { isValidString, parseVersionString, isValidNumber } from './utils/isvalid.js';
|
|
18
|
+
import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
|
|
41
19
|
import { withTimeout, waiter, wait } from './utils/wait.js';
|
|
42
|
-
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ
|
|
20
|
+
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from './matterbridgeTypes.js';
|
|
43
21
|
import { PluginManager } from './pluginManager.js';
|
|
44
22
|
import { DeviceManager } from './deviceManager.js';
|
|
45
23
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
46
24
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
47
25
|
import { Frontend } from './frontend.js';
|
|
48
26
|
import { addVirtualDevices } from './helpers.js';
|
|
49
|
-
/**
|
|
50
|
-
* Represents the Matterbridge application.
|
|
51
|
-
*/
|
|
52
27
|
export class Matterbridge extends EventEmitter {
|
|
53
28
|
systemInformation = {
|
|
54
29
|
interfaceName: '',
|
|
@@ -71,38 +46,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
71
46
|
heapTotal: '',
|
|
72
47
|
heapUsed: '',
|
|
73
48
|
};
|
|
74
|
-
matterbridgeInformation = {
|
|
75
|
-
homeDirectory: '',
|
|
76
|
-
rootDirectory: '',
|
|
77
|
-
matterbridgeDirectory: '',
|
|
78
|
-
matterbridgePluginDirectory: '',
|
|
79
|
-
matterbridgeCertDirectory: '',
|
|
80
|
-
globalModulesDirectory: '',
|
|
81
|
-
matterbridgeVersion: '',
|
|
82
|
-
matterbridgeLatestVersion: '',
|
|
83
|
-
matterbridgeDevVersion: '',
|
|
84
|
-
bridgeMode: '',
|
|
85
|
-
restartMode: '',
|
|
86
|
-
virtualMode: 'outlet',
|
|
87
|
-
readOnly: hasParameter('readonly') || hasParameter('shelly'),
|
|
88
|
-
shellyBoard: hasParameter('shelly'),
|
|
89
|
-
shellySysUpdate: false,
|
|
90
|
-
shellyMainUpdate: false,
|
|
91
|
-
profile: getParameter('profile'),
|
|
92
|
-
loggerLevel: "info" /* LogLevel.INFO */,
|
|
93
|
-
fileLogger: false,
|
|
94
|
-
matterLoggerLevel: MatterLogLevel.INFO,
|
|
95
|
-
matterFileLogger: false,
|
|
96
|
-
matterMdnsInterface: undefined,
|
|
97
|
-
matterIpv4Address: undefined,
|
|
98
|
-
matterIpv6Address: undefined,
|
|
99
|
-
matterPort: 5540,
|
|
100
|
-
matterDiscriminator: undefined,
|
|
101
|
-
matterPasscode: undefined,
|
|
102
|
-
restartRequired: false,
|
|
103
|
-
fixedRestartRequired: false,
|
|
104
|
-
updateRequired: false,
|
|
105
|
-
};
|
|
106
49
|
homeDirectory = '';
|
|
107
50
|
rootDirectory = '';
|
|
108
51
|
matterbridgeDirectory = '';
|
|
@@ -112,23 +55,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
112
55
|
matterbridgeVersion = '';
|
|
113
56
|
matterbridgeLatestVersion = '';
|
|
114
57
|
matterbridgeDevVersion = '';
|
|
58
|
+
frontendVersion = '';
|
|
115
59
|
bridgeMode = '';
|
|
116
60
|
restartMode = '';
|
|
61
|
+
virtualMode = 'outlet';
|
|
117
62
|
profile = getParameter('profile');
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
63
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
64
|
+
fileLogger = false;
|
|
65
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
66
|
+
matterFileLogger = false;
|
|
67
|
+
readOnly = hasParameter('readonly') || hasParameter('shelly');
|
|
68
|
+
shellyBoard = hasParameter('shelly');
|
|
69
|
+
shellySysUpdate = false;
|
|
70
|
+
shellyMainUpdate = false;
|
|
71
|
+
restartRequired = false;
|
|
72
|
+
fixedRestartRequired = false;
|
|
73
|
+
updateRequired = false;
|
|
125
74
|
plugins = new PluginManager(this);
|
|
126
|
-
devices = new DeviceManager(
|
|
75
|
+
devices = new DeviceManager();
|
|
127
76
|
frontend = new Frontend(this);
|
|
128
|
-
// Matterbridge storage
|
|
129
77
|
nodeStorage;
|
|
130
78
|
nodeContext;
|
|
131
|
-
|
|
79
|
+
static instance;
|
|
80
|
+
shutdown = false;
|
|
81
|
+
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
132
82
|
hasCleanupStarted = false;
|
|
133
83
|
initialized = false;
|
|
134
84
|
startMatterInterval;
|
|
@@ -141,22 +91,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
141
91
|
sigtermHandler;
|
|
142
92
|
exceptionHandler;
|
|
143
93
|
rejectionHandler;
|
|
144
|
-
// Matter environment
|
|
145
94
|
environment = Environment.default;
|
|
146
|
-
// Matter storage
|
|
147
95
|
matterStorageService;
|
|
148
96
|
matterStorageManager;
|
|
149
97
|
matterbridgeContext;
|
|
150
98
|
controllerContext;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
certification; // device certification
|
|
159
|
-
// Matter nodes
|
|
99
|
+
mdnsInterface;
|
|
100
|
+
ipv4Address;
|
|
101
|
+
ipv6Address;
|
|
102
|
+
port;
|
|
103
|
+
passcode;
|
|
104
|
+
discriminator;
|
|
105
|
+
certification;
|
|
160
106
|
serverNode;
|
|
161
107
|
aggregatorNode;
|
|
162
108
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -166,57 +112,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
166
112
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
167
113
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
168
114
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
169
|
-
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
170
115
|
advertisingNodes = new Map();
|
|
171
|
-
static instance;
|
|
172
|
-
// We load asyncronously so is private
|
|
173
116
|
constructor() {
|
|
174
117
|
super();
|
|
175
118
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
176
119
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Set the logger logLevel for the Matterbridge classes and call onChangeLoggerLevel() for each plugin.
|
|
179
|
-
*
|
|
180
|
-
* @param {LogLevel} logLevel The logger logLevel to set.
|
|
181
|
-
*/
|
|
182
|
-
async setLogLevel(logLevel) {
|
|
183
|
-
if (this.log)
|
|
184
|
-
this.log.logLevel = logLevel;
|
|
185
|
-
this.matterbridgeInformation.loggerLevel = logLevel;
|
|
186
|
-
this.frontend.logLevel = logLevel;
|
|
187
|
-
MatterbridgeEndpoint.logLevel = logLevel;
|
|
188
|
-
if (this.devices)
|
|
189
|
-
this.devices.logLevel = logLevel;
|
|
190
|
-
if (this.plugins)
|
|
191
|
-
this.plugins.logLevel = logLevel;
|
|
192
|
-
for (const plugin of this.plugins) {
|
|
193
|
-
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
194
|
-
continue;
|
|
195
|
-
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel;
|
|
196
|
-
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" /* LogLevel.DEBUG */ : this.log.logLevel);
|
|
197
|
-
}
|
|
198
|
-
// Set the global logger callback for the WebSocketServer to the common minimum logLevel
|
|
199
|
-
let callbackLogLevel = "notice" /* LogLevel.NOTICE */;
|
|
200
|
-
if (this.matterbridgeInformation.loggerLevel === "info" /* LogLevel.INFO */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.INFO)
|
|
201
|
-
callbackLogLevel = "info" /* LogLevel.INFO */;
|
|
202
|
-
if (this.matterbridgeInformation.loggerLevel === "debug" /* LogLevel.DEBUG */ || this.matterbridgeInformation.matterLoggerLevel === MatterLogLevel.DEBUG)
|
|
203
|
-
callbackLogLevel = "debug" /* LogLevel.DEBUG */;
|
|
204
|
-
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
205
|
-
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
206
|
-
}
|
|
207
|
-
//* ************************************************************************************************************************************ */
|
|
208
|
-
// loadInstance() and cleanup() methods */
|
|
209
|
-
//* ************************************************************************************************************************************ */
|
|
210
|
-
/**
|
|
211
|
-
* Loads an instance of the Matterbridge class.
|
|
212
|
-
* If an instance already exists, return that instance.
|
|
213
|
-
*
|
|
214
|
-
* @param {boolean} initialize - Whether to initialize the Matterbridge instance after loading. Defaults to false.
|
|
215
|
-
* @returns {Matterbridge} A promise that resolves to the Matterbridge instance.
|
|
216
|
-
*/
|
|
217
120
|
static async loadInstance(initialize = false) {
|
|
218
121
|
if (!Matterbridge.instance) {
|
|
219
|
-
// eslint-disable-next-line no-console
|
|
220
122
|
if (hasParameter('debug'))
|
|
221
123
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
222
124
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -225,17 +127,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
225
127
|
}
|
|
226
128
|
return Matterbridge.instance;
|
|
227
129
|
}
|
|
228
|
-
/**
|
|
229
|
-
* Call cleanup() and dispose MdnsService.
|
|
230
|
-
*
|
|
231
|
-
* @param {number} [timeout] - The timeout duration to wait for the cleanup to complete in milliseconds. Default is 1000.
|
|
232
|
-
* @param {number} [pause] - The pause duration after the cleanup in milliseconds. Default is 250.
|
|
233
|
-
*
|
|
234
|
-
* @deprecated This method is deprecated and is ONLY used for jest tests.
|
|
235
|
-
*/
|
|
236
130
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
237
131
|
this.log.info(`Destroy instance...`);
|
|
238
|
-
// Save server nodes to close
|
|
239
132
|
const servers = [];
|
|
240
133
|
if (this.bridgeMode === 'bridge') {
|
|
241
134
|
if (this.serverNode)
|
|
@@ -253,106 +146,67 @@ export class Matterbridge extends EventEmitter {
|
|
|
253
146
|
servers.push(device.serverNode);
|
|
254
147
|
}
|
|
255
148
|
}
|
|
256
|
-
// Let any already‐queued microtasks run first
|
|
257
149
|
await Promise.resolve();
|
|
258
|
-
// Wait for the cleanup to finish
|
|
259
150
|
await wait(pause, 'destroyInstance start', true);
|
|
260
|
-
// Cleanup
|
|
261
151
|
await this.cleanup('destroying instance...', false, timeout);
|
|
262
|
-
// Close servers mdns service
|
|
263
152
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
264
153
|
for (const server of servers) {
|
|
265
154
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
266
155
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
267
156
|
}
|
|
268
|
-
// Let any already‐queued microtasks run first
|
|
269
157
|
await Promise.resolve();
|
|
270
|
-
// Wait for the cleanup to finish
|
|
271
158
|
await wait(pause, 'destroyInstance stop', true);
|
|
272
159
|
}
|
|
273
|
-
/**
|
|
274
|
-
* Initializes the Matterbridge application.
|
|
275
|
-
*
|
|
276
|
-
* @remarks
|
|
277
|
-
* This method performs the necessary setup and initialization steps for the Matterbridge application.
|
|
278
|
-
* It displays the help information if the 'help' parameter is provided, sets up the logger, checks the
|
|
279
|
-
* node version, registers signal handlers, initializes storage, and parses the command line.
|
|
280
|
-
*
|
|
281
|
-
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
282
|
-
*/
|
|
283
160
|
async initialize() {
|
|
284
|
-
// for (let i = 1; i <= 255; i++) console.log(`\x1b[38;5;${i}mColor: ${i}`);
|
|
285
|
-
// Emit the initialize_started event
|
|
286
161
|
this.emit('initialize_started');
|
|
287
|
-
// Set the restart mode
|
|
288
162
|
if (hasParameter('service'))
|
|
289
163
|
this.restartMode = 'service';
|
|
290
164
|
if (hasParameter('docker'))
|
|
291
165
|
this.restartMode = 'docker';
|
|
292
|
-
// Set the matterbridge home directory
|
|
293
166
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
294
|
-
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
295
167
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
296
|
-
// Set the matterbridge directory
|
|
297
168
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
298
|
-
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
299
169
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
300
170
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
301
171
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
302
|
-
// Set the matterbridge plugin directory
|
|
303
172
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
304
|
-
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
305
173
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
306
|
-
// Set the matterbridge cert directory
|
|
307
174
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
308
|
-
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
309
175
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
310
|
-
// Set the matterbridge root directory
|
|
311
176
|
const { fileURLToPath } = await import('node:url');
|
|
312
177
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
313
178
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
314
|
-
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
315
|
-
// Setup the matter environment
|
|
316
179
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
317
180
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
318
181
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
319
182
|
this.environment.vars.set('runtime.signals', false);
|
|
320
183
|
this.environment.vars.set('runtime.exitcode', false);
|
|
321
|
-
// Register process handlers
|
|
322
184
|
this.registerProcessHandlers();
|
|
323
|
-
// Initialize nodeStorage and nodeContext
|
|
324
185
|
try {
|
|
325
186
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
326
187
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
327
188
|
this.log.debug('Creating node storage context for matterbridge');
|
|
328
189
|
this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
|
|
329
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
330
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
331
190
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
332
191
|
for (const key of keys) {
|
|
333
192
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
334
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
335
193
|
await this.nodeStorage?.storage.get(key);
|
|
336
194
|
}
|
|
337
195
|
const storages = await this.nodeStorage.getStorageNames();
|
|
338
196
|
for (const storage of storages) {
|
|
339
197
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
340
198
|
const nodeContext = await this.nodeStorage?.createStorage(storage);
|
|
341
|
-
// TODO: Remove this code when node-persist-manager is updated
|
|
342
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
343
199
|
const keys = (await nodeContext?.storage.keys());
|
|
344
200
|
keys.forEach(async (key) => {
|
|
345
201
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
346
202
|
await nodeContext?.get(key);
|
|
347
203
|
});
|
|
348
204
|
}
|
|
349
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
350
205
|
this.log.debug('Creating node storage backup...');
|
|
351
206
|
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
352
207
|
this.log.debug('Created node storage backup');
|
|
353
208
|
}
|
|
354
209
|
catch (error) {
|
|
355
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
356
210
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
357
211
|
if (hasParameter('norestore')) {
|
|
358
212
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -366,19 +220,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
366
220
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
367
221
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
368
222
|
}
|
|
369
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
370
223
|
this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
|
|
371
|
-
// Set the first passcode to use for the commissioning server (will be incremented in childbridge mode)
|
|
372
224
|
this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode')) ?? PaseClient.generateRandomPasscode(this.environment.get(Crypto));
|
|
373
|
-
// Set the first discriminator to use for the commissioning server (will be incremented in childbridge mode)
|
|
374
225
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
375
|
-
// Certificate management
|
|
376
226
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
377
227
|
try {
|
|
378
228
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
379
229
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
380
230
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
381
|
-
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
382
231
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
383
232
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
384
233
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -407,13 +256,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
407
256
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
408
257
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
409
258
|
}
|
|
410
|
-
// Override the passcode and discriminator if they are present in the pairing file
|
|
411
259
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
412
260
|
this.passcode = pairingFileJson.passcode;
|
|
413
261
|
this.discriminator = pairingFileJson.discriminator;
|
|
414
262
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
|
|
415
263
|
}
|
|
416
|
-
// Set the certification for matter.js if it is present in the pairing file
|
|
417
264
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
418
265
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
419
266
|
this.certification = {
|
|
@@ -428,53 +275,48 @@ export class Matterbridge extends EventEmitter {
|
|
|
428
275
|
catch (error) {
|
|
429
276
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
430
277
|
}
|
|
431
|
-
// Store the passcode, discriminator and port in the node context
|
|
432
278
|
await this.nodeContext.set('matterport', this.port);
|
|
433
279
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
434
280
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
435
281
|
this.log.debug(`Initializing server node for Matterbridge on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
|
|
436
|
-
// Set matterbridge logger level (context: matterbridgeLogLevel)
|
|
437
282
|
if (hasParameter('logger')) {
|
|
438
283
|
const level = getParameter('logger');
|
|
439
284
|
if (level === 'debug') {
|
|
440
|
-
this.log.logLevel = "debug"
|
|
285
|
+
this.log.logLevel = "debug";
|
|
441
286
|
}
|
|
442
287
|
else if (level === 'info') {
|
|
443
|
-
this.log.logLevel = "info"
|
|
288
|
+
this.log.logLevel = "info";
|
|
444
289
|
}
|
|
445
290
|
else if (level === 'notice') {
|
|
446
|
-
this.log.logLevel = "notice"
|
|
291
|
+
this.log.logLevel = "notice";
|
|
447
292
|
}
|
|
448
293
|
else if (level === 'warn') {
|
|
449
|
-
this.log.logLevel = "warn"
|
|
294
|
+
this.log.logLevel = "warn";
|
|
450
295
|
}
|
|
451
296
|
else if (level === 'error') {
|
|
452
|
-
this.log.logLevel = "error"
|
|
297
|
+
this.log.logLevel = "error";
|
|
453
298
|
}
|
|
454
299
|
else if (level === 'fatal') {
|
|
455
|
-
this.log.logLevel = "fatal"
|
|
300
|
+
this.log.logLevel = "fatal";
|
|
456
301
|
}
|
|
457
302
|
else {
|
|
458
303
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
459
|
-
this.log.logLevel = "info"
|
|
304
|
+
this.log.logLevel = "info";
|
|
460
305
|
}
|
|
461
306
|
}
|
|
462
307
|
else {
|
|
463
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.
|
|
308
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
464
309
|
}
|
|
465
310
|
this.frontend.logLevel = this.log.logLevel;
|
|
466
311
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
467
|
-
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
468
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
469
312
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
470
313
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
471
|
-
this.
|
|
314
|
+
this.fileLogger = true;
|
|
472
315
|
}
|
|
473
316
|
this.log.notice('Matterbridge is starting...');
|
|
474
|
-
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.
|
|
317
|
+
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
475
318
|
if (this.profile !== undefined)
|
|
476
319
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
477
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
478
320
|
if (hasParameter('matterlogger')) {
|
|
479
321
|
const level = getParameter('matterlogger');
|
|
480
322
|
if (level === 'debug') {
|
|
@@ -501,20 +343,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
501
343
|
}
|
|
502
344
|
}
|
|
503
345
|
else {
|
|
504
|
-
Logger.level = (await this.nodeContext.get('matterLogLevel', this.
|
|
346
|
+
Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
505
347
|
}
|
|
506
348
|
Logger.format = MatterLogFormat.ANSI;
|
|
507
|
-
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
508
349
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
509
|
-
this.
|
|
350
|
+
this.matterFileLogger = true;
|
|
510
351
|
}
|
|
511
|
-
Logger.destinations.default.write = this.createDestinationMatterLogger(this.
|
|
512
|
-
this.
|
|
513
|
-
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterbridgeInformation.matterFileLogger}.`);
|
|
514
|
-
// Log network interfaces
|
|
352
|
+
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
353
|
+
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
|
|
515
354
|
const networkInterfaces = os.networkInterfaces();
|
|
516
355
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
517
|
-
const
|
|
356
|
+
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
518
357
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
519
358
|
if (ifaces && ifaces.length > 0) {
|
|
520
359
|
this.log.debug(`Network interface ${BLUE}${ifaceName}${db}:`);
|
|
@@ -524,7 +363,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
524
363
|
});
|
|
525
364
|
}
|
|
526
365
|
}
|
|
527
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
528
366
|
if (hasParameter('mdnsinterface')) {
|
|
529
367
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
530
368
|
}
|
|
@@ -533,10 +371,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
533
371
|
if (this.mdnsInterface === '')
|
|
534
372
|
this.mdnsInterface = undefined;
|
|
535
373
|
}
|
|
536
|
-
// Validate mdnsInterface
|
|
537
374
|
if (this.mdnsInterface) {
|
|
538
|
-
if (!
|
|
539
|
-
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${
|
|
375
|
+
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
376
|
+
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
540
377
|
this.mdnsInterface = undefined;
|
|
541
378
|
await this.nodeContext.remove('mattermdnsinterface');
|
|
542
379
|
}
|
|
@@ -546,86 +383,75 @@ export class Matterbridge extends EventEmitter {
|
|
|
546
383
|
}
|
|
547
384
|
if (this.mdnsInterface)
|
|
548
385
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
549
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
550
386
|
if (hasParameter('ipv4address')) {
|
|
551
|
-
this.
|
|
387
|
+
this.ipv4Address = getParameter('ipv4address');
|
|
552
388
|
}
|
|
553
389
|
else {
|
|
554
|
-
this.
|
|
555
|
-
if (this.
|
|
556
|
-
this.
|
|
390
|
+
this.ipv4Address = await this.nodeContext.get('matteripv4address', undefined);
|
|
391
|
+
if (this.ipv4Address === '')
|
|
392
|
+
this.ipv4Address = undefined;
|
|
557
393
|
}
|
|
558
|
-
|
|
559
|
-
if (this.ipv4address) {
|
|
394
|
+
if (this.ipv4Address) {
|
|
560
395
|
let isValid = false;
|
|
561
396
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
562
|
-
if (ifaces && ifaces.find((iface) => iface.address === this.
|
|
563
|
-
this.log.info(`Using ipv4address ${CYAN}${this.
|
|
397
|
+
if (ifaces && ifaces.find((iface) => iface.address === this.ipv4Address)) {
|
|
398
|
+
this.log.info(`Using ipv4address ${CYAN}${this.ipv4Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
564
399
|
isValid = true;
|
|
565
400
|
break;
|
|
566
401
|
}
|
|
567
402
|
}
|
|
568
403
|
if (!isValid) {
|
|
569
|
-
this.log.error(`Invalid ipv4address: ${this.
|
|
570
|
-
this.
|
|
404
|
+
this.log.error(`Invalid ipv4address: ${this.ipv4Address}. Using all available addresses.`);
|
|
405
|
+
this.ipv4Address = undefined;
|
|
571
406
|
await this.nodeContext.remove('matteripv4address');
|
|
572
407
|
}
|
|
573
408
|
}
|
|
574
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
575
409
|
if (hasParameter('ipv6address')) {
|
|
576
|
-
this.
|
|
410
|
+
this.ipv6Address = getParameter('ipv6address');
|
|
577
411
|
}
|
|
578
412
|
else {
|
|
579
|
-
this.
|
|
580
|
-
if (this.
|
|
581
|
-
this.
|
|
413
|
+
this.ipv6Address = await this.nodeContext?.get('matteripv6address', undefined);
|
|
414
|
+
if (this.ipv6Address === '')
|
|
415
|
+
this.ipv6Address = undefined;
|
|
582
416
|
}
|
|
583
|
-
|
|
584
|
-
if (this.ipv6address) {
|
|
417
|
+
if (this.ipv6Address) {
|
|
585
418
|
let isValid = false;
|
|
586
419
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
587
|
-
if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.
|
|
588
|
-
this.log.info(`Using ipv6address ${CYAN}${this.
|
|
420
|
+
if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.ipv6Address)) {
|
|
421
|
+
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
589
422
|
isValid = true;
|
|
590
423
|
break;
|
|
591
424
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
425
|
+
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
426
|
+
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
595
427
|
isValid = true;
|
|
596
428
|
break;
|
|
597
429
|
}
|
|
598
430
|
}
|
|
599
431
|
if (!isValid) {
|
|
600
|
-
this.log.error(`Invalid ipv6address: ${this.
|
|
601
|
-
this.
|
|
432
|
+
this.log.error(`Invalid ipv6address: ${this.ipv6Address}. Using all available addresses.`);
|
|
433
|
+
this.ipv6Address = undefined;
|
|
602
434
|
await this.nodeContext.remove('matteripv6address');
|
|
603
435
|
}
|
|
604
436
|
}
|
|
605
|
-
// Initialize the virtual mode
|
|
606
437
|
if (hasParameter('novirtual')) {
|
|
607
|
-
this.
|
|
438
|
+
this.virtualMode = 'disabled';
|
|
608
439
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
609
440
|
}
|
|
610
441
|
else {
|
|
611
|
-
this.
|
|
442
|
+
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
612
443
|
}
|
|
613
|
-
this.log.debug(`Virtual mode ${this.
|
|
614
|
-
// Initialize PluginManager
|
|
444
|
+
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
615
445
|
this.plugins.logLevel = this.log.logLevel;
|
|
616
446
|
await this.plugins.loadFromStorage();
|
|
617
|
-
// Initialize DeviceManager
|
|
618
447
|
this.devices.logLevel = this.log.logLevel;
|
|
619
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
620
448
|
for (const plugin of this.plugins) {
|
|
621
449
|
const packageJson = await this.plugins.parse(plugin);
|
|
622
450
|
if (packageJson === null && !hasParameter('add') && !hasParameter('remove') && !hasParameter('enable') && !hasParameter('disable') && !hasParameter('reset') && !hasParameter('factoryreset')) {
|
|
623
|
-
// Try to reinstall the plugin from npm (for Docker pull and external plugins)
|
|
624
|
-
// We don't do this when the add and other parameters are set because we shut down the process after adding the plugin
|
|
625
451
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
626
452
|
try {
|
|
627
453
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
628
|
-
await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], plugin.name);
|
|
454
|
+
await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], 'install', plugin.name);
|
|
629
455
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
630
456
|
plugin.error = false;
|
|
631
457
|
}
|
|
@@ -644,7 +470,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
644
470
|
await plugin.nodeContext.set('description', plugin.description);
|
|
645
471
|
await plugin.nodeContext.set('author', plugin.author);
|
|
646
472
|
}
|
|
647
|
-
// Log system info and create .matterbridge directory
|
|
648
473
|
await this.logNodeAndSystemInfo();
|
|
649
474
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
650
475
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -652,7 +477,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
652
477
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
653
478
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
654
479
|
`running on ${this.systemInformation.osType} (v.${this.systemInformation.osRelease}) platform ${this.systemInformation.osPlatform} arch ${this.systemInformation.osArch}`);
|
|
655
|
-
// Check node version and throw error
|
|
656
480
|
const minNodeVersion = 18;
|
|
657
481
|
const nodeVersion = process.versions.node;
|
|
658
482
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -660,18 +484,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
660
484
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
661
485
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
662
486
|
}
|
|
663
|
-
// Parse command line
|
|
664
487
|
await this.parseCommandLine();
|
|
665
|
-
// Emit the initialize_completed event
|
|
666
488
|
this.emit('initialize_completed');
|
|
667
489
|
this.initialized = true;
|
|
668
490
|
}
|
|
669
|
-
/**
|
|
670
|
-
* Parses the command line arguments and performs the corresponding actions.
|
|
671
|
-
*
|
|
672
|
-
* @private
|
|
673
|
-
* @returns {Promise<void>} A promise that resolves when the command line arguments have been processed, or the process exits.
|
|
674
|
-
*/
|
|
675
491
|
async parseCommandLine() {
|
|
676
492
|
if (hasParameter('help')) {
|
|
677
493
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -733,19 +549,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
733
549
|
}
|
|
734
550
|
index++;
|
|
735
551
|
}
|
|
736
|
-
/*
|
|
737
|
-
const serializedRegisteredDevices = await this.nodeContext?.get<SerializedMatterbridgeEndpoint[]>('devices', []);
|
|
738
|
-
this.log.info(`│ Registered devices (${serializedRegisteredDevices?.length})`);
|
|
739
|
-
serializedRegisteredDevices?.forEach((device, index) => {
|
|
740
|
-
if (index !== serializedRegisteredDevices.length - 1) {
|
|
741
|
-
this.log.info(`├─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
742
|
-
this.log.info(`│ └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
743
|
-
} else {
|
|
744
|
-
this.log.info(`└─┬─ plugin ${plg}${device.pluginName}${nf} device: ${dev}${device.deviceName}${nf} uniqueId: ${YELLOW}${device.uniqueId}${nf}`);
|
|
745
|
-
this.log.info(` └─ endpoint ${RED}${device.endpoint}${nf} ${typ}${device.endpointName}${nf} ${debugStringify(device.clusterServersId)}`);
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
*/
|
|
749
552
|
this.shutdown = true;
|
|
750
553
|
return;
|
|
751
554
|
}
|
|
@@ -795,10 +598,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
795
598
|
this.shutdown = true;
|
|
796
599
|
return;
|
|
797
600
|
}
|
|
798
|
-
// Initialize frontend
|
|
799
601
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
800
602
|
await this.frontend.start(getIntParameter('frontend'));
|
|
801
|
-
// Start the matter storage and create the matterbridge context
|
|
802
603
|
try {
|
|
803
604
|
await this.startMatterStorage();
|
|
804
605
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -814,21 +615,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
814
615
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
815
616
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
816
617
|
}
|
|
817
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
818
618
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
819
619
|
this.initialized = true;
|
|
820
620
|
await this.shutdownProcessAndReset();
|
|
821
621
|
this.shutdown = true;
|
|
822
622
|
return;
|
|
823
623
|
}
|
|
824
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
825
624
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
826
625
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
827
626
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
828
627
|
if (plugin) {
|
|
829
628
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
830
629
|
if (!matterStorageManager) {
|
|
831
|
-
/* istanbul ignore next */
|
|
832
630
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
833
631
|
}
|
|
834
632
|
else {
|
|
@@ -847,42 +645,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
847
645
|
this.shutdown = true;
|
|
848
646
|
return;
|
|
849
647
|
}
|
|
850
|
-
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
851
648
|
clearTimeout(this.checkUpdateTimeout);
|
|
852
649
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
853
650
|
const { checkUpdates } = await import('./update.js');
|
|
854
651
|
checkUpdates(this);
|
|
855
652
|
}, 30 * 1000).unref();
|
|
856
|
-
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
857
653
|
clearInterval(this.checkUpdateInterval);
|
|
858
654
|
this.checkUpdateInterval = setInterval(async () => {
|
|
859
655
|
const { checkUpdates } = await import('./update.js');
|
|
860
656
|
checkUpdates(this);
|
|
861
657
|
}, 12 * 60 * 60 * 1000).unref();
|
|
862
|
-
// Start the matterbridge in mode test
|
|
863
658
|
if (hasParameter('test')) {
|
|
864
659
|
this.bridgeMode = 'bridge';
|
|
865
660
|
return;
|
|
866
661
|
}
|
|
867
|
-
// Start the matterbridge in mode controller
|
|
868
662
|
if (hasParameter('controller')) {
|
|
869
663
|
this.bridgeMode = 'controller';
|
|
870
664
|
await this.startController();
|
|
871
665
|
return;
|
|
872
666
|
}
|
|
873
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
874
667
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
875
668
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
876
669
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
877
670
|
}
|
|
878
|
-
// Start matterbridge in bridge mode
|
|
879
671
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
880
672
|
this.bridgeMode = 'bridge';
|
|
881
673
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
882
674
|
await this.startBridge();
|
|
883
675
|
return;
|
|
884
676
|
}
|
|
885
|
-
// Start matterbridge in childbridge mode
|
|
886
677
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
887
678
|
this.bridgeMode = 'childbridge';
|
|
888
679
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -890,22 +681,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
890
681
|
return;
|
|
891
682
|
}
|
|
892
683
|
}
|
|
893
|
-
/**
|
|
894
|
-
* Asynchronously loads and starts the registered plugins.
|
|
895
|
-
*
|
|
896
|
-
* This method is responsible for initializing and starting all enabled plugins.
|
|
897
|
-
* It ensures that each plugin is properly loaded and started before the bridge starts.
|
|
898
|
-
*
|
|
899
|
-
* @param {boolean} [wait] - If true, the method will wait for all plugins to be fully loaded and started before resolving.
|
|
900
|
-
* @param {boolean} [start] - If true, the method will start the plugins after loading them.
|
|
901
|
-
* @returns {Promise<void>} A promise that resolves when all plugins have been loaded and started.
|
|
902
|
-
*/
|
|
903
684
|
async startPlugins(wait = false, start = true) {
|
|
904
|
-
// Check, load and start the plugins
|
|
905
685
|
for (const plugin of this.plugins) {
|
|
906
686
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
907
687
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
908
|
-
// Check if the plugin is available
|
|
909
688
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
910
689
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
911
690
|
plugin.enabled = false;
|
|
@@ -925,16 +704,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
925
704
|
if (wait)
|
|
926
705
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
927
706
|
else
|
|
928
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
707
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
929
708
|
}
|
|
930
709
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
931
710
|
}
|
|
932
|
-
/**
|
|
933
|
-
* Registers the process handlers for uncaughtException, unhandledRejection, SIGINT and SIGTERM.
|
|
934
|
-
* - When an uncaught exception occurs, the exceptionHandler logs the error message and stack trace.
|
|
935
|
-
* - When an unhandled promise rejection occurs, the rejectionHandler logs the reason and stack trace.
|
|
936
|
-
* - When either of SIGINT and SIGTERM signals are received, the cleanup method is called with an appropriate message.
|
|
937
|
-
*/
|
|
938
711
|
registerProcessHandlers() {
|
|
939
712
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
940
713
|
process.removeAllListeners('uncaughtException');
|
|
@@ -961,9 +734,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
961
734
|
};
|
|
962
735
|
process.on('SIGTERM', this.sigtermHandler);
|
|
963
736
|
}
|
|
964
|
-
/**
|
|
965
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
966
|
-
*/
|
|
967
737
|
deregisterProcessHandlers() {
|
|
968
738
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
969
739
|
if (this.exceptionHandler)
|
|
@@ -980,17 +750,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
980
750
|
process.off('SIGTERM', this.sigtermHandler);
|
|
981
751
|
this.sigtermHandler = undefined;
|
|
982
752
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Logs the node and system information.
|
|
985
|
-
*/
|
|
986
753
|
async logNodeAndSystemInfo() {
|
|
987
|
-
// IP address information
|
|
988
754
|
const networkInterfaces = os.networkInterfaces();
|
|
989
755
|
this.systemInformation.interfaceName = '';
|
|
990
756
|
this.systemInformation.ipv4Address = '';
|
|
991
757
|
this.systemInformation.ipv6Address = '';
|
|
758
|
+
this.systemInformation.macAddress = '';
|
|
992
759
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
993
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
994
760
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
995
761
|
continue;
|
|
996
762
|
if (!interfaceDetails) {
|
|
@@ -1016,22 +782,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1016
782
|
break;
|
|
1017
783
|
}
|
|
1018
784
|
}
|
|
1019
|
-
// Node information
|
|
1020
785
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
1021
786
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
1022
787
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
1023
788
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1024
|
-
// Host system information
|
|
1025
789
|
this.systemInformation.hostname = os.hostname();
|
|
1026
790
|
this.systemInformation.user = os.userInfo().username;
|
|
1027
|
-
this.systemInformation.osType = os.type();
|
|
1028
|
-
this.systemInformation.osRelease = os.release();
|
|
1029
|
-
this.systemInformation.osPlatform = os.platform();
|
|
1030
|
-
this.systemInformation.osArch = os.arch();
|
|
1031
|
-
this.systemInformation.totalMemory = (os.totalmem()
|
|
1032
|
-
this.systemInformation.freeMemory = (os.freemem()
|
|
1033
|
-
this.systemInformation.systemUptime = (os.uptime()
|
|
1034
|
-
|
|
791
|
+
this.systemInformation.osType = os.type();
|
|
792
|
+
this.systemInformation.osRelease = os.release();
|
|
793
|
+
this.systemInformation.osPlatform = os.platform();
|
|
794
|
+
this.systemInformation.osArch = os.arch();
|
|
795
|
+
this.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
796
|
+
this.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
797
|
+
this.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
798
|
+
this.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
|
|
799
|
+
this.systemInformation.cpuUsage = '0.00 %';
|
|
800
|
+
this.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
801
|
+
this.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
802
|
+
this.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1035
803
|
this.log.debug('Host System Information:');
|
|
1036
804
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
1037
805
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -1047,21 +815,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1047
815
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
1048
816
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
1049
817
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1050
|
-
|
|
818
|
+
this.log.debug(`- Process Uptime: ${this.systemInformation.processUptime}`);
|
|
819
|
+
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
820
|
+
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
821
|
+
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
1051
822
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1052
823
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1053
824
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1054
825
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1055
826
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1056
|
-
// Global node_modules directory
|
|
1057
827
|
if (this.nodeContext)
|
|
1058
|
-
this.globalModulesDirectory =
|
|
828
|
+
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1059
829
|
if (this.globalModulesDirectory === '') {
|
|
1060
|
-
// First run of Matterbridge so the node storage is empty
|
|
1061
830
|
this.log.debug(`Getting global node_modules directory...`);
|
|
1062
831
|
try {
|
|
1063
832
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
1064
|
-
this.
|
|
833
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
1065
834
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1066
835
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
1067
836
|
}
|
|
@@ -1070,11 +839,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1070
839
|
}
|
|
1071
840
|
}
|
|
1072
841
|
else {
|
|
1073
|
-
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
1074
842
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
1075
843
|
try {
|
|
1076
844
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
1077
|
-
this.
|
|
845
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
1078
846
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1079
847
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
1080
848
|
}
|
|
@@ -1082,91 +850,93 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
850
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
1083
851
|
}
|
|
1084
852
|
}
|
|
1085
|
-
|
|
853
|
+
this.log.debug(`Reading matterbridge package.json...`);
|
|
1086
854
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1087
855
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
1088
|
-
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
1089
856
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
-
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
1091
857
|
if (this.nodeContext)
|
|
1092
|
-
this.matterbridgeLatestVersion =
|
|
858
|
+
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1093
859
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
-
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
1095
860
|
if (this.nodeContext)
|
|
1096
|
-
this.matterbridgeDevVersion =
|
|
861
|
+
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
1097
862
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1098
|
-
|
|
863
|
+
this.log.debug(`Reading frontend package.json...`);
|
|
864
|
+
const frontendPackageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
865
|
+
this.frontendVersion = frontendPackageJson.version;
|
|
866
|
+
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1099
867
|
const currentDir = process.cwd();
|
|
1100
868
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1101
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1102
869
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1103
870
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1104
871
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
872
|
+
async setLogLevel(logLevel) {
|
|
873
|
+
if (this.log)
|
|
874
|
+
this.log.logLevel = logLevel;
|
|
875
|
+
this.frontend.logLevel = logLevel;
|
|
876
|
+
MatterbridgeEndpoint.logLevel = logLevel;
|
|
877
|
+
if (this.devices)
|
|
878
|
+
this.devices.logLevel = logLevel;
|
|
879
|
+
if (this.plugins)
|
|
880
|
+
this.plugins.logLevel = logLevel;
|
|
881
|
+
for (const plugin of this.plugins) {
|
|
882
|
+
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
883
|
+
continue;
|
|
884
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
885
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
886
|
+
}
|
|
887
|
+
let callbackLogLevel = "notice";
|
|
888
|
+
if (this.log.logLevel === "info" || Logger.level === MatterLogLevel.INFO)
|
|
889
|
+
callbackLogLevel = "info";
|
|
890
|
+
if (this.log.logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
891
|
+
callbackLogLevel = "debug";
|
|
892
|
+
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
893
|
+
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
894
|
+
}
|
|
895
|
+
getLogLevel() {
|
|
896
|
+
return this.log.logLevel;
|
|
897
|
+
}
|
|
1112
898
|
createDestinationMatterLogger(fileLogger) {
|
|
1113
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
899
|
+
this.matterLog.logNameColor = '\x1b[34m';
|
|
1114
900
|
if (fileLogger) {
|
|
1115
901
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
1116
902
|
}
|
|
1117
903
|
return (text, message) => {
|
|
1118
|
-
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
1119
904
|
const logger = text.slice(44, 44 + 20).trim();
|
|
1120
905
|
const msg = text.slice(65);
|
|
1121
906
|
this.matterLog.logName = logger;
|
|
1122
907
|
switch (message.level) {
|
|
1123
908
|
case MatterLogLevel.DEBUG:
|
|
1124
|
-
this.matterLog.log("debug"
|
|
909
|
+
this.matterLog.log("debug", msg);
|
|
1125
910
|
break;
|
|
1126
911
|
case MatterLogLevel.INFO:
|
|
1127
|
-
this.matterLog.log("info"
|
|
912
|
+
this.matterLog.log("info", msg);
|
|
1128
913
|
break;
|
|
1129
914
|
case MatterLogLevel.NOTICE:
|
|
1130
|
-
this.matterLog.log("notice"
|
|
915
|
+
this.matterLog.log("notice", msg);
|
|
1131
916
|
break;
|
|
1132
917
|
case MatterLogLevel.WARN:
|
|
1133
|
-
this.matterLog.log("warn"
|
|
918
|
+
this.matterLog.log("warn", msg);
|
|
1134
919
|
break;
|
|
1135
920
|
case MatterLogLevel.ERROR:
|
|
1136
|
-
this.matterLog.log("error"
|
|
921
|
+
this.matterLog.log("error", msg);
|
|
1137
922
|
break;
|
|
1138
923
|
case MatterLogLevel.FATAL:
|
|
1139
|
-
this.matterLog.log("fatal"
|
|
924
|
+
this.matterLog.log("fatal", msg);
|
|
1140
925
|
break;
|
|
1141
926
|
}
|
|
1142
927
|
};
|
|
1143
928
|
}
|
|
1144
|
-
/**
|
|
1145
|
-
* Restarts the process by exiting the current instance and loading a new instance (/api/restart).
|
|
1146
|
-
*
|
|
1147
|
-
* @returns {Promise<void>} A promise that resolves when the restart is completed.
|
|
1148
|
-
*/
|
|
1149
929
|
async restartProcess() {
|
|
1150
930
|
await this.cleanup('restarting...', true);
|
|
1151
931
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Shut down the process (/api/shutdown).
|
|
1154
|
-
*
|
|
1155
|
-
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1156
|
-
*/
|
|
1157
932
|
async shutdownProcess() {
|
|
1158
933
|
await this.cleanup('shutting down...', false);
|
|
1159
934
|
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Update matterbridge and shut down the process (virtual device 'Update Matterbridge').
|
|
1162
|
-
*
|
|
1163
|
-
* @returns {Promise<void>} A promise that resolves when the update is completed.
|
|
1164
|
-
*/
|
|
1165
935
|
async updateProcess() {
|
|
1166
936
|
this.log.info('Updating matterbridge...');
|
|
1167
937
|
try {
|
|
1168
938
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1169
|
-
await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'matterbridge');
|
|
939
|
+
await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'install', 'matterbridge');
|
|
1170
940
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
1171
941
|
}
|
|
1172
942
|
catch (error) {
|
|
@@ -1175,13 +945,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1175
945
|
this.frontend.wssSendRestartRequired();
|
|
1176
946
|
await this.cleanup('updating...', false);
|
|
1177
947
|
}
|
|
1178
|
-
/**
|
|
1179
|
-
* Unregister all devices and shut down the process (/api/unregister).
|
|
1180
|
-
*
|
|
1181
|
-
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1182
|
-
*
|
|
1183
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1184
|
-
*/
|
|
1185
948
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
1186
949
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1187
950
|
for (const plugin of this.plugins.array()) {
|
|
@@ -1193,71 +956,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
1193
956
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
1194
957
|
}
|
|
1195
958
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1196
|
-
await wait(timeout);
|
|
959
|
+
await wait(timeout);
|
|
1197
960
|
this.log.debug('Cleaning up and shutting down...');
|
|
1198
961
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
1199
962
|
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Reset commissioning and shut down the process (/api/reset).
|
|
1202
|
-
*
|
|
1203
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1204
|
-
*/
|
|
1205
963
|
async shutdownProcessAndReset() {
|
|
1206
964
|
await this.cleanup('shutting down with reset...', false);
|
|
1207
965
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Factory reset and shut down the process (/api/factory-reset).
|
|
1210
|
-
*
|
|
1211
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1212
|
-
*/
|
|
1213
966
|
async shutdownProcessAndFactoryReset() {
|
|
1214
967
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1215
968
|
}
|
|
1216
|
-
/**
|
|
1217
|
-
* Cleans up the Matterbridge instance.
|
|
1218
|
-
*
|
|
1219
|
-
* @param {string} message - The cleanup message.
|
|
1220
|
-
* @param {boolean} [restart] - Indicates whether to restart the instance after cleanup. Default is `false`.
|
|
1221
|
-
* @param {number} [timeout] - The timeout duration to wait for the message exchange to complete in milliseconds. Default is 1000.
|
|
1222
|
-
*
|
|
1223
|
-
* @returns {Promise<void>} A promise that resolves when the cleanup is completed.
|
|
1224
|
-
*/
|
|
1225
969
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
1226
970
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1227
971
|
this.emit('cleanup_started');
|
|
1228
972
|
this.hasCleanupStarted = true;
|
|
1229
973
|
this.log.info(message);
|
|
1230
|
-
// Clear the start matter interval
|
|
1231
974
|
if (this.startMatterInterval) {
|
|
1232
975
|
clearInterval(this.startMatterInterval);
|
|
1233
976
|
this.startMatterInterval = undefined;
|
|
1234
977
|
this.log.debug('Start matter interval cleared');
|
|
1235
978
|
}
|
|
1236
|
-
// Clear the check update timeout
|
|
1237
979
|
if (this.checkUpdateTimeout) {
|
|
1238
980
|
clearTimeout(this.checkUpdateTimeout);
|
|
1239
981
|
this.checkUpdateTimeout = undefined;
|
|
1240
982
|
this.log.debug('Check update timeout cleared');
|
|
1241
983
|
}
|
|
1242
|
-
// Clear the check update interval
|
|
1243
984
|
if (this.checkUpdateInterval) {
|
|
1244
985
|
clearInterval(this.checkUpdateInterval);
|
|
1245
986
|
this.checkUpdateInterval = undefined;
|
|
1246
987
|
this.log.debug('Check update interval cleared');
|
|
1247
988
|
}
|
|
1248
|
-
// Clear the configure timeout
|
|
1249
989
|
if (this.configureTimeout) {
|
|
1250
990
|
clearTimeout(this.configureTimeout);
|
|
1251
991
|
this.configureTimeout = undefined;
|
|
1252
992
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1253
993
|
}
|
|
1254
|
-
// Clear the reachability timeout
|
|
1255
994
|
if (this.reachabilityTimeout) {
|
|
1256
995
|
clearTimeout(this.reachabilityTimeout);
|
|
1257
996
|
this.reachabilityTimeout = undefined;
|
|
1258
997
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1259
998
|
}
|
|
1260
|
-
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1261
999
|
for (const plugin of this.plugins) {
|
|
1262
1000
|
if (!plugin.enabled || plugin.error)
|
|
1263
1001
|
continue;
|
|
@@ -1268,7 +1006,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1268
1006
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1269
1007
|
}
|
|
1270
1008
|
}
|
|
1271
|
-
// Stop matter server nodes
|
|
1272
1009
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1273
1010
|
if (timeout > 0) {
|
|
1274
1011
|
this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
|
|
@@ -1295,7 +1032,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1295
1032
|
}
|
|
1296
1033
|
}
|
|
1297
1034
|
this.log.notice('Stopped matter server nodes');
|
|
1298
|
-
// Matter commisioning reset
|
|
1299
1035
|
if (message === 'shutting down with reset...') {
|
|
1300
1036
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1301
1037
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1305,7 +1041,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1305
1041
|
await this.matterbridgeContext?.clearAll();
|
|
1306
1042
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1307
1043
|
}
|
|
1308
|
-
// Unregister all devices
|
|
1309
1044
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1310
1045
|
if (this.bridgeMode === 'bridge') {
|
|
1311
1046
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1323,29 +1058,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1323
1058
|
}
|
|
1324
1059
|
this.log.info('Matter storage reset done!');
|
|
1325
1060
|
}
|
|
1326
|
-
// Stop matter storage
|
|
1327
1061
|
await this.stopMatterStorage();
|
|
1328
|
-
// Stop the frontend
|
|
1329
1062
|
await this.frontend.stop();
|
|
1330
|
-
|
|
1063
|
+
this.frontend.destroy();
|
|
1064
|
+
this.plugins.destroy();
|
|
1065
|
+
this.devices.destroy();
|
|
1331
1066
|
if (this.nodeStorage && this.nodeContext) {
|
|
1332
|
-
/*
|
|
1333
|
-
TODO: Implement serialization of registered devices in edge mode
|
|
1334
|
-
this.log.info('Saving registered devices...');
|
|
1335
|
-
const serializedRegisteredDevices: SerializedMatterbridgeEndpoint[] = [];
|
|
1336
|
-
this.devices.forEach(async (device) => {
|
|
1337
|
-
const serializedMatterbridgeDevice = MatterbridgeEndpoint.serialize(device);
|
|
1338
|
-
// this.log.info(`- ${serializedMatterbridgeDevice.deviceName}${rs}\n`, serializedMatterbridgeDevice);
|
|
1339
|
-
if (serializedMatterbridgeDevice) serializedRegisteredDevices.push(serializedMatterbridgeDevice);
|
|
1340
|
-
});
|
|
1341
|
-
await this.nodeContext.set<SerializedMatterbridgeEndpoint[]>('devices', serializedRegisteredDevices);
|
|
1342
|
-
this.log.info(`Saved registered devices (${serializedRegisteredDevices?.length})`);
|
|
1343
|
-
*/
|
|
1344
|
-
// Clear nodeContext and nodeStorage (they just need 1000ms to write the data to disk)
|
|
1345
1067
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1346
1068
|
await this.nodeContext.close();
|
|
1347
1069
|
this.nodeContext = undefined;
|
|
1348
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1349
1070
|
for (const plugin of this.plugins) {
|
|
1350
1071
|
if (plugin.nodeContext) {
|
|
1351
1072
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1362,10 +1083,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1362
1083
|
}
|
|
1363
1084
|
this.plugins.clear();
|
|
1364
1085
|
this.devices.clear();
|
|
1365
|
-
// Factory reset
|
|
1366
1086
|
if (message === 'shutting down with factory reset...') {
|
|
1367
1087
|
try {
|
|
1368
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1369
1088
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1370
1089
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1371
1090
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1374,13 +1093,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1374
1093
|
await fs.rm(backup, { recursive: true });
|
|
1375
1094
|
}
|
|
1376
1095
|
catch (error) {
|
|
1377
|
-
// istanbul ignore next if
|
|
1378
1096
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1379
1097
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1380
1098
|
}
|
|
1381
1099
|
}
|
|
1382
1100
|
try {
|
|
1383
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1384
1101
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1385
1102
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1386
1103
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1389,20 +1106,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1106
|
await fs.rm(backup, { recursive: true });
|
|
1390
1107
|
}
|
|
1391
1108
|
catch (error) {
|
|
1392
|
-
// istanbul ignore next if
|
|
1393
1109
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1394
1110
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1395
1111
|
}
|
|
1396
1112
|
}
|
|
1397
1113
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1398
1114
|
}
|
|
1399
|
-
// Deregisters the process handlers
|
|
1400
1115
|
this.deregisterProcessHandlers();
|
|
1401
1116
|
if (restart) {
|
|
1402
1117
|
if (message === 'updating...') {
|
|
1403
1118
|
this.log.info('Cleanup completed. Updating...');
|
|
1404
1119
|
Matterbridge.instance = undefined;
|
|
1405
|
-
this.emit('update');
|
|
1120
|
+
this.emit('update');
|
|
1406
1121
|
}
|
|
1407
1122
|
else if (message === 'restarting...') {
|
|
1408
1123
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1426,14 +1141,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1426
1141
|
this.log.debug('Cleanup already started...');
|
|
1427
1142
|
}
|
|
1428
1143
|
}
|
|
1429
|
-
/**
|
|
1430
|
-
* Starts the Matterbridge in bridge mode.
|
|
1431
|
-
*
|
|
1432
|
-
* @private
|
|
1433
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1434
|
-
*/
|
|
1435
1144
|
async startBridge() {
|
|
1436
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1437
1145
|
if (!this.matterStorageManager)
|
|
1438
1146
|
throw new Error('No storage manager initialized');
|
|
1439
1147
|
if (!this.matterbridgeContext)
|
|
@@ -1472,16 +1180,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1472
1180
|
clearInterval(this.startMatterInterval);
|
|
1473
1181
|
this.startMatterInterval = undefined;
|
|
1474
1182
|
this.log.debug('Cleared startMatterInterval interval for Matterbridge');
|
|
1475
|
-
|
|
1476
|
-
this.startServerNode(this.serverNode); // We don't await this, because the server node is started in the background
|
|
1477
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1183
|
+
this.startServerNode(this.serverNode);
|
|
1478
1184
|
for (const device of this.devices.array()) {
|
|
1479
1185
|
if (device.mode === 'server' && device.serverNode) {
|
|
1480
1186
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1481
|
-
this.startServerNode(device.serverNode);
|
|
1187
|
+
this.startServerNode(device.serverNode);
|
|
1482
1188
|
}
|
|
1483
1189
|
}
|
|
1484
|
-
// Configure the plugins
|
|
1485
1190
|
this.configureTimeout = setTimeout(async () => {
|
|
1486
1191
|
for (const plugin of this.plugins.array()) {
|
|
1487
1192
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1499,40 +1204,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1499
1204
|
}
|
|
1500
1205
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1501
1206
|
}, 30 * 1000).unref();
|
|
1502
|
-
// Setting reachability to true
|
|
1503
1207
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1504
1208
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1505
1209
|
if (this.aggregatorNode)
|
|
1506
1210
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1507
1211
|
}, 60 * 1000).unref();
|
|
1508
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1509
1212
|
this.emit('bridge_started');
|
|
1510
1213
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1511
1214
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1512
1215
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1513
1216
|
}, this.startMatterIntervalMs);
|
|
1514
1217
|
}
|
|
1515
|
-
/**
|
|
1516
|
-
* Starts the Matterbridge in childbridge mode.
|
|
1517
|
-
*
|
|
1518
|
-
* @param {number} [delay] - The delay before starting the childbridge. Default is 1000 milliseconds.
|
|
1519
|
-
*
|
|
1520
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1521
|
-
*/
|
|
1522
1218
|
async startChildbridge(delay = 1000) {
|
|
1523
1219
|
if (!this.matterStorageManager)
|
|
1524
1220
|
throw new Error('No storage manager initialized');
|
|
1525
|
-
// Load with await all plugins but don't start them. We get the platform.type to pre-create server nodes for DynamicPlatform plugins
|
|
1526
1221
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1527
1222
|
await this.startPlugins(true, false);
|
|
1528
|
-
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1529
1223
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1530
1224
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1531
1225
|
if (plugin.type === 'DynamicPlatform')
|
|
1532
1226
|
await this.createDynamicPlugin(plugin);
|
|
1533
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1227
|
+
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1534
1228
|
}
|
|
1535
|
-
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1536
1229
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1537
1230
|
let failCount = 0;
|
|
1538
1231
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1566,9 +1259,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1566
1259
|
clearInterval(this.startMatterInterval);
|
|
1567
1260
|
this.startMatterInterval = undefined;
|
|
1568
1261
|
if (delay > 0)
|
|
1569
|
-
await wait(delay);
|
|
1262
|
+
await wait(delay);
|
|
1570
1263
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1571
|
-
// Configure the plugins
|
|
1572
1264
|
this.configureTimeout = setTimeout(async () => {
|
|
1573
1265
|
for (const plugin of this.plugins.array()) {
|
|
1574
1266
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1593,7 +1285,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1593
1285
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1594
1286
|
continue;
|
|
1595
1287
|
}
|
|
1596
|
-
// istanbul ignore next if cause is just a safety check
|
|
1597
1288
|
if (!plugin.serverNode) {
|
|
1598
1289
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1599
1290
|
continue;
|
|
@@ -1606,252 +1297,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1606
1297
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1607
1298
|
continue;
|
|
1608
1299
|
}
|
|
1609
|
-
|
|
1610
|
-
this.startServerNode(plugin.serverNode); // We don't await this, because the server node is started in the background
|
|
1611
|
-
// Setting reachability to true
|
|
1300
|
+
this.startServerNode(plugin.serverNode);
|
|
1612
1301
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1613
1302
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1614
1303
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1615
1304
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1616
1305
|
}, 60 * 1000).unref();
|
|
1617
1306
|
}
|
|
1618
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1619
1307
|
for (const device of this.devices.array()) {
|
|
1620
1308
|
if (device.mode === 'server' && device.serverNode) {
|
|
1621
1309
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1622
|
-
this.startServerNode(device.serverNode);
|
|
1310
|
+
this.startServerNode(device.serverNode);
|
|
1623
1311
|
}
|
|
1624
1312
|
}
|
|
1625
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1626
1313
|
this.emit('childbridge_started');
|
|
1627
1314
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1628
1315
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1629
1316
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1630
1317
|
}, this.startMatterIntervalMs);
|
|
1631
1318
|
}
|
|
1632
|
-
/**
|
|
1633
|
-
* Starts the Matterbridge controller.
|
|
1634
|
-
*
|
|
1635
|
-
* @private
|
|
1636
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1637
|
-
*/
|
|
1638
1319
|
async startController() {
|
|
1639
|
-
/*
|
|
1640
|
-
if (!this.matterStorageManager) {
|
|
1641
|
-
this.log.error('No storage manager initialized');
|
|
1642
|
-
await this.cleanup('No storage manager initialized');
|
|
1643
|
-
return;
|
|
1644
|
-
}
|
|
1645
|
-
this.log.info('Creating context: mattercontrollerContext');
|
|
1646
|
-
this.controllerContext = this.matterStorageManager.createContext('mattercontrollerContext');
|
|
1647
|
-
if (!this.controllerContext) {
|
|
1648
|
-
this.log.error('No storage context mattercontrollerContext initialized');
|
|
1649
|
-
await this.cleanup('No storage context mattercontrollerContext initialized');
|
|
1650
|
-
return;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
this.log.debug('Starting matterbridge in mode', this.bridgeMode);
|
|
1654
|
-
this.matterServer = await this.createMatterServer(this.storageManager);
|
|
1655
|
-
this.log.info('Creating matter commissioning controller');
|
|
1656
|
-
this.commissioningController = new CommissioningController({
|
|
1657
|
-
autoConnect: false,
|
|
1658
|
-
});
|
|
1659
|
-
this.log.info('Adding matter commissioning controller to matter server');
|
|
1660
|
-
await this.matterServer.addCommissioningController(this.commissioningController);
|
|
1661
|
-
|
|
1662
|
-
this.log.info('Starting matter server');
|
|
1663
|
-
await this.matterServer.start();
|
|
1664
|
-
this.log.info('Matter server started');
|
|
1665
|
-
const commissioningOptions: ControllerCommissioningFlowOptions = {
|
|
1666
|
-
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
|
|
1667
|
-
regulatoryCountryCode: 'XX',
|
|
1668
|
-
};
|
|
1669
|
-
const commissioningController = new CommissioningController({
|
|
1670
|
-
environment: {
|
|
1671
|
-
environment,
|
|
1672
|
-
id: uniqueId,
|
|
1673
|
-
},
|
|
1674
|
-
autoConnect: false, // Do not auto connect to the commissioned nodes
|
|
1675
|
-
adminFabricLabel,
|
|
1676
|
-
});
|
|
1677
|
-
|
|
1678
|
-
if (hasParameter('pairingcode')) {
|
|
1679
|
-
this.log.info('Pairing device with pairingcode:', getParameter('pairingcode'));
|
|
1680
|
-
const pairingCode = getParameter('pairingcode');
|
|
1681
|
-
const ip = this.controllerContext.has('ip') ? this.controllerContext.get<string>('ip') : undefined;
|
|
1682
|
-
const port = this.controllerContext.has('port') ? this.controllerContext.get<number>('port') : undefined;
|
|
1683
|
-
|
|
1684
|
-
let longDiscriminator, setupPin, shortDiscriminator;
|
|
1685
|
-
if (pairingCode !== undefined) {
|
|
1686
|
-
const pairingCodeCodec = ManualPairingCodeCodec.decode(pairingCode);
|
|
1687
|
-
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
|
|
1688
|
-
longDiscriminator = undefined;
|
|
1689
|
-
setupPin = pairingCodeCodec.passcode;
|
|
1690
|
-
this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingCodeCodec)}`);
|
|
1691
|
-
} else {
|
|
1692
|
-
longDiscriminator = await this.controllerContext.get('longDiscriminator', 3840);
|
|
1693
|
-
if (longDiscriminator > 4095) throw new Error('Discriminator value must be less than 4096');
|
|
1694
|
-
setupPin = this.controllerContext.get('pin', 20202021);
|
|
1695
|
-
}
|
|
1696
|
-
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
1697
|
-
throw new Error('Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with -passcode');
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
const options = {
|
|
1701
|
-
commissioning: commissioningOptions,
|
|
1702
|
-
discovery: {
|
|
1703
|
-
knownAddress: ip !== undefined && port !== undefined ? { ip, port, type: 'udp' } : undefined,
|
|
1704
|
-
identifierData: longDiscriminator !== undefined ? { longDiscriminator } : shortDiscriminator !== undefined ? { shortDiscriminator } : {},
|
|
1705
|
-
},
|
|
1706
|
-
passcode: setupPin,
|
|
1707
|
-
} as NodeCommissioningOptions;
|
|
1708
|
-
this.log.info('Commissioning with options:', options);
|
|
1709
|
-
const nodeId = await this.commissioningController.commissionNode(options);
|
|
1710
|
-
this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
|
|
1711
|
-
this.log.info('ActiveSessionInformation:', this.commissioningController.getActiveSessionInformation());
|
|
1712
|
-
} // (hasParameter('pairingcode'))
|
|
1713
|
-
|
|
1714
|
-
if (hasParameter('unpairall')) {
|
|
1715
|
-
this.log.info('***Commissioning controller unpairing all nodes...');
|
|
1716
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1717
|
-
for (const nodeId of nodeIds) {
|
|
1718
|
-
this.log.info('***Commissioning controller unpairing node:', nodeId);
|
|
1719
|
-
await this.commissioningController.removeNode(nodeId);
|
|
1720
|
-
}
|
|
1721
|
-
return;
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
if (hasParameter('discover')) {
|
|
1725
|
-
// const discover = await this.commissioningController.discoverCommissionableDevices({ productId: 0x8000, deviceType: 0xfff1 });
|
|
1726
|
-
// console.log(discover);
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
if (!this.commissioningController.isCommissioned()) {
|
|
1730
|
-
this.log.info('***Commissioning controller is not commissioned: use matterbridge -controller -pairingcode [pairingcode] to commission a device');
|
|
1731
|
-
return;
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
const nodeIds = this.commissioningController.getCommissionedNodes();
|
|
1735
|
-
this.log.info(`***Commissioning controller is commissioned ${this.commissioningController.isCommissioned()} and has ${nodeIds.length} nodes commisioned: `);
|
|
1736
|
-
for (const nodeId of nodeIds) {
|
|
1737
|
-
this.log.info(`***Connecting to commissioned node: ${nodeId}`);
|
|
1738
|
-
|
|
1739
|
-
const node = await this.commissioningController.connectNode(nodeId, {
|
|
1740
|
-
autoSubscribe: false,
|
|
1741
|
-
attributeChangedCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, attributeName }, value }) =>
|
|
1742
|
-
this.log.info(`***Commissioning controller attributeChangedCallback ${peerNodeId}: attribute ${nodeId}/${endpointId}/${clusterId}/${attributeName} changed to ${Logger.toJSON(value)}`),
|
|
1743
|
-
eventTriggeredCallback: (peerNodeId, { path: { nodeId, clusterId, endpointId, eventName }, events }) =>
|
|
1744
|
-
this.log.info(`***Commissioning controller eventTriggeredCallback ${peerNodeId}: Event ${nodeId}/${endpointId}/${clusterId}/${eventName} triggered with ${Logger.toJSON(events)}`),
|
|
1745
|
-
stateInformationCallback: (peerNodeId, info) => {
|
|
1746
|
-
switch (info) {
|
|
1747
|
-
case NodeStateInformation.Connected:
|
|
1748
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} connected`);
|
|
1749
|
-
break;
|
|
1750
|
-
case NodeStateInformation.Disconnected:
|
|
1751
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} disconnected`);
|
|
1752
|
-
break;
|
|
1753
|
-
case NodeStateInformation.Reconnecting:
|
|
1754
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} reconnecting`);
|
|
1755
|
-
break;
|
|
1756
|
-
case NodeStateInformation.WaitingForDeviceDiscovery:
|
|
1757
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} waiting for device discovery`);
|
|
1758
|
-
break;
|
|
1759
|
-
case NodeStateInformation.StructureChanged:
|
|
1760
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} structure changed`);
|
|
1761
|
-
break;
|
|
1762
|
-
case NodeStateInformation.Decommissioned:
|
|
1763
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} decommissioned`);
|
|
1764
|
-
break;
|
|
1765
|
-
default:
|
|
1766
|
-
this.log.info(`***Commissioning controller stateInformationCallback ${peerNodeId}: Node ${nodeId} NodeStateInformation.${info}`);
|
|
1767
|
-
break;
|
|
1768
|
-
}
|
|
1769
|
-
},
|
|
1770
|
-
});
|
|
1771
|
-
|
|
1772
|
-
node.logStructure();
|
|
1773
|
-
|
|
1774
|
-
// Get the interaction client
|
|
1775
|
-
this.log.info('Getting the interaction client');
|
|
1776
|
-
const interactionClient = await node.getInteractionClient();
|
|
1777
|
-
let cluster;
|
|
1778
|
-
let attributes;
|
|
1779
|
-
|
|
1780
|
-
// Log BasicInformationCluster
|
|
1781
|
-
cluster = BasicInformationCluster;
|
|
1782
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1783
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1784
|
-
});
|
|
1785
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1786
|
-
attributes.forEach((attribute) => {
|
|
1787
|
-
this.log.info(
|
|
1788
|
-
`- 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}`,
|
|
1789
|
-
);
|
|
1790
|
-
});
|
|
1791
|
-
|
|
1792
|
-
// Log PowerSourceCluster
|
|
1793
|
-
cluster = PowerSourceCluster;
|
|
1794
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1795
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1796
|
-
});
|
|
1797
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1798
|
-
attributes.forEach((attribute) => {
|
|
1799
|
-
this.log.info(
|
|
1800
|
-
`- 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}`,
|
|
1801
|
-
);
|
|
1802
|
-
});
|
|
1803
|
-
|
|
1804
|
-
// Log ThreadNetworkDiagnostics
|
|
1805
|
-
cluster = ThreadNetworkDiagnosticsCluster;
|
|
1806
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1807
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1808
|
-
});
|
|
1809
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1810
|
-
attributes.forEach((attribute) => {
|
|
1811
|
-
this.log.info(
|
|
1812
|
-
`- 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}`,
|
|
1813
|
-
);
|
|
1814
|
-
});
|
|
1815
|
-
|
|
1816
|
-
// Log SwitchCluster
|
|
1817
|
-
cluster = SwitchCluster;
|
|
1818
|
-
attributes = await interactionClient.getMultipleAttributes({
|
|
1819
|
-
attributes: [{ clusterId: cluster.id }],
|
|
1820
|
-
});
|
|
1821
|
-
if (attributes.length > 0) this.log.info(`Cluster: ${idn}${cluster.name}${rs}${nf} attributes:`);
|
|
1822
|
-
attributes.forEach((attribute) => {
|
|
1823
|
-
this.log.info(
|
|
1824
|
-
`- 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}`,
|
|
1825
|
-
);
|
|
1826
|
-
});
|
|
1827
|
-
|
|
1828
|
-
this.log.info('Subscribing to all attributes and events');
|
|
1829
|
-
await node.subscribeAllAttributesAndEvents({
|
|
1830
|
-
ignoreInitialTriggers: false,
|
|
1831
|
-
attributeChangedCallback: ({ path: { nodeId, clusterId, endpointId, attributeName }, version, value }) =>
|
|
1832
|
-
this.log.info(
|
|
1833
|
-
`***${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}`,
|
|
1834
|
-
),
|
|
1835
|
-
eventTriggeredCallback: ({ path: { nodeId, clusterId, endpointId, eventName }, events }) => {
|
|
1836
|
-
this.log.info(
|
|
1837
|
-
`***${db}Commissioning controller eventTriggeredCallback: event ${BLUE}${nodeId}${db}/${or}${endpointId}${db}/${hk}${getClusterNameById(clusterId)}${db}/${zb}${eventName}${db} triggered with ${debugStringify(events ?? { none: true })}`,
|
|
1838
|
-
);
|
|
1839
|
-
},
|
|
1840
|
-
});
|
|
1841
|
-
this.log.info('Subscribed to all attributes and events');
|
|
1842
|
-
}
|
|
1843
|
-
*/
|
|
1844
1320
|
}
|
|
1845
|
-
/** */
|
|
1846
|
-
/** Matter.js methods */
|
|
1847
|
-
/** */
|
|
1848
|
-
/**
|
|
1849
|
-
* Starts the matter storage with name Matterbridge, create the matterbridge context and performs a backup.
|
|
1850
|
-
*
|
|
1851
|
-
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
1852
|
-
*/
|
|
1853
1321
|
async startMatterStorage() {
|
|
1854
|
-
// Setup Matter storage
|
|
1855
1322
|
this.log.info(`Starting matter node storage...`);
|
|
1856
1323
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1857
1324
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1859,17 +1326,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1859
1326
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1860
1327
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1861
1328
|
this.log.info('Matter node storage started');
|
|
1862
|
-
// Backup matter storage since it is created/opened correctly
|
|
1863
1329
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1864
1330
|
}
|
|
1865
|
-
/**
|
|
1866
|
-
* Makes a backup copy of the specified matter storage directory.
|
|
1867
|
-
*
|
|
1868
|
-
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
1869
|
-
* @param {string} backupName - The name of the backup directory to be created.
|
|
1870
|
-
* @private
|
|
1871
|
-
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
1872
|
-
*/
|
|
1873
1331
|
async backupMatterStorage(storageName, backupName) {
|
|
1874
1332
|
this.log.info('Creating matter node storage backup...');
|
|
1875
1333
|
try {
|
|
@@ -1880,11 +1338,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1880
1338
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1881
1339
|
}
|
|
1882
1340
|
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Stops the matter storage.
|
|
1885
|
-
*
|
|
1886
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1887
|
-
*/
|
|
1888
1341
|
async stopMatterStorage() {
|
|
1889
1342
|
this.log.info('Closing matter node storage...');
|
|
1890
1343
|
await this.matterStorageManager?.close();
|
|
@@ -1893,20 +1346,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1893
1346
|
this.matterbridgeContext = undefined;
|
|
1894
1347
|
this.log.info('Matter node storage closed');
|
|
1895
1348
|
}
|
|
1896
|
-
/**
|
|
1897
|
-
* Creates a server node storage context.
|
|
1898
|
-
*
|
|
1899
|
-
* @param {string} storeId - The storeId.
|
|
1900
|
-
* @param {string} deviceName - The name of the device.
|
|
1901
|
-
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
1902
|
-
* @param {number} vendorId - The vendor ID.
|
|
1903
|
-
* @param {string} vendorName - The vendor name.
|
|
1904
|
-
* @param {number} productId - The product ID.
|
|
1905
|
-
* @param {string} productName - The product name.
|
|
1906
|
-
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
1907
|
-
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
1908
|
-
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
1909
|
-
*/
|
|
1910
1349
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1911
1350
|
const { randomBytes } = await import('node:crypto');
|
|
1912
1351
|
if (!this.matterStorageService)
|
|
@@ -1946,15 +1385,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1946
1385
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1947
1386
|
return storageContext;
|
|
1948
1387
|
}
|
|
1949
|
-
/**
|
|
1950
|
-
* Creates a server node.
|
|
1951
|
-
*
|
|
1952
|
-
* @param {StorageContext} storageContext - The storage context for the server node.
|
|
1953
|
-
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
1954
|
-
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
1955
|
-
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
1956
|
-
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
1957
|
-
*/
|
|
1958
1388
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1959
1389
|
const storeId = await storageContext.get('storeId');
|
|
1960
1390
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1964,37 +1394,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1964
1394
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1965
1395
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1966
1396
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1967
|
-
/**
|
|
1968
|
-
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
1969
|
-
*/
|
|
1970
1397
|
const serverNode = await ServerNode.create({
|
|
1971
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1972
1398
|
id: storeId,
|
|
1973
|
-
// Provide Network relevant configuration like the port
|
|
1974
|
-
// Optional when operating only one device on a host, Default port is 5540
|
|
1975
1399
|
network: {
|
|
1976
|
-
listeningAddressIpv4: this.
|
|
1977
|
-
listeningAddressIpv6: this.
|
|
1400
|
+
listeningAddressIpv4: this.ipv4Address,
|
|
1401
|
+
listeningAddressIpv6: this.ipv6Address,
|
|
1978
1402
|
port,
|
|
1979
1403
|
},
|
|
1980
|
-
// Provide the certificate for the device
|
|
1981
1404
|
operationalCredentials: {
|
|
1982
1405
|
certification: this.certification,
|
|
1983
1406
|
},
|
|
1984
|
-
// Provide Commissioning relevant settings
|
|
1985
|
-
// Optional for development/testing purposes
|
|
1986
1407
|
commissioning: {
|
|
1987
1408
|
passcode,
|
|
1988
1409
|
discriminator,
|
|
1989
1410
|
},
|
|
1990
|
-
// Provide Node announcement settings
|
|
1991
|
-
// Optional: If Ommitted some development defaults are used
|
|
1992
1411
|
productDescription: {
|
|
1993
1412
|
name: await storageContext.get('deviceName'),
|
|
1994
1413
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1995
1414
|
},
|
|
1996
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
-
// Optional: If Omitted some development defaults are used
|
|
1998
1415
|
basicInformation: {
|
|
1999
1416
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2000
1417
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2011,23 +1428,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2011
1428
|
reachable: true,
|
|
2012
1429
|
},
|
|
2013
1430
|
});
|
|
2014
|
-
/**
|
|
2015
|
-
* This event is triggered when the device is initially commissioned successfully.
|
|
2016
|
-
* This means: It is added to the first fabric.
|
|
2017
|
-
*/
|
|
2018
1431
|
serverNode.lifecycle.commissioned.on(() => {
|
|
2019
1432
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
2020
1433
|
this.advertisingNodes.delete(storeId);
|
|
2021
1434
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2022
1435
|
});
|
|
2023
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2024
1436
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
2025
1437
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
2026
1438
|
this.advertisingNodes.delete(storeId);
|
|
2027
1439
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2028
1440
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2029
1441
|
});
|
|
2030
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2031
1442
|
serverNode.lifecycle.online.on(async () => {
|
|
2032
1443
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2033
1444
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2038,16 +1449,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2038
1449
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2039
1450
|
}
|
|
2040
1451
|
else {
|
|
2041
|
-
// istanbul ignore next
|
|
2042
1452
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2043
|
-
// istanbul ignore next
|
|
2044
1453
|
this.advertisingNodes.delete(storeId);
|
|
2045
1454
|
}
|
|
2046
1455
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2047
1456
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2048
1457
|
this.emit('online', storeId);
|
|
2049
1458
|
});
|
|
2050
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2051
1459
|
serverNode.lifecycle.offline.on(() => {
|
|
2052
1460
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2053
1461
|
this.advertisingNodes.delete(storeId);
|
|
@@ -2055,15 +1463,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2055
1463
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2056
1464
|
this.emit('offline', storeId);
|
|
2057
1465
|
});
|
|
2058
|
-
/**
|
|
2059
|
-
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
2060
|
-
* information is needed.
|
|
2061
|
-
*/
|
|
2062
1466
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2063
1467
|
let action = '';
|
|
2064
1468
|
switch (fabricAction) {
|
|
2065
1469
|
case FabricAction.Added:
|
|
2066
|
-
this.advertisingNodes.delete(storeId);
|
|
1470
|
+
this.advertisingNodes.delete(storeId);
|
|
2067
1471
|
action = 'added';
|
|
2068
1472
|
break;
|
|
2069
1473
|
case FabricAction.Removed:
|
|
@@ -2076,22 +1480,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2076
1480
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
2077
1481
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2078
1482
|
});
|
|
2079
|
-
/**
|
|
2080
|
-
* This event is triggered when an operative new session was opened by a Controller.
|
|
2081
|
-
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
2082
|
-
*/
|
|
2083
1483
|
serverNode.events.sessions.opened.on((session) => {
|
|
2084
1484
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2085
1485
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2086
1486
|
});
|
|
2087
|
-
/**
|
|
2088
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2089
|
-
*/
|
|
2090
1487
|
serverNode.events.sessions.closed.on((session) => {
|
|
2091
1488
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2092
1489
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2093
1490
|
});
|
|
2094
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2095
1491
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2096
1492
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2097
1493
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -2099,12 +1495,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2099
1495
|
this.log.info(`Created server node for ${storeId}`);
|
|
2100
1496
|
return serverNode;
|
|
2101
1497
|
}
|
|
2102
|
-
/**
|
|
2103
|
-
* Gets the matter sanitized data of the specified server node.
|
|
2104
|
-
*
|
|
2105
|
-
* @param {ServerNode} [serverNode] - The server node to start.
|
|
2106
|
-
* @returns {ApiMatter} The sanitized data of the server node.
|
|
2107
|
-
*/
|
|
2108
1498
|
getServerNodeData(serverNode) {
|
|
2109
1499
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
2110
1500
|
return {
|
|
@@ -2121,25 +1511,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2121
1511
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
2122
1512
|
};
|
|
2123
1513
|
}
|
|
2124
|
-
/**
|
|
2125
|
-
* Starts the specified server node.
|
|
2126
|
-
*
|
|
2127
|
-
* @param {ServerNode} [matterServerNode] - The server node to start.
|
|
2128
|
-
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
2129
|
-
*/
|
|
2130
1514
|
async startServerNode(matterServerNode) {
|
|
2131
1515
|
if (!matterServerNode)
|
|
2132
1516
|
return;
|
|
2133
1517
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2134
1518
|
await matterServerNode.start();
|
|
2135
1519
|
}
|
|
2136
|
-
/**
|
|
2137
|
-
* Stops the specified server node.
|
|
2138
|
-
*
|
|
2139
|
-
* @param {ServerNode} matterServerNode - The server node to stop.
|
|
2140
|
-
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
2141
|
-
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
2142
|
-
*/
|
|
2143
1520
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
2144
1521
|
if (!matterServerNode)
|
|
2145
1522
|
return;
|
|
@@ -2152,25 +1529,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2152
1529
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2153
1530
|
}
|
|
2154
1531
|
}
|
|
2155
|
-
/**
|
|
2156
|
-
* Creates an aggregator node with the specified storage context.
|
|
2157
|
-
*
|
|
2158
|
-
* @param {StorageContext} storageContext - The storage context for the aggregator node.
|
|
2159
|
-
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
2160
|
-
*/
|
|
2161
1532
|
async createAggregatorNode(storageContext) {
|
|
2162
1533
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2163
1534
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2164
1535
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2165
1536
|
return aggregatorNode;
|
|
2166
1537
|
}
|
|
2167
|
-
/**
|
|
2168
|
-
* Creates and configures the server node for an accessory plugin for a given device.
|
|
2169
|
-
*
|
|
2170
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
2171
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2172
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2173
|
-
*/
|
|
2174
1538
|
async createAccessoryPlugin(plugin, device) {
|
|
2175
1539
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
2176
1540
|
plugin.locked = true;
|
|
@@ -2182,12 +1546,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2182
1546
|
await plugin.serverNode.add(device);
|
|
2183
1547
|
}
|
|
2184
1548
|
}
|
|
2185
|
-
/**
|
|
2186
|
-
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
2187
|
-
*
|
|
2188
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
2189
|
-
* @returns {Promise<void>} A promise that resolves when the server node and the aggregator node for the dynamic plugin is created and configured.
|
|
2190
|
-
*/
|
|
2191
1549
|
async createDynamicPlugin(plugin) {
|
|
2192
1550
|
if (!plugin.locked) {
|
|
2193
1551
|
plugin.locked = true;
|
|
@@ -2200,13 +1558,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2200
1558
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
2201
1559
|
}
|
|
2202
1560
|
}
|
|
2203
|
-
/**
|
|
2204
|
-
* Creates and configures the server node for a single not bridged device.
|
|
2205
|
-
*
|
|
2206
|
-
* @param {RegisteredPlugin} plugin - The plugin to configure.
|
|
2207
|
-
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
2208
|
-
* @returns {Promise<void>} A promise that resolves when the server node for the accessory plugin is created and configured.
|
|
2209
|
-
*/
|
|
2210
1561
|
async createDeviceServerNode(plugin, device) {
|
|
2211
1562
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
2212
1563
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -2217,15 +1568,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2217
1568
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
2218
1569
|
}
|
|
2219
1570
|
}
|
|
2220
|
-
/**
|
|
2221
|
-
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
2222
|
-
*
|
|
2223
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2224
|
-
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
2225
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been added.
|
|
2226
|
-
*/
|
|
2227
1571
|
async addBridgedEndpoint(pluginName, device) {
|
|
2228
|
-
// Check if the plugin is registered
|
|
2229
1572
|
const plugin = this.plugins.get(pluginName);
|
|
2230
1573
|
if (!plugin) {
|
|
2231
1574
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -2245,7 +1588,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2245
1588
|
}
|
|
2246
1589
|
else if (this.bridgeMode === 'bridge') {
|
|
2247
1590
|
if (device.mode === 'matter') {
|
|
2248
|
-
// Register and add the device to the matterbridge server node
|
|
2249
1591
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
2250
1592
|
if (!this.serverNode) {
|
|
2251
1593
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -2262,7 +1604,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2262
1604
|
}
|
|
2263
1605
|
}
|
|
2264
1606
|
else {
|
|
2265
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2266
1607
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2267
1608
|
if (!this.aggregatorNode) {
|
|
2268
1609
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2280,7 +1621,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2280
1621
|
}
|
|
2281
1622
|
}
|
|
2282
1623
|
else if (this.bridgeMode === 'childbridge') {
|
|
2283
|
-
// Register and add the device to the plugin server node
|
|
2284
1624
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2285
1625
|
try {
|
|
2286
1626
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2304,12 +1644,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2304
1644
|
return;
|
|
2305
1645
|
}
|
|
2306
1646
|
}
|
|
2307
|
-
// Register and add the device to the plugin aggregator node
|
|
2308
1647
|
if (plugin.type === 'DynamicPlatform') {
|
|
2309
1648
|
try {
|
|
2310
1649
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2311
1650
|
await this.createDynamicPlugin(plugin);
|
|
2312
|
-
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
2313
1651
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2314
1652
|
if (!plugin.aggregatorNode) {
|
|
2315
1653
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2330,28 +1668,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2330
1668
|
}
|
|
2331
1669
|
if (plugin.registeredDevices !== undefined)
|
|
2332
1670
|
plugin.registeredDevices++;
|
|
2333
|
-
// Add the device to the DeviceManager
|
|
2334
1671
|
this.devices.set(device);
|
|
2335
|
-
// Subscribe to the attributes changed event
|
|
2336
1672
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2337
1673
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
2338
1674
|
}
|
|
2339
|
-
/**
|
|
2340
|
-
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
2341
|
-
*
|
|
2342
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2343
|
-
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
2344
|
-
* @returns {Promise<void>} A promise that resolves when the bridged endpoint has been removed.
|
|
2345
|
-
*/
|
|
2346
1675
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2347
1676
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
|
|
2348
|
-
// Check if the plugin is registered
|
|
2349
1677
|
const plugin = this.plugins.get(pluginName);
|
|
2350
1678
|
if (!plugin) {
|
|
2351
1679
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2352
1680
|
return;
|
|
2353
1681
|
}
|
|
2354
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2355
1682
|
if (this.bridgeMode === 'bridge') {
|
|
2356
1683
|
if (!this.aggregatorNode) {
|
|
2357
1684
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator node not found`);
|
|
@@ -2364,7 +1691,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2364
1691
|
}
|
|
2365
1692
|
else if (this.bridgeMode === 'childbridge') {
|
|
2366
1693
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2367
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2368
1694
|
}
|
|
2369
1695
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2370
1696
|
if (!plugin.aggregatorNode) {
|
|
@@ -2377,21 +1703,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2377
1703
|
if (plugin.registeredDevices !== undefined)
|
|
2378
1704
|
plugin.registeredDevices--;
|
|
2379
1705
|
}
|
|
2380
|
-
// Remove the device from the DeviceManager
|
|
2381
1706
|
this.devices.remove(device);
|
|
2382
1707
|
}
|
|
2383
|
-
/**
|
|
2384
|
-
* Removes all bridged endpoints from the specified plugin.
|
|
2385
|
-
*
|
|
2386
|
-
* @param {string} pluginName - The name of the plugin.
|
|
2387
|
-
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
2388
|
-
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
2389
|
-
*
|
|
2390
|
-
* @remarks
|
|
2391
|
-
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
2392
|
-
* It also applies a delay between each removal if specified.
|
|
2393
|
-
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
2394
|
-
*/
|
|
2395
1708
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2396
1709
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2397
1710
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2402,24 +1715,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2402
1715
|
if (delay > 0)
|
|
2403
1716
|
await wait(2000);
|
|
2404
1717
|
}
|
|
2405
|
-
/**
|
|
2406
|
-
* Subscribes to the attribute change event for the given device and plugin.
|
|
2407
|
-
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
2408
|
-
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
2409
|
-
*
|
|
2410
|
-
* @param {RegisteredPlugin} plugin - The plugin associated with the device.
|
|
2411
|
-
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
2412
|
-
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
2413
|
-
*/
|
|
2414
1718
|
async subscribeAttributeChanged(plugin, device) {
|
|
2415
1719
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
2416
1720
|
return;
|
|
2417
1721
|
this.log.info(`Subscribing attributes for endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) plugin ${plg}${plugin.name}${nf}`);
|
|
2418
|
-
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
2419
1722
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2420
1723
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
2421
1724
|
this.log.info(`Accessory endpoint ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
2422
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2423
1725
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
2424
1726
|
});
|
|
2425
1727
|
}
|
|
@@ -2467,30 +1769,20 @@ export class Matterbridge extends EventEmitter {
|
|
|
2467
1769
|
this.log.debug(`Subscribing to endpoint ${or}${device.id}${db}:${or}${device.number}${db} attribute ${dev}${sub.cluster}${db}.${dev}${sub.attribute}${db} changes...`);
|
|
2468
1770
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
2469
1771
|
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}${value}${db}`);
|
|
2470
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2471
1772
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
2472
1773
|
});
|
|
2473
1774
|
}
|
|
2474
1775
|
for (const child of device.getChildEndpoints()) {
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
2482
|
-
});
|
|
2483
|
-
}
|
|
1776
|
+
if (child.hasAttributeServer(sub.cluster, sub.attribute)) {
|
|
1777
|
+
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...`);
|
|
1778
|
+
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
1779
|
+
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}${value}${db}`);
|
|
1780
|
+
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
1781
|
+
});
|
|
2484
1782
|
}
|
|
2485
1783
|
}
|
|
2486
1784
|
}
|
|
2487
1785
|
}
|
|
2488
|
-
/**
|
|
2489
|
-
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2490
|
-
*
|
|
2491
|
-
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
2492
|
-
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
2493
|
-
*/
|
|
2494
1786
|
sanitizeFabricInformations(fabricInfo) {
|
|
2495
1787
|
return fabricInfo.map((info) => {
|
|
2496
1788
|
return {
|
|
@@ -2504,12 +1796,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2504
1796
|
};
|
|
2505
1797
|
});
|
|
2506
1798
|
}
|
|
2507
|
-
/**
|
|
2508
|
-
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
2509
|
-
*
|
|
2510
|
-
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
2511
|
-
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
2512
|
-
*/
|
|
2513
1799
|
sanitizeSessionInformation(sessions) {
|
|
2514
1800
|
return sessions
|
|
2515
1801
|
.filter((session) => session.isPeerActive)
|
|
@@ -2536,21 +1822,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2536
1822
|
};
|
|
2537
1823
|
});
|
|
2538
1824
|
}
|
|
2539
|
-
/**
|
|
2540
|
-
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
2541
|
-
*
|
|
2542
|
-
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
2543
|
-
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
2544
|
-
*/
|
|
2545
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2546
1825
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
2547
|
-
/*
|
|
2548
|
-
for (const child of aggregatorNode.parts) {
|
|
2549
|
-
this.log.debug(`Setting reachability of ${(child as unknown as MatterbridgeEndpoint)?.deviceName} to ${reachable}`);
|
|
2550
|
-
await child.setStateOf(BridgedDeviceBasicInformationServer, { reachable });
|
|
2551
|
-
child.act((agent) => child.eventsOf(BridgedDeviceBasicInformationServer).reachableChanged.emit({ reachableNewValue: true }, agent.context));
|
|
2552
|
-
}
|
|
2553
|
-
*/
|
|
2554
1826
|
}
|
|
2555
1827
|
getVendorIdName = (vendorId) => {
|
|
2556
1828
|
if (!vendorId)
|
|
@@ -2590,11 +1862,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2590
1862
|
case 0x1488:
|
|
2591
1863
|
vendorName = '(ShortcutLabsFlic)';
|
|
2592
1864
|
break;
|
|
2593
|
-
case 65521:
|
|
1865
|
+
case 65521:
|
|
2594
1866
|
vendorName = '(MatterTest)';
|
|
2595
1867
|
break;
|
|
2596
1868
|
}
|
|
2597
1869
|
return vendorName;
|
|
2598
1870
|
};
|
|
2599
1871
|
}
|
|
2600
|
-
//# sourceMappingURL=matterbridge.js.map
|