matterbridge 3.4.3-dev-20251209-e6cb85f → 3.4.3
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/README.md +2 -3
- package/dist/broadcastServer.d.ts +144 -0
- package/dist/broadcastServer.d.ts.map +1 -0
- package/dist/broadcastServer.js +119 -0
- package/dist/broadcastServer.js.map +1 -0
- package/dist/broadcastServerTypes.d.ts +841 -0
- package/dist/broadcastServerTypes.d.ts.map +1 -0
- package/dist/broadcastServerTypes.js +24 -0
- package/dist/broadcastServerTypes.js.map +1 -0
- package/dist/cli.d.ts +30 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -1
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +50 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +37 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +48 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +38 -0
- package/dist/cliHistory.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +2 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/deviceManager.d.ts +135 -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 +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +57 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +48 -1
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/cooktop.d.ts +61 -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 +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +57 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +76 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +74 -10
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +17 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +5 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +43 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +50 -2
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +62 -3
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +70 -4
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +88 -5
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +85 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +102 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +100 -9
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +38 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +84 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +24 -3
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +82 -2
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/coap.d.ts +205 -0
- package/dist/dgram/coap.d.ts.map +1 -0
- package/dist/dgram/coap.js +126 -13
- package/dist/dgram/coap.js.map +1 -0
- package/dist/dgram/dgram.d.ts +141 -0
- package/dist/dgram/dgram.d.ts.map +1 -0
- package/dist/dgram/dgram.js +114 -2
- package/dist/dgram/dgram.js.map +1 -0
- package/dist/dgram/mb_coap.d.ts +24 -0
- package/dist/dgram/mb_coap.d.ts.map +1 -0
- package/dist/dgram/mb_coap.js +41 -3
- package/dist/dgram/mb_coap.js.map +1 -0
- package/dist/dgram/mb_mdns.d.ts +24 -0
- package/dist/dgram/mb_mdns.d.ts.map +1 -0
- package/dist/dgram/mb_mdns.js +80 -15
- package/dist/dgram/mb_mdns.js.map +1 -0
- package/dist/dgram/mdns.d.ts +290 -0
- package/dist/dgram/mdns.d.ts.map +1 -0
- package/dist/dgram/mdns.js +299 -137
- package/dist/dgram/mdns.js.map +1 -0
- package/dist/dgram/multicast.d.ts +67 -0
- package/dist/dgram/multicast.d.ts.map +1 -0
- package/dist/dgram/multicast.js +62 -1
- package/dist/dgram/multicast.js.map +1 -0
- package/dist/dgram/unicast.d.ts +56 -0
- package/dist/dgram/unicast.d.ts.map +1 -0
- package/dist/dgram/unicast.js +54 -0
- package/dist/dgram/unicast.js.map +1 -0
- package/dist/frontend.d.ts +238 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +455 -35
- package/dist/frontend.js.map +1 -0
- package/dist/frontendTypes.d.ts +529 -0
- package/dist/frontendTypes.d.ts.map +1 -0
- package/dist/frontendTypes.js +45 -0
- package/dist/frontendTypes.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +53 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/jestutils/export.d.ts +2 -0
- package/dist/jestutils/export.d.ts.map +1 -0
- package/dist/jestutils/export.js +1 -0
- package/dist/jestutils/export.js.map +1 -0
- package/dist/jestutils/jestHelpers.d.ts +345 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +371 -14
- package/dist/jestutils/jestHelpers.js.map +1 -0
- package/dist/logger/export.d.ts +2 -0
- package/dist/logger/export.d.ts.map +1 -0
- package/dist/logger/export.js +1 -0
- package/dist/logger/export.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +2 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +2 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +2 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +2 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +5 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +3 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +3 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterNode.d.ts +342 -0
- package/dist/matterNode.d.ts.map +1 -0
- package/dist/matterNode.js +369 -8
- package/dist/matterNode.js.map +1 -0
- package/dist/matterbridge.d.ts +492 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +811 -46
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +41 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +38 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +2404 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +68 -5
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +698 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +635 -14
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +41 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +38 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1507 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +1444 -53
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +787 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +483 -20
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgeEndpointTypes.d.ts +166 -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 +539 -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 +251 -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 +372 -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 +181 -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 +2 -0
- package/dist/storage/export.d.ts.map +1 -0
- package/dist/storage/export.js +1 -0
- package/dist/storage/export.js.map +1 -0
- package/dist/update.d.ts +84 -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 +101 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/colorUtils.js +97 -2
- package/dist/utils/colorUtils.js.map +1 -0
- package/dist/utils/commandLine.d.ts +66 -0
- package/dist/utils/commandLine.d.ts.map +1 -0
- package/dist/utils/commandLine.js +60 -0
- package/dist/utils/commandLine.js.map +1 -0
- package/dist/utils/copyDirectory.d.ts +35 -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 +34 -0
- package/dist/utils/createDirectory.d.ts.map +1 -0
- package/dist/utils/createDirectory.js +33 -0
- package/dist/utils/createDirectory.js.map +1 -0
- package/dist/utils/createZip.d.ts +39 -0
- package/dist/utils/createZip.d.ts.map +1 -0
- package/dist/utils/createZip.js +47 -2
- package/dist/utils/createZip.js.map +1 -0
- package/dist/utils/deepCopy.d.ts +32 -0
- package/dist/utils/deepCopy.d.ts.map +1 -0
- package/dist/utils/deepCopy.js +39 -0
- package/dist/utils/deepCopy.js.map +1 -0
- package/dist/utils/deepEqual.d.ts +54 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +72 -1
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/error.d.ts +45 -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 +13 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +1 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/utils/format.d.ts +53 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +49 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hex.d.ts +89 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/hex.js +124 -0
- package/dist/utils/hex.js.map +1 -0
- package/dist/utils/inspector.d.ts +87 -0
- package/dist/utils/inspector.d.ts.map +1 -0
- package/dist/utils/inspector.js +69 -1
- package/dist/utils/inspector.js.map +1 -0
- package/dist/utils/isvalid.d.ts +103 -0
- package/dist/utils/isvalid.d.ts.map +1 -0
- package/dist/utils/isvalid.js +101 -0
- package/dist/utils/isvalid.js.map +1 -0
- package/dist/utils/network.d.ts +111 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +96 -5
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/spawn.d.ts +33 -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 +108 -0
- package/dist/utils/tracker.d.ts.map +1 -0
- package/dist/utils/tracker.js +64 -1
- package/dist/utils/tracker.js.map +1 -0
- package/dist/utils/wait.d.ts +54 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +60 -8
- package/dist/utils/wait.js.map +1 -0
- package/dist/workerGlobalPrefix.d.ts +25 -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 +52 -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 +69 -0
- package/dist/workers.d.ts.map +1 -0
- package/dist/workers.js +68 -4
- package/dist/workers.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/scripts/data_model.mjs +2058 -0
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 { FabricAction, 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;
|
|
@@ -68,12 +127,16 @@ export class MatterNode extends EventEmitter {
|
|
|
68
127
|
this.server.on('broadcast_message', this.msgHandler.bind(this));
|
|
69
128
|
if (this.verbose)
|
|
70
129
|
this.log.debug(`BroadcastServer is ready`);
|
|
130
|
+
// Ensure the matterbridge directory exists
|
|
71
131
|
fs.mkdirSync(matterbridge.matterbridgeDirectory, { recursive: true });
|
|
132
|
+
// Setup the plugin manager with thread server closed
|
|
72
133
|
this.pluginManager = new PluginManager(this.matterbridge);
|
|
73
|
-
this.pluginManager.logLevel = this.debug ? "debug" : "info"
|
|
134
|
+
this.pluginManager.logLevel = this.debug ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */;
|
|
135
|
+
// @ts-expect-error access private property
|
|
74
136
|
this.pluginManager.server.close();
|
|
75
137
|
if (this.verbose)
|
|
76
138
|
this.log.debug(`PluginManager is ready`);
|
|
139
|
+
// Setup the matter environment
|
|
77
140
|
this.environment.vars.set('log.level', MatterLogLevel.DEBUG);
|
|
78
141
|
this.environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
79
142
|
this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME));
|
|
@@ -81,9 +144,20 @@ export class MatterNode extends EventEmitter {
|
|
|
81
144
|
this.environment.vars.set('runtime.exitcode', false);
|
|
82
145
|
if (this.verbose)
|
|
83
146
|
this.log.debug(`Matter Environment is ready`);
|
|
147
|
+
// Ensure MdnsService is registered in the default environment
|
|
148
|
+
// this.matterMdnsService = this.environment.get(MdnsService);
|
|
149
|
+
/*
|
|
150
|
+
this.matterMdnsService = new MdnsService(this.environment);
|
|
151
|
+
setImmediate(async () => {
|
|
152
|
+
await this.matterMdnsService?.construction.ready;
|
|
153
|
+
if (this.verbose) this.log.debug(`Matter MdnsService is ready`);
|
|
154
|
+
});
|
|
155
|
+
*/
|
|
156
|
+
// Setup the matterbridge logger
|
|
84
157
|
if (this.matterbridge.fileLogger) {
|
|
85
158
|
AnsiLogger.setGlobalLogfile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_LOGGER_FILE), this.matterbridge.logLevel);
|
|
86
159
|
}
|
|
160
|
+
// Setup the matter logger
|
|
87
161
|
Logger.destinations.default.write = this.createDestinationMatterLogger();
|
|
88
162
|
const levels = ['debug', 'info', 'notice', 'warn', 'error', 'fatal'];
|
|
89
163
|
if (this.verbose)
|
|
@@ -91,6 +165,11 @@ export class MatterNode extends EventEmitter {
|
|
|
91
165
|
if (this.debug)
|
|
92
166
|
this.log.debug(`MatterNode ${this.pluginName ? 'for plugin ' + this.pluginName : 'bridge'} loaded`);
|
|
93
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Handles incoming messages from the broadcast server.
|
|
170
|
+
*
|
|
171
|
+
* @param {WorkerMessage} msg - The incoming message.
|
|
172
|
+
*/
|
|
94
173
|
async msgHandler(msg) {
|
|
95
174
|
if (this.server.isWorkerRequest(msg) && (msg.dst === 'all' || msg.dst === 'matter')) {
|
|
96
175
|
if (this.verbose)
|
|
@@ -121,32 +200,52 @@ export class MatterNode extends EventEmitter {
|
|
|
121
200
|
}
|
|
122
201
|
}
|
|
123
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Destroys the Matter instance.
|
|
205
|
+
* It closes the mDNS service and the broadcast server.
|
|
206
|
+
*
|
|
207
|
+
* @param {boolean} closeMdns - Whether to close the mDNS service. Default is true.
|
|
208
|
+
* @returns {Promise<void>} A promise that resolves when the instance is destroyed.
|
|
209
|
+
*/
|
|
124
210
|
async destroy(closeMdns = true) {
|
|
125
211
|
if (this.verbose)
|
|
126
212
|
this.log.debug(`Destroying MatterNode instance for ${this.storeId}...`);
|
|
213
|
+
// Close mDNS service
|
|
127
214
|
if (closeMdns) {
|
|
128
215
|
if (this.verbose)
|
|
129
216
|
this.log.debug(`Closing Matter MdnsService for ${this.storeId}...`);
|
|
130
217
|
this.matterMdnsService = this.environment.get(MdnsService);
|
|
218
|
+
// istanbul ignore next
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
220
|
if (typeof this.matterMdnsService[Symbol.asyncDispose] === 'function')
|
|
132
221
|
await this.matterMdnsService[Symbol.asyncDispose]();
|
|
222
|
+
// istanbul ignore next
|
|
223
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
224
|
else
|
|
134
225
|
await this.matterMdnsService.close();
|
|
135
226
|
if (this.verbose)
|
|
136
227
|
this.log.debug(`Closed Matter MdnsService for ${this.storeId}`);
|
|
137
228
|
}
|
|
229
|
+
// Close the plugin manager
|
|
138
230
|
this.pluginManager.destroy();
|
|
231
|
+
// Close the broadcast server
|
|
139
232
|
this.server.close();
|
|
233
|
+
// Yield to the Node.js event loop to allow all resources to be released
|
|
140
234
|
await this.yieldToNode();
|
|
141
235
|
if (this.verbose)
|
|
142
236
|
this.log.debug(`Destroyed MatterNode instance for ${this.storeId}`);
|
|
143
237
|
}
|
|
144
238
|
async create() {
|
|
145
239
|
this.log.info('Creating Matter node...');
|
|
240
|
+
// Start matter storage
|
|
146
241
|
await this.startMatterStorage();
|
|
242
|
+
// Load plugins from storage
|
|
243
|
+
// @ts-expect-error access private property
|
|
147
244
|
this.pluginManager.matterbridge.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR), writeQueue: false, expiredInterval: undefined, logging: false });
|
|
245
|
+
// @ts-expect-error access private property
|
|
148
246
|
this.pluginManager.matterbridge.nodeContext = await this.pluginManager.matterbridge.nodeStorage.createStorage('matterbridge');
|
|
149
247
|
await this.pluginManager.loadFromStorage();
|
|
248
|
+
// Create Matter node for a server mode device
|
|
150
249
|
if (this.pluginName && this.device && this.device.deviceName) {
|
|
151
250
|
this.log.debug(`Creating MatterNode instance for server node device ${CYAN}${this.device.deviceName}${db}...`);
|
|
152
251
|
await this.createDeviceServerNode(this.pluginName, this.device);
|
|
@@ -157,6 +256,7 @@ export class MatterNode extends EventEmitter {
|
|
|
157
256
|
if (!this.pluginName) {
|
|
158
257
|
this.log.debug('Creating MatterNode instance for all plugins...');
|
|
159
258
|
await this.createMatterbridgeServerNode();
|
|
259
|
+
// Load all enabled plugins
|
|
160
260
|
this.log.debug('Loading all plugins...');
|
|
161
261
|
const loadPromises = [];
|
|
162
262
|
for (const plugin of this.pluginManager.array().filter((p) => p.enabled)) {
|
|
@@ -169,6 +269,7 @@ export class MatterNode extends EventEmitter {
|
|
|
169
269
|
}
|
|
170
270
|
else {
|
|
171
271
|
this.log.debug(`Creating MatterNode instance for plugin ${CYAN}${this.pluginName}${db}...`);
|
|
272
|
+
// Load only the specified plugin
|
|
172
273
|
this.log.debug(`Loading plugin ${CYAN}${this.pluginName}${db}...`);
|
|
173
274
|
await this.pluginManager.load(this.pluginName);
|
|
174
275
|
this.log.debug(`Loaded plugin ${CYAN}${this.pluginName}${db}`);
|
|
@@ -182,13 +283,16 @@ export class MatterNode extends EventEmitter {
|
|
|
182
283
|
if (!this.serverNode && !this.pluginName)
|
|
183
284
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
184
285
|
this.log.info('Starting MatterNode...');
|
|
286
|
+
// Start Matter node for a server mode device
|
|
185
287
|
if (this.pluginName && this.device && this.device.deviceName) {
|
|
288
|
+
// Start the server node
|
|
186
289
|
this.log.debug(`Starting MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
|
|
187
290
|
await this.startServerNode();
|
|
188
291
|
this.log.debug(`Started MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
|
|
189
292
|
return;
|
|
190
293
|
}
|
|
191
294
|
if (!this.pluginName) {
|
|
295
|
+
// Start all loaded plugins
|
|
192
296
|
this.log.debug('Starting all plugins...');
|
|
193
297
|
const startPromises = [];
|
|
194
298
|
for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.loaded)) {
|
|
@@ -196,9 +300,11 @@ export class MatterNode extends EventEmitter {
|
|
|
196
300
|
}
|
|
197
301
|
await Promise.all(startPromises);
|
|
198
302
|
this.log.debug('Started all plugins');
|
|
303
|
+
// Start the server node
|
|
199
304
|
this.log.debug('Starting MatterNode for all plugins...');
|
|
200
305
|
await this.startServerNode();
|
|
201
306
|
this.log.debug('Started MatterNode for all plugins');
|
|
307
|
+
// Configure all loaded plugins
|
|
202
308
|
this.log.debug('Configuring all plugins...');
|
|
203
309
|
const configurePromises = [];
|
|
204
310
|
for (const plugin of this.pluginManager.array().filter((p) => p.enabled && p.started)) {
|
|
@@ -208,12 +314,16 @@ export class MatterNode extends EventEmitter {
|
|
|
208
314
|
this.log.debug('Configured all plugins');
|
|
209
315
|
}
|
|
210
316
|
else {
|
|
317
|
+
// Start the loaded plugin
|
|
211
318
|
await this.pluginManager.start(this.pluginName, 'Starting MatterNode');
|
|
319
|
+
// Start the server node
|
|
212
320
|
this.log.debug(`Starting MatterNode for plugin ${this.pluginName}...`);
|
|
213
321
|
await this.startServerNode();
|
|
214
322
|
this.log.debug(`Started MatterNode for plugin ${this.pluginName}`);
|
|
323
|
+
// Configure the plugin
|
|
215
324
|
await this.pluginManager.configure(this.pluginName);
|
|
216
325
|
}
|
|
326
|
+
// Start the dependant MatterNodes
|
|
217
327
|
this.log.debug(`Starting dependant MatterNodes...`);
|
|
218
328
|
for (const dependantMatterNode of this.dependantMatterNodes.values()) {
|
|
219
329
|
await dependantMatterNode.start();
|
|
@@ -226,13 +336,15 @@ export class MatterNode extends EventEmitter {
|
|
|
226
336
|
if (!this.serverNode)
|
|
227
337
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
228
338
|
this.log.info('Stopping MatterNode...');
|
|
339
|
+
// Stop Matter node for a server mode device
|
|
229
340
|
if (this.pluginName && this.device && this.device.deviceName) {
|
|
341
|
+
// Stop the server node
|
|
230
342
|
this.log.debug(`Stopping MatterNode for server device ${this.pluginName}:${this.device.deviceName}...`);
|
|
231
343
|
await this.stopServerNode();
|
|
232
344
|
this.serverNode = undefined;
|
|
233
345
|
this.aggregatorNode = undefined;
|
|
234
346
|
await this.stopMatterStorage();
|
|
235
|
-
await this.destroy(false);
|
|
347
|
+
await this.destroy(false); // Do not close mDNS since it is shared
|
|
236
348
|
this.log.debug(`Stopped MatterNode for server device ${this.pluginName}:${this.device.deviceName}`);
|
|
237
349
|
this.log.info('Stopped MatterNode');
|
|
238
350
|
await this.yieldToNode();
|
|
@@ -252,6 +364,7 @@ export class MatterNode extends EventEmitter {
|
|
|
252
364
|
await this.pluginManager.shutdown(this.pluginName, 'Stopping MatterNode');
|
|
253
365
|
this.log.debug(`Stopped plugin ${this.pluginName}`);
|
|
254
366
|
}
|
|
367
|
+
// Stop the dependant MatterNodes
|
|
255
368
|
this.log.debug(`Stopping dependant MatterNodes...`);
|
|
256
369
|
for (const dependantMatterNode of this.dependantMatterNodes.values()) {
|
|
257
370
|
await dependantMatterNode.stop();
|
|
@@ -264,24 +377,46 @@ export class MatterNode extends EventEmitter {
|
|
|
264
377
|
this.log.info('Stopped MatterNode');
|
|
265
378
|
await this.yieldToNode();
|
|
266
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Creates a MatterLogger function to show the matter.js log messages in AnsiLogger (console and frontend).
|
|
382
|
+
* It also logs to file (matter.log) if fileLogger is true.
|
|
383
|
+
*
|
|
384
|
+
* @returns {Function} The MatterLogger function. \x1b[35m for violet \x1b[34m is blue
|
|
385
|
+
*/
|
|
267
386
|
createDestinationMatterLogger() {
|
|
268
|
-
this.matterLog.logNameColor = '\x1b[34m';
|
|
387
|
+
this.matterLog.logNameColor = '\x1b[34m'; // Blue matter.js Logger
|
|
269
388
|
if (this.matterbridge.matterFileLogger) {
|
|
270
389
|
this.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
|
|
271
390
|
}
|
|
272
391
|
return (text, message) => {
|
|
392
|
+
// 2024-08-21 08:55:19.488 DEBUG InteractionMessenger Sending DataReport chunk with 28 attributes and 0 events: 1004 bytes
|
|
273
393
|
const logger = text.slice(44, 44 + 20).trim();
|
|
274
394
|
const msg = text.slice(65);
|
|
275
395
|
this.matterLog.logName = logger;
|
|
276
396
|
this.matterLog.log(MatterLogLevel.names[message.level], msg);
|
|
277
397
|
};
|
|
278
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Starts the matter storage with name Matterbridge and performs a backup.
|
|
401
|
+
*
|
|
402
|
+
* @returns {Promise<void>} - A promise that resolves when the storage is started.
|
|
403
|
+
*/
|
|
279
404
|
async startMatterStorage() {
|
|
405
|
+
// Setup Matter storage
|
|
280
406
|
this.log.info(`Starting matter node storage...`);
|
|
281
407
|
this.matterStorageService = this.environment.get(StorageService);
|
|
282
408
|
this.log.info(`Started matter node storage in ${CYAN}${this.matterStorageService.location}${nf}`);
|
|
409
|
+
// Backup matter storage since it is created/opened correctly
|
|
283
410
|
await this.backupMatterStorage(path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME), path.join(this.matterbridge.matterbridgeDirectory, MATTER_STORAGE_NAME + '.backup'));
|
|
284
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Makes a backup copy of the specified matter storage directory.
|
|
414
|
+
*
|
|
415
|
+
* @param {string} storageName - The name of the storage directory to be backed up.
|
|
416
|
+
* @param {string} backupName - The name of the backup directory to be created.
|
|
417
|
+
* @private
|
|
418
|
+
* @returns {Promise<void>} A promise that resolves when the has been done.
|
|
419
|
+
*/
|
|
285
420
|
async backupMatterStorage(storageName, backupName) {
|
|
286
421
|
this.log.info(`Creating matter node storage backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}...`);
|
|
287
422
|
try {
|
|
@@ -289,6 +424,7 @@ export class MatterNode extends EventEmitter {
|
|
|
289
424
|
this.log.info('Created matter node storage backup');
|
|
290
425
|
}
|
|
291
426
|
catch (error) {
|
|
427
|
+
// istanbul ignore next if
|
|
292
428
|
if (error instanceof Error && error?.code === 'ENOENT') {
|
|
293
429
|
this.log.info(`No matter node storage found to backup from ${CYAN}${storageName}${nf} to ${CYAN}${backupName}${nf}`);
|
|
294
430
|
}
|
|
@@ -297,6 +433,11 @@ export class MatterNode extends EventEmitter {
|
|
|
297
433
|
}
|
|
298
434
|
}
|
|
299
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Stops the matter storage.
|
|
438
|
+
*
|
|
439
|
+
* @returns {Promise<void>} A promise that resolves when the storage is stopped.
|
|
440
|
+
*/
|
|
300
441
|
async stopMatterStorage() {
|
|
301
442
|
this.log.info('Closing matter node storage...');
|
|
302
443
|
await this.matterStorageManager?.close();
|
|
@@ -306,6 +447,21 @@ export class MatterNode extends EventEmitter {
|
|
|
306
447
|
this.log.info('Closed matter node storage');
|
|
307
448
|
this.emit('closed');
|
|
308
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Creates a server node storage context.
|
|
452
|
+
*
|
|
453
|
+
* @param {string} storeId - The storeId.
|
|
454
|
+
* @param {string} deviceName - The name of the device.
|
|
455
|
+
* @param {DeviceTypeId} deviceType - The device type of the device.
|
|
456
|
+
* @param {VendorId} vendorId - The vendor ID.
|
|
457
|
+
* @param {string} vendorName - The vendor name.
|
|
458
|
+
* @param {number} productId - The product ID.
|
|
459
|
+
* @param {string} productName - The product name.
|
|
460
|
+
* @param {string} [serialNumber] - The serial number of the device (optional).
|
|
461
|
+
* @param {string} [uniqueId] - The unique ID of the device (optional).
|
|
462
|
+
* @returns {Promise<StorageContext>} The storage context for the commissioning server.
|
|
463
|
+
* @throws {Error} If the storage service is not initialized.
|
|
464
|
+
*/
|
|
309
465
|
async createServerNodeContext(storeId, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber, uniqueId) {
|
|
310
466
|
if (!this.matterStorageService) {
|
|
311
467
|
throw new Error('No storage service initialized');
|
|
@@ -348,33 +504,52 @@ export class MatterNode extends EventEmitter {
|
|
|
348
504
|
this.log.debug(`- hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
|
|
349
505
|
return storageContext;
|
|
350
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Creates a server node.
|
|
509
|
+
*
|
|
510
|
+
* @param {number} [port] - The port number for the server node. Defaults to 5540.
|
|
511
|
+
* @param {number} [passcode] - The passcode for the server node. Defaults to 20242025.
|
|
512
|
+
* @param {number} [discriminator] - The discriminator for the server node. Defaults to 3850.
|
|
513
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created server node.
|
|
514
|
+
* @throws {Error} If the matter storage context is not created yet.
|
|
515
|
+
*/
|
|
351
516
|
async createServerNode(port = 5540, passcode = 20252026, discriminator = 3850) {
|
|
352
517
|
if (!this.matterStorageContext) {
|
|
353
518
|
throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
|
|
354
519
|
}
|
|
355
520
|
const storeId = await this.matterStorageContext.get('storeId');
|
|
356
521
|
this.log.notice(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
|
|
522
|
+
/**
|
|
523
|
+
* Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
|
|
524
|
+
*/
|
|
357
525
|
const serverNode = await ServerNode.create({
|
|
526
|
+
// Required: Give the Node a unique ID which is used to store the state of this node
|
|
358
527
|
id: storeId,
|
|
528
|
+
// Provide the environment to run this node in
|
|
359
529
|
environment: this.environment,
|
|
530
|
+
// Provide Network relevant configuration like the port
|
|
360
531
|
network: {
|
|
361
532
|
listeningAddressIpv4: this.ipv4Address,
|
|
362
533
|
listeningAddressIpv6: this.ipv6Address,
|
|
363
534
|
port,
|
|
364
535
|
},
|
|
536
|
+
// Provide the certificate for the device
|
|
365
537
|
operationalCredentials: {
|
|
366
538
|
certification: this.certification,
|
|
367
539
|
},
|
|
540
|
+
// Provide Commissioning relevant settings
|
|
368
541
|
commissioning: {
|
|
369
542
|
passcode,
|
|
370
543
|
discriminator,
|
|
371
544
|
},
|
|
545
|
+
// Provide Node announcement settings
|
|
372
546
|
productDescription: {
|
|
373
547
|
name: await this.matterStorageContext.get('deviceName'),
|
|
374
548
|
deviceType: DeviceTypeId(await this.matterStorageContext.get('deviceType')),
|
|
375
549
|
vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
|
|
376
550
|
productId: await this.matterStorageContext.get('productId'),
|
|
377
551
|
},
|
|
552
|
+
// Provide defaults for the BasicInformation cluster on the Root endpoint
|
|
378
553
|
basicInformation: {
|
|
379
554
|
vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
|
|
380
555
|
vendorName: await this.matterStorageContext.get('vendorName'),
|
|
@@ -391,17 +566,23 @@ export class MatterNode extends EventEmitter {
|
|
|
391
566
|
reachable: true,
|
|
392
567
|
},
|
|
393
568
|
});
|
|
569
|
+
/**
|
|
570
|
+
* This event is triggered when the device is initially commissioned successfully.
|
|
571
|
+
* This means: It is added to the first fabric.
|
|
572
|
+
*/
|
|
394
573
|
serverNode.lifecycle.commissioned.on(() => {
|
|
395
574
|
this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`);
|
|
396
575
|
this.advertisingNodes.delete(storeId);
|
|
397
576
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
398
577
|
});
|
|
578
|
+
/** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
|
|
399
579
|
serverNode.lifecycle.decommissioned.on(() => {
|
|
400
580
|
this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`);
|
|
401
581
|
this.advertisingNodes.delete(storeId);
|
|
402
582
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
403
583
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
|
|
404
584
|
});
|
|
585
|
+
/** This event is triggered when the device went online. This means that it is discoverable in the network. */
|
|
405
586
|
serverNode.lifecycle.online.on(async () => {
|
|
406
587
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
407
588
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
@@ -419,6 +600,7 @@ export class MatterNode extends EventEmitter {
|
|
|
419
600
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is online`, timeout: 5, severity: 'success' } });
|
|
420
601
|
this.emit('online', storeId);
|
|
421
602
|
});
|
|
603
|
+
/** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
|
|
422
604
|
serverNode.lifecycle.offline.on(() => {
|
|
423
605
|
this.log.notice(`Server node for ${storeId} is offline`);
|
|
424
606
|
this.advertisingNodes.delete(storeId);
|
|
@@ -426,11 +608,15 @@ export class MatterNode extends EventEmitter {
|
|
|
426
608
|
this.server.request({ type: 'frontend_snackbarmessage', src: 'matter', dst: 'frontend', params: { message: `${storeId} is offline`, timeout: 5, severity: 'warning' } });
|
|
427
609
|
this.emit('offline', storeId);
|
|
428
610
|
});
|
|
611
|
+
/**
|
|
612
|
+
* This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
|
|
613
|
+
* information is needed.
|
|
614
|
+
*/
|
|
429
615
|
serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
|
|
430
616
|
let action = '';
|
|
431
617
|
switch (fabricAction) {
|
|
432
618
|
case FabricAction.Added:
|
|
433
|
-
this.advertisingNodes.delete(storeId);
|
|
619
|
+
this.advertisingNodes.delete(storeId); // The advertising stops when a fabric is added
|
|
434
620
|
action = 'added';
|
|
435
621
|
break;
|
|
436
622
|
case FabricAction.Removed:
|
|
@@ -443,14 +629,22 @@ export class MatterNode extends EventEmitter {
|
|
|
443
629
|
this.log.notice(`Commissioned fabric index ${fabricIndex} ${action} on server node for ${storeId}: ${debugStringify(serverNode.state.commissioning.fabrics[fabricIndex])}`);
|
|
444
630
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
445
631
|
});
|
|
632
|
+
/**
|
|
633
|
+
* This event is triggered when an operative new session was opened by a Controller.
|
|
634
|
+
* It is not triggered for the initial commissioning process, just afterwards for real connections.
|
|
635
|
+
*/
|
|
446
636
|
serverNode.events.sessions.opened.on((session) => {
|
|
447
637
|
this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
|
|
448
638
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
449
639
|
});
|
|
640
|
+
/**
|
|
641
|
+
* This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
|
|
642
|
+
*/
|
|
450
643
|
serverNode.events.sessions.closed.on((session) => {
|
|
451
644
|
this.log.notice(`Session closed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
452
645
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
453
646
|
});
|
|
647
|
+
/** This event is triggered when a subscription gets added or removed on an operative session. */
|
|
454
648
|
serverNode.events.sessions.subscriptionsChanged.on((session) => {
|
|
455
649
|
this.log.notice(`Session subscriptions changed on server node for ${storeId}: ${debugStringify(session)}`);
|
|
456
650
|
this.server.request({ type: 'frontend_refreshrequired', src: 'matter', dst: 'frontend', params: { changed: 'matter', matter: { ...this.getServerNodeData(serverNode) } } });
|
|
@@ -459,6 +653,12 @@ export class MatterNode extends EventEmitter {
|
|
|
459
653
|
this.log.info(`Created server node for ${this.storeId}`);
|
|
460
654
|
return serverNode;
|
|
461
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Gets the matter serializable data of the specified server node.
|
|
658
|
+
*
|
|
659
|
+
* @param {ServerNode} [serverNode] - The server node to start.
|
|
660
|
+
* @returns {ApiMatter} The serializable data of the server node.
|
|
661
|
+
*/
|
|
462
662
|
getServerNodeData(serverNode) {
|
|
463
663
|
const advertiseTime = this.advertisingNodes.get(serverNode.id) || 0;
|
|
464
664
|
return {
|
|
@@ -475,6 +675,13 @@ export class MatterNode extends EventEmitter {
|
|
|
475
675
|
serialNumber: serverNode.state.basicInformation.serialNumber,
|
|
476
676
|
};
|
|
477
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* Starts the specified server node.
|
|
680
|
+
*
|
|
681
|
+
* @param {number} [timeout] - The timeout in milliseconds for starting the server node. Defaults to 30 seconds.
|
|
682
|
+
* @returns {Promise<void>} A promise that resolves when the server node has started.
|
|
683
|
+
* @throws {Error} If the server node is not created yet.
|
|
684
|
+
*/
|
|
478
685
|
async startServerNode(timeout = 30000) {
|
|
479
686
|
if (!this.serverNode) {
|
|
480
687
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
@@ -485,9 +692,17 @@ export class MatterNode extends EventEmitter {
|
|
|
485
692
|
this.log.notice(`Started ${this.serverNode.id} server node`);
|
|
486
693
|
}
|
|
487
694
|
catch (error) {
|
|
695
|
+
// istanbul ignore next
|
|
488
696
|
this.log.error(`Failed to start ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
489
697
|
}
|
|
490
698
|
}
|
|
699
|
+
/**
|
|
700
|
+
* Stops the specified server node.
|
|
701
|
+
*
|
|
702
|
+
* @param {number} [timeout] - The timeout in milliseconds for stopping the server node. Defaults to 30 seconds.
|
|
703
|
+
* @returns {Promise<void>} A promise that resolves when the server node has stopped.
|
|
704
|
+
* @throws {Error} If the server node is not created yet.
|
|
705
|
+
*/
|
|
491
706
|
async stopServerNode(timeout = 30000) {
|
|
492
707
|
if (!this.serverNode) {
|
|
493
708
|
throw new Error('Matter server node not created yet. Call create() first.');
|
|
@@ -498,9 +713,16 @@ export class MatterNode extends EventEmitter {
|
|
|
498
713
|
this.log.info(`Closed ${this.serverNode.id} server node`);
|
|
499
714
|
}
|
|
500
715
|
catch (error) {
|
|
716
|
+
// istanbul ignore next
|
|
501
717
|
this.log.error(`Failed to close ${this.serverNode.id} server node: ${error instanceof Error ? error.message : error}`);
|
|
502
718
|
}
|
|
503
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* Creates an aggregator node with the specified storage context.
|
|
722
|
+
*
|
|
723
|
+
* @returns {Promise<Endpoint<AggregatorEndpoint>>} A promise that resolves to the created aggregator node.
|
|
724
|
+
* @throws {Error} If the matter storage context is not created yet.
|
|
725
|
+
*/
|
|
504
726
|
async createAggregatorNode() {
|
|
505
727
|
if (!this.matterStorageContext) {
|
|
506
728
|
throw new Error('Matter server node context not created yet. Call createServerNodeContext() first.');
|
|
@@ -510,9 +732,16 @@ export class MatterNode extends EventEmitter {
|
|
|
510
732
|
this.log.info(`Created ${await this.matterStorageContext.get('storeId')} aggregator`);
|
|
511
733
|
return aggregatorNode;
|
|
512
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Creates the matterbridge server node.
|
|
737
|
+
*
|
|
738
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint>>} A promise that resolves to the created matterbridge server node.
|
|
739
|
+
*/
|
|
513
740
|
async createMatterbridgeServerNode() {
|
|
514
741
|
this.log.debug(`Creating ${plg}Matterbridge${db} server node...`);
|
|
515
|
-
this.matterStorageContext = await this.createServerNodeContext('Matterbridge',
|
|
742
|
+
this.matterStorageContext = await this.createServerNodeContext('Matterbridge', // storeId
|
|
743
|
+
'Matterbridge', // deviceName
|
|
744
|
+
this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, this.aggregatorSerialNumber, this.aggregatorUniqueId);
|
|
516
745
|
this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
517
746
|
this.aggregatorNode = await this.createAggregatorNode();
|
|
518
747
|
this.log.debug(`Adding ${plg}Matterbridge${db} aggregator node...`);
|
|
@@ -523,6 +752,13 @@ export class MatterNode extends EventEmitter {
|
|
|
523
752
|
this.log.debug(`Created ${plg}Matterbridge${db} server node`);
|
|
524
753
|
return this.serverNode;
|
|
525
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Creates and configures the server node for an accessory plugin for a given device.
|
|
757
|
+
*
|
|
758
|
+
* @param {Plugin | PluginName} plugin - The plugin to configure.
|
|
759
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
760
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the accessory plugin.
|
|
761
|
+
*/
|
|
526
762
|
async createAccessoryPlugin(plugin, device) {
|
|
527
763
|
if (typeof plugin === 'string') {
|
|
528
764
|
const _plugin = this.pluginManager.get(plugin);
|
|
@@ -544,6 +780,12 @@ export class MatterNode extends EventEmitter {
|
|
|
544
780
|
}
|
|
545
781
|
return this.serverNode;
|
|
546
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* Creates and configures the server node and the aggregator node for a dynamic plugin.
|
|
785
|
+
*
|
|
786
|
+
* @param {Plugin | PluginName} plugin - The plugin to configure.
|
|
787
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the dynamic plugin.
|
|
788
|
+
*/
|
|
547
789
|
async createDynamicPlugin(plugin) {
|
|
548
790
|
if (typeof plugin === 'string') {
|
|
549
791
|
const _plugin = this.pluginManager.get(plugin);
|
|
@@ -567,6 +809,13 @@ export class MatterNode extends EventEmitter {
|
|
|
567
809
|
}
|
|
568
810
|
return this.serverNode;
|
|
569
811
|
}
|
|
812
|
+
/**
|
|
813
|
+
* Creates and configures the server node for a single not bridged device.
|
|
814
|
+
*
|
|
815
|
+
* @param {Plugin | PluginName} plugin - The plugin to configure.
|
|
816
|
+
* @param {MatterbridgeEndpoint} device - The device to associate with the plugin.
|
|
817
|
+
* @returns {Promise<ServerNode<ServerNode.RootEndpoint> | undefined>} A promise that resolves to the server node for the device with mode server.
|
|
818
|
+
*/
|
|
570
819
|
async createDeviceServerNode(plugin, device) {
|
|
571
820
|
if (typeof plugin === 'string') {
|
|
572
821
|
const _plugin = this.pluginManager.get(plugin);
|
|
@@ -587,13 +836,22 @@ export class MatterNode extends EventEmitter {
|
|
|
587
836
|
}
|
|
588
837
|
return this.serverNode;
|
|
589
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Adds a MatterbridgeEndpoint to the specified plugin.
|
|
841
|
+
*
|
|
842
|
+
* @param {string} pluginName - The name of the plugin.
|
|
843
|
+
* @param {MatterbridgeEndpoint} device - The device to add as a bridged endpoint.
|
|
844
|
+
* @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the added bridged endpoint, or undefined if there was an error.
|
|
845
|
+
*/
|
|
590
846
|
async addBridgedEndpoint(pluginName, device) {
|
|
847
|
+
// Check if the plugin is registered
|
|
591
848
|
const plugin = this.pluginManager.get(pluginName);
|
|
592
849
|
if (!plugin)
|
|
593
850
|
throw new Error(`Error adding bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
|
|
594
851
|
if (device.mode === 'server') {
|
|
595
852
|
try {
|
|
596
853
|
this.log.debug(`Creating MatterNode for device ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
854
|
+
// Create the MatterNode to manage the device
|
|
597
855
|
const matterNode = new MatterNode(this.matterbridge, pluginName, device);
|
|
598
856
|
this.dependantMatterNodes.set(device.id, matterNode);
|
|
599
857
|
await matterNode.create();
|
|
@@ -606,6 +864,7 @@ export class MatterNode extends EventEmitter {
|
|
|
606
864
|
}
|
|
607
865
|
else if (this.matterbridge.bridgeMode === 'bridge') {
|
|
608
866
|
if (device.mode === 'matter') {
|
|
867
|
+
// Register and add the device to the Matter server node
|
|
609
868
|
this.log.debug(`Adding matter endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
610
869
|
if (!this.serverNode)
|
|
611
870
|
throw new Error(`Server node not found for matter endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
|
|
@@ -618,6 +877,7 @@ export class MatterNode extends EventEmitter {
|
|
|
618
877
|
}
|
|
619
878
|
}
|
|
620
879
|
else {
|
|
880
|
+
// Register and add the device to the Matter aggregator node
|
|
621
881
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
622
882
|
if (!this.aggregatorNode)
|
|
623
883
|
throw new Error(`Aggregator node not found for endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er})`);
|
|
@@ -631,6 +891,7 @@ export class MatterNode extends EventEmitter {
|
|
|
631
891
|
}
|
|
632
892
|
}
|
|
633
893
|
else if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
894
|
+
// Register and add the device to the plugin server node
|
|
634
895
|
if (plugin.type === 'AccessoryPlatform') {
|
|
635
896
|
try {
|
|
636
897
|
this.log.debug(`Adding accessory endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
@@ -646,10 +907,12 @@ export class MatterNode extends EventEmitter {
|
|
|
646
907
|
return;
|
|
647
908
|
}
|
|
648
909
|
}
|
|
910
|
+
// Register and add the device to the plugin aggregator node
|
|
649
911
|
if (plugin.type === 'DynamicPlatform') {
|
|
650
912
|
try {
|
|
651
913
|
this.log.debug(`Adding bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
652
914
|
if (!this.serverNode) {
|
|
915
|
+
// Fast plugins can add another device before the server node is ready, so we wait for the server node to be ready
|
|
653
916
|
await this.createDynamicPlugin(plugin);
|
|
654
917
|
}
|
|
655
918
|
if (device.mode === 'matter')
|
|
@@ -665,18 +928,30 @@ export class MatterNode extends EventEmitter {
|
|
|
665
928
|
}
|
|
666
929
|
if (plugin.registeredDevices !== undefined)
|
|
667
930
|
plugin.registeredDevices++;
|
|
931
|
+
// Add the device to the DeviceManager
|
|
668
932
|
await this.server.fetch({ type: 'devices_set', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
933
|
+
// Add the device to the DeviceManager
|
|
669
934
|
await device.construction.ready;
|
|
935
|
+
// Subscribe to the attributes changed event
|
|
670
936
|
await this.subscribeAttributeChanged(plugin, device);
|
|
671
937
|
this.log.info(`Added endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
|
|
672
938
|
await this.yieldToNode(10);
|
|
673
939
|
return device;
|
|
674
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Removes a MatterbridgeEndpoint from the specified plugin.
|
|
943
|
+
*
|
|
944
|
+
* @param {string} pluginName - The name of the plugin.
|
|
945
|
+
* @param {MatterbridgeEndpoint} device - The device to remove as a bridged endpoint.
|
|
946
|
+
* @returns {Promise<MatterbridgeEndpoint | undefined>} A promise that resolves to the removed bridged endpoint, or undefined if there was an error.
|
|
947
|
+
*/
|
|
675
948
|
async removeBridgedEndpoint(pluginName, device) {
|
|
676
949
|
this.log.debug(`Removing bridged endpoint ${plg}${pluginName}${db}:${dev}${device.deviceName}${db} (${zb}${device.name}${db})...`);
|
|
950
|
+
// Check if the plugin is registered
|
|
677
951
|
const plugin = this.pluginManager.get(pluginName);
|
|
678
952
|
if (!plugin)
|
|
679
953
|
throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): plugin not found`);
|
|
954
|
+
// Remove the device from the Matter aggregator node
|
|
680
955
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
681
956
|
if (!this.aggregatorNode)
|
|
682
957
|
throw new Error(`Error removing bridged endpoint ${plg}${pluginName}${er}:${dev}${device.deviceName}${er} (${zb}${device.name}${er}): aggregator node not found`);
|
|
@@ -697,11 +972,25 @@ export class MatterNode extends EventEmitter {
|
|
|
697
972
|
this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${device.name}${nf})`);
|
|
698
973
|
if (plugin.registeredDevices !== undefined)
|
|
699
974
|
plugin.registeredDevices--;
|
|
975
|
+
// Remove the device from the DeviceManager
|
|
700
976
|
await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
701
977
|
await this.yieldToNode(10);
|
|
702
978
|
return device;
|
|
703
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
|
+
*/
|
|
704
992
|
async removeAllBridgedEndpoints(pluginName, delay = 0) {
|
|
993
|
+
// Check if the plugin is registered
|
|
705
994
|
const plugin = this.pluginManager.get(pluginName);
|
|
706
995
|
if (!plugin)
|
|
707
996
|
throw new Error(`Error removing all bridged endpoints for plugin ${plg}${pluginName}${er}: plugin not found`);
|
|
@@ -716,6 +1005,7 @@ export class MatterNode extends EventEmitter {
|
|
|
716
1005
|
this.log.info(`Removed bridged endpoint #${plugin.registeredDevices} ${plg}${pluginName}${nf}:${dev}${device.deviceName}${nf} (${zb}${endpoint?.name}${nf})`);
|
|
717
1006
|
if (plugin.registeredDevices !== undefined)
|
|
718
1007
|
plugin.registeredDevices--;
|
|
1008
|
+
// Remove the device from the DeviceManager
|
|
719
1009
|
await this.server.fetch({ type: 'devices_remove', src: this.server.name, dst: 'devices', params: { device: toBaseDevice(device) } });
|
|
720
1010
|
await this.yieldToNode(10);
|
|
721
1011
|
if (delay > 0)
|
|
@@ -724,6 +1014,25 @@ export class MatterNode extends EventEmitter {
|
|
|
724
1014
|
if (delay > 0)
|
|
725
1015
|
await wait(Number(process.env['MATTERBRIDGE_REMOVE_ALL_ENDPOINT_TIMEOUT_MS']) || 2000);
|
|
726
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
|
+
*/
|
|
727
1036
|
async addVirtualEndpoint(pluginName, name, type, callback) {
|
|
728
1037
|
this.log.debug(`Creating virtual device ${plg}${pluginName}${db}:${dev}${name}${db}...`);
|
|
729
1038
|
const plugin = this.pluginManager.get(pluginName);
|
|
@@ -748,10 +1057,20 @@ export class MatterNode extends EventEmitter {
|
|
|
748
1057
|
await this.yieldToNode(10);
|
|
749
1058
|
return true;
|
|
750
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
|
+
*/
|
|
751
1069
|
async subscribeAttributeChanged(plugin, device) {
|
|
752
1070
|
if (!plugin || !device || !device.plugin || !device.serialNumber || !device.uniqueId || !device.maybeNumber)
|
|
753
1071
|
return;
|
|
754
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
|
|
755
1074
|
if (this.matterbridge.bridgeMode === 'childbridge' && plugin.type === 'AccessoryPlatform' && this.serverNode) {
|
|
756
1075
|
this.serverNode.eventsOf(BasicInformationServer).reachable$Changed?.on((reachable) => {
|
|
757
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'}`);
|
|
@@ -759,6 +1078,7 @@ export class MatterNode extends EventEmitter {
|
|
|
759
1078
|
type: 'frontend_attributechanged',
|
|
760
1079
|
src: 'matter',
|
|
761
1080
|
dst: 'frontend',
|
|
1081
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
762
1082
|
params: { plugin: device.plugin, serialNumber: device.serialNumber, uniqueId: device.uniqueId, number: device.number, id: device.id, cluster: 'BasicInformation', attribute: 'reachable', value: reachable },
|
|
763
1083
|
});
|
|
764
1084
|
});
|
|
@@ -811,6 +1131,7 @@ export class MatterNode extends EventEmitter {
|
|
|
811
1131
|
type: 'frontend_attributechanged',
|
|
812
1132
|
src: 'matter',
|
|
813
1133
|
dst: 'frontend',
|
|
1134
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
814
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 },
|
|
815
1136
|
});
|
|
816
1137
|
});
|
|
@@ -824,6 +1145,7 @@ export class MatterNode extends EventEmitter {
|
|
|
824
1145
|
type: 'frontend_attributechanged',
|
|
825
1146
|
src: 'matter',
|
|
826
1147
|
dst: 'frontend',
|
|
1148
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
827
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 },
|
|
828
1150
|
});
|
|
829
1151
|
});
|
|
@@ -831,6 +1153,12 @@ export class MatterNode extends EventEmitter {
|
|
|
831
1153
|
}
|
|
832
1154
|
}
|
|
833
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
|
+
*/
|
|
834
1162
|
sanitizeFabricInformations(fabricInfo) {
|
|
835
1163
|
return fabricInfo.map((info) => {
|
|
836
1164
|
return {
|
|
@@ -844,6 +1172,12 @@ export class MatterNode extends EventEmitter {
|
|
|
844
1172
|
};
|
|
845
1173
|
});
|
|
846
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
|
+
*/
|
|
847
1181
|
sanitizeSessionInformation(sessions) {
|
|
848
1182
|
return sessions
|
|
849
1183
|
.filter((session) => session.isPeerActive)
|
|
@@ -870,10 +1204,21 @@ export class MatterNode extends EventEmitter {
|
|
|
870
1204
|
};
|
|
871
1205
|
});
|
|
872
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
|
+
*/
|
|
873
1212
|
async setServerReachability(reachable) {
|
|
874
1213
|
await this.serverNode?.setStateOf(BasicInformationServer, { reachable });
|
|
875
1214
|
this.serverNode?.act((agent) => this.serverNode?.eventsOf(BasicInformationServer).reachableChanged?.emit({ reachableNewValue: reachable }, agent.context));
|
|
876
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
|
+
*/
|
|
877
1222
|
async setAggregatorReachability(aggregatorNode, reachable) {
|
|
878
1223
|
for (const child of aggregatorNode.parts) {
|
|
879
1224
|
this.log.debug(`Setting reachability of ${child?.deviceName} to ${reachable}`);
|
|
@@ -919,19 +1264,35 @@ export class MatterNode extends EventEmitter {
|
|
|
919
1264
|
case 0x1488:
|
|
920
1265
|
vendorName = '(ShortcutLabsFlic)';
|
|
921
1266
|
break;
|
|
922
|
-
case 65521:
|
|
1267
|
+
case 65521: // 0xFFF1
|
|
923
1268
|
vendorName = '(MatterTest)';
|
|
924
1269
|
break;
|
|
925
1270
|
}
|
|
926
1271
|
return vendorName;
|
|
927
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
|
+
*/
|
|
928
1285
|
async yieldToNode(timeout = 100) {
|
|
1286
|
+
// 1. Let all currently queued microtasks run
|
|
929
1287
|
await Promise.resolve();
|
|
1288
|
+
// 2. Yield to the next event-loop turn (macrotask + its microtasks)
|
|
930
1289
|
await new Promise((resolve) => {
|
|
931
1290
|
setImmediate(resolve);
|
|
932
1291
|
});
|
|
1292
|
+
// 3. Pause a bit to allow other macrotasks to run
|
|
933
1293
|
await new Promise((resolve) => {
|
|
934
1294
|
setTimeout(resolve, Math.min(timeout, 10));
|
|
935
1295
|
});
|
|
936
1296
|
}
|
|
937
1297
|
}
|
|
1298
|
+
//# sourceMappingURL=matterNode.js.map
|