matterbridge 3.3.0 → 3.3.1-dev-20251007-4e5eaac
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 +29 -3
- package/README.md +9 -1
- package/dist/broadcastServer.js +73 -0
- package/dist/broadcastServerTypes.js +1 -0
- package/dist/cli.js +2 -91
- 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 +172 -604
- package/dist/frontendTypes.js +0 -45
- package/dist/helpers.js +4 -57
- package/dist/index.js +0 -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 +148 -877
- 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 +2 -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,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file contains the class Matterbridge.
|
|
3
|
-
*
|
|
4
|
-
* @file matterbridge.ts
|
|
5
|
-
* @author Luca Liguori
|
|
6
|
-
* @created 2023-12-29
|
|
7
|
-
* @version 1.6.0
|
|
8
|
-
* @license Apache-2.0
|
|
9
|
-
*
|
|
10
|
-
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
11
|
-
*
|
|
12
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
-
* you may not use this file except in compliance with the License.
|
|
14
|
-
* You may obtain a copy of the License at
|
|
15
|
-
*
|
|
16
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
-
*
|
|
18
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
-
* See the License for the specific language governing permissions and
|
|
22
|
-
* limitations under the License.
|
|
23
|
-
*/
|
|
24
|
-
// Node.js modules
|
|
25
1
|
import os from 'node:os';
|
|
26
2
|
import path from 'node:path';
|
|
27
3
|
import { promises as fs } from 'node:fs';
|
|
28
4
|
import EventEmitter from 'node:events';
|
|
29
5
|
import { inspect } from 'node:util';
|
|
30
|
-
// AnsiLogger module
|
|
31
6
|
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
7
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
34
|
-
// @matter
|
|
35
8
|
import { DeviceTypeId, Endpoint, Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, StorageService, Environment, ServerNode, UINT32_MAX, UINT16_MAX, Crypto, } from '@matter/main';
|
|
36
9
|
import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
|
|
37
10
|
import { AggregatorEndpoint } from '@matter/main/endpoints';
|
|
38
11
|
import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
|
|
39
|
-
|
|
40
|
-
import {
|
|
12
|
+
import { getParameter, getIntParameter, hasParameter } from './utils/commandLine.js';
|
|
13
|
+
import { copyDirectory } from './utils/copyDirectory.js';
|
|
14
|
+
import { createDirectory } from './utils/createDirectory.js';
|
|
15
|
+
import { isValidString, parseVersionString, isValidNumber } from './utils/isvalid.js';
|
|
16
|
+
import { formatMemoryUsage, formatOsUpTime } from './utils/network.js';
|
|
41
17
|
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
|
|
18
|
+
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from './matterbridgeTypes.js';
|
|
43
19
|
import { PluginManager } from './pluginManager.js';
|
|
44
20
|
import { DeviceManager } from './deviceManager.js';
|
|
45
21
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
46
22
|
import { bridge } from './matterbridgeDeviceTypes.js';
|
|
47
23
|
import { Frontend } from './frontend.js';
|
|
48
24
|
import { addVirtualDevices } from './helpers.js';
|
|
49
|
-
/**
|
|
50
|
-
* Represents the Matterbridge application.
|
|
51
|
-
*/
|
|
52
25
|
export class Matterbridge extends EventEmitter {
|
|
53
26
|
systemInformation = {
|
|
54
27
|
interfaceName: '',
|
|
@@ -71,38 +44,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
71
44
|
heapTotal: '',
|
|
72
45
|
heapUsed: '',
|
|
73
46
|
};
|
|
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
47
|
homeDirectory = '';
|
|
107
48
|
rootDirectory = '';
|
|
108
49
|
matterbridgeDirectory = '';
|
|
@@ -112,23 +53,30 @@ export class Matterbridge extends EventEmitter {
|
|
|
112
53
|
matterbridgeVersion = '';
|
|
113
54
|
matterbridgeLatestVersion = '';
|
|
114
55
|
matterbridgeDevVersion = '';
|
|
56
|
+
frontendVersion = '';
|
|
115
57
|
bridgeMode = '';
|
|
116
58
|
restartMode = '';
|
|
59
|
+
virtualMode = 'outlet';
|
|
117
60
|
profile = getParameter('profile');
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
61
|
+
log = new AnsiLogger({ logName: 'Matterbridge', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
|
|
62
|
+
fileLogger = false;
|
|
63
|
+
matterLog = new AnsiLogger({ logName: 'Matter', logTimestampFormat: 4, logLevel: "debug" });
|
|
64
|
+
matterFileLogger = false;
|
|
65
|
+
readOnly = hasParameter('readonly') || hasParameter('shelly');
|
|
66
|
+
shellyBoard = hasParameter('shelly');
|
|
67
|
+
shellySysUpdate = false;
|
|
68
|
+
shellyMainUpdate = false;
|
|
69
|
+
restartRequired = false;
|
|
70
|
+
fixedRestartRequired = false;
|
|
71
|
+
updateRequired = false;
|
|
125
72
|
plugins = new PluginManager(this);
|
|
126
|
-
devices = new DeviceManager(
|
|
73
|
+
devices = new DeviceManager();
|
|
127
74
|
frontend = new Frontend(this);
|
|
128
|
-
// Matterbridge storage
|
|
129
75
|
nodeStorage;
|
|
130
76
|
nodeContext;
|
|
131
|
-
|
|
77
|
+
static instance;
|
|
78
|
+
shutdown = false;
|
|
79
|
+
failCountLimit = hasParameter('shelly') ? 600 : 120;
|
|
132
80
|
hasCleanupStarted = false;
|
|
133
81
|
initialized = false;
|
|
134
82
|
startMatterInterval;
|
|
@@ -141,22 +89,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
141
89
|
sigtermHandler;
|
|
142
90
|
exceptionHandler;
|
|
143
91
|
rejectionHandler;
|
|
144
|
-
// Matter environment
|
|
145
92
|
environment = Environment.default;
|
|
146
|
-
// Matter storage
|
|
147
93
|
matterStorageService;
|
|
148
94
|
matterStorageManager;
|
|
149
95
|
matterbridgeContext;
|
|
150
96
|
controllerContext;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
certification; // device certification
|
|
159
|
-
// Matter nodes
|
|
97
|
+
mdnsInterface;
|
|
98
|
+
ipv4Address;
|
|
99
|
+
ipv6Address;
|
|
100
|
+
port;
|
|
101
|
+
passcode;
|
|
102
|
+
discriminator;
|
|
103
|
+
certification;
|
|
160
104
|
serverNode;
|
|
161
105
|
aggregatorNode;
|
|
162
106
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
@@ -166,57 +110,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
166
110
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
167
111
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
168
112
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
169
|
-
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
170
113
|
advertisingNodes = new Map();
|
|
171
|
-
static instance;
|
|
172
|
-
// We load asyncronously so is private
|
|
173
114
|
constructor() {
|
|
174
115
|
super();
|
|
175
116
|
this.log.logNameColor = '\x1b[38;5;115m';
|
|
176
117
|
}
|
|
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
118
|
static async loadInstance(initialize = false) {
|
|
218
119
|
if (!Matterbridge.instance) {
|
|
219
|
-
// eslint-disable-next-line no-console
|
|
220
120
|
if (hasParameter('debug'))
|
|
221
121
|
console.log(GREEN + 'Creating a new instance of Matterbridge.', initialize ? 'Initializing...' : 'Not initializing...', rs);
|
|
222
122
|
Matterbridge.instance = new Matterbridge();
|
|
@@ -225,17 +125,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
225
125
|
}
|
|
226
126
|
return Matterbridge.instance;
|
|
227
127
|
}
|
|
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
128
|
async destroyInstance(timeout = 1000, pause = 250) {
|
|
237
129
|
this.log.info(`Destroy instance...`);
|
|
238
|
-
// Save server nodes to close
|
|
239
130
|
const servers = [];
|
|
240
131
|
if (this.bridgeMode === 'bridge') {
|
|
241
132
|
if (this.serverNode)
|
|
@@ -253,106 +144,67 @@ export class Matterbridge extends EventEmitter {
|
|
|
253
144
|
servers.push(device.serverNode);
|
|
254
145
|
}
|
|
255
146
|
}
|
|
256
|
-
// Let any already‐queued microtasks run first
|
|
257
147
|
await Promise.resolve();
|
|
258
|
-
// Wait for the cleanup to finish
|
|
259
148
|
await wait(pause, 'destroyInstance start', true);
|
|
260
|
-
// Cleanup
|
|
261
149
|
await this.cleanup('destroying instance...', false, timeout);
|
|
262
|
-
// Close servers mdns service
|
|
263
150
|
this.log.info(`Dispose ${servers.length} MdnsService...`);
|
|
264
151
|
for (const server of servers) {
|
|
265
152
|
await server.env.get(MdnsService)[Symbol.asyncDispose]();
|
|
266
153
|
this.log.info(`Closed ${server.id} MdnsService`);
|
|
267
154
|
}
|
|
268
|
-
// Let any already‐queued microtasks run first
|
|
269
155
|
await Promise.resolve();
|
|
270
|
-
// Wait for the cleanup to finish
|
|
271
156
|
await wait(pause, 'destroyInstance stop', true);
|
|
272
157
|
}
|
|
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
158
|
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
159
|
this.emit('initialize_started');
|
|
287
|
-
// Set the restart mode
|
|
288
160
|
if (hasParameter('service'))
|
|
289
161
|
this.restartMode = 'service';
|
|
290
162
|
if (hasParameter('docker'))
|
|
291
163
|
this.restartMode = 'docker';
|
|
292
|
-
// Set the matterbridge home directory
|
|
293
164
|
this.homeDirectory = getParameter('homedir') ?? os.homedir();
|
|
294
|
-
this.matterbridgeInformation.homeDirectory = this.homeDirectory;
|
|
295
165
|
await createDirectory(this.homeDirectory, 'Matterbridge Home Directory', this.log);
|
|
296
|
-
// Set the matterbridge directory
|
|
297
166
|
this.matterbridgeDirectory = this.profile ? path.join(this.homeDirectory, '.matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, '.matterbridge');
|
|
298
|
-
this.matterbridgeInformation.matterbridgeDirectory = this.matterbridgeDirectory;
|
|
299
167
|
await createDirectory(this.matterbridgeDirectory, 'Matterbridge Directory', this.log);
|
|
300
168
|
await createDirectory(path.join(this.matterbridgeDirectory, 'certs'), 'Matterbridge Frontend Certificate Directory', this.log);
|
|
301
169
|
await createDirectory(path.join(this.matterbridgeDirectory, 'uploads'), 'Matterbridge Frontend Uploads Directory', this.log);
|
|
302
|
-
// Set the matterbridge plugin directory
|
|
303
170
|
this.matterbridgePluginDirectory = this.profile ? path.join(this.homeDirectory, 'Matterbridge', 'profiles', this.profile) : path.join(this.homeDirectory, 'Matterbridge');
|
|
304
|
-
this.matterbridgeInformation.matterbridgePluginDirectory = this.matterbridgePluginDirectory;
|
|
305
171
|
await createDirectory(this.matterbridgePluginDirectory, 'Matterbridge Plugin Directory', this.log);
|
|
306
|
-
// Set the matterbridge cert directory
|
|
307
172
|
this.matterbridgeCertDirectory = this.profile ? path.join(this.homeDirectory, '.mattercert', 'profiles', this.profile) : path.join(this.homeDirectory, '.mattercert');
|
|
308
|
-
this.matterbridgeInformation.matterbridgeCertDirectory = this.matterbridgeCertDirectory;
|
|
309
173
|
await createDirectory(this.matterbridgeCertDirectory, 'Matterbridge Matter Certificate Directory', this.log);
|
|
310
|
-
// Set the matterbridge root directory
|
|
311
174
|
const { fileURLToPath } = await import('node:url');
|
|
312
175
|
const currentFileDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
313
176
|
this.rootDirectory = path.resolve(currentFileDirectory, '../');
|
|
314
|
-
this.matterbridgeInformation.rootDirectory = this.rootDirectory;
|
|
315
|
-
// Setup the matter environment
|
|
316
177
|
this.environment.vars.set('log.level', MatterLogLevel.INFO);
|
|
317
178
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
318
179
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
319
180
|
this.environment.vars.set('runtime.signals', false);
|
|
320
181
|
this.environment.vars.set('runtime.exitcode', false);
|
|
321
|
-
// Register process handlers
|
|
322
182
|
this.registerProcessHandlers();
|
|
323
|
-
// Initialize nodeStorage and nodeContext
|
|
324
183
|
try {
|
|
325
184
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
326
185
|
this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
327
186
|
this.log.debug('Creating node storage context for matterbridge');
|
|
328
187
|
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
188
|
const keys = (await this.nodeStorage?.storage.keys());
|
|
332
189
|
for (const key of keys) {
|
|
333
190
|
this.log.debug(`Checking node storage manager key: ${CYAN}${key}${db}`);
|
|
334
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
335
191
|
await this.nodeStorage?.storage.get(key);
|
|
336
192
|
}
|
|
337
193
|
const storages = await this.nodeStorage.getStorageNames();
|
|
338
194
|
for (const storage of storages) {
|
|
339
195
|
this.log.debug(`Checking storage: ${CYAN}${storage}${db}`);
|
|
340
196
|
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
197
|
const keys = (await nodeContext?.storage.keys());
|
|
344
198
|
keys.forEach(async (key) => {
|
|
345
199
|
this.log.debug(`Checking key: ${CYAN}${storage}:${key}${db}`);
|
|
346
200
|
await nodeContext?.get(key);
|
|
347
201
|
});
|
|
348
202
|
}
|
|
349
|
-
// Creating a backup of the node storage since it is not corrupted
|
|
350
203
|
this.log.debug('Creating node storage backup...');
|
|
351
204
|
await copyDirectory(path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR), path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR + '.backup'));
|
|
352
205
|
this.log.debug('Created node storage backup');
|
|
353
206
|
}
|
|
354
207
|
catch (error) {
|
|
355
|
-
// Restoring the backup of the node storage since it is corrupted
|
|
356
208
|
this.log.error(`Error creating node storage manager and context: ${error instanceof Error ? error.message : error}`);
|
|
357
209
|
if (hasParameter('norestore')) {
|
|
358
210
|
this.log.fatal(`The matterbridge storage is corrupted. Found -norestore parameter: exiting...`);
|
|
@@ -366,19 +218,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
366
218
|
if (!this.nodeStorage || !this.nodeContext) {
|
|
367
219
|
throw new Error('Fatal error creating node storage manager and context for matterbridge');
|
|
368
220
|
}
|
|
369
|
-
// Set the first port to use for the commissioning server (will be incremented in childbridge mode)
|
|
370
221
|
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
222
|
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
223
|
this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator')) ?? PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
|
|
375
|
-
// Certificate management
|
|
376
224
|
const pairingFilePath = path.join(this.matterbridgeCertDirectory, 'pairing.json');
|
|
377
225
|
try {
|
|
378
226
|
await fs.access(pairingFilePath, fs.constants.R_OK);
|
|
379
227
|
const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
|
|
380
228
|
const pairingFileJson = JSON.parse(pairingFileContent);
|
|
381
|
-
// Set the vendorId, vendorName, productId, productName, deviceType, serialNumber, uniqueId if they are present in the pairing file
|
|
382
229
|
if (isValidNumber(pairingFileJson.vendorId)) {
|
|
383
230
|
this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
|
|
384
231
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using vendorId ${CYAN}${this.aggregatorVendorId}${nf} from pairing file.`);
|
|
@@ -407,13 +254,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
407
254
|
this.aggregatorUniqueId = pairingFileJson.uniqueId;
|
|
408
255
|
this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using uniqueId ${CYAN}${this.aggregatorUniqueId}${nf} from pairing file.`);
|
|
409
256
|
}
|
|
410
|
-
// Override the passcode and discriminator if they are present in the pairing file
|
|
411
257
|
if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
|
|
412
258
|
this.passcode = pairingFileJson.passcode;
|
|
413
259
|
this.discriminator = pairingFileJson.discriminator;
|
|
414
260
|
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
261
|
}
|
|
416
|
-
// Set the certification for matter.js if it is present in the pairing file
|
|
417
262
|
if (pairingFileJson.privateKey && pairingFileJson.certificate && pairingFileJson.intermediateCertificate && pairingFileJson.declaration) {
|
|
418
263
|
const { hexToBuffer } = await import('./utils/hex.js');
|
|
419
264
|
this.certification = {
|
|
@@ -428,53 +273,48 @@ export class Matterbridge extends EventEmitter {
|
|
|
428
273
|
catch (error) {
|
|
429
274
|
this.log.debug(`Pairing file ${CYAN}${pairingFilePath}${db} not found: ${error instanceof Error ? error.message : error}`);
|
|
430
275
|
}
|
|
431
|
-
// Store the passcode, discriminator and port in the node context
|
|
432
276
|
await this.nodeContext.set('matterport', this.port);
|
|
433
277
|
await this.nodeContext.set('matterpasscode', this.passcode);
|
|
434
278
|
await this.nodeContext.set('matterdiscriminator', this.discriminator);
|
|
435
279
|
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
280
|
if (hasParameter('logger')) {
|
|
438
281
|
const level = getParameter('logger');
|
|
439
282
|
if (level === 'debug') {
|
|
440
|
-
this.log.logLevel = "debug"
|
|
283
|
+
this.log.logLevel = "debug";
|
|
441
284
|
}
|
|
442
285
|
else if (level === 'info') {
|
|
443
|
-
this.log.logLevel = "info"
|
|
286
|
+
this.log.logLevel = "info";
|
|
444
287
|
}
|
|
445
288
|
else if (level === 'notice') {
|
|
446
|
-
this.log.logLevel = "notice"
|
|
289
|
+
this.log.logLevel = "notice";
|
|
447
290
|
}
|
|
448
291
|
else if (level === 'warn') {
|
|
449
|
-
this.log.logLevel = "warn"
|
|
292
|
+
this.log.logLevel = "warn";
|
|
450
293
|
}
|
|
451
294
|
else if (level === 'error') {
|
|
452
|
-
this.log.logLevel = "error"
|
|
295
|
+
this.log.logLevel = "error";
|
|
453
296
|
}
|
|
454
297
|
else if (level === 'fatal') {
|
|
455
|
-
this.log.logLevel = "fatal"
|
|
298
|
+
this.log.logLevel = "fatal";
|
|
456
299
|
}
|
|
457
300
|
else {
|
|
458
301
|
this.log.warn(`Invalid matterbridge logger level: ${level}. Using default level "info".`);
|
|
459
|
-
this.log.logLevel = "info"
|
|
302
|
+
this.log.logLevel = "info";
|
|
460
303
|
}
|
|
461
304
|
}
|
|
462
305
|
else {
|
|
463
|
-
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.
|
|
306
|
+
this.log.logLevel = await this.nodeContext.get('matterbridgeLogLevel', this.shellyBoard ? "notice" : "info");
|
|
464
307
|
}
|
|
465
308
|
this.frontend.logLevel = this.log.logLevel;
|
|
466
309
|
MatterbridgeEndpoint.logLevel = this.log.logLevel;
|
|
467
|
-
this.matterbridgeInformation.loggerLevel = this.log.logLevel;
|
|
468
|
-
// Create the file logger for matterbridge (context: matterbridgeFileLog)
|
|
469
310
|
if (hasParameter('filelogger') || (await this.nodeContext.get('matterbridgeFileLog', false))) {
|
|
470
311
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.log.logLevel, true);
|
|
471
|
-
this.
|
|
312
|
+
this.fileLogger = true;
|
|
472
313
|
}
|
|
473
314
|
this.log.notice('Matterbridge is starting...');
|
|
474
|
-
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.
|
|
315
|
+
this.log.debug(`Matterbridge logLevel: ${this.log.logLevel} fileLoger: ${this.fileLogger}.`);
|
|
475
316
|
if (this.profile !== undefined)
|
|
476
317
|
this.log.debug(`Matterbridge profile: ${this.profile}.`);
|
|
477
|
-
// Set matter.js logger level, format and logger (context: matterLogLevel)
|
|
478
318
|
if (hasParameter('matterlogger')) {
|
|
479
319
|
const level = getParameter('matterlogger');
|
|
480
320
|
if (level === 'debug') {
|
|
@@ -501,20 +341,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
501
341
|
}
|
|
502
342
|
}
|
|
503
343
|
else {
|
|
504
|
-
Logger.level = (await this.nodeContext.get('matterLogLevel', this.
|
|
344
|
+
Logger.level = (await this.nodeContext.get('matterLogLevel', this.shellyBoard ? MatterLogLevel.NOTICE : MatterLogLevel.INFO));
|
|
505
345
|
}
|
|
506
346
|
Logger.format = MatterLogFormat.ANSI;
|
|
507
|
-
// Create the logger for matter.js with file logging (context: matterFileLog)
|
|
508
347
|
if (hasParameter('matterfilelogger') || (await this.nodeContext.get('matterFileLog', false))) {
|
|
509
|
-
this.
|
|
348
|
+
this.matterFileLogger = true;
|
|
510
349
|
}
|
|
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
|
|
350
|
+
Logger.destinations.default.write = this.createDestinationMatterLogger(this.matterFileLogger);
|
|
351
|
+
this.log.debug(`Matter logLevel: ${Logger.level} fileLoger: ${this.matterFileLogger}.`);
|
|
515
352
|
const networkInterfaces = os.networkInterfaces();
|
|
516
353
|
const availableAddresses = Object.entries(networkInterfaces);
|
|
517
|
-
const
|
|
354
|
+
const availableInterfaceNames = Object.keys(networkInterfaces);
|
|
518
355
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
519
356
|
if (ifaces && ifaces.length > 0) {
|
|
520
357
|
this.log.debug(`Network interface ${BLUE}${ifaceName}${db}:`);
|
|
@@ -524,7 +361,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
524
361
|
});
|
|
525
362
|
}
|
|
526
363
|
}
|
|
527
|
-
// Set the interface to use for matter server node mdnsInterface
|
|
528
364
|
if (hasParameter('mdnsinterface')) {
|
|
529
365
|
this.mdnsInterface = getParameter('mdnsinterface');
|
|
530
366
|
}
|
|
@@ -533,10 +369,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
533
369
|
if (this.mdnsInterface === '')
|
|
534
370
|
this.mdnsInterface = undefined;
|
|
535
371
|
}
|
|
536
|
-
// Validate mdnsInterface
|
|
537
372
|
if (this.mdnsInterface) {
|
|
538
|
-
if (!
|
|
539
|
-
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${
|
|
373
|
+
if (!availableInterfaceNames.includes(this.mdnsInterface)) {
|
|
374
|
+
this.log.error(`Invalid mdnsinterface: ${this.mdnsInterface}. Available interfaces are: ${availableInterfaceNames.join(', ')}. Using all available interfaces.`);
|
|
540
375
|
this.mdnsInterface = undefined;
|
|
541
376
|
await this.nodeContext.remove('mattermdnsinterface');
|
|
542
377
|
}
|
|
@@ -546,86 +381,75 @@ export class Matterbridge extends EventEmitter {
|
|
|
546
381
|
}
|
|
547
382
|
if (this.mdnsInterface)
|
|
548
383
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
549
|
-
// Set the listeningAddressIpv4 for the matter commissioning server
|
|
550
384
|
if (hasParameter('ipv4address')) {
|
|
551
|
-
this.
|
|
385
|
+
this.ipv4Address = getParameter('ipv4address');
|
|
552
386
|
}
|
|
553
387
|
else {
|
|
554
|
-
this.
|
|
555
|
-
if (this.
|
|
556
|
-
this.
|
|
388
|
+
this.ipv4Address = await this.nodeContext.get('matteripv4address', undefined);
|
|
389
|
+
if (this.ipv4Address === '')
|
|
390
|
+
this.ipv4Address = undefined;
|
|
557
391
|
}
|
|
558
|
-
|
|
559
|
-
if (this.ipv4address) {
|
|
392
|
+
if (this.ipv4Address) {
|
|
560
393
|
let isValid = false;
|
|
561
394
|
for (const [ifaceName, ifaces] of availableAddresses) {
|
|
562
|
-
if (ifaces && ifaces.find((iface) => iface.address === this.
|
|
563
|
-
this.log.info(`Using ipv4address ${CYAN}${this.
|
|
395
|
+
if (ifaces && ifaces.find((iface) => iface.address === this.ipv4Address)) {
|
|
396
|
+
this.log.info(`Using ipv4address ${CYAN}${this.ipv4Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
564
397
|
isValid = true;
|
|
565
398
|
break;
|
|
566
399
|
}
|
|
567
400
|
}
|
|
568
401
|
if (!isValid) {
|
|
569
|
-
this.log.error(`Invalid ipv4address: ${this.
|
|
570
|
-
this.
|
|
402
|
+
this.log.error(`Invalid ipv4address: ${this.ipv4Address}. Using all available addresses.`);
|
|
403
|
+
this.ipv4Address = undefined;
|
|
571
404
|
await this.nodeContext.remove('matteripv4address');
|
|
572
405
|
}
|
|
573
406
|
}
|
|
574
|
-
// Set the listeningAddressIpv6 for the matter commissioning server
|
|
575
407
|
if (hasParameter('ipv6address')) {
|
|
576
|
-
this.
|
|
408
|
+
this.ipv6Address = getParameter('ipv6address');
|
|
577
409
|
}
|
|
578
410
|
else {
|
|
579
|
-
this.
|
|
580
|
-
if (this.
|
|
581
|
-
this.
|
|
411
|
+
this.ipv6Address = await this.nodeContext?.get('matteripv6address', undefined);
|
|
412
|
+
if (this.ipv6Address === '')
|
|
413
|
+
this.ipv6Address = undefined;
|
|
582
414
|
}
|
|
583
|
-
|
|
584
|
-
if (this.ipv6address) {
|
|
415
|
+
if (this.ipv6Address) {
|
|
585
416
|
let isValid = false;
|
|
586
417
|
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.
|
|
418
|
+
if (ifaces && ifaces.find((iface) => (iface.scopeid === undefined || iface.scopeid === 0) && iface.address === this.ipv6Address)) {
|
|
419
|
+
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
589
420
|
isValid = true;
|
|
590
421
|
break;
|
|
591
422
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
this.log.info(`Using ipv6address ${CYAN}${this.ipv6address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
423
|
+
if (ifaces && ifaces.find((iface) => iface.scopeid && iface.scopeid > 0 && iface.address + '%' + (process.platform === 'win32' ? iface.scopeid : ifaceName) === this.ipv6Address)) {
|
|
424
|
+
this.log.info(`Using ipv6address ${CYAN}${this.ipv6Address}${nf} on interface ${CYAN}${ifaceName}${nf} for the Matter server node.`);
|
|
595
425
|
isValid = true;
|
|
596
426
|
break;
|
|
597
427
|
}
|
|
598
428
|
}
|
|
599
429
|
if (!isValid) {
|
|
600
|
-
this.log.error(`Invalid ipv6address: ${this.
|
|
601
|
-
this.
|
|
430
|
+
this.log.error(`Invalid ipv6address: ${this.ipv6Address}. Using all available addresses.`);
|
|
431
|
+
this.ipv6Address = undefined;
|
|
602
432
|
await this.nodeContext.remove('matteripv6address');
|
|
603
433
|
}
|
|
604
434
|
}
|
|
605
|
-
// Initialize the virtual mode
|
|
606
435
|
if (hasParameter('novirtual')) {
|
|
607
|
-
this.
|
|
436
|
+
this.virtualMode = 'disabled';
|
|
608
437
|
await this.nodeContext.set('virtualmode', 'disabled');
|
|
609
438
|
}
|
|
610
439
|
else {
|
|
611
|
-
this.
|
|
440
|
+
this.virtualMode = (await this.nodeContext.get('virtualmode', 'outlet'));
|
|
612
441
|
}
|
|
613
|
-
this.log.debug(`Virtual mode ${this.
|
|
614
|
-
// Initialize PluginManager
|
|
442
|
+
this.log.debug(`Virtual mode ${this.virtualMode}.`);
|
|
615
443
|
this.plugins.logLevel = this.log.logLevel;
|
|
616
444
|
await this.plugins.loadFromStorage();
|
|
617
|
-
// Initialize DeviceManager
|
|
618
445
|
this.devices.logLevel = this.log.logLevel;
|
|
619
|
-
// Get the plugins from node storage and create the plugins node storage contexts
|
|
620
446
|
for (const plugin of this.plugins) {
|
|
621
447
|
const packageJson = await this.plugins.parse(plugin);
|
|
622
448
|
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
449
|
this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm.`);
|
|
626
450
|
try {
|
|
627
451
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
628
|
-
await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], plugin.name);
|
|
452
|
+
await spawnCommand(this, 'npm', ['install', '-g', plugin.name, '--omit=dev', '--verbose'], 'install', plugin.name);
|
|
629
453
|
this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
|
|
630
454
|
plugin.error = false;
|
|
631
455
|
}
|
|
@@ -644,7 +468,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
644
468
|
await plugin.nodeContext.set('description', plugin.description);
|
|
645
469
|
await plugin.nodeContext.set('author', plugin.author);
|
|
646
470
|
}
|
|
647
|
-
// Log system info and create .matterbridge directory
|
|
648
471
|
await this.logNodeAndSystemInfo();
|
|
649
472
|
this.log.notice(`Matterbridge version ${this.matterbridgeVersion} ` +
|
|
650
473
|
`${hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge') ? 'mode bridge ' : ''}` +
|
|
@@ -652,7 +475,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
652
475
|
`${hasParameter('controller') ? 'mode controller ' : ''}` +
|
|
653
476
|
`${this.restartMode !== '' ? 'restart mode ' + this.restartMode + ' ' : ''}` +
|
|
654
477
|
`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
478
|
const minNodeVersion = 18;
|
|
657
479
|
const nodeVersion = process.versions.node;
|
|
658
480
|
const versionMajor = parseInt(nodeVersion.split('.')[0]);
|
|
@@ -660,18 +482,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
660
482
|
this.log.error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
661
483
|
throw new Error(`Node version ${versionMajor} is not supported. Please upgrade to ${minNodeVersion} or above.`);
|
|
662
484
|
}
|
|
663
|
-
// Parse command line
|
|
664
485
|
await this.parseCommandLine();
|
|
665
|
-
// Emit the initialize_completed event
|
|
666
486
|
this.emit('initialize_completed');
|
|
667
487
|
this.initialized = true;
|
|
668
488
|
}
|
|
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
489
|
async parseCommandLine() {
|
|
676
490
|
if (hasParameter('help')) {
|
|
677
491
|
this.log.info(`\nUsage: matterbridge [options]\n
|
|
@@ -733,19 +547,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
733
547
|
}
|
|
734
548
|
index++;
|
|
735
549
|
}
|
|
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
550
|
this.shutdown = true;
|
|
750
551
|
return;
|
|
751
552
|
}
|
|
@@ -795,10 +596,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
795
596
|
this.shutdown = true;
|
|
796
597
|
return;
|
|
797
598
|
}
|
|
798
|
-
// Initialize frontend
|
|
799
599
|
if (getIntParameter('frontend') !== 0 || getIntParameter('frontend') === undefined)
|
|
800
600
|
await this.frontend.start(getIntParameter('frontend'));
|
|
801
|
-
// Start the matter storage and create the matterbridge context
|
|
802
601
|
try {
|
|
803
602
|
await this.startMatterStorage();
|
|
804
603
|
if (this.aggregatorSerialNumber && this.aggregatorUniqueId && this.matterStorageService) {
|
|
@@ -814,21 +613,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
814
613
|
this.log.fatal(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
815
614
|
throw new Error(`Fatal error creating matter storage: ${error instanceof Error ? error.message : error}`);
|
|
816
615
|
}
|
|
817
|
-
// Clear the matterbridge context if the reset parameter is set
|
|
818
616
|
if (hasParameter('reset') && getParameter('reset') === undefined) {
|
|
819
617
|
this.initialized = true;
|
|
820
618
|
await this.shutdownProcessAndReset();
|
|
821
619
|
this.shutdown = true;
|
|
822
620
|
return;
|
|
823
621
|
}
|
|
824
|
-
// Clear matterbridge plugin context if the reset parameter is set
|
|
825
622
|
if (hasParameter('reset') && getParameter('reset') !== undefined) {
|
|
826
623
|
this.log.debug(`Reset plugin ${getParameter('reset')}`);
|
|
827
624
|
const plugin = this.plugins.get(getParameter('reset'));
|
|
828
625
|
if (plugin) {
|
|
829
626
|
const matterStorageManager = await this.matterStorageService?.open(plugin.name);
|
|
830
627
|
if (!matterStorageManager) {
|
|
831
|
-
/* istanbul ignore next */
|
|
832
628
|
this.log.error(`Plugin ${plg}${plugin.name}${er} storageManager not found`);
|
|
833
629
|
}
|
|
834
630
|
else {
|
|
@@ -847,42 +643,35 @@ export class Matterbridge extends EventEmitter {
|
|
|
847
643
|
this.shutdown = true;
|
|
848
644
|
return;
|
|
849
645
|
}
|
|
850
|
-
// Check in 30 seconds the latest and dev versions of matterbridge and the plugins
|
|
851
646
|
clearTimeout(this.checkUpdateTimeout);
|
|
852
647
|
this.checkUpdateTimeout = setTimeout(async () => {
|
|
853
648
|
const { checkUpdates } = await import('./update.js');
|
|
854
649
|
checkUpdates(this);
|
|
855
650
|
}, 30 * 1000).unref();
|
|
856
|
-
// Check each 12 hours the latest and dev versions of matterbridge and the plugins
|
|
857
651
|
clearInterval(this.checkUpdateInterval);
|
|
858
652
|
this.checkUpdateInterval = setInterval(async () => {
|
|
859
653
|
const { checkUpdates } = await import('./update.js');
|
|
860
654
|
checkUpdates(this);
|
|
861
655
|
}, 12 * 60 * 60 * 1000).unref();
|
|
862
|
-
// Start the matterbridge in mode test
|
|
863
656
|
if (hasParameter('test')) {
|
|
864
657
|
this.bridgeMode = 'bridge';
|
|
865
658
|
return;
|
|
866
659
|
}
|
|
867
|
-
// Start the matterbridge in mode controller
|
|
868
660
|
if (hasParameter('controller')) {
|
|
869
661
|
this.bridgeMode = 'controller';
|
|
870
662
|
await this.startController();
|
|
871
663
|
return;
|
|
872
664
|
}
|
|
873
|
-
// Check if the bridge mode is set and start matterbridge in bridge mode if not set
|
|
874
665
|
if (!hasParameter('bridge') && !hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === '') {
|
|
875
666
|
this.log.info('Setting default matterbridge start mode to bridge');
|
|
876
667
|
await this.nodeContext?.set('bridgeMode', 'bridge');
|
|
877
668
|
}
|
|
878
|
-
// Start matterbridge in bridge mode
|
|
879
669
|
if (hasParameter('bridge') || (!hasParameter('childbridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'bridge')) {
|
|
880
670
|
this.bridgeMode = 'bridge';
|
|
881
671
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
882
672
|
await this.startBridge();
|
|
883
673
|
return;
|
|
884
674
|
}
|
|
885
|
-
// Start matterbridge in childbridge mode
|
|
886
675
|
if (hasParameter('childbridge') || (!hasParameter('bridge') && (await this.nodeContext?.get('bridgeMode', '')) === 'childbridge')) {
|
|
887
676
|
this.bridgeMode = 'childbridge';
|
|
888
677
|
this.log.debug(`Starting matterbridge in mode ${this.bridgeMode}`);
|
|
@@ -890,22 +679,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
890
679
|
return;
|
|
891
680
|
}
|
|
892
681
|
}
|
|
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
682
|
async startPlugins(wait = false, start = true) {
|
|
904
|
-
// Check, load and start the plugins
|
|
905
683
|
for (const plugin of this.plugins) {
|
|
906
684
|
plugin.configJson = await this.plugins.loadConfig(plugin);
|
|
907
685
|
plugin.schemaJson = await this.plugins.loadSchema(plugin);
|
|
908
|
-
// Check if the plugin is available
|
|
909
686
|
if (!(await this.plugins.resolve(plugin.path))) {
|
|
910
687
|
this.log.error(`Plugin ${plg}${plugin.name}${er} not found or not validated. Disabling it.`);
|
|
911
688
|
plugin.enabled = false;
|
|
@@ -925,16 +702,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
925
702
|
if (wait)
|
|
926
703
|
await this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
927
704
|
else
|
|
928
|
-
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
705
|
+
this.plugins.load(plugin, start, 'Matterbridge is starting');
|
|
929
706
|
}
|
|
930
707
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
931
708
|
}
|
|
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
709
|
registerProcessHandlers() {
|
|
939
710
|
this.log.debug(`Registering uncaughtException and unhandledRejection handlers...`);
|
|
940
711
|
process.removeAllListeners('uncaughtException');
|
|
@@ -961,9 +732,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
961
732
|
};
|
|
962
733
|
process.on('SIGTERM', this.sigtermHandler);
|
|
963
734
|
}
|
|
964
|
-
/**
|
|
965
|
-
* Deregisters the process uncaughtException, unhandledRejection, SIGINT and SIGTERM signal handlers.
|
|
966
|
-
*/
|
|
967
735
|
deregisterProcessHandlers() {
|
|
968
736
|
this.log.debug(`Deregistering uncaughtException and unhandledRejection handlers...`);
|
|
969
737
|
if (this.exceptionHandler)
|
|
@@ -980,17 +748,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
980
748
|
process.off('SIGTERM', this.sigtermHandler);
|
|
981
749
|
this.sigtermHandler = undefined;
|
|
982
750
|
}
|
|
983
|
-
/**
|
|
984
|
-
* Logs the node and system information.
|
|
985
|
-
*/
|
|
986
751
|
async logNodeAndSystemInfo() {
|
|
987
|
-
// IP address information
|
|
988
752
|
const networkInterfaces = os.networkInterfaces();
|
|
989
753
|
this.systemInformation.interfaceName = '';
|
|
990
754
|
this.systemInformation.ipv4Address = '';
|
|
991
755
|
this.systemInformation.ipv6Address = '';
|
|
756
|
+
this.systemInformation.macAddress = '';
|
|
992
757
|
for (const [interfaceName, interfaceDetails] of Object.entries(networkInterfaces)) {
|
|
993
|
-
// this.log.debug(`Checking interface: '${interfaceName}' for '${this.mdnsInterface}'`);
|
|
994
758
|
if (this.mdnsInterface && interfaceName !== this.mdnsInterface)
|
|
995
759
|
continue;
|
|
996
760
|
if (!interfaceDetails) {
|
|
@@ -1016,22 +780,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1016
780
|
break;
|
|
1017
781
|
}
|
|
1018
782
|
}
|
|
1019
|
-
// Node information
|
|
1020
783
|
this.systemInformation.nodeVersion = process.versions.node;
|
|
1021
784
|
const versionMajor = parseInt(this.systemInformation.nodeVersion.split('.')[0]);
|
|
1022
785
|
const versionMinor = parseInt(this.systemInformation.nodeVersion.split('.')[1]);
|
|
1023
786
|
const versionPatch = parseInt(this.systemInformation.nodeVersion.split('.')[2]);
|
|
1024
|
-
// Host system information
|
|
1025
787
|
this.systemInformation.hostname = os.hostname();
|
|
1026
788
|
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
|
-
|
|
789
|
+
this.systemInformation.osType = os.type();
|
|
790
|
+
this.systemInformation.osRelease = os.release();
|
|
791
|
+
this.systemInformation.osPlatform = os.platform();
|
|
792
|
+
this.systemInformation.osArch = os.arch();
|
|
793
|
+
this.systemInformation.totalMemory = formatMemoryUsage(os.totalmem());
|
|
794
|
+
this.systemInformation.freeMemory = formatMemoryUsage(os.freemem());
|
|
795
|
+
this.systemInformation.systemUptime = formatOsUpTime(os.uptime());
|
|
796
|
+
this.systemInformation.processUptime = formatOsUpTime(Math.floor(process.uptime()));
|
|
797
|
+
this.systemInformation.cpuUsage = '0.00 %';
|
|
798
|
+
this.systemInformation.rss = formatMemoryUsage(process.memoryUsage().rss);
|
|
799
|
+
this.systemInformation.heapTotal = formatMemoryUsage(process.memoryUsage().heapTotal);
|
|
800
|
+
this.systemInformation.heapUsed = formatMemoryUsage(process.memoryUsage().heapUsed);
|
|
1035
801
|
this.log.debug('Host System Information:');
|
|
1036
802
|
this.log.debug(`- Hostname: ${this.systemInformation.hostname}`);
|
|
1037
803
|
this.log.debug(`- User: ${this.systemInformation.user}`);
|
|
@@ -1047,21 +813,22 @@ export class Matterbridge extends EventEmitter {
|
|
|
1047
813
|
this.log.debug(`- Total Memory: ${this.systemInformation.totalMemory}`);
|
|
1048
814
|
this.log.debug(`- Free Memory: ${this.systemInformation.freeMemory}`);
|
|
1049
815
|
this.log.debug(`- System Uptime: ${this.systemInformation.systemUptime}`);
|
|
1050
|
-
|
|
816
|
+
this.log.debug(`- Process Uptime: ${this.systemInformation.processUptime}`);
|
|
817
|
+
this.log.debug(`- RSS: ${this.systemInformation.rss}`);
|
|
818
|
+
this.log.debug(`- Heap Total: ${this.systemInformation.heapTotal}`);
|
|
819
|
+
this.log.debug(`- Heap Used: ${this.systemInformation.heapUsed}`);
|
|
1051
820
|
this.log.debug(`Root Directory: ${this.rootDirectory}`);
|
|
1052
821
|
this.log.debug(`Home Directory: ${this.homeDirectory}`);
|
|
1053
822
|
this.log.debug(`Matterbridge Directory: ${this.matterbridgeDirectory}`);
|
|
1054
823
|
this.log.debug(`Matterbridge Plugin Directory: ${this.matterbridgePluginDirectory}`);
|
|
1055
824
|
this.log.debug(`Matterbridge Matter Certificate Directory: ${this.matterbridgeCertDirectory}`);
|
|
1056
|
-
// Global node_modules directory
|
|
1057
825
|
if (this.nodeContext)
|
|
1058
|
-
this.globalModulesDirectory =
|
|
826
|
+
this.globalModulesDirectory = await this.nodeContext.get('globalModulesDirectory', '');
|
|
1059
827
|
if (this.globalModulesDirectory === '') {
|
|
1060
|
-
// First run of Matterbridge so the node storage is empty
|
|
1061
828
|
this.log.debug(`Getting global node_modules directory...`);
|
|
1062
829
|
try {
|
|
1063
830
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
1064
|
-
this.
|
|
831
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
1065
832
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1066
833
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
1067
834
|
}
|
|
@@ -1070,11 +837,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1070
837
|
}
|
|
1071
838
|
}
|
|
1072
839
|
else {
|
|
1073
|
-
// The global node_modules directory is already set in the node storage and we check if it is still valid
|
|
1074
840
|
this.log.debug(`Checking global node_modules directory: ${this.globalModulesDirectory}`);
|
|
1075
841
|
try {
|
|
1076
842
|
const { getGlobalNodeModules } = await import('./utils/network.js');
|
|
1077
|
-
this.
|
|
843
|
+
this.globalModulesDirectory = await getGlobalNodeModules();
|
|
1078
844
|
this.log.debug(`Global node_modules Directory: ${this.globalModulesDirectory}`);
|
|
1079
845
|
await this.nodeContext?.set('globalModulesDirectory', this.globalModulesDirectory);
|
|
1080
846
|
}
|
|
@@ -1082,91 +848,93 @@ export class Matterbridge extends EventEmitter {
|
|
|
1082
848
|
this.log.error(`Error checking global node_modules directory: ${error}`);
|
|
1083
849
|
}
|
|
1084
850
|
}
|
|
1085
|
-
|
|
851
|
+
this.log.debug(`Reading matterbridge package.json...`);
|
|
1086
852
|
const packageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'package.json'), 'utf-8'));
|
|
1087
853
|
this.matterbridgeVersion = this.matterbridgeLatestVersion = this.matterbridgeDevVersion = packageJson.version;
|
|
1088
|
-
this.matterbridgeInformation.matterbridgeVersion = this.matterbridgeInformation.matterbridgeLatestVersion = this.matterbridgeInformation.matterbridgeDevVersion = packageJson.version;
|
|
1089
854
|
this.log.debug(`Matterbridge Version: ${this.matterbridgeVersion}`);
|
|
1090
|
-
// Matterbridge latest version (will be set in the checkUpdate function)
|
|
1091
855
|
if (this.nodeContext)
|
|
1092
|
-
this.matterbridgeLatestVersion =
|
|
856
|
+
this.matterbridgeLatestVersion = await this.nodeContext.get('matterbridgeLatestVersion', this.matterbridgeVersion);
|
|
1093
857
|
this.log.debug(`Matterbridge Latest Version: ${this.matterbridgeLatestVersion}`);
|
|
1094
|
-
// Matterbridge dev version (will be set in the checkUpdate function)
|
|
1095
858
|
if (this.nodeContext)
|
|
1096
|
-
this.matterbridgeDevVersion =
|
|
859
|
+
this.matterbridgeDevVersion = await this.nodeContext.get('matterbridgeDevVersion', this.matterbridgeVersion);
|
|
1097
860
|
this.log.debug(`Matterbridge Dev Version: ${this.matterbridgeDevVersion}`);
|
|
1098
|
-
|
|
861
|
+
this.log.debug(`Reading frontend package.json...`);
|
|
862
|
+
const frontendPackageJson = JSON.parse(await fs.readFile(path.join(this.rootDirectory, 'frontend/package.json'), 'utf8'));
|
|
863
|
+
this.frontendVersion = frontendPackageJson.version;
|
|
864
|
+
this.log.debug(`Frontend version ${CYAN}${this.frontendVersion}${db}`);
|
|
1099
865
|
const currentDir = process.cwd();
|
|
1100
866
|
this.log.debug(`Current Working Directory: ${currentDir}`);
|
|
1101
|
-
// Command line arguments (excluding 'node' and the script name)
|
|
1102
867
|
const cmdArgs = process.argv.slice(2).join(' ');
|
|
1103
868
|
this.log.debug(`Command Line Arguments: ${cmdArgs}`);
|
|
1104
869
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
870
|
+
async setLogLevel(logLevel) {
|
|
871
|
+
if (this.log)
|
|
872
|
+
this.log.logLevel = logLevel;
|
|
873
|
+
this.frontend.logLevel = logLevel;
|
|
874
|
+
MatterbridgeEndpoint.logLevel = logLevel;
|
|
875
|
+
if (this.devices)
|
|
876
|
+
this.devices.logLevel = logLevel;
|
|
877
|
+
if (this.plugins)
|
|
878
|
+
this.plugins.logLevel = logLevel;
|
|
879
|
+
for (const plugin of this.plugins) {
|
|
880
|
+
if (!plugin.platform || !plugin.platform.log || !plugin.platform.config)
|
|
881
|
+
continue;
|
|
882
|
+
plugin.platform.log.logLevel = plugin.platform.config.debug === true ? "debug" : this.log.logLevel;
|
|
883
|
+
await plugin.platform.onChangeLoggerLevel(plugin.platform.config.debug === true ? "debug" : this.log.logLevel);
|
|
884
|
+
}
|
|
885
|
+
let callbackLogLevel = "notice";
|
|
886
|
+
if (this.log.logLevel === "info" || Logger.level === MatterLogLevel.INFO)
|
|
887
|
+
callbackLogLevel = "info";
|
|
888
|
+
if (this.log.logLevel === "debug" || Logger.level === MatterLogLevel.DEBUG)
|
|
889
|
+
callbackLogLevel = "debug";
|
|
890
|
+
AnsiLogger.setGlobalCallback(this.frontend.wssSendLogMessage.bind(this.frontend), callbackLogLevel);
|
|
891
|
+
this.log.debug(`WebSocketServer logger global callback set to ${callbackLogLevel}`);
|
|
892
|
+
}
|
|
893
|
+
getLogLevel() {
|
|
894
|
+
return this.log.logLevel;
|
|
895
|
+
}
|
|
1112
896
|
createDestinationMatterLogger(fileLogger) {
|
|
1113
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
897
|
+
this.matterLog.logNameColor = '\x1b[34m';
|
|
1114
898
|
if (fileLogger) {
|
|
1115
899
|
this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
1116
900
|
}
|
|
1117
901
|
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
902
|
const logger = text.slice(44, 44 + 20).trim();
|
|
1120
903
|
const msg = text.slice(65);
|
|
1121
904
|
this.matterLog.logName = logger;
|
|
1122
905
|
switch (message.level) {
|
|
1123
906
|
case MatterLogLevel.DEBUG:
|
|
1124
|
-
this.matterLog.log("debug"
|
|
907
|
+
this.matterLog.log("debug", msg);
|
|
1125
908
|
break;
|
|
1126
909
|
case MatterLogLevel.INFO:
|
|
1127
|
-
this.matterLog.log("info"
|
|
910
|
+
this.matterLog.log("info", msg);
|
|
1128
911
|
break;
|
|
1129
912
|
case MatterLogLevel.NOTICE:
|
|
1130
|
-
this.matterLog.log("notice"
|
|
913
|
+
this.matterLog.log("notice", msg);
|
|
1131
914
|
break;
|
|
1132
915
|
case MatterLogLevel.WARN:
|
|
1133
|
-
this.matterLog.log("warn"
|
|
916
|
+
this.matterLog.log("warn", msg);
|
|
1134
917
|
break;
|
|
1135
918
|
case MatterLogLevel.ERROR:
|
|
1136
|
-
this.matterLog.log("error"
|
|
919
|
+
this.matterLog.log("error", msg);
|
|
1137
920
|
break;
|
|
1138
921
|
case MatterLogLevel.FATAL:
|
|
1139
|
-
this.matterLog.log("fatal"
|
|
922
|
+
this.matterLog.log("fatal", msg);
|
|
1140
923
|
break;
|
|
1141
924
|
}
|
|
1142
925
|
};
|
|
1143
926
|
}
|
|
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
927
|
async restartProcess() {
|
|
1150
928
|
await this.cleanup('restarting...', true);
|
|
1151
929
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Shut down the process (/api/shutdown).
|
|
1154
|
-
*
|
|
1155
|
-
* @returns {Promise<void>} A promise that resolves when the shutdown is completed.
|
|
1156
|
-
*/
|
|
1157
930
|
async shutdownProcess() {
|
|
1158
931
|
await this.cleanup('shutting down...', false);
|
|
1159
932
|
}
|
|
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
933
|
async updateProcess() {
|
|
1166
934
|
this.log.info('Updating matterbridge...');
|
|
1167
935
|
try {
|
|
1168
936
|
const { spawnCommand } = await import('./utils/spawn.js');
|
|
1169
|
-
await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'matterbridge');
|
|
937
|
+
await spawnCommand(this, 'npm', ['install', '-g', 'matterbridge', '--omit=dev', '--verbose'], 'install', 'matterbridge');
|
|
1170
938
|
this.log.info('Matterbridge has been updated. Full restart required.');
|
|
1171
939
|
}
|
|
1172
940
|
catch (error) {
|
|
@@ -1175,13 +943,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1175
943
|
this.frontend.wssSendRestartRequired();
|
|
1176
944
|
await this.cleanup('updating...', false);
|
|
1177
945
|
}
|
|
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
946
|
async unregisterAndShutdownProcess(timeout = 1000) {
|
|
1186
947
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1187
948
|
for (const plugin of this.plugins.array()) {
|
|
@@ -1193,71 +954,46 @@ export class Matterbridge extends EventEmitter {
|
|
|
1193
954
|
await this.removeAllBridgedEndpoints(plugin.name, 100);
|
|
1194
955
|
}
|
|
1195
956
|
this.log.debug('Waiting for the MessageExchange to finish...');
|
|
1196
|
-
await wait(timeout);
|
|
957
|
+
await wait(timeout);
|
|
1197
958
|
this.log.debug('Cleaning up and shutting down...');
|
|
1198
959
|
await this.cleanup('unregistered all devices and shutting down...', false, timeout);
|
|
1199
960
|
}
|
|
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
961
|
async shutdownProcessAndReset() {
|
|
1206
962
|
await this.cleanup('shutting down with reset...', false);
|
|
1207
963
|
}
|
|
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
964
|
async shutdownProcessAndFactoryReset() {
|
|
1214
965
|
await this.cleanup('shutting down with factory reset...', false);
|
|
1215
966
|
}
|
|
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
967
|
async cleanup(message, restart = false, timeout = 1000) {
|
|
1226
968
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
1227
969
|
this.emit('cleanup_started');
|
|
1228
970
|
this.hasCleanupStarted = true;
|
|
1229
971
|
this.log.info(message);
|
|
1230
|
-
// Clear the start matter interval
|
|
1231
972
|
if (this.startMatterInterval) {
|
|
1232
973
|
clearInterval(this.startMatterInterval);
|
|
1233
974
|
this.startMatterInterval = undefined;
|
|
1234
975
|
this.log.debug('Start matter interval cleared');
|
|
1235
976
|
}
|
|
1236
|
-
// Clear the check update timeout
|
|
1237
977
|
if (this.checkUpdateTimeout) {
|
|
1238
978
|
clearTimeout(this.checkUpdateTimeout);
|
|
1239
979
|
this.checkUpdateTimeout = undefined;
|
|
1240
980
|
this.log.debug('Check update timeout cleared');
|
|
1241
981
|
}
|
|
1242
|
-
// Clear the check update interval
|
|
1243
982
|
if (this.checkUpdateInterval) {
|
|
1244
983
|
clearInterval(this.checkUpdateInterval);
|
|
1245
984
|
this.checkUpdateInterval = undefined;
|
|
1246
985
|
this.log.debug('Check update interval cleared');
|
|
1247
986
|
}
|
|
1248
|
-
// Clear the configure timeout
|
|
1249
987
|
if (this.configureTimeout) {
|
|
1250
988
|
clearTimeout(this.configureTimeout);
|
|
1251
989
|
this.configureTimeout = undefined;
|
|
1252
990
|
this.log.debug('Matterbridge configure timeout cleared');
|
|
1253
991
|
}
|
|
1254
|
-
// Clear the reachability timeout
|
|
1255
992
|
if (this.reachabilityTimeout) {
|
|
1256
993
|
clearTimeout(this.reachabilityTimeout);
|
|
1257
994
|
this.reachabilityTimeout = undefined;
|
|
1258
995
|
this.log.debug('Matterbridge reachability timeout cleared');
|
|
1259
996
|
}
|
|
1260
|
-
// Call the shutdown method of each plugin and clear the plugins reachability timeout
|
|
1261
997
|
for (const plugin of this.plugins) {
|
|
1262
998
|
if (!plugin.enabled || plugin.error)
|
|
1263
999
|
continue;
|
|
@@ -1268,7 +1004,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1268
1004
|
this.log.debug(`Plugin ${plg}${plugin.name}${db} reachability timeout cleared`);
|
|
1269
1005
|
}
|
|
1270
1006
|
}
|
|
1271
|
-
// Stop matter server nodes
|
|
1272
1007
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
1273
1008
|
if (timeout > 0) {
|
|
1274
1009
|
this.log.debug(`Waiting ${timeout}ms for the MessageExchange to finish...`);
|
|
@@ -1295,7 +1030,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1295
1030
|
}
|
|
1296
1031
|
}
|
|
1297
1032
|
this.log.notice('Stopped matter server nodes');
|
|
1298
|
-
// Matter commisioning reset
|
|
1299
1033
|
if (message === 'shutting down with reset...') {
|
|
1300
1034
|
this.log.info('Resetting Matterbridge commissioning information...');
|
|
1301
1035
|
await this.matterStorageManager?.createContext('events')?.clearAll();
|
|
@@ -1305,7 +1039,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1305
1039
|
await this.matterbridgeContext?.clearAll();
|
|
1306
1040
|
this.log.info('Matter storage reset done! Remove the bridge from the controller.');
|
|
1307
1041
|
}
|
|
1308
|
-
// Unregister all devices
|
|
1309
1042
|
if (message === 'unregistered all devices and shutting down...') {
|
|
1310
1043
|
if (this.bridgeMode === 'bridge') {
|
|
1311
1044
|
await this.matterStorageManager?.createContext('root')?.createContext('parts')?.createContext('Matterbridge')?.createContext('parts')?.clearAll();
|
|
@@ -1323,29 +1056,15 @@ export class Matterbridge extends EventEmitter {
|
|
|
1323
1056
|
}
|
|
1324
1057
|
this.log.info('Matter storage reset done!');
|
|
1325
1058
|
}
|
|
1326
|
-
// Stop matter storage
|
|
1327
1059
|
await this.stopMatterStorage();
|
|
1328
|
-
// Stop the frontend
|
|
1329
1060
|
await this.frontend.stop();
|
|
1330
|
-
|
|
1061
|
+
this.frontend.destroy();
|
|
1062
|
+
this.plugins.destroy();
|
|
1063
|
+
this.devices.destroy();
|
|
1331
1064
|
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
1065
|
this.log.debug(`Closing node storage context for ${plg}Matterbridge${db}...`);
|
|
1346
1066
|
await this.nodeContext.close();
|
|
1347
1067
|
this.nodeContext = undefined;
|
|
1348
|
-
// Clear nodeContext for each plugin (they just need 1000ms to write the data to disk)
|
|
1349
1068
|
for (const plugin of this.plugins) {
|
|
1350
1069
|
if (plugin.nodeContext) {
|
|
1351
1070
|
this.log.debug(`Closing node storage context for plugin ${plg}${plugin.name}${db}...`);
|
|
@@ -1362,10 +1081,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1362
1081
|
}
|
|
1363
1082
|
this.plugins.clear();
|
|
1364
1083
|
this.devices.clear();
|
|
1365
|
-
// Factory reset
|
|
1366
1084
|
if (message === 'shutting down with factory reset...') {
|
|
1367
1085
|
try {
|
|
1368
|
-
// Delete matter storage directory with its subdirectories and backup
|
|
1369
1086
|
const dir = path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME);
|
|
1370
1087
|
this.log.info(`Removing matter storage directory: ${dir}`);
|
|
1371
1088
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1374,13 +1091,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
1374
1091
|
await fs.rm(backup, { recursive: true });
|
|
1375
1092
|
}
|
|
1376
1093
|
catch (error) {
|
|
1377
|
-
// istanbul ignore next if
|
|
1378
1094
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1379
1095
|
this.log.error(`Error removing matter storage directory: ${error}`);
|
|
1380
1096
|
}
|
|
1381
1097
|
}
|
|
1382
1098
|
try {
|
|
1383
|
-
// Delete matterbridge storage directory with its subdirectories and backup
|
|
1384
1099
|
const dir = path.join(this.matterbridgeDirectory, NODE_STORAGE_DIR);
|
|
1385
1100
|
this.log.info(`Removing matterbridge storage directory: ${dir}`);
|
|
1386
1101
|
await fs.rm(dir, { recursive: true });
|
|
@@ -1389,20 +1104,18 @@ export class Matterbridge extends EventEmitter {
|
|
|
1389
1104
|
await fs.rm(backup, { recursive: true });
|
|
1390
1105
|
}
|
|
1391
1106
|
catch (error) {
|
|
1392
|
-
// istanbul ignore next if
|
|
1393
1107
|
if (error instanceof Error && error.code !== 'ENOENT') {
|
|
1394
1108
|
this.log.error(`Error removing matterbridge storage directory: ${error}`);
|
|
1395
1109
|
}
|
|
1396
1110
|
}
|
|
1397
1111
|
this.log.info('Factory reset done! Remove all paired fabrics from the controllers.');
|
|
1398
1112
|
}
|
|
1399
|
-
// Deregisters the process handlers
|
|
1400
1113
|
this.deregisterProcessHandlers();
|
|
1401
1114
|
if (restart) {
|
|
1402
1115
|
if (message === 'updating...') {
|
|
1403
1116
|
this.log.info('Cleanup completed. Updating...');
|
|
1404
1117
|
Matterbridge.instance = undefined;
|
|
1405
|
-
this.emit('update');
|
|
1118
|
+
this.emit('update');
|
|
1406
1119
|
}
|
|
1407
1120
|
else if (message === 'restarting...') {
|
|
1408
1121
|
this.log.info('Cleanup completed. Restarting...');
|
|
@@ -1426,14 +1139,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1426
1139
|
this.log.debug('Cleanup already started...');
|
|
1427
1140
|
}
|
|
1428
1141
|
}
|
|
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
1142
|
async startBridge() {
|
|
1436
|
-
// Plugins are configured by a timer when matter server is started and plugin.configured is set to true
|
|
1437
1143
|
if (!this.matterStorageManager)
|
|
1438
1144
|
throw new Error('No storage manager initialized');
|
|
1439
1145
|
if (!this.matterbridgeContext)
|
|
@@ -1472,16 +1178,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1472
1178
|
clearInterval(this.startMatterInterval);
|
|
1473
1179
|
this.startMatterInterval = undefined;
|
|
1474
1180
|
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'
|
|
1181
|
+
this.startServerNode(this.serverNode);
|
|
1478
1182
|
for (const device of this.devices.array()) {
|
|
1479
1183
|
if (device.mode === 'server' && device.serverNode) {
|
|
1480
1184
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1481
|
-
this.startServerNode(device.serverNode);
|
|
1185
|
+
this.startServerNode(device.serverNode);
|
|
1482
1186
|
}
|
|
1483
1187
|
}
|
|
1484
|
-
// Configure the plugins
|
|
1485
1188
|
this.configureTimeout = setTimeout(async () => {
|
|
1486
1189
|
for (const plugin of this.plugins.array()) {
|
|
1487
1190
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1499,40 +1202,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1499
1202
|
}
|
|
1500
1203
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1501
1204
|
}, 30 * 1000).unref();
|
|
1502
|
-
// Setting reachability to true
|
|
1503
1205
|
this.reachabilityTimeout = setTimeout(() => {
|
|
1504
1206
|
this.log.info(`Setting reachability to true for ${plg}Matterbridge${db}`);
|
|
1505
1207
|
if (this.aggregatorNode)
|
|
1506
1208
|
this.setAggregatorReachability(this.aggregatorNode, true);
|
|
1507
1209
|
}, 60 * 1000).unref();
|
|
1508
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1509
1210
|
this.emit('bridge_started');
|
|
1510
1211
|
this.log.notice('Matterbridge bridge started successfully');
|
|
1511
1212
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1512
1213
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1513
1214
|
}, this.startMatterIntervalMs);
|
|
1514
1215
|
}
|
|
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
1216
|
async startChildbridge(delay = 1000) {
|
|
1523
1217
|
if (!this.matterStorageManager)
|
|
1524
1218
|
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
1219
|
this.log.debug('Loading all plugins in childbridge mode...');
|
|
1527
1220
|
await this.startPlugins(true, false);
|
|
1528
|
-
// Create server nodes for DynamicPlatform plugins and start all plugins in the background
|
|
1529
1221
|
this.log.debug('Creating server nodes for DynamicPlatform plugins and starting all plugins in childbridge mode...');
|
|
1530
1222
|
for (const plugin of this.plugins.array().filter((p) => p.enabled && !p.error)) {
|
|
1531
1223
|
if (plugin.type === 'DynamicPlatform')
|
|
1532
1224
|
await this.createDynamicPlugin(plugin);
|
|
1533
|
-
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1225
|
+
this.plugins.start(plugin, 'Matterbridge is starting');
|
|
1534
1226
|
}
|
|
1535
|
-
// Start the Matterbridge in childbridge mode when all plugins are loaded and started
|
|
1536
1227
|
this.log.debug('Starting start matter interval in childbridge mode...');
|
|
1537
1228
|
let failCount = 0;
|
|
1538
1229
|
this.startMatterInterval = setInterval(async () => {
|
|
@@ -1566,9 +1257,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1566
1257
|
clearInterval(this.startMatterInterval);
|
|
1567
1258
|
this.startMatterInterval = undefined;
|
|
1568
1259
|
if (delay > 0)
|
|
1569
|
-
await wait(delay);
|
|
1260
|
+
await wait(delay);
|
|
1570
1261
|
this.log.debug('Cleared startMatterInterval interval in childbridge mode');
|
|
1571
|
-
// Configure the plugins
|
|
1572
1262
|
this.configureTimeout = setTimeout(async () => {
|
|
1573
1263
|
for (const plugin of this.plugins.array()) {
|
|
1574
1264
|
if (!plugin.enabled || !plugin.loaded || !plugin.started || plugin.error)
|
|
@@ -1593,7 +1283,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1593
1283
|
this.log.error(`Plugin ${plg}${plugin.name}${er} didn't register any devices to Matterbridge. Verify the plugin configuration.`);
|
|
1594
1284
|
continue;
|
|
1595
1285
|
}
|
|
1596
|
-
// istanbul ignore next if cause is just a safety check
|
|
1597
1286
|
if (!plugin.serverNode) {
|
|
1598
1287
|
this.log.error(`Server node not found for plugin ${plg}${plugin.name}${er}`);
|
|
1599
1288
|
continue;
|
|
@@ -1606,252 +1295,28 @@ export class Matterbridge extends EventEmitter {
|
|
|
1606
1295
|
this.log.error(`Node storage context not found for plugin ${plg}${plugin.name}${er}`);
|
|
1607
1296
|
continue;
|
|
1608
1297
|
}
|
|
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
|
|
1298
|
+
this.startServerNode(plugin.serverNode);
|
|
1612
1299
|
plugin.reachabilityTimeout = setTimeout(() => {
|
|
1613
1300
|
this.log.info(`Setting reachability to true for ${plg}${plugin.name}${nf}`);
|
|
1614
1301
|
if (plugin.type === 'DynamicPlatform' && plugin.aggregatorNode)
|
|
1615
1302
|
this.setAggregatorReachability(plugin.aggregatorNode, true);
|
|
1616
1303
|
}, 60 * 1000).unref();
|
|
1617
1304
|
}
|
|
1618
|
-
// Start the Matter server node of single devices in mode 'server'
|
|
1619
1305
|
for (const device of this.devices.array()) {
|
|
1620
1306
|
if (device.mode === 'server' && device.serverNode) {
|
|
1621
1307
|
this.log.debug(`Starting server node for device ${dev}${device.deviceName}${db} in server mode...`);
|
|
1622
|
-
this.startServerNode(device.serverNode);
|
|
1308
|
+
this.startServerNode(device.serverNode);
|
|
1623
1309
|
}
|
|
1624
1310
|
}
|
|
1625
|
-
// Logger.get('LogServerNode').info(this.serverNode);
|
|
1626
1311
|
this.emit('childbridge_started');
|
|
1627
1312
|
this.log.notice('Matterbridge childbridge started successfully');
|
|
1628
1313
|
this.frontend.wssSendRefreshRequired('settings');
|
|
1629
1314
|
this.frontend.wssSendRefreshRequired('plugins');
|
|
1630
1315
|
}, this.startMatterIntervalMs);
|
|
1631
1316
|
}
|
|
1632
|
-
/**
|
|
1633
|
-
* Starts the Matterbridge controller.
|
|
1634
|
-
*
|
|
1635
|
-
* @private
|
|
1636
|
-
* @returns {Promise<void>} A promise that resolves when the Matterbridge is started.
|
|
1637
|
-
*/
|
|
1638
1317
|
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
1318
|
}
|
|
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
1319
|
async startMatterStorage() {
|
|
1854
|
-
// Setup Matter storage
|
|
1855
1320
|
this.log.info(`Starting matter node storage...`);
|
|
1856
1321
|
this.matterStorageService = this.environment.get(StorageService);
|
|
1857
1322
|
this.log.info(`Matter node storage service created: ${this.matterStorageService.location}`);
|
|
@@ -1859,17 +1324,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
1859
1324
|
this.log.info('Matter node storage manager "Matterbridge" created');
|
|
1860
1325
|
this.matterbridgeContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
1861
1326
|
this.log.info('Matter node storage started');
|
|
1862
|
-
// Backup matter storage since it is created/opened correctly
|
|
1863
1327
|
await this.backupMatterStorage(path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
1864
1328
|
}
|
|
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
1329
|
async backupMatterStorage(storageName, backupName) {
|
|
1874
1330
|
this.log.info('Creating matter node storage backup...');
|
|
1875
1331
|
try {
|
|
@@ -1880,11 +1336,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1880
1336
|
this.log.error(`Error creating matter node storage backup from ${storageName} to ${backupName}:`, error);
|
|
1881
1337
|
}
|
|
1882
1338
|
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Stops the matter storage.
|
|
1885
|
-
*
|
|
1886
|
-
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
1887
|
-
*/
|
|
1888
1339
|
async stopMatterStorage() {
|
|
1889
1340
|
this.log.info('Closing matter node storage...');
|
|
1890
1341
|
await this.matterStorageManager?.close();
|
|
@@ -1893,20 +1344,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1893
1344
|
this.matterbridgeContext = undefined;
|
|
1894
1345
|
this.log.info('Matter node storage closed');
|
|
1895
1346
|
}
|
|
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
1347
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
1911
1348
|
const { randomBytes } = await import('node:crypto');
|
|
1912
1349
|
if (!this.matterStorageService)
|
|
@@ -1946,15 +1383,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
1946
1383
|
this.log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
1947
1384
|
return storageContext;
|
|
1948
1385
|
}
|
|
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
1386
|
async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
|
|
1959
1387
|
const storeId = await storageContext.get('storeId');
|
|
1960
1388
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
@@ -1964,37 +1392,24 @@ export class Matterbridge extends EventEmitter {
|
|
|
1964
1392
|
this.log.debug(`- uniqueId: ${await storageContext.get('uniqueId')}`);
|
|
1965
1393
|
this.log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
|
|
1966
1394
|
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
1395
|
const serverNode = await ServerNode.create({
|
|
1971
|
-
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
1972
1396
|
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
1397
|
network: {
|
|
1976
|
-
listeningAddressIpv4: this.
|
|
1977
|
-
listeningAddressIpv6: this.
|
|
1398
|
+
listeningAddressIpv4: this.ipv4Address,
|
|
1399
|
+
listeningAddressIpv6: this.ipv6Address,
|
|
1978
1400
|
port,
|
|
1979
1401
|
},
|
|
1980
|
-
// Provide the certificate for the device
|
|
1981
1402
|
operationalCredentials: {
|
|
1982
1403
|
certification: this.certification,
|
|
1983
1404
|
},
|
|
1984
|
-
// Provide Commissioning relevant settings
|
|
1985
|
-
// Optional for development/testing purposes
|
|
1986
1405
|
commissioning: {
|
|
1987
1406
|
passcode,
|
|
1988
1407
|
discriminator,
|
|
1989
1408
|
},
|
|
1990
|
-
// Provide Node announcement settings
|
|
1991
|
-
// Optional: If Ommitted some development defaults are used
|
|
1992
1409
|
productDescription: {
|
|
1993
1410
|
name: await storageContext.get('deviceName'),
|
|
1994
1411
|
deviceType: DeviceTypeId(await storageContext.get('deviceType')),
|
|
1995
1412
|
},
|
|
1996
|
-
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
1997
|
-
// Optional: If Omitted some development defaults are used
|
|
1998
1413
|
basicInformation: {
|
|
1999
1414
|
vendorId: VendorId(await storageContext.get('vendorId')),
|
|
2000
1415
|
vendorName: await storageContext.get('vendorName'),
|
|
@@ -2011,23 +1426,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2011
1426
|
reachable: true,
|
|
2012
1427
|
},
|
|
2013
1428
|
});
|
|
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
1429
|
serverNode.lifecycle.commissioned.on(() => {
|
|
2019
1430
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
2020
1431
|
this.advertisingNodes.delete(storeId);
|
|
2021
1432
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2022
1433
|
});
|
|
2023
|
-
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
2024
1434
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
2025
1435
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
2026
1436
|
this.advertisingNodes.delete(storeId);
|
|
2027
1437
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2028
1438
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2029
1439
|
});
|
|
2030
|
-
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
2031
1440
|
serverNode.lifecycle.online.on(async () => {
|
|
2032
1441
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
2033
1442
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -2038,16 +1447,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2038
1447
|
this.log.notice(`Manual pairing code: ${manualPairingCode}`);
|
|
2039
1448
|
}
|
|
2040
1449
|
else {
|
|
2041
|
-
// istanbul ignore next
|
|
2042
1450
|
this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
|
|
2043
|
-
// istanbul ignore next
|
|
2044
1451
|
this.advertisingNodes.delete(storeId);
|
|
2045
1452
|
}
|
|
2046
1453
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2047
1454
|
this.frontend.wssSendSnackbarMessage(`${storeId} is online`, 5, 'success');
|
|
2048
1455
|
this.emit('online', storeId);
|
|
2049
1456
|
});
|
|
2050
|
-
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
2051
1457
|
serverNode.lifecycle.offline.on(() => {
|
|
2052
1458
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
2053
1459
|
this.advertisingNodes.delete(storeId);
|
|
@@ -2055,15 +1461,11 @@ export class Matterbridge extends EventEmitter {
|
|
|
2055
1461
|
this.frontend.wssSendSnackbarMessage(`${storeId} is offline`, 5, 'warning');
|
|
2056
1462
|
this.emit('offline', storeId);
|
|
2057
1463
|
});
|
|
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
1464
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
2063
1465
|
let action = '';
|
|
2064
1466
|
switch (fabricAction) {
|
|
2065
1467
|
case FabricAction.Added:
|
|
2066
|
-
this.advertisingNodes.delete(storeId);
|
|
1468
|
+
this.advertisingNodes.delete(storeId);
|
|
2067
1469
|
action = 'added';
|
|
2068
1470
|
break;
|
|
2069
1471
|
case FabricAction.Removed:
|
|
@@ -2076,22 +1478,14 @@ export class Matterbridge extends EventEmitter {
|
|
|
2076
1478
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
2077
1479
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2078
1480
|
});
|
|
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
1481
|
serverNode.events.sessions.opened.on((session) => {
|
|
2084
1482
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2085
1483
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2086
1484
|
});
|
|
2087
|
-
/**
|
|
2088
|
-
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
2089
|
-
*/
|
|
2090
1485
|
serverNode.events.sessions.closed.on((session) => {
|
|
2091
1486
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2092
1487
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
2093
1488
|
});
|
|
2094
|
-
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
2095
1489
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
2096
1490
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
2097
1491
|
this.frontend.wssSendRefreshRequired('matter', { matter: { ...this.getServerNodeData(serverNode) } });
|
|
@@ -2099,12 +1493,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2099
1493
|
this.log.info(`Created server node for ${storeId}`);
|
|
2100
1494
|
return serverNode;
|
|
2101
1495
|
}
|
|
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
1496
|
getServerNodeData(serverNode) {
|
|
2109
1497
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
2110
1498
|
return {
|
|
@@ -2121,25 +1509,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2121
1509
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
2122
1510
|
};
|
|
2123
1511
|
}
|
|
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
1512
|
async startServerNode(matterServerNode) {
|
|
2131
1513
|
if (!matterServerNode)
|
|
2132
1514
|
return;
|
|
2133
1515
|
this.log.notice(`Starting ${matterServerNode.id} server node`);
|
|
2134
1516
|
await matterServerNode.start();
|
|
2135
1517
|
}
|
|
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
1518
|
async stopServerNode(matterServerNode, timeout = 30000) {
|
|
2144
1519
|
if (!matterServerNode)
|
|
2145
1520
|
return;
|
|
@@ -2152,25 +1527,12 @@ export class Matterbridge extends EventEmitter {
|
|
|
2152
1527
|
this.log.error(`Failed to close ${matterServerNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
2153
1528
|
}
|
|
2154
1529
|
}
|
|
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
1530
|
async createAggregatorNode(storageContext) {
|
|
2162
1531
|
this.log.notice(`Creating ${await storageContext.get('storeId')} aggregator...`);
|
|
2163
1532
|
const aggregatorNode = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')}` });
|
|
2164
1533
|
this.log.info(`Created ${await storageContext.get('storeId')} aggregator`);
|
|
2165
1534
|
return aggregatorNode;
|
|
2166
1535
|
}
|
|
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
1536
|
async createAccessoryPlugin(plugin, device) {
|
|
2175
1537
|
if (!plugin.locked && device.deviceType && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
|
|
2176
1538
|
plugin.locked = true;
|
|
@@ -2182,12 +1544,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2182
1544
|
await plugin.serverNode.add(device);
|
|
2183
1545
|
}
|
|
2184
1546
|
}
|
|
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
1547
|
async createDynamicPlugin(plugin) {
|
|
2192
1548
|
if (!plugin.locked) {
|
|
2193
1549
|
plugin.locked = true;
|
|
@@ -2200,13 +1556,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2200
1556
|
await plugin.serverNode.add(plugin.aggregatorNode);
|
|
2201
1557
|
}
|
|
2202
1558
|
}
|
|
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
1559
|
async createDeviceServerNode(plugin, device) {
|
|
2211
1560
|
if (device.mode === 'server' && !device.serverNode && device.deviceType && device.deviceName && device.vendorId && device.vendorName && device.productId && device.productName) {
|
|
2212
1561
|
this.log.debug(`Creating device ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} server node...`);
|
|
@@ -2217,15 +1566,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2217
1566
|
this.log.debug(`Added ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db} to server node`);
|
|
2218
1567
|
}
|
|
2219
1568
|
}
|
|
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
1569
|
async addBridgedEndpoint(pluginName, device) {
|
|
2228
|
-
// Check if the plugin is registered
|
|
2229
1570
|
const plugin = this.plugins.get(pluginName);
|
|
2230
1571
|
if (!plugin) {
|
|
2231
1572
|
this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
|
|
@@ -2245,7 +1586,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2245
1586
|
}
|
|
2246
1587
|
else if (this.bridgeMode === 'bridge') {
|
|
2247
1588
|
if (device.mode === 'matter') {
|
|
2248
|
-
// Register and add the device to the matterbridge server node
|
|
2249
1589
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge server node...`);
|
|
2250
1590
|
if (!this.serverNode) {
|
|
2251
1591
|
this.log.error('Server node not found for Matterbridge');
|
|
@@ -2262,7 +1602,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2262
1602
|
}
|
|
2263
1603
|
}
|
|
2264
1604
|
else {
|
|
2265
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2266
1605
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} to Matterbridge aggregator node`);
|
|
2267
1606
|
if (!this.aggregatorNode) {
|
|
2268
1607
|
this.log.error('Aggregator node not found for Matterbridge');
|
|
@@ -2280,7 +1619,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2280
1619
|
}
|
|
2281
1620
|
}
|
|
2282
1621
|
else if (this.bridgeMode === 'childbridge') {
|
|
2283
|
-
// Register and add the device to the plugin server node
|
|
2284
1622
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2285
1623
|
try {
|
|
2286
1624
|
this.log.debug(`Creating endpoint ${dev}${device.deviceName}${db} for AccessoryPlatform plugin ${plg}${plugin.name}${db} server node`);
|
|
@@ -2304,12 +1642,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
2304
1642
|
return;
|
|
2305
1643
|
}
|
|
2306
1644
|
}
|
|
2307
|
-
// Register and add the device to the plugin aggregator node
|
|
2308
1645
|
if (plugin.type === 'DynamicPlatform') {
|
|
2309
1646
|
try {
|
|
2310
1647
|
this.log.debug(`Adding bridged endpoint ${dev}${device.deviceName}${db} for DynamicPlatform plugin ${plg}${plugin.name}${db} aggregator node`);
|
|
2311
1648
|
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
1649
|
await waiter(`createDynamicPlugin(${plugin.name})`, () => plugin.serverNode?.hasParts === true);
|
|
2314
1650
|
if (!plugin.aggregatorNode) {
|
|
2315
1651
|
this.log.error(`Aggregator node not found for plugin ${plg}${plugin.name}${er}`);
|
|
@@ -2330,28 +1666,17 @@ export class Matterbridge extends EventEmitter {
|
|
|
2330
1666
|
}
|
|
2331
1667
|
if (plugin.registeredDevices !== undefined)
|
|
2332
1668
|
plugin.registeredDevices++;
|
|
2333
|
-
// Add the device to the DeviceManager
|
|
2334
1669
|
this.devices.set(device);
|
|
2335
|
-
// Subscribe to the attributes changed event
|
|
2336
1670
|
await this.subscribeAttributeChanged(plugin, device);
|
|
2337
1671
|
this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
|
|
2338
1672
|
}
|
|
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
1673
|
async removeBridgedEndpoint(pluginName, device) {
|
|
2347
1674
|
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
1675
|
const plugin = this.plugins.get(pluginName);
|
|
2350
1676
|
if (!plugin) {
|
|
2351
1677
|
this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
2352
1678
|
return;
|
|
2353
1679
|
}
|
|
2354
|
-
// Register and add the device to the matterbridge aggregator node
|
|
2355
1680
|
if (this.bridgeMode === 'bridge') {
|
|
2356
1681
|
if (!this.aggregatorNode) {
|
|
2357
1682
|
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 +1689,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2364
1689
|
}
|
|
2365
1690
|
else if (this.bridgeMode === 'childbridge') {
|
|
2366
1691
|
if (plugin.type === 'AccessoryPlatform') {
|
|
2367
|
-
// Nothing to do here since the server node has no aggregator node but only the device itself
|
|
2368
1692
|
}
|
|
2369
1693
|
else if (plugin.type === 'DynamicPlatform') {
|
|
2370
1694
|
if (!plugin.aggregatorNode) {
|
|
@@ -2377,21 +1701,8 @@ export class Matterbridge extends EventEmitter {
|
|
|
2377
1701
|
if (plugin.registeredDevices !== undefined)
|
|
2378
1702
|
plugin.registeredDevices--;
|
|
2379
1703
|
}
|
|
2380
|
-
// Remove the device from the DeviceManager
|
|
2381
1704
|
this.devices.remove(device);
|
|
2382
1705
|
}
|
|
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
1706
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
2396
1707
|
this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}${delay > 0 ? ` with delay ${delay} ms` : ''}`);
|
|
2397
1708
|
for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
|
|
@@ -2402,24 +1713,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
2402
1713
|
if (delay > 0)
|
|
2403
1714
|
await wait(2000);
|
|
2404
1715
|
}
|
|
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
1716
|
async subscribeAttributeChanged(plugin, device) {
|
|
2415
1717
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
2416
1718
|
return;
|
|
2417
1719
|
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
1720
|
if (this.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && plugin.serverNode) {
|
|
2420
1721
|
plugin.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
2421
1722
|
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
1723
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, 'BasicInformation', 'reachable', reachable);
|
|
2424
1724
|
});
|
|
2425
1725
|
}
|
|
@@ -2467,7 +1767,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2467
1767
|
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
1768
|
await device.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
2469
1769
|
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
1770
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, device.number, device.id, sub.cluster, sub.attribute, value);
|
|
2472
1771
|
});
|
|
2473
1772
|
}
|
|
@@ -2477,7 +1776,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
2477
1776
|
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...`);
|
|
2478
1777
|
await child.subscribeAttribute(sub.cluster, sub.attribute, (value) => {
|
|
2479
1778
|
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}`);
|
|
2480
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2481
1779
|
this.frontend.wssSendAttributeChangedMessage(device.plugin, device.serialNumber, device.uniqueId, child.number, child.id, sub.cluster, sub.attribute, value);
|
|
2482
1780
|
});
|
|
2483
1781
|
}
|
|
@@ -2485,12 +1783,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
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
|