matterbridge 3.5.0-dev-20260119-f9ea00e → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -117
- package/dist/broadcastServer.d.ts +115 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +117 -0
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +43 -0
- package/dist/broadcastServerTypes.d.ts.map +1 -0
- package/dist/broadcastServerTypes.js +24 -0
- package/dist/broadcastServerTypes.js.map +1 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -1
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +36 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +37 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +42 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +38 -0
- package/dist/cliHistory.js.map +1 -0
- package/dist/clusters/export.d.ts +1 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/deviceManager.d.ts +108 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +113 -1
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +75 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +43 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +55 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +56 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +55 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +57 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +1 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +41 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +43 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +43 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +58 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +64 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +77 -1
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +82 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +100 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +83 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +36 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +79 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +21 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +24 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +74 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +171 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +99 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +23 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +23 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -24
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +187 -4
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +371 -139
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +49 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +53 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +60 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +187 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +498 -37
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +57 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.js.map +1 -0
- package/dist/helpers.d.ts +43 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/jestutils/export.d.ts +1 -0
- package/dist/jestutils/export.d.ts.map +1 -0
- package/dist/jestutils/export.js +1 -0
- package/dist/jestutils/export.js.map +1 -0
- package/dist/jestutils/jestHelpers.d.ts +255 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +372 -14
- package/dist/jestutils/jestHelpers.js.map +1 -0
- package/dist/logger/export.d.ts +1 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +1 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +1 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +1 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +1 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +1 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +2 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +1 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +2 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterNode.d.ts +258 -0
- package/dist/matterNode.d.ts.map +1 -0
- package/dist/matterNode.js +359 -8
- package/dist/matterNode.js.map +1 -0
- package/dist/matterbridge.d.ts +362 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +842 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +36 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +38 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +24 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +649 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +673 -6
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +36 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +38 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1332 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1457 -53
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +425 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +483 -20
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgeEndpointTypes.d.ts +70 -0
- package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
- package/dist/matterbridgeEndpointTypes.js +25 -0
- package/dist/matterbridgeEndpointTypes.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +425 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +451 -1
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/matterbridgeTypes.d.ts +46 -0
- package/dist/matterbridgeTypes.d.ts.map +1 -0
- package/dist/matterbridgeTypes.js +26 -0
- package/dist/matterbridgeTypes.js.map +1 -0
- package/dist/pluginManager.d.ts +305 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +341 -5
- package/dist/pluginManager.js.map +1 -0
- package/dist/shelly.d.ts +157 -0
- package/dist/shelly.d.ts.map +1 -0
- package/dist/shelly.js +178 -7
- package/dist/shelly.js.map +1 -0
- package/dist/storage/export.d.ts +1 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +75 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +93 -1
- package/dist/update.js.map +1 -0
- package/dist/utils/colorUtils.d.ts +77 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +60 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +60 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +33 -0
- package/dist/utils/copyDirectory.d.ts.map +1 -0
- package/dist/utils/copyDirectory.js +37 -0
- package/dist/utils/copyDirectory.js.map +1 -0
- package/dist/utils/createDirectory.d.ts +32 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +38 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +31 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +53 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +42 -0
- package/dist/utils/error.d.ts.map +1 -0
- package/dist/utils/error.js +42 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/export.d.ts +1 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/format.d.ts +49 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +49 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hex.d.ts +85 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/inspector.d.ts +63 -0
- package/dist/utils/inspector.d.ts.map +1 -0
- package/dist/utils/inspector.js +69 -1
- package/dist/utils/inspector.js.map +1 -0
- package/dist/utils/isValid.d.ts +93 -0
- package/dist/utils/isValid.d.ts.map +1 -0
- package/dist/utils/isValid.js +93 -0
- package/dist/utils/isValid.js.map +1 -0
- package/dist/utils/network.d.ts +116 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +126 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +32 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +71 -1
- package/dist/utils/spawn.js.map +1 -0
- package/dist/utils/tracker.d.ts +56 -0
- package/dist/utils/tracker.d.ts.map +1 -0
- package/dist/utils/tracker.js +64 -1
- package/dist/utils/tracker.js.map +1 -0
- package/dist/utils/wait.d.ts +51 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/dist/workerGlobalPrefix.d.ts +24 -0
- package/dist/workerGlobalPrefix.d.ts.map +1 -0
- package/dist/workerGlobalPrefix.js +37 -5
- package/dist/workerGlobalPrefix.js.map +1 -0
- package/dist/workerTypes.d.ts +25 -0
- package/dist/workerTypes.d.ts.map +1 -0
- package/dist/workerTypes.js +24 -0
- package/dist/workerTypes.js.map +1 -0
- package/dist/workers.d.ts +61 -0
- package/dist/workers.d.ts.map +1 -0
- package/dist/workers.js +68 -4
- package/dist/workers.js.map +1 -0
- package/frontend/build/assets/index.js +4 -4
- package/frontend/package.json +1 -1
- package/npm-shrinkwrap.json +5 -35
- package/package.json +7 -7
package/dist/matterNode.js
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the class MatterNode.
|
|
3
|
+
*
|
|
4
|
+
* @file matterNode.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2025-10-01
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 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 modules
|
|
1
25
|
import path from 'node:path';
|
|
2
26
|
import fs from 'node:fs';
|
|
3
27
|
import EventEmitter from 'node:events';
|
|
28
|
+
// AnsiLogger module
|
|
4
29
|
import { AnsiLogger, BLUE, CYAN, db, debugStringify, er, nf, or, zb } from 'node-ansi-logger';
|
|
30
|
+
// Node persist manager module
|
|
5
31
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
32
|
+
// @matter
|
|
6
33
|
import '@matter/nodejs';
|
|
7
34
|
import { Logger, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, StorageService, UINT32_MAX, UINT16_MAX, Environment } from '@matter/general';
|
|
8
35
|
import { MdnsService } from '@matter/protocol';
|
|
@@ -22,27 +49,48 @@ import { BroadcastServer } from './broadcastServer.js';
|
|
|
22
49
|
import { toBaseDevice } from './deviceManager.js';
|
|
23
50
|
import { PluginManager } from './pluginManager.js';
|
|
24
51
|
import { addVirtualDevice } from './helpers.js';
|
|
52
|
+
/**
|
|
53
|
+
* Represents the Matter class.
|
|
54
|
+
*/
|
|
25
55
|
export class MatterNode extends EventEmitter {
|
|
26
56
|
matterbridge;
|
|
27
57
|
pluginName;
|
|
28
58
|
device;
|
|
29
|
-
|
|
30
|
-
|
|
59
|
+
/** Matterbridge logger */
|
|
60
|
+
log = new AnsiLogger({ logName: 'MatterNode', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
61
|
+
/** Matter logger */
|
|
62
|
+
matterLog = new AnsiLogger({ logName: 'MatterNode', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: "debug" /* LogLevel.DEBUG */ });
|
|
63
|
+
/** Matter environment default */
|
|
31
64
|
environment = Environment.default;
|
|
65
|
+
/** Matter storage id */
|
|
32
66
|
storeId;
|
|
67
|
+
/** Matter mdns service from environment default */
|
|
33
68
|
matterMdnsService;
|
|
69
|
+
/** Matter storage service from environment default */
|
|
34
70
|
matterStorageService;
|
|
71
|
+
/** Matter storage manager created with name 'Matterbridge' */
|
|
35
72
|
matterStorageManager;
|
|
73
|
+
/** Matter storage context created in the storage manager with name 'persist' */
|
|
36
74
|
matterStorageContext;
|
|
75
|
+
/** Matter mdns interface name e.g. 'eth0' or 'wlan0' or 'Wi-Fi' */
|
|
37
76
|
mdnsInterface;
|
|
77
|
+
/** Matter listeningAddressIpv4 address */
|
|
38
78
|
ipv4Address;
|
|
79
|
+
/** Matter listeningAddressIpv6 address */
|
|
39
80
|
ipv6Address;
|
|
81
|
+
/** Matter commissioning port It is incremented in childbridge mode. */
|
|
40
82
|
port;
|
|
83
|
+
/** Matter commissioning passcode. It is incremented in childbridge mode. */
|
|
41
84
|
passcode;
|
|
85
|
+
/** Matter commissioning discriminator. It is incremented in childbridge mode. */
|
|
42
86
|
discriminator;
|
|
87
|
+
/** Matter device certification */
|
|
43
88
|
certification;
|
|
89
|
+
/** Matter server node */
|
|
44
90
|
serverNode;
|
|
91
|
+
/** Matter aggregator node */
|
|
45
92
|
aggregatorNode;
|
|
93
|
+
// Default values for the aggregator node
|
|
46
94
|
aggregatorVendorId = VendorId(getIntParameter('vendorId') ?? 0xfff1);
|
|
47
95
|
aggregatorVendorName = getParameter('vendorName') ?? 'Matterbridge';
|
|
48
96
|
aggregatorProductId = getIntParameter('productId') ?? 0x8000;
|
|
@@ -50,12 +98,23 @@ export class MatterNode extends EventEmitter {
|
|
|
50
98
|
aggregatorDeviceType = DeviceTypeId(getIntParameter('deviceType') ?? bridge.code);
|
|
51
99
|
aggregatorSerialNumber = getParameter('serialNumber');
|
|
52
100
|
aggregatorUniqueId = getParameter('uniqueId');
|
|
101
|
+
/** Advertising nodes map: time advertising started keyed by storeId */
|
|
53
102
|
advertisingNodes = new Map();
|
|
103
|
+
/** Plugins */
|
|
54
104
|
pluginManager;
|
|
105
|
+
/** Dependant MatterNodes keyed by device id */
|
|
55
106
|
dependantMatterNodes = new Map();
|
|
107
|
+
/** Broadcast server */
|
|
56
108
|
server;
|
|
57
109
|
debug = hasParameter('debug') || hasParameter('verbose');
|
|
58
110
|
verbose = hasParameter('verbose');
|
|
111
|
+
/**
|
|
112
|
+
* Creates an instance of the Matter class.
|
|
113
|
+
*
|
|
114
|
+
* @param {SharedMatterbridge} matterbridge - The shared matterbridge instance.
|
|
115
|
+
* @param {PluginName} [pluginName] - The plugin name (optional). If not provided, it is assumed to be the main matter node instance and all plugins are included.
|
|
116
|
+
* @param {MatterbridgeEndpoint} [device] - The matterbridge endpoint device (optional). It is used to create a server mode device.
|
|
117
|
+
*/
|
|
59
118
|
constructor(matterbridge, pluginName, device) {
|
|
60
119
|
super();
|
|
61
120
|
this.matterbridge = matterbridge;
|
|
@@ -64,19 +123,25 @@ export class MatterNode extends EventEmitter {
|
|
|
64
123
|
this.log.logNameColor = '\x1b[38;5;65m';
|
|
65
124
|
if (this.debug)
|
|
66
125
|
this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loading...`);
|
|
126
|
+
// Setup Matter parameters
|
|
67
127
|
this.port = matterbridge.port;
|
|
68
128
|
this.passcode = matterbridge.passcode;
|
|
69
129
|
this.discriminator = matterbridge.discriminator;
|
|
130
|
+
// Setup the broadcast server
|
|
70
131
|
this.server = new BroadcastServer('matter', this.log);
|
|
71
132
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
72
133
|
if (this.verbose)
|
|
73
134
|
this.log.debug(`BroadcastServer is ready`);
|
|
135
|
+
// Ensure the matterbridge directory exists
|
|
74
136
|
fs.mkdirSync(matterbridge.matterbridgeDirectory, { recursive: true });
|
|
137
|
+
// Setup the plugin manager with thread server closed
|
|
75
138
|
this.pluginManager = new PluginManager(this.matterbridge);
|
|
76
|
-
this.pluginManager.logLevel = this.debug ? "debug" : "info"
|
|
139
|
+
this.pluginManager.logLevel = this.debug ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */;
|
|
140
|
+
// @ts-expect-error access private property
|
|
77
141
|
this.pluginManager.server.close();
|
|
78
142
|
if (this.verbose)
|
|
79
143
|
this.log.debug(`PluginManager is ready`);
|
|
144
|
+
// Setup the matter environment
|
|
80
145
|
this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
|
|
81
146
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
82
147
|
this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
@@ -84,15 +149,18 @@ export class MatterNode extends EventEmitter {
|
|
|
84
149
|
this.environment.vars.set('runtime.exitcode', false);
|
|
85
150
|
if (this.verbose)
|
|
86
151
|
this.log.debug(`Matter Environment is ready`);
|
|
152
|
+
// Ensure MdnsService is registered in the default environment
|
|
87
153
|
this.matterMdnsService = new MdnsService(this.environment);
|
|
88
154
|
setImmediate(async () => {
|
|
89
155
|
await this.matterMdnsService?.construction.ready;
|
|
90
156
|
if (this.verbose)
|
|
91
157
|
this.log.debug(`Matter MdnsService is ready`);
|
|
92
158
|
});
|
|
159
|
+
// Setup the matterbridge logger
|
|
93
160
|
if (this.matterbridge.fileLogger) {
|
|
94
161
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.logLevel);
|
|
95
162
|
}
|
|
163
|
+
// Setup the matter logger
|
|
96
164
|
Logger.destinations.default.write = this.createDestinationMatterLogger();
|
|
97
165
|
const levels = ['debug', 'info', 'notice', 'warn', 'error', 'fatal'];
|
|
98
166
|
if (this.verbose)
|
|
@@ -100,6 +168,11 @@ export class MatterNode extends EventEmitter {
|
|
|
100
168
|
if (this.debug)
|
|
101
169
|
this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loaded`);
|
|
102
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Handles incoming messages from the broadcast server.
|
|
173
|
+
*
|
|
174
|
+
* @param {WorkerMessage} msg - The incoming message.
|
|
175
|
+
*/
|
|
103
176
|
async msgHandler(msg) {
|
|
104
177
|
if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
|
|
105
178
|
if (this.verbose)
|
|
@@ -130,9 +203,17 @@ export class MatterNode extends EventEmitter {
|
|
|
130
203
|
}
|
|
131
204
|
}
|
|
132
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Destroys the Matter instance.
|
|
208
|
+
* It closes the mDNS service and the broadcast server.
|
|
209
|
+
*
|
|
210
|
+
* @param {boolean} closeMdns - Whether to close the mDNS service. Default is true.
|
|
211
|
+
* @returns {Promise<void>} A promise that resolves when the instance is destroyed.
|
|
212
|
+
*/
|
|
133
213
|
async destroy(closeMdns = true) {
|
|
134
214
|
if (this.verbose)
|
|
135
215
|
this.log.debug(`Destroying MatterNode instance for ${this.storeId}...`);
|
|
216
|
+
// Close mDNS service
|
|
136
217
|
if (closeMdns) {
|
|
137
218
|
if (this.verbose)
|
|
138
219
|
this.log.debug(`Closing Matter MdnsService for ${this.storeId}...`);
|
|
@@ -140,18 +221,26 @@ export class MatterNode extends EventEmitter {
|
|
|
140
221
|
if (this.verbose)
|
|
141
222
|
this.log.debug(`Closed Matter MdnsService for ${this.storeId}`);
|
|
142
223
|
}
|
|
224
|
+
// Close the plugin manager
|
|
143
225
|
this.pluginManager.destroy();
|
|
226
|
+
// Close the broadcast server
|
|
144
227
|
this.server.close();
|
|
228
|
+
// Yield to the Node.js event loop to allow all resources to be released
|
|
145
229
|
await this.yieldToNode();
|
|
146
230
|
if (this.verbose)
|
|
147
231
|
this.log.debug(`Destroyed MatterNode instance for ${this.storeId}`);
|
|
148
232
|
}
|
|
149
233
|
async create() {
|
|
150
234
|
this.log.info('Creating Matter node...');
|
|
235
|
+
// Start matter storage
|
|
151
236
|
await this.startMatterStorage();
|
|
237
|
+
// Load plugins from storage
|
|
238
|
+
// @ts-expect-error access private property
|
|
152
239
|
this.pluginManager.matterbridge.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
240
|
+
// @ts-expect-error access private property
|
|
153
241
|
this.pluginManager.matterbridge.nodeContext = await this.pluginManager.matterbridge.nodeStorage.createStorage('matterbridge');
|
|
154
242
|
await this.pluginManager.loadFromStorage();
|
|
243
|
+
// Create Matter node for a server mode device
|
|
155
244
|
if (this.pluginName && this.device && this.device.deviceName) {
|
|
156
245
|
this.log.debug(`Creating MatterNode instance for server node device ${CYAN}${this.device.deviceName}${db}...`);
|
|
157
246
|
await this.createDeviceServerNode(this.pluginName, this.device);
|
|
@@ -162,6 +251,7 @@ export class MatterNode extends EventEmitter {
|
|
|
162
251
|
if (!this.pluginName) {
|
|
163
252
|
this.log.debug('Creating MatterNode instance for all plugins...');
|
|
164
253
|
await this.createMatterbridgeServerNode();
|
|
254
|
+
// Load all enabled plugins
|
|
165
255
|
this.log.debug('Loading all plugins...');
|
|
166
256
|
const loadPromises = [];
|
|
167
257
|
for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
|
|
@@ -174,6 +264,7 @@ export class MatterNode extends EventEmitter {
|
|
|
174
264
|
}
|
|
175
265
|
else {
|
|
176
266
|
this.log.debug(`Creating MatterNode instance for plugin ${CYAN}${this.pluginName}${db}...`);
|
|
267
|
+
// Load only the specified plugin
|
|
177
268
|
this.log.debug(`Loading plugin ${CYAN}${this.pluginName}${db}...`);
|
|
178
269
|
await this.pluginManager.load(this.pluginName);
|
|
179
270
|
this.log.debug(`Loaded plugin ${CYAN}${this.pluginName}${db}`);
|
|
@@ -187,13 +278,16 @@ export class MatterNode extends EventEmitter {
|
|
|
187
278
|
if (!this.serverNode && !this.pluginName)
|
|
188
279
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
189
280
|
this.log.info('Starting MatterNode...');
|
|
281
|
+
// Start Matter node for a server mode device
|
|
190
282
|
if (this.pluginName && this.device && this.device.deviceName) {
|
|
283
|
+
// Start the server node
|
|
191
284
|
this.log.debug(`Starting MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
|
|
192
285
|
await this.startServerNode();
|
|
193
286
|
this.log.debug(`Started MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
|
|
194
287
|
return;
|
|
195
288
|
}
|
|
196
289
|
if (!this.pluginName) {
|
|
290
|
+
// Start all loaded plugins
|
|
197
291
|
this.log.debug('Starting all plugins...');
|
|
198
292
|
const startPromises = [];
|
|
199
293
|
for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.loaded)) {
|
|
@@ -201,9 +295,11 @@ export class MatterNode extends EventEmitter {
|
|
|
201
295
|
}
|
|
202
296
|
await Promise.all(startPromises);
|
|
203
297
|
this.log.debug('Started all plugins');
|
|
298
|
+
// Start the server node
|
|
204
299
|
this.log.debug('Starting MatterNode for all plugins...');
|
|
205
300
|
await this.startServerNode();
|
|
206
301
|
this.log.debug('Started MatterNode for all plugins');
|
|
302
|
+
// Configure all loaded plugins
|
|
207
303
|
this.log.debug('Configuring all plugins...');
|
|
208
304
|
const configurePromises = [];
|
|
209
305
|
for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.started)) {
|
|
@@ -213,12 +309,16 @@ export class MatterNode extends EventEmitter {
|
|
|
213
309
|
this.log.debug('Configured all plugins');
|
|
214
310
|
}
|
|
215
311
|
else {
|
|
312
|
+
// Start the loaded plugin
|
|
216
313
|
await this.pluginManager.start(this.pluginName, 'Starting MatterNode');
|
|
314
|
+
// Start the server node
|
|
217
315
|
this.log.debug(`Starting MatterNode for plugin ${this.pluginName}...`);
|
|
218
316
|
await this.startServerNode();
|
|
219
317
|
this.log.debug(`Started MatterNode for plugin ${this.pluginName}`);
|
|
318
|
+
// Configure the plugin
|
|
220
319
|
await this.pluginManager.configure(this.pluginName);
|
|
221
320
|
}
|
|
321
|
+
// Start the dependant MatterNodes
|
|
222
322
|
this.log.debug(`Starting dependant MatterNodes...`);
|
|
223
323
|
for (const dependantMatterNode of this.dependantMatterNodes.values()) {
|
|
224
324
|
await dependantMatterNode.start();
|
|
@@ -231,13 +331,15 @@ export class MatterNode extends EventEmitter {
|
|
|
231
331
|
if (!this.serverNode)
|
|
232
332
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
233
333
|
this.log.info('Stopping MatterNode...');
|
|
334
|
+
// Stop Matter node for a server mode device
|
|
234
335
|
if (this.pluginName && this.device && this.device.deviceName) {
|
|
336
|
+
// Stop the server node
|
|
235
337
|
this.log.debug(`Stopping MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
|
|
236
338
|
await this.stopServerNode();
|
|
237
339
|
this.serverNode = undefined;
|
|
238
340
|
this.aggregatorNode = undefined;
|
|
239
341
|
await this.stopMatterStorage();
|
|
240
|
-
await this.destroy(false);
|
|
342
|
+
await this.destroy(false); // Do not close mDNS since it is shared
|
|
241
343
|
this.log.debug(`Stopped MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
|
|
242
344
|
this.log.info('Stopped MatterNode');
|
|
243
345
|
await this.yieldToNode();
|
|
@@ -257,6 +359,7 @@ export class MatterNode extends EventEmitter {
|
|
|
257
359
|
await this.pluginManager.shutdown(this.pluginName, 'Stopping MatterNode');
|
|
258
360
|
this.log.debug(`Stopped plugin ${this.pluginName}`);
|
|
259
361
|
}
|
|
362
|
+
// Stop the dependant MatterNodes
|
|
260
363
|
this.log.debug(`Stopping dependant MatterNodes...`);
|
|
261
364
|
for (const dependantMatterNode of this.dependantMatterNodes.values()) {
|
|
262
365
|
await dependantMatterNode.stop();
|
|
@@ -269,24 +372,46 @@ export class MatterNode extends EventEmitter {
|
|
|
269
372
|
this.log.info('Stopped MatterNode');
|
|
270
373
|
await this.yieldToNode();
|
|
271
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (console and frontend).
|
|
377
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
378
|
+
*
|
|
379
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
380
|
+
*/
|
|
272
381
|
createDestinationMatterLogger() {
|
|
273
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
382
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
274
383
|
if (this.matterbridge.matterFileLogger) {
|
|
275
384
|
this.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
276
385
|
}
|
|
277
386
|
return (text, message) => {
|
|
387
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
278
388
|
const logger = text.slice(44, 44 + 20).trim();
|
|
279
389
|
const msg = text.slice(65);
|
|
280
390
|
this.matterLog.logName = logger;
|
|
281
391
|
this.matterLog.log(MatterLogLevel.names[message.level], msg);
|
|
282
392
|
};
|
|
283
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Starts the matter storage with name Matterbridge and performs a backup.
|
|
396
|
+
*
|
|
397
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
398
|
+
*/
|
|
284
399
|
async startMatterStorage() {
|
|
400
|
+
// Setup Matter storage
|
|
285
401
|
this.log.info(`Starting matter node storage...`);
|
|
286
402
|
this.matterStorageService = this.environment.get(StorageService);
|
|
287
403
|
this.log.info(`Started matter node storage in ${CYAN}${this.matterStorageService.location}${nf}`);
|
|
404
|
+
// Backup matter storage since it is created/opened correctly
|
|
288
405
|
await this.backupMatterStorage(path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
289
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
409
|
+
*
|
|
410
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
411
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
412
|
+
* @private
|
|
413
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
414
|
+
*/
|
|
290
415
|
async backupMatterStorage(storageName, backupName) {
|
|
291
416
|
this.log.info(`Creating matter node storage backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}...`);
|
|
292
417
|
try {
|
|
@@ -294,6 +419,7 @@ export class MatterNode extends EventEmitter {
|
|
|
294
419
|
this.log.info('Created matter node storage backup');
|
|
295
420
|
}
|
|
296
421
|
catch (error) {
|
|
422
|
+
// istanbul ignore next if
|
|
297
423
|
if (error instanceof Error && error?.code === 'ENOENT') {
|
|
298
424
|
this.log.info(`No matter node storage found to backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}`);
|
|
299
425
|
}
|
|
@@ -302,6 +428,11 @@ export class MatterNode extends EventEmitter {
|
|
|
302
428
|
}
|
|
303
429
|
}
|
|
304
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Stops the matter storage.
|
|
433
|
+
*
|
|
434
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
435
|
+
*/
|
|
305
436
|
async stopMatterStorage() {
|
|
306
437
|
this.log.info('Closing matter node storage...');
|
|
307
438
|
await this.matterStorageManager?.close();
|
|
@@ -311,6 +442,21 @@ export class MatterNode extends EventEmitter {
|
|
|
311
442
|
this.log.info('Closed matter node storage');
|
|
312
443
|
this.emit('closed');
|
|
313
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Creates a server node storage context.
|
|
447
|
+
*
|
|
448
|
+
* @param {string} storeId - The storeId.
|
|
449
|
+
* @param {string} deviceName - The name of the device.
|
|
450
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
451
|
+
* @param {VendorId} vendorId - The vendor ID.
|
|
452
|
+
* @param {string} vendorName - The vendor name.
|
|
453
|
+
* @param {number} productId - The product ID.
|
|
454
|
+
* @param {string} productName - The product name.
|
|
455
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
456
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
457
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
458
|
+
* @throws {Error} If the storage service is not initialized.
|
|
459
|
+
*/
|
|
314
460
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
315
461
|
if (!this.matterStorageService) {
|
|
316
462
|
throw new Error('No storage service initialized');
|
|
@@ -353,33 +499,52 @@ export class MatterNode extends EventEmitter {
|
|
|
353
499
|
this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
354
500
|
return storageContext;
|
|
355
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Creates a server node.
|
|
504
|
+
*
|
|
505
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
506
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
507
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
508
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
509
|
+
* @throws {Error} If the matter storage context is not created yet.
|
|
510
|
+
*/
|
|
356
511
|
async createServerNode(port = 5540, passcode = 20252026, discriminator = 3850) {
|
|
357
512
|
if (!this.matterStorageContext) {
|
|
358
513
|
throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
|
|
359
514
|
}
|
|
360
515
|
const storeId = await this.matterStorageContext.get('storeId');
|
|
361
516
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
517
|
+
/**
|
|
518
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
519
|
+
*/
|
|
362
520
|
const serverNode = await ServerNode.create({
|
|
521
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
363
522
|
id: storeId,
|
|
523
|
+
// Provide the environment to run this node in
|
|
364
524
|
environment: this.environment,
|
|
525
|
+
// Provide Network relevant configuration like the port
|
|
365
526
|
network: {
|
|
366
527
|
listeningAddressIpv4: this.ipv4Address,
|
|
367
528
|
listeningAddressIpv6: this.ipv6Address,
|
|
368
529
|
port,
|
|
369
530
|
},
|
|
531
|
+
// Provide the certificate for the device
|
|
370
532
|
operationalCredentials: {
|
|
371
533
|
certification: this.certification,
|
|
372
534
|
},
|
|
535
|
+
// Provide Commissioning relevant settings
|
|
373
536
|
commissioning: {
|
|
374
537
|
passcode,
|
|
375
538
|
discriminator,
|
|
376
539
|
},
|
|
540
|
+
// Provide Node announcement settings
|
|
377
541
|
productDescription: {
|
|
378
542
|
name: await this.matterStorageContext.get('deviceName'),
|
|
379
543
|
deviceType: DeviceTypeId(await this.matterStorageContext.get('deviceType')),
|
|
380
544
|
vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
|
|
381
545
|
productId: await this.matterStorageContext.get('productId'),
|
|
382
546
|
},
|
|
547
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
383
548
|
basicInformation: {
|
|
384
549
|
vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
|
|
385
550
|
vendorName: await this.matterStorageContext.get('vendorName'),
|
|
@@ -396,17 +561,23 @@ export class MatterNode extends EventEmitter {
|
|
|
396
561
|
reachable: true,
|
|
397
562
|
},
|
|
398
563
|
});
|
|
564
|
+
/**
|
|
565
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
566
|
+
* This means: It is added to the first fabric.
|
|
567
|
+
*/
|
|
399
568
|
serverNode.lifecycle.commissioned.on(() => {
|
|
400
569
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
401
570
|
this.advertisingNodes.delete(storeId);
|
|
402
571
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
403
572
|
});
|
|
573
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
404
574
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
405
575
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
406
576
|
this.advertisingNodes.delete(storeId);
|
|
407
577
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
408
578
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
|
|
409
579
|
});
|
|
580
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
410
581
|
serverNode.lifecycle.online.on(async () => {
|
|
411
582
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
412
583
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -424,6 +595,7 @@ export class MatterNode extends EventEmitter {
|
|
|
424
595
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is online`, timeout: 5, severity: 'success' } });
|
|
425
596
|
this.emit('online', storeId);
|
|
426
597
|
});
|
|
598
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
427
599
|
serverNode.lifecycle.offline.on(() => {
|
|
428
600
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
429
601
|
this.advertisingNodes.delete(storeId);
|
|
@@ -431,11 +603,15 @@ export class MatterNode extends EventEmitter {
|
|
|
431
603
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
|
|
432
604
|
this.emit('offline', storeId);
|
|
433
605
|
});
|
|
606
|
+
/**
|
|
607
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
608
|
+
* information is needed.
|
|
609
|
+
*/
|
|
434
610
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
435
611
|
let action = '';
|
|
436
612
|
switch (fabricAction) {
|
|
437
613
|
case 'added':
|
|
438
|
-
this.advertisingNodes.delete(storeId);
|
|
614
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
439
615
|
action = 'added';
|
|
440
616
|
break;
|
|
441
617
|
case 'deleted':
|
|
@@ -448,14 +624,22 @@ export class MatterNode extends EventEmitter {
|
|
|
448
624
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
449
625
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
450
626
|
});
|
|
627
|
+
/**
|
|
628
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
629
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
630
|
+
*/
|
|
451
631
|
serverNode.events.sessions.opened.on((session) => {
|
|
452
632
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
453
633
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
454
634
|
});
|
|
635
|
+
/**
|
|
636
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
637
|
+
*/
|
|
455
638
|
serverNode.events.sessions.closed.on((session) => {
|
|
456
639
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
457
640
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
458
641
|
});
|
|
642
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
459
643
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
460
644
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
461
645
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
@@ -464,6 +648,12 @@ export class MatterNode extends EventEmitter {
|
|
|
464
648
|
this.log.info(`Created server node for ${this.storeId}`);
|
|
465
649
|
return serverNode;
|
|
466
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Gets the matter serializable data of the specified server node.
|
|
653
|
+
*
|
|
654
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
655
|
+
* @returns {ApiMatter} The serializable data of the server node.
|
|
656
|
+
*/
|
|
467
657
|
getServerNodeData(serverNode) {
|
|
468
658
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
469
659
|
return {
|
|
@@ -480,6 +670,13 @@ export class MatterNode extends EventEmitter {
|
|
|
480
670
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
481
671
|
};
|
|
482
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Starts the specified server node.
|
|
675
|
+
*
|
|
676
|
+
* @param {number} [timeout] - The timeout in milliseconds for starting the server node. Defaults to 30 seconds.
|
|
677
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
678
|
+
* @throws {Error} If the server node is not created yet.
|
|
679
|
+
*/
|
|
483
680
|
async startServerNode(timeout = 30000) {
|
|
484
681
|
if (!this.serverNode) {
|
|
485
682
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
@@ -490,9 +687,17 @@ export class MatterNode extends EventEmitter {
|
|
|
490
687
|
this.log.notice(`Started ${this.serverNode.id} server node`);
|
|
491
688
|
}
|
|
492
689
|
catch (error) {
|
|
690
|
+
// istanbul ignore next
|
|
493
691
|
this.log.error(`Failed to start ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
494
692
|
}
|
|
495
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Stops the specified server node.
|
|
696
|
+
*
|
|
697
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
698
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
699
|
+
* @throws {Error} If the server node is not created yet.
|
|
700
|
+
*/
|
|
496
701
|
async stopServerNode(timeout = 30000) {
|
|
497
702
|
if (!this.serverNode) {
|
|
498
703
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
@@ -503,9 +708,16 @@ export class MatterNode extends EventEmitter {
|
|
|
503
708
|
this.log.info(`Closed ${this.serverNode.id} server node`);
|
|
504
709
|
}
|
|
505
710
|
catch (error) {
|
|
711
|
+
// istanbul ignore next
|
|
506
712
|
this.log.error(`Failed to close ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
507
713
|
}
|
|
508
714
|
}
|
|
715
|
+
/**
|
|
716
|
+
* Creates an aggregator node with the specified storage context.
|
|
717
|
+
*
|
|
718
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
719
|
+
* @throws {Error} If the matter storage context is not created yet.
|
|
720
|
+
*/
|
|
509
721
|
async createAggregatorNode() {
|
|
510
722
|
if (!this.matterStorageContext) {
|
|
511
723
|
throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
|
|
@@ -515,9 +727,16 @@ export class MatterNode extends EventEmitter {
|
|
|
515
727
|
this.log.info(`Created ${await this.matterStorageContext.get('storeId')} aggregator`);
|
|
516
728
|
return aggregatorNode;
|
|
517
729
|
}
|
|
730
|
+
/**
|
|
731
|
+
* Creates the matterbridge server node.
|
|
732
|
+
*
|
|
733
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created matterbridge server node.
|
|
734
|
+
*/
|
|
518
735
|
async createMatterbridgeServerNode() {
|
|
519
736
|
this.log.debug(`Creating ${plg}Matterbridge${db} server node...`);
|
|
520
|
-
this.matterStorageContext = await this.createServerNodeContext('Matterbridge',
|
|
737
|
+
this.matterStorageContext = await this.createServerNodeContext('Matterbridge', // storeId
|
|
738
|
+
'Matterbridge', // deviceName
|
|
739
|
+
this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
521
740
|
this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
522
741
|
this.aggregatorNode = await this.createAggregatorNode();
|
|
523
742
|
this.log.debug(`Adding ${plg}Matterbridge${db} aggregator node...`);
|
|
@@ -528,6 +747,13 @@ export class MatterNode extends EventEmitter {
|
|
|
528
747
|
this.log.debug(`Created ${plg}Matterbridge${db} server node`);
|
|
529
748
|
return this.serverNode;
|
|
530
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
752
|
+
*
|
|
753
|
+
* @param {Plugin | PluginName} plugin - The plugin to configure.
|
|
754
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
755
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the accessory plugin.
|
|
756
|
+
*/
|
|
531
757
|
async createAccessoryPlugin(plugin, device) {
|
|
532
758
|
if (typeof plugin === 'string') {
|
|
533
759
|
const _plugin = this.pluginManager.get(plugin);
|
|
@@ -549,6 +775,12 @@ export class MatterNode extends EventEmitter {
|
|
|
549
775
|
}
|
|
550
776
|
return this.serverNode;
|
|
551
777
|
}
|
|
778
|
+
/**
|
|
779
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
780
|
+
*
|
|
781
|
+
* @param {Plugin | PluginName} plugin - The plugin to configure.
|
|
782
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the dynamic plugin.
|
|
783
|
+
*/
|
|
552
784
|
async createDynamicPlugin(plugin) {
|
|
553
785
|
if (typeof plugin === 'string') {
|
|
554
786
|
const _plugin = this.pluginManager.get(plugin);
|
|
@@ -572,6 +804,13 @@ export class MatterNode extends EventEmitter {
|
|
|
572
804
|
}
|
|
573
805
|
return this.serverNode;
|
|
574
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* Creates and configures the server node for a single not bridged device.
|
|
809
|
+
*
|
|
810
|
+
* @param {Plugin | PluginName} plugin - The plugin to configure.
|
|
811
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
812
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the device with mode server.
|
|
813
|
+
*/
|
|
575
814
|
async createDeviceServerNode(plugin, device) {
|
|
576
815
|
if (typeof plugin === 'string') {
|
|
577
816
|
const _plugin = this.pluginManager.get(plugin);
|
|
@@ -592,13 +831,22 @@ export class MatterNode extends EventEmitter {
|
|
|
592
831
|
}
|
|
593
832
|
return this.serverNode;
|
|
594
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
836
|
+
*
|
|
837
|
+
* @param {string} pluginName - The name of the plugin.
|
|
838
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
839
|
+
* @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the added bridged endpoint, or undefined if there was an error.
|
|
840
|
+
*/
|
|
595
841
|
async addBridgedEndpoint(pluginName, device) {
|
|
842
|
+
// Check if the plugin is registered
|
|
596
843
|
const plugin = this.pluginManager.get(pluginName);
|
|
597
844
|
if (!plugin)
|
|
598
845
|
throw new Error(`Error adding bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
|
|
599
846
|
if (device.mode === 'server') {
|
|
600
847
|
try {
|
|
601
848
|
this.log.debug(`Creating MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
849
|
+
// Create the MatterNode to manage the device
|
|
602
850
|
const matterNode = new MatterNode(this.matterbridge, pluginName, device);
|
|
603
851
|
matterNode.port = this.port ? this.port++ : undefined;
|
|
604
852
|
matterNode.passcode = this.passcode ? this.passcode++ : undefined;
|
|
@@ -614,6 +862,7 @@ export class MatterNode extends EventEmitter {
|
|
|
614
862
|
}
|
|
615
863
|
else if (this.matterbridge.bridgeMode === 'bridge') {
|
|
616
864
|
if (device.mode === 'matter') {
|
|
865
|
+
// Register and add the device to the Matter server node
|
|
617
866
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
618
867
|
if (!this.serverNode)
|
|
619
868
|
throw new Error(`Server node not found for matter endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
|
|
@@ -626,6 +875,7 @@ export class MatterNode extends EventEmitter {
|
|
|
626
875
|
}
|
|
627
876
|
}
|
|
628
877
|
else {
|
|
878
|
+
// Register and add the device to the Matter aggregator node
|
|
629
879
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
630
880
|
if (!this.aggregatorNode)
|
|
631
881
|
throw new Error(`Aggregator node not found for endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
|
|
@@ -639,6 +889,7 @@ export class MatterNode extends EventEmitter {
|
|
|
639
889
|
}
|
|
640
890
|
}
|
|
641
891
|
else if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
892
|
+
// Register and add the device to the plugin server node
|
|
642
893
|
if (plugin.type === 'AccessoryPlatform') {
|
|
643
894
|
try {
|
|
644
895
|
this.log.debug(`Adding accessory endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
@@ -654,10 +905,12 @@ export class MatterNode extends EventEmitter {
|
|
|
654
905
|
return;
|
|
655
906
|
}
|
|
656
907
|
}
|
|
908
|
+
// Register and add the device to the plugin aggregator node
|
|
657
909
|
if (plugin.type === 'DynamicPlatform') {
|
|
658
910
|
try {
|
|
659
911
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
660
912
|
if (!this.serverNode) {
|
|
913
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
661
914
|
await this.createDynamicPlugin(plugin);
|
|
662
915
|
}
|
|
663
916
|
if (device.mode === 'matter')
|
|
@@ -673,19 +926,31 @@ export class MatterNode extends EventEmitter {
|
|
|
673
926
|
}
|
|
674
927
|
if (plugin.registeredDevices !== undefined)
|
|
675
928
|
plugin.registeredDevices++;
|
|
929
|
+
// Add the device to the DeviceManager
|
|
676
930
|
await this.server.fetch({ type: 'devices_set', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
931
|
+
// Add the device to the DeviceManager
|
|
677
932
|
await device.construction.ready;
|
|
933
|
+
// Subscribe to the attributes changed event
|
|
678
934
|
await this.subscribeAttributeChanged(plugin, device);
|
|
679
935
|
this.log.info(`Added endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
|
|
680
936
|
await this.yieldToNode(10);
|
|
681
937
|
return device;
|
|
682
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
941
|
+
*
|
|
942
|
+
* @param {string} pluginName - The name of the plugin.
|
|
943
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
944
|
+
* @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the removed bridged endpoint, or undefined if there was an error.
|
|
945
|
+
*/
|
|
683
946
|
async removeBridgedEndpoint(pluginName, device) {
|
|
684
947
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
948
|
+
// Check if the plugin is registered
|
|
685
949
|
const plugin = this.pluginManager.get(pluginName);
|
|
686
950
|
if (!plugin)
|
|
687
951
|
throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
|
|
688
952
|
if (device.serverNode) {
|
|
953
|
+
// TODO: Close and remove the MatterNode managing the device
|
|
689
954
|
}
|
|
690
955
|
else if (this.matterbridge.bridgeMode === 'bridge') {
|
|
691
956
|
if (!this.aggregatorNode)
|
|
@@ -707,11 +972,25 @@ export class MatterNode extends EventEmitter {
|
|
|
707
972
|
this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
|
|
708
973
|
if (plugin.registeredDevices !== undefined)
|
|
709
974
|
plugin.registeredDevices--;
|
|
975
|
+
// Remove the device from the DeviceManager
|
|
710
976
|
await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
711
977
|
await this.yieldToNode(10);
|
|
712
978
|
return device;
|
|
713
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* Removes all bridged endpoints from the specified plugin.
|
|
982
|
+
*
|
|
983
|
+
* @param {string} pluginName - The name of the plugin.
|
|
984
|
+
* @param {number} [delay] - The delay in milliseconds between removing each bridged endpoint (default: 0).
|
|
985
|
+
* @returns {Promise<void>} A promise that resolves when all bridged endpoints have been removed.
|
|
986
|
+
*
|
|
987
|
+
* @remarks
|
|
988
|
+
* This method iterates through all devices in the DeviceManager and removes each bridged endpoint associated with the specified plugin.
|
|
989
|
+
* It also applies a delay between each removal if specified.
|
|
990
|
+
* The delay is useful to allow the controllers to receive a single subscription for each device removed.
|
|
991
|
+
*/
|
|
714
992
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
993
|
+
// Check if the plugin is registered
|
|
715
994
|
const plugin = this.pluginManager.get(pluginName);
|
|
716
995
|
if (!plugin)
|
|
717
996
|
throw new Error(`Error removing all bridged endpoints for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
@@ -726,6 +1005,7 @@ export class MatterNode extends EventEmitter {
|
|
|
726
1005
|
this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${endpoint?.name}${nf})`);
|
|
727
1006
|
if (plugin.registeredDevices !== undefined)
|
|
728
1007
|
plugin.registeredDevices--;
|
|
1008
|
+
// Remove the device from the DeviceManager
|
|
729
1009
|
await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
730
1010
|
await this.yieldToNode(10);
|
|
731
1011
|
if (delay > 0)
|
|
@@ -734,6 +1014,25 @@ export class MatterNode extends EventEmitter {
|
|
|
734
1014
|
if (delay > 0)
|
|
735
1015
|
await wait(Number(process.env['MATTERBRIDGE_REMOVE_ALL_ENDPOINT_TIMEOUT_MS']) || 2000);
|
|
736
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Registers a virtual device with the Matterbridge platform.
|
|
1019
|
+
* Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.
|
|
1020
|
+
*
|
|
1021
|
+
* The virtual device is created as an instance of `Endpoint` with the provided device type.
|
|
1022
|
+
* When the virtual device is turned on, the provided callback function is executed.
|
|
1023
|
+
* The onOff state of the virtual device always reverts to false when the device is turned on.
|
|
1024
|
+
*
|
|
1025
|
+
* @param {string} pluginName - The name of the plugin.
|
|
1026
|
+
* @param { string } name - The name of the virtual device.
|
|
1027
|
+
* @param { 'light' | 'outlet' | 'switch' | 'mounted_switch' } type - The type of the virtual device.
|
|
1028
|
+
* @param { () => Promise<void> } callback - The callback to call when the virtual device is turned on.
|
|
1029
|
+
*
|
|
1030
|
+
* @returns {Promise<boolean>} A promise that resolves to true if the virtual device was successfully registered, false otherwise.
|
|
1031
|
+
*
|
|
1032
|
+
* @remarks
|
|
1033
|
+
* The virtual devices don't show up in the device list of the frontend.
|
|
1034
|
+
* Type 'switch' is not supported by Alexa and 'mounted_switch' is not supported by Apple Home.
|
|
1035
|
+
*/
|
|
737
1036
|
async addVirtualEndpoint(pluginName, name, type, callback) {
|
|
738
1037
|
this.log.debug(`Creating virtual device ${plg}${pluginName}${db}:${dev}${name}${db}...`);
|
|
739
1038
|
const plugin = this.pluginManager.get(pluginName);
|
|
@@ -758,10 +1057,20 @@ export class MatterNode extends EventEmitter {
|
|
|
758
1057
|
await this.yieldToNode(10);
|
|
759
1058
|
return true;
|
|
760
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Subscribes to the attribute change event for the given device and plugin.
|
|
1062
|
+
* Specifically, it listens for changes in the 'reachable' attribute of the
|
|
1063
|
+
* BridgedDeviceBasicInformationServer cluster server of the bridged device or BasicInformationServer cluster server of server node.
|
|
1064
|
+
*
|
|
1065
|
+
* @param {Plugin} plugin - The plugin associated with the device.
|
|
1066
|
+
* @param {MatterbridgeEndpoint} device - The device to subscribe to attribute changes for.
|
|
1067
|
+
* @returns {Promise<void>} A promise that resolves when the subscription is set up.
|
|
1068
|
+
*/
|
|
761
1069
|
async subscribeAttributeChanged(plugin, device) {
|
|
762
1070
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
763
1071
|
return;
|
|
764
1072
|
this.log.debug(`Subscribing attributes for endpoint ${plg}${plugin.name}${db}:${dev}${device.deviceName}${db}:${or}${device.id}${db}:${or}${device.number}${db} (${zb}${device.name}${db})`);
|
|
1073
|
+
// Subscribe to the reachable$Changed event of the BasicInformationServer cluster server of the server node in childbridge mode
|
|
765
1074
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && this.serverNode) {
|
|
766
1075
|
this.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
767
1076
|
this.log.debug(`Accessory endpoint ${plg}${plugin.name}${nf}:${dev}${device.deviceName}${nf}:${or}${device.id}${nf}:${or}${device.number}${nf} is ${reachable ? 'reachable' : 'unreachable'}`);
|
|
@@ -769,6 +1078,7 @@ export class MatterNode extends EventEmitter {
|
|
|
769
1078
|
type: 'frontend_attributechanged',
|
|
770
1079
|
src: 'matter',
|
|
771
1080
|
dst: 'frontend',
|
|
1081
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
772
1082
|
params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: device.number, id: device.id, cluster: 'BasicInformation', attribute: 'reachable', value: reachable },
|
|
773
1083
|
});
|
|
774
1084
|
});
|
|
@@ -821,6 +1131,7 @@ export class MatterNode extends EventEmitter {
|
|
|
821
1131
|
type: 'frontend_attributechanged',
|
|
822
1132
|
src: 'matter',
|
|
823
1133
|
dst: 'frontend',
|
|
1134
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
824
1135
|
params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: device.number, id: device.id, cluster: sub.cluster, attribute: sub.attribute, value: value },
|
|
825
1136
|
});
|
|
826
1137
|
});
|
|
@@ -834,6 +1145,7 @@ export class MatterNode extends EventEmitter {
|
|
|
834
1145
|
type: 'frontend_attributechanged',
|
|
835
1146
|
src: 'matter',
|
|
836
1147
|
dst: 'frontend',
|
|
1148
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
837
1149
|
params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: child.number, id: child.id, cluster: sub.cluster, attribute: sub.attribute, value: value },
|
|
838
1150
|
});
|
|
839
1151
|
});
|
|
@@ -841,6 +1153,12 @@ export class MatterNode extends EventEmitter {
|
|
|
841
1153
|
}
|
|
842
1154
|
}
|
|
843
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Sanitizes the fabric information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
1158
|
+
*
|
|
1159
|
+
* @param {ExposedFabricInformation[]} fabricInfo - The array of exposed fabric information objects.
|
|
1160
|
+
* @returns {SanitizedExposedFabricInformation[]} An array of sanitized exposed fabric information objects.
|
|
1161
|
+
*/
|
|
844
1162
|
sanitizeFabricInformations(fabricInfo) {
|
|
845
1163
|
return fabricInfo.map((info) => {
|
|
846
1164
|
return {
|
|
@@ -854,6 +1172,12 @@ export class MatterNode extends EventEmitter {
|
|
|
854
1172
|
};
|
|
855
1173
|
});
|
|
856
1174
|
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Sanitizes the session information by converting bigint properties to strings because `res.json` doesn't support bigint.
|
|
1177
|
+
*
|
|
1178
|
+
* @param {SessionsBehavior.Session[]} sessions - The array of session information objects.
|
|
1179
|
+
* @returns {SanitizedSession[]} An array of sanitized session information objects.
|
|
1180
|
+
*/
|
|
857
1181
|
sanitizeSessionInformation(sessions) {
|
|
858
1182
|
return sessions
|
|
859
1183
|
.filter((session) => session.isPeerActive)
|
|
@@ -880,10 +1204,21 @@ export class MatterNode extends EventEmitter {
|
|
|
880
1204
|
};
|
|
881
1205
|
});
|
|
882
1206
|
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Sets the reachability of the specified server node and trigger the corresponding event.
|
|
1209
|
+
*
|
|
1210
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
1211
|
+
*/
|
|
883
1212
|
async setServerReachability(reachable) {
|
|
884
1213
|
await this.serverNode?.setStateOf(BasicInformationServer, { reachable });
|
|
885
1214
|
this.serverNode?.act((agent) => this.serverNode?.eventsOf(BasicInformationServer).reachableChanged?.emit({ reachableNewValue: reachable }, agent.context));
|
|
886
1215
|
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Sets the reachability of the specified aggregator node bridged devices and trigger.
|
|
1218
|
+
*
|
|
1219
|
+
* @param {Endpoint<AggregatorEndpoint>} aggregatorNode - The aggregator node to set the reachability for.
|
|
1220
|
+
* @param {boolean} reachable - A boolean indicating the reachability status to set.
|
|
1221
|
+
*/
|
|
887
1222
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
888
1223
|
for (const child of aggregatorNode.parts) {
|
|
889
1224
|
this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
|
|
@@ -929,19 +1264,35 @@ export class MatterNode extends EventEmitter {
|
|
|
929
1264
|
case 0x1488:
|
|
930
1265
|
vendorName = '(ShortcutLabsFlic)';
|
|
931
1266
|
break;
|
|
932
|
-
case 65521:
|
|
1267
|
+
case 65521: // 0xFFF1
|
|
933
1268
|
vendorName = '(MatterTest)';
|
|
934
1269
|
break;
|
|
935
1270
|
}
|
|
936
1271
|
return vendorName;
|
|
937
1272
|
};
|
|
1273
|
+
/**
|
|
1274
|
+
* Yield to the Node.js event loop:
|
|
1275
|
+
* 1. Flushes the current microtask queue (Promise/async continuations queued so far).
|
|
1276
|
+
* 2. Yields one macrotask turn (setImmediate) and then its microtasks.
|
|
1277
|
+
* 3. Waits a bit (setTimeout) to allow other macrotasks to run.
|
|
1278
|
+
*
|
|
1279
|
+
* This does **not** guarantee that every promise in the process is settled,
|
|
1280
|
+
* but it gives all already-scheduled work a very good chance to run before continuing.
|
|
1281
|
+
*
|
|
1282
|
+
* @param {number} [timeout] - Optional timeout in milliseconds to wait after yielding. Default is 100 ms (minimum 10 ms).
|
|
1283
|
+
* @returns {Promise<void>}
|
|
1284
|
+
*/
|
|
938
1285
|
async yieldToNode(timeout = 100) {
|
|
1286
|
+
// 1. Let all currently queued microtasks run
|
|
939
1287
|
await Promise.resolve();
|
|
1288
|
+
// 2. Yield to the next event-loop turn (macrotask + its microtasks)
|
|
940
1289
|
await new Promise((resolve) => {
|
|
941
1290
|
setImmediate(resolve);
|
|
942
1291
|
});
|
|
1292
|
+
// 3. Pause a bit to allow other macrotasks to run
|
|
943
1293
|
await new Promise((resolve) => {
|
|
944
1294
|
setTimeout(resolve, Math.min(timeout, 10));
|
|
945
1295
|
});
|
|
946
1296
|
}
|
|
947
1297
|
}
|
|
1298
|
+
//# sourceMappingURL=matterNode.js.map
|