matterbridge 1.6.0 → 1.6.2

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.
Files changed (148) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/README-DEV.md +0 -4
  3. package/README-NGINX.md +63 -0
  4. package/README.md +8 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +18 -8
  7. package/dist/cli.js.map +1 -1
  8. package/dist/defaultConfigSchema.d.ts +1 -1
  9. package/dist/defaultConfigSchema.js +1 -1
  10. package/dist/deviceManager.d.ts +2 -2
  11. package/dist/deviceManager.d.ts.map +1 -1
  12. package/dist/deviceManager.js +2 -2
  13. package/dist/deviceManager.js.map +1 -1
  14. package/dist/index.d.ts +10 -10
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +24 -11
  17. package/dist/index.js.map +1 -1
  18. package/dist/matter/export.d.ts +5 -0
  19. package/dist/matter/export.d.ts.map +1 -0
  20. package/dist/matter/export.js +5 -0
  21. package/dist/matter/export.js.map +1 -0
  22. package/dist/matterbridge.d.ts +32 -16
  23. package/dist/matterbridge.d.ts.map +1 -1
  24. package/dist/matterbridge.js +254 -115
  25. package/dist/matterbridge.js.map +1 -1
  26. package/dist/matterbridgeAccessoryPlatform.d.ts +1 -1
  27. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  28. package/dist/matterbridgeAccessoryPlatform.js +1 -1
  29. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  30. package/dist/matterbridgeBehaviors.d.ts +1123 -0
  31. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  32. package/dist/matterbridgeBehaviors.js +281 -0
  33. package/dist/matterbridgeBehaviors.js.map +1 -0
  34. package/dist/matterbridgeDevice.d.ts +2069 -1511
  35. package/dist/matterbridgeDevice.d.ts.map +1 -1
  36. package/dist/matterbridgeDevice.js +192 -196
  37. package/dist/matterbridgeDevice.js.map +1 -1
  38. package/dist/matterbridgeDeviceTypes.d.ts +58 -0
  39. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  40. package/dist/matterbridgeDeviceTypes.js +308 -0
  41. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  42. package/dist/matterbridgeDynamicPlatform.d.ts +1 -1
  43. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  44. package/dist/matterbridgeDynamicPlatform.js +1 -1
  45. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  46. package/dist/matterbridgeEdge.d.ts +20 -21
  47. package/dist/matterbridgeEdge.d.ts.map +1 -1
  48. package/dist/matterbridgeEdge.js +506 -103
  49. package/dist/matterbridgeEdge.js.map +1 -1
  50. package/dist/matterbridgeEndpoint.d.ts +3545 -2336
  51. package/dist/matterbridgeEndpoint.d.ts.map +1 -1
  52. package/dist/matterbridgeEndpoint.js +672 -468
  53. package/dist/matterbridgeEndpoint.js.map +1 -1
  54. package/dist/matterbridgePlatform.d.ts +5 -4
  55. package/dist/matterbridgePlatform.d.ts.map +1 -1
  56. package/dist/matterbridgePlatform.js +11 -3
  57. package/dist/matterbridgePlatform.js.map +1 -1
  58. package/dist/matterbridgeTypes.d.ts +7 -8
  59. package/dist/matterbridgeTypes.d.ts.map +1 -1
  60. package/dist/matterbridgeTypes.js.map +1 -1
  61. package/dist/matterbridgeWebsocket.d.ts +1 -1
  62. package/dist/matterbridgeWebsocket.d.ts.map +1 -1
  63. package/dist/matterbridgeWebsocket.js +41 -3
  64. package/dist/matterbridgeWebsocket.js.map +1 -1
  65. package/dist/pluginManager.d.ts +1 -1
  66. package/dist/pluginManager.d.ts.map +1 -1
  67. package/dist/pluginManager.js +17 -8
  68. package/dist/pluginManager.js.map +1 -1
  69. package/dist/utils/colorUtils.d.ts +1 -1
  70. package/dist/utils/colorUtils.js +2 -2
  71. package/dist/utils/colorUtils.js.map +1 -1
  72. package/dist/utils/utils.d.ts +42 -1
  73. package/dist/utils/utils.d.ts.map +1 -1
  74. package/dist/utils/utils.js +105 -3
  75. package/dist/utils/utils.js.map +1 -1
  76. package/frontend/build/asset-manifest.json +62 -6
  77. package/frontend/build/index.html +1 -1
  78. package/frontend/build/static/css/main.823e08b6.css +2 -0
  79. package/frontend/build/static/css/main.823e08b6.css.map +1 -0
  80. package/frontend/build/static/js/main.a14c87e7.js +115 -0
  81. package/frontend/build/static/js/{main.045d08f7.js.LICENSE.txt → main.a14c87e7.js.LICENSE.txt} +3 -3
  82. package/frontend/build/static/js/main.a14c87e7.js.map +1 -0
  83. package/frontend/build/static/media/roboto-cyrillic-300-normal.1b79538ccd585c259996.woff2 +0 -0
  84. package/frontend/build/static/media/roboto-cyrillic-300-normal.5f077fd7b977d1715acf.woff +0 -0
  85. package/frontend/build/static/media/roboto-cyrillic-400-normal.5d2930082227d172f62c.woff +0 -0
  86. package/frontend/build/static/media/roboto-cyrillic-400-normal.a9e19870cf6c4b973427.woff2 +0 -0
  87. package/frontend/build/static/media/roboto-cyrillic-500-normal.0ae2428323939af5e1ad.woff2 +0 -0
  88. package/frontend/build/static/media/roboto-cyrillic-500-normal.dd7bc8a52c6c70c5a3f5.woff +0 -0
  89. package/frontend/build/static/media/roboto-cyrillic-700-normal.3f6e1548bd5175a8c342.woff +0 -0
  90. package/frontend/build/static/media/roboto-cyrillic-700-normal.4fdfc29a10e7d4b7c527.woff2 +0 -0
  91. package/frontend/build/static/media/roboto-cyrillic-ext-300-normal.795dbc8140e3fef82983.woff +0 -0
  92. package/frontend/build/static/media/roboto-cyrillic-ext-300-normal.80947a31d23c70204b47.woff2 +0 -0
  93. package/frontend/build/static/media/roboto-cyrillic-ext-400-normal.135d076fa32aa0b4d105.woff +0 -0
  94. package/frontend/build/static/media/roboto-cyrillic-ext-400-normal.5cec61a21cc20180fbe1.woff2 +0 -0
  95. package/frontend/build/static/media/roboto-cyrillic-ext-500-normal.6de16332fda843a3dc3d.woff2 +0 -0
  96. package/frontend/build/static/media/roboto-cyrillic-ext-500-normal.c0a0638f90b31d6454ba.woff +0 -0
  97. package/frontend/build/static/media/roboto-cyrillic-ext-700-normal.4750292c47fa2bc6ac1a.woff2 +0 -0
  98. package/frontend/build/static/media/roboto-cyrillic-ext-700-normal.ca247189fc12d00de361.woff +0 -0
  99. package/frontend/build/static/media/roboto-greek-300-normal.285f3e6261d8eb20417d.woff2 +0 -0
  100. package/frontend/build/static/media/roboto-greek-300-normal.889beddda1c9bd9f97df.woff +0 -0
  101. package/frontend/build/static/media/roboto-greek-400-normal.160a791a8e4f46bca3cc.woff +0 -0
  102. package/frontend/build/static/media/roboto-greek-400-normal.2c32b1315be61477013a.woff2 +0 -0
  103. package/frontend/build/static/media/roboto-greek-500-normal.60810e07c7b0273013aa.woff +0 -0
  104. package/frontend/build/static/media/roboto-greek-500-normal.f95e757c5483310f9c11.woff2 +0 -0
  105. package/frontend/build/static/media/roboto-greek-700-normal.77dd370f2001e184ba0d.woff2 +0 -0
  106. package/frontend/build/static/media/roboto-greek-700-normal.df87b053fae3d7ad5f7a.woff +0 -0
  107. package/frontend/build/static/media/roboto-greek-ext-300-normal.b590dbe5c639944366d1.woff +0 -0
  108. package/frontend/build/static/media/roboto-greek-ext-300-normal.d6049cb54aa6fbe14c42.woff2 +0 -0
  109. package/frontend/build/static/media/roboto-greek-ext-400-normal.16eb83b4a3b1ea994243.woff +0 -0
  110. package/frontend/build/static/media/roboto-greek-ext-400-normal.1df4abad55796d11a0c8.woff2 +0 -0
  111. package/frontend/build/static/media/roboto-greek-ext-500-normal.4a96ba31abcce0f5d52b.woff2 +0 -0
  112. package/frontend/build/static/media/roboto-greek-ext-500-normal.fd28d9c008bf3af1bed7.woff +0 -0
  113. package/frontend/build/static/media/roboto-greek-ext-700-normal.2dd6febad11502dec6a6.woff2 +0 -0
  114. package/frontend/build/static/media/roboto-greek-ext-700-normal.4abdc9fff4507f17d726.woff +0 -0
  115. package/frontend/build/static/media/roboto-latin-300-normal.b850f1ff581ea232fac9.woff2 +0 -0
  116. package/frontend/build/static/media/roboto-latin-300-normal.c4bc0593c9954d79cb3a.woff +0 -0
  117. package/frontend/build/static/media/roboto-latin-400-normal.047a7839f69b209db815.woff +0 -0
  118. package/frontend/build/static/media/roboto-latin-400-normal.297d48e1b5a10c0831a9.woff2 +0 -0
  119. package/frontend/build/static/media/roboto-latin-500-normal.68d40d6d01c6f85d24ba.woff +0 -0
  120. package/frontend/build/static/media/roboto-latin-500-normal.7077203b1982951ecf76.woff2 +0 -0
  121. package/frontend/build/static/media/roboto-latin-700-normal.4535474e1cf8598695ad.woff2 +0 -0
  122. package/frontend/build/static/media/roboto-latin-700-normal.9f6a16a7770c87b2042b.woff +0 -0
  123. package/frontend/build/static/media/roboto-latin-ext-300-normal.14982a9e4857a93b6dce.woff +0 -0
  124. package/frontend/build/static/media/roboto-latin-ext-300-normal.97cbc447d4a8d41a9543.woff2 +0 -0
  125. package/frontend/build/static/media/roboto-latin-ext-400-normal.27da5b36b6d3a16f53f4.woff +0 -0
  126. package/frontend/build/static/media/roboto-latin-ext-400-normal.2eeae187764baf05867d.woff2 +0 -0
  127. package/frontend/build/static/media/roboto-latin-ext-500-normal.06c30711d588145a4541.woff +0 -0
  128. package/frontend/build/static/media/roboto-latin-ext-500-normal.9a18d7bb9ff7a6af7b32.woff2 +0 -0
  129. package/frontend/build/static/media/roboto-latin-ext-700-normal.18841836e391d39e83a8.woff2 +0 -0
  130. package/frontend/build/static/media/roboto-latin-ext-700-normal.3c5bcdd0e69c4c3ffafe.woff +0 -0
  131. package/frontend/build/static/media/roboto-vietnamese-300-normal.c96b16e5c05c7b7c3e89.woff2 +0 -0
  132. package/frontend/build/static/media/roboto-vietnamese-300-normal.f5e7cea32756dfe7af40.woff +0 -0
  133. package/frontend/build/static/media/roboto-vietnamese-400-normal.0dc97c66f9b542d6fa17.woff +0 -0
  134. package/frontend/build/static/media/roboto-vietnamese-400-normal.d3f8e26d6c27de8102b6.woff2 +0 -0
  135. package/frontend/build/static/media/roboto-vietnamese-500-normal.090fabef926bdc0e9b9f.woff2 +0 -0
  136. package/frontend/build/static/media/roboto-vietnamese-500-normal.23b7b8a2524d2d4b637b.woff +0 -0
  137. package/frontend/build/static/media/roboto-vietnamese-700-normal.0a79a9fabfc32e33f360.woff2 +0 -0
  138. package/frontend/build/static/media/roboto-vietnamese-700-normal.35ed0597568ff6f19c16.woff +0 -0
  139. package/npm-shrinkwrap.json +120 -39
  140. package/package.json +8 -3
  141. package/dist/matterbridgeController.d.ts +0 -24
  142. package/dist/matterbridgeController.d.ts.map +0 -1
  143. package/dist/matterbridgeController.js +0 -386
  144. package/dist/matterbridgeController.js.map +0 -1
  145. package/frontend/build/static/css/main.1cf003ae.css +0 -2
  146. package/frontend/build/static/css/main.1cf003ae.css.map +0 -1
  147. package/frontend/build/static/js/main.045d08f7.js +0 -3
  148. package/frontend/build/static/js/main.045d08f7.js.map +0 -1
@@ -6,7 +6,7 @@
6
6
  * @date 2024-10-01
7
7
  * @version 1.0.0
8
8
  *
9
- * Copyright 2024 Luca Liguori.
9
+ * Copyright 2024, 2025, 2026 Luca Liguori.
10
10
  *
11
11
  * Licensed under the Apache License, Version 2.0 (the "License");
12
12
  * you may not use this file except in compliance with the License.
@@ -21,81 +21,124 @@
21
21
  * limitations under the License. *
22
22
  */
23
23
  /* eslint-disable @typescript-eslint/no-unused-vars */
24
- // New API imports
25
- import { Endpoint } from '@project-chip/matter.js/endpoint';
26
- import { MutableEndpoint } from '@project-chip/matter.js/endpoint/type';
27
- import { SupportedBehaviors } from '@project-chip/matter.js/endpoint/properties';
28
- import { DescriptorServer } from '@project-chip/matter.js/behavior/definitions/descriptor';
29
- import { IdentifyServer } from '@project-chip/matter.js/behavior/definitions/identify';
30
- import { GroupsServer } from '@project-chip/matter.js/behavior/definitions/groups';
31
- // import { ScenesServer, ScenesBehavior } from '@project-chip/matter.js/behavior/definitions/scenes';
32
- import { OnOffServer } from '@project-chip/matter.js/behavior/definitions/on-off';
33
- import { TemperatureMeasurementServer } from '@project-chip/matter.js/behavior/definitions/temperature-measurement';
34
- import { RelativeHumidityMeasurementServer } from '@project-chip/matter.js/behavior/definitions/relative-humidity-measurement';
35
- import { PressureMeasurementServer } from '@project-chip/matter.js/behavior/definitions/pressure-measurement';
36
- import { BridgedDeviceBasicInformationServer } from '@project-chip/matter.js/behavior/definitions/bridged-device-basic-information';
37
- import { AirQuality, AirQualityCluster, BasicInformationCluster, BooleanState, BooleanStateCluster, BooleanStateConfiguration, BooleanStateConfigurationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, CarbonDioxideConcentrationMeasurement, CarbonDioxideConcentrationMeasurementCluster, CarbonMonoxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurementCluster, ClusterServer, ColorControl, ColorControlCluster, ConcentrationMeasurement, DoorLock, DoorLockCluster, ElectricalEnergyMeasurement, ElectricalEnergyMeasurementCluster, ElectricalPowerMeasurement, ElectricalPowerMeasurementCluster, FanControl, FanControlCluster, FlowMeasurement, FlowMeasurementCluster, FormaldehydeConcentrationMeasurement, FormaldehydeConcentrationMeasurementCluster, Groups, GroupsCluster, GroupsClusterHandler, Identify, IdentifyCluster, IlluminanceMeasurement, IlluminanceMeasurementCluster, LevelControl, LevelControlCluster, MeasurementType, ModeSelectCluster, NitrogenDioxideConcentrationMeasurement, NitrogenDioxideConcentrationMeasurementCluster, OccupancySensing, OccupancySensingCluster, OnOff, OnOffCluster, OzoneConcentrationMeasurement, OzoneConcentrationMeasurementCluster, Pm10ConcentrationMeasurement, Pm10ConcentrationMeasurementCluster, Pm1ConcentrationMeasurement, Pm1ConcentrationMeasurementCluster, Pm25ConcentrationMeasurement, Pm25ConcentrationMeasurementCluster, PowerSource, PowerSourceCluster, PowerSourceConfigurationCluster, PowerTopology, PowerTopologyCluster, PressureMeasurement, PressureMeasurementCluster, RadonConcentrationMeasurement, RadonConcentrationMeasurementCluster, RelativeHumidityMeasurement, RelativeHumidityMeasurementCluster, SmokeCoAlarm, SmokeCoAlarmCluster, Switch, SwitchCluster, TemperatureMeasurement, TemperatureMeasurementCluster, Thermostat, ThermostatCluster, ThreadNetworkDiagnostics, ThreadNetworkDiagnosticsCluster, TimeSynchronization, TimeSynchronizationCluster, TotalVolatileOrganicCompoundsConcentrationMeasurement, TotalVolatileOrganicCompoundsConcentrationMeasurementCluster, WindowCovering, WindowCoveringCluster, getClusterNameById, } from '@project-chip/matter-node.js/cluster';
38
- import { NamedHandler } from '@project-chip/matter-node.js/util';
39
- import { EndpointNumber, VendorId } from '@project-chip/matter-node.js/datatype';
40
- // Matterbridge imports
41
- import { AnsiLogger, YELLOW, db, debugStringify, er, hk, or, rs, zb } from 'node-ansi-logger';
24
+ // Node.js modules
42
25
  import { createHash } from 'crypto';
43
- import { Specification } from '@project-chip/matter-node.js/model';
44
- import { LevelControlServer } from '@project-chip/matter.js/behaviors/level-control';
45
- import { ColorControlServer } from '@project-chip/matter.js/behaviors/color-control';
46
- import { FlowMeasurementServer } from '@project-chip/matter.js/behaviors/flow-measurement';
47
- import { DoorLockServer } from '@project-chip/matter.js/behaviors/door-lock';
48
- import { ThermostatServer } from '@project-chip/matter.js/behaviors/thermostat';
49
- import { WindowCoveringServer } from '@project-chip/matter.js/behaviors/window-covering';
50
- import { FanControlServer } from '@project-chip/matter.js/behaviors/fan-control';
51
- import { TimeSynchronizationServer } from '@project-chip/matter.js/behaviors/time-synchronization';
52
- import { IlluminanceMeasurementServer } from '@project-chip/matter.js/behaviors/illuminance-measurement';
53
- import { BooleanStateServer } from '@project-chip/matter.js/behaviors/boolean-state';
54
- import { BooleanStateConfigurationServer } from '@project-chip/matter.js/behaviors/boolean-state-configuration';
55
- import { OccupancySensingServer } from '@project-chip/matter.js/behaviors/occupancy-sensing';
26
+ // AnsiLogger module
27
+ import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, rs, zb } from 'node-ansi-logger';
28
+ // Matterbridge
29
+ import { MatterbridgeBehavior, MatterbridgeBehaviorDevice, MatterbridgeBooleanStateConfigurationServer, MatterbridgeColorControlServer, MatterbridgeDoorLockServer, MatterbridgeFanControlServer, MatterbridgeIdentifyServer, MatterbridgeLevelControlServer, MatterbridgeOnOffServer, MatterbridgeThermostatServer, MatterbridgeWindowCoveringServer, } from './matterbridgeBehaviors.js';
30
+ import { bridgedNode } from './matterbridgeDeviceTypes.js';
31
+ import { deepCopy, isValidNumber } from './utils/utils.js';
32
+ // @matter
33
+ import { Endpoint, MutableEndpoint, SupportedBehaviors, NamedHandler, Lifecycle } from '@matter/main';
34
+ import { EndpointNumber, VendorId } from '@matter/main';
35
+ import { AirQuality, AirQualityCluster, BasicInformation, BasicInformationCluster, BooleanState, BooleanStateCluster, BooleanStateConfiguration, BooleanStateConfigurationCluster, BridgedDeviceBasicInformation, BridgedDeviceBasicInformationCluster, CarbonDioxideConcentrationMeasurement, CarbonDioxideConcentrationMeasurementCluster, CarbonMonoxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurementCluster, ColorControl, ColorControlCluster, ConcentrationMeasurement, Descriptor, DoorLock, DoorLockCluster, ElectricalEnergyMeasurement, ElectricalEnergyMeasurementCluster, ElectricalPowerMeasurement, ElectricalPowerMeasurementCluster, FanControl, FanControlCluster, FixedLabel, FixedLabelCluster, FlowMeasurement, FlowMeasurementCluster, FormaldehydeConcentrationMeasurement, FormaldehydeConcentrationMeasurementCluster, Groups, GroupsCluster, Identify, IdentifyCluster, IlluminanceMeasurement, IlluminanceMeasurementCluster, LevelControl, LevelControlCluster, ModeSelect, ModeSelectCluster, NitrogenDioxideConcentrationMeasurement, NitrogenDioxideConcentrationMeasurementCluster, OccupancySensing, OccupancySensingCluster, OnOff, OnOffCluster, OzoneConcentrationMeasurement, OzoneConcentrationMeasurementCluster, Pm10ConcentrationMeasurement, Pm10ConcentrationMeasurementCluster, Pm1ConcentrationMeasurement, Pm1ConcentrationMeasurementCluster, Pm25ConcentrationMeasurement, Pm25ConcentrationMeasurementCluster, PowerSource, PowerSourceCluster, PowerSourceConfigurationCluster, PowerTopology, PowerTopologyCluster, PressureMeasurement, PressureMeasurementCluster, RadonConcentrationMeasurement, RadonConcentrationMeasurementCluster, RelativeHumidityMeasurement, RelativeHumidityMeasurementCluster, SmokeCoAlarm, SmokeCoAlarmCluster, Switch, SwitchCluster, TemperatureMeasurement, TemperatureMeasurementCluster, Thermostat, ThermostatCluster, TotalVolatileOrganicCompoundsConcentrationMeasurement, TotalVolatileOrganicCompoundsConcentrationMeasurementCluster, UserLabel, UserLabelCluster, WindowCovering, WindowCoveringCluster, } from '@matter/main/clusters';
36
+ import { MeasurementType, getClusterNameById } from '@matter/main/types';
37
+ import { Specification } from '@matter/main/model';
38
+ import { DescriptorServer } from '@matter/node/behaviors/descriptor';
39
+ import { IdentifyBehavior } from '@matter/node/behaviors/identify';
40
+ import { GroupsServer } from '@matter/node/behaviors/groups';
41
+ import { TemperatureMeasurementServer } from '@matter/node/behaviors/temperature-measurement';
42
+ import { RelativeHumidityMeasurementServer } from '@matter/node/behaviors/relative-humidity-measurement';
43
+ import { PressureMeasurementServer } from '@matter/node/behaviors/pressure-measurement';
44
+ import { BridgedDeviceBasicInformationServer } from '@matter/node/behaviors/bridged-device-basic-information';
45
+ import { FlowMeasurementServer } from '@matter/node/behaviors/flow-measurement';
46
+ import { IlluminanceMeasurementServer } from '@matter/node/behaviors/illuminance-measurement';
47
+ import { BooleanStateServer } from '@matter/node/behaviors/boolean-state';
48
+ import { OccupancySensingServer } from '@matter/node/behaviors/occupancy-sensing';
49
+ import { AirQualityServer, BasicInformationServer, CarbonDioxideConcentrationMeasurementServer, CarbonMonoxideConcentrationMeasurementServer, ElectricalEnergyMeasurementServer, ElectricalPowerMeasurementServer, FixedLabelServer, FormaldehydeConcentrationMeasurementServer, ModeSelectServer, NitrogenDioxideConcentrationMeasurementServer, OzoneConcentrationMeasurementServer, Pm10ConcentrationMeasurementServer, Pm1ConcentrationMeasurementServer, Pm25ConcentrationMeasurementServer, PowerSourceServer, PowerTopologyServer, RadonConcentrationMeasurementServer, SmokeCoAlarmServer, SwitchServer, TotalVolatileOrganicCompoundsConcentrationMeasurementServer, UserLabelServer, } from '@matter/main/behaviors';
50
+ import { ClusterServer, GroupsClusterHandler } from '@project-chip/matter.js/cluster';
56
51
  export class MatterbridgeEndpoint extends Endpoint {
57
52
  static bridgeMode = '';
53
+ static logLevel = "info" /* LogLevel.INFO */;
58
54
  log;
59
- serialNumber = undefined;
55
+ plugin = undefined;
60
56
  deviceName = undefined;
57
+ serialNumber = undefined;
61
58
  uniqueId = undefined;
59
+ vendorId = undefined;
60
+ vendorName = undefined;
61
+ productId = undefined;
62
+ productName = undefined;
63
+ softwareVersion = undefined;
64
+ softwareVersionString = undefined;
65
+ hardwareVersion = undefined;
66
+ hardwareVersionString = undefined;
67
+ uniqueStorageKey = undefined;
68
+ tagList = undefined;
62
69
  // Maps matter deviceTypes and endpoints
63
70
  deviceTypes = new Map();
64
71
  clusterServers = new Map();
65
72
  clusterClients = new Map();
66
73
  commandHandler = new NamedHandler();
67
74
  /**
68
- * Represents a MatterbridgeEndpoint device.
75
+ * Represents a MatterbridgeEndpoint.
69
76
  * @constructor
70
- * @param {DeviceTypeDefinition} definition - The definition of the device.
77
+ * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The DeviceTypeDefinition(s) of the endpoint.
71
78
  * @param {EndpointOptions} [options={}] - The options for the device.
72
79
  */
73
- constructor(definition, options = {}) {
74
- // Convert the DeviceTypeDefinition to a EndpointType.Options
80
+ constructor(definition, options = {}, debug = false) {
81
+ const { uniqueStorageKey, endpointId, tagList, colorControlFeatures } = options;
82
+ // Get the first DeviceTypeDefinition
83
+ let firstDefinition;
84
+ if (Array.isArray(definition))
85
+ firstDefinition = definition[0];
86
+ else
87
+ firstDefinition = definition;
88
+ // Convert the first DeviceTypeDefinition to an EndpointType.Options
75
89
  const deviceTypeDefinitionV8 = {
76
- name: definition.name.replace('-', '_'),
77
- deviceType: definition.code,
78
- deviceRevision: definition.revision,
79
- deviceClass: definition.deviceClass,
90
+ name: firstDefinition.name.replace('-', '_'),
91
+ deviceType: firstDefinition.code,
92
+ deviceRevision: firstDefinition.revision,
93
+ deviceClass: firstDefinition.deviceClass.toLowerCase(),
80
94
  requirements: {
81
95
  server: {
82
- mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(definition.requiredServerClusters)),
83
- optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(definition.optionalServerClusters)),
96
+ mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(firstDefinition.requiredServerClusters)),
97
+ optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(firstDefinition.optionalServerClusters)),
84
98
  },
85
99
  client: {
86
- mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(definition.requiredClientClusters)),
87
- optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(definition.optionalClientClusters)),
100
+ mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(firstDefinition.requiredClientClusters)),
101
+ optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(firstDefinition.optionalClientClusters)),
88
102
  },
89
103
  },
90
- behaviors: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(definition.requiredServerClusters)),
104
+ behaviors: tagList ? SupportedBehaviors(DescriptorServer.with(Descriptor.Feature.TagList)) : {},
91
105
  };
92
106
  const endpointV8 = MutableEndpoint(deviceTypeDefinitionV8);
107
+ // Convert the options to an Endpoint.Options
108
+ // [{ mfgCode: null, namespaceId: 0x07, tag: 1, label: 'Switch1' }]
109
+ // endpoint = endpoint.enable({features: { tagList: true }});
93
110
  const optionsV8 = {
94
- id: options.uniqueStorageKey,
111
+ // id: uniqueStorageKey,
112
+ id: uniqueStorageKey?.replace(/[ .]/g, ''),
113
+ // id: uniqueStorageKey?.replace(/[^a-zA-Z0-9_-]/g, ''),
114
+ number: endpointId,
115
+ descriptor: tagList ? { tagList } : undefined,
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
117
  };
96
118
  super(endpointV8, optionsV8);
97
- this.log = new AnsiLogger({ logName: 'MatterbridgeDevice', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: true });
98
- this.deviceTypes.set(definition.code, definition);
119
+ this.uniqueStorageKey = uniqueStorageKey;
120
+ this.tagList = tagList;
121
+ // Update the endpoint
122
+ this.log = new AnsiLogger({ logName: 'MatterbridgeEndpoint', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: debug === true ? "debug" /* LogLevel.DEBUG */ : MatterbridgeEndpoint.logLevel });
123
+ this.log.debug(`${YELLOW}new${db} MatterbridgeEndpoint: ${zb}${'0x' + firstDefinition.code.toString(16).padStart(4, '0')}${db}-${zb}${firstDefinition.name}${db} id: ${CYAN}${this.id}${db}`);
124
+ this.deviceTypes.set(firstDefinition.code, firstDefinition);
125
+ // Add the other device types to the descriptor server
126
+ if (Array.isArray(definition)) {
127
+ definition.forEach((deviceType) => {
128
+ this.addDeviceType(deviceType);
129
+ });
130
+ }
131
+ // Add MatterbridgeBehavior with MatterbridgeBehaviorDevice
132
+ this.behaviors.require(MatterbridgeBehavior, { deviceCommand: new MatterbridgeBehaviorDevice(this.log, this.commandHandler, undefined) });
133
+ }
134
+ /**
135
+ * Loads an instance of the MatterbridgeDevice class.
136
+ *
137
+ * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The DeviceTypeDefinition(s) of the device.
138
+ * @returns MatterbridgeDevice instance.
139
+ */
140
+ static async loadInstance(definition, options = {}, debug = false) {
141
+ return new MatterbridgeEndpoint(definition, options, debug);
99
142
  }
100
143
  static getBehaviourTypesFromClusterServerIds(clusterServerList) {
101
144
  // Map Server ClusterId to Behavior.Type
@@ -113,57 +156,97 @@ export class MatterbridgeEndpoint extends Endpoint {
113
156
  });
114
157
  return behaviorTypes;
115
158
  }
116
- static getBehaviourTypeFromClusterServerId(clusterId) {
159
+ static getBehaviourTypeFromClusterServerId(clusterId, type) {
117
160
  // Map ClusterId to Behavior.Type
118
161
  if (clusterId === Identify.Cluster.id)
119
- return IdentifyServer;
162
+ return MatterbridgeIdentifyServer;
120
163
  if (clusterId === Groups.Cluster.id)
121
164
  return GroupsServer;
122
- // if (clusterId === Scenes.Cluster.id) return ScenesServer;
123
165
  if (clusterId === OnOff.Cluster.id)
124
- return OnOffServer;
166
+ return MatterbridgeOnOffServer;
125
167
  if (clusterId === LevelControl.Cluster.id)
126
- return LevelControlServer;
168
+ return MatterbridgeLevelControlServer;
127
169
  if (clusterId === ColorControl.Cluster.id)
128
- return ColorControlServer.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.Xy, ColorControl.Feature.ColorTemperature);
170
+ return MatterbridgeColorControlServer;
129
171
  if (clusterId === DoorLock.Cluster.id)
130
- return DoorLockServer;
172
+ return MatterbridgeDoorLockServer;
131
173
  if (clusterId === Thermostat.Cluster.id)
132
- return ThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode);
133
- if (clusterId === TimeSynchronization.Cluster.id)
134
- return TimeSynchronizationServer.with(TimeSynchronization.Feature.TimeZone);
174
+ return MatterbridgeThermostatServer;
135
175
  if (clusterId === WindowCovering.Cluster.id)
136
- return WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift);
176
+ return MatterbridgeWindowCoveringServer;
137
177
  if (clusterId === FanControl.Cluster.id)
138
- return FanControlServer.with(FanControl.Feature.MultiSpeed, FanControl.Feature.Auto, FanControl.Feature.Step);
178
+ return MatterbridgeFanControlServer;
179
+ if (clusterId === Switch.Cluster.id && type === 'MomentarySwitch')
180
+ return SwitchServer.with('MomentarySwitch', 'MomentarySwitchRelease', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress');
181
+ if (clusterId === Switch.Cluster.id && type === 'LatchingSwitch')
182
+ return SwitchServer.with('LatchingSwitch');
139
183
  if (clusterId === TemperatureMeasurement.Cluster.id)
140
184
  return TemperatureMeasurementServer;
141
185
  if (clusterId === RelativeHumidityMeasurement.Cluster.id)
142
186
  return RelativeHumidityMeasurementServer;
143
187
  if (clusterId === PressureMeasurement.Cluster.id)
144
- return PressureMeasurementServer.with(PressureMeasurement.Feature.Extended);
188
+ return PressureMeasurementServer;
145
189
  if (clusterId === FlowMeasurement.Cluster.id)
146
190
  return FlowMeasurementServer;
147
191
  if (clusterId === BooleanState.Cluster.id)
148
192
  return BooleanStateServer;
149
193
  if (clusterId === BooleanStateConfiguration.Cluster.id)
150
- return BooleanStateConfigurationServer;
194
+ return MatterbridgeBooleanStateConfigurationServer;
151
195
  if (clusterId === OccupancySensing.Cluster.id)
152
196
  return OccupancySensingServer;
153
197
  if (clusterId === IlluminanceMeasurement.Cluster.id)
154
198
  return IlluminanceMeasurementServer;
199
+ if (clusterId === SmokeCoAlarm.Cluster.id)
200
+ return SmokeCoAlarmServer.with(SmokeCoAlarm.Feature.SmokeAlarm, SmokeCoAlarm.Feature.CoAlarm);
201
+ if (clusterId === AirQuality.Cluster.id)
202
+ return AirQualityServer.with(AirQuality.Feature.Fair, AirQuality.Feature.Moderate, AirQuality.Feature.VeryPoor, AirQuality.Feature.ExtremelyPoor);
203
+ if (clusterId === CarbonMonoxideConcentrationMeasurement.Cluster.id)
204
+ return CarbonMonoxideConcentrationMeasurementServer.with('NumericMeasurement');
205
+ if (clusterId === CarbonDioxideConcentrationMeasurement.Cluster.id)
206
+ return CarbonDioxideConcentrationMeasurementServer.with('NumericMeasurement');
207
+ if (clusterId === NitrogenDioxideConcentrationMeasurement.Cluster.id)
208
+ return NitrogenDioxideConcentrationMeasurementServer.with('NumericMeasurement');
209
+ if (clusterId === OzoneConcentrationMeasurement.Cluster.id)
210
+ return OzoneConcentrationMeasurementServer.with('NumericMeasurement');
211
+ if (clusterId === FormaldehydeConcentrationMeasurement.Cluster.id)
212
+ return FormaldehydeConcentrationMeasurementServer.with('NumericMeasurement');
213
+ if (clusterId === Pm1ConcentrationMeasurement.Cluster.id)
214
+ return Pm1ConcentrationMeasurementServer.with('NumericMeasurement');
215
+ if (clusterId === Pm25ConcentrationMeasurement.Cluster.id)
216
+ return Pm25ConcentrationMeasurementServer.with('NumericMeasurement');
217
+ if (clusterId === Pm10ConcentrationMeasurement.Cluster.id)
218
+ return Pm10ConcentrationMeasurementServer.with('NumericMeasurement');
219
+ if (clusterId === RadonConcentrationMeasurement.Cluster.id)
220
+ return RadonConcentrationMeasurementServer.with('NumericMeasurement');
221
+ if (clusterId === TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id)
222
+ return TotalVolatileOrganicCompoundsConcentrationMeasurementServer.with('NumericMeasurement');
223
+ if (clusterId === ModeSelect.Cluster.id)
224
+ return ModeSelectServer;
225
+ if (clusterId === UserLabel.Cluster.id)
226
+ return UserLabelServer;
227
+ if (clusterId === FixedLabel.Cluster.id)
228
+ return FixedLabelServer;
229
+ if (clusterId === PowerTopology.Cluster.id)
230
+ return PowerTopologyServer.with('TreeTopology');
231
+ if (clusterId === ElectricalPowerMeasurement.Cluster.id)
232
+ return ElectricalPowerMeasurementServer.with('AlternatingCurrent');
233
+ if (clusterId === ElectricalEnergyMeasurement.Cluster.id)
234
+ return ElectricalEnergyMeasurementServer.with('ImportedEnergy', 'ExportedEnergy', 'CumulativeEnergy');
235
+ if (clusterId === PowerSource.Cluster.id && type === 'WiredPowerSource')
236
+ return PowerSourceServer.with(PowerSource.Feature.Wired);
237
+ if (clusterId === PowerSource.Cluster.id && type === 'BatteryReplaceablePowerSource')
238
+ return PowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Replaceable);
239
+ if (clusterId === PowerSource.Cluster.id && type === 'BatteryRechargeablePowerSource')
240
+ return PowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable);
241
+ if (clusterId === BasicInformation.Cluster.id)
242
+ return BasicInformationServer;
155
243
  if (clusterId === BridgedDeviceBasicInformation.Cluster.id)
156
244
  return BridgedDeviceBasicInformationServer;
157
- return IdentifyServer;
245
+ return MatterbridgeIdentifyServer;
158
246
  }
159
- /**
160
- * Loads an instance of the MatterbridgeDevice class.
161
- *
162
- * @param {DeviceTypeDefinition} definition - The DeviceTypeDefinition of the device.
163
- * @returns MatterbridgeDevice instance.
164
- */
165
- static async loadInstance(definition, options = {}) {
166
- return new MatterbridgeEndpoint(definition, options);
247
+ static getBehaviourTypeFromClusterClientId(clusterId) {
248
+ // Map ClusterId to Behavior.Type
249
+ return IdentifyBehavior;
167
250
  }
168
251
  /**
169
252
  * Adds a device type to the list of device types.
@@ -174,16 +257,24 @@ export class MatterbridgeEndpoint extends Endpoint {
174
257
  addDeviceType(deviceType) {
175
258
  if (!this.deviceTypes.has(deviceType.code)) {
176
259
  // Keep the Matterbridge internal map
177
- this.log.debug(`addDeviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`);
260
+ this.log.debug(`addDeviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
178
261
  this.deviceTypes.set(deviceType.code, deviceType);
179
262
  // Add the device types to the descriptor server
180
263
  const deviceTypeList = Array.from(this.deviceTypes.values()).map((dt) => ({
181
264
  deviceType: dt.code,
182
265
  revision: dt.revision,
183
266
  }));
184
- this.behaviors.require(DescriptorServer, {
185
- deviceTypeList,
186
- });
267
+ if (this.tagList) {
268
+ this.behaviors.require(DescriptorServer.with(Descriptor.Feature.TagList), {
269
+ tagList: this.tagList,
270
+ deviceTypeList,
271
+ });
272
+ }
273
+ else {
274
+ this.behaviors.require(DescriptorServer, {
275
+ deviceTypeList,
276
+ });
277
+ }
187
278
  }
188
279
  }
189
280
  /**
@@ -195,20 +286,64 @@ export class MatterbridgeEndpoint extends Endpoint {
195
286
  addDeviceTypeWithClusterServer(deviceTypes, includeServerList) {
196
287
  this.log.debug('addDeviceTypeWithClusterServer:');
197
288
  deviceTypes.forEach((deviceType) => {
198
- this.log.debug(`- with deviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`);
289
+ this.log.debug(`- with deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
199
290
  deviceType.requiredServerClusters.forEach((clusterId) => {
200
291
  if (!includeServerList.includes(clusterId))
201
292
  includeServerList.push(clusterId);
202
293
  });
203
294
  });
204
295
  includeServerList.forEach((clusterId) => {
205
- this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
296
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
206
297
  });
207
298
  deviceTypes.forEach((deviceType) => {
208
299
  this.addDeviceType(deviceType);
209
300
  });
210
301
  this.addClusterServerFromList(this, includeServerList);
211
302
  }
303
+ /**
304
+ * Adds the required cluster servers (only if they are not present) for the device types of the specified endpoint.
305
+ *
306
+ * @param {MatterbridgeEndpoint} endpoint - The endpoint to add the required cluster servers to.
307
+ * @returns {MatterbridgeEndpoint} The updated endpoint with the required cluster servers added.
308
+ */
309
+ addRequiredClusterServers(endpoint) {
310
+ const requiredServerList = [];
311
+ this.log.debug(`addRequiredClusterServer for ${CYAN}${endpoint.id}${db}`);
312
+ endpoint.getDeviceTypes().forEach((deviceType) => {
313
+ this.log.debug(`- for deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
314
+ deviceType.requiredServerClusters.forEach((clusterId) => {
315
+ if (!requiredServerList.includes(clusterId) && !endpoint.getClusterServerById(clusterId))
316
+ requiredServerList.push(clusterId);
317
+ });
318
+ });
319
+ requiredServerList.forEach((clusterId) => {
320
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
321
+ });
322
+ this.addClusterServerFromList(endpoint, requiredServerList);
323
+ return endpoint;
324
+ }
325
+ /**
326
+ * Adds the optional cluster servers (only if they are not present) for the device types of the specified endpoint.
327
+ *
328
+ * @param {MatterbridgeEndpoint} endpoint - The endpoint to add the required cluster servers to.
329
+ * @returns {MatterbridgeEndpoint} The updated endpoint with the required cluster servers added.
330
+ */
331
+ addOptionalClusterServers(endpoint) {
332
+ const optionalServerList = [];
333
+ this.log.debug(`addRequiredClusterServer for ${CYAN}${endpoint.id}${db}`);
334
+ endpoint.getDeviceTypes().forEach((deviceType) => {
335
+ this.log.debug(`- for deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
336
+ deviceType.optionalServerClusters.forEach((clusterId) => {
337
+ if (!optionalServerList.includes(clusterId) && !endpoint.getClusterServerById(clusterId))
338
+ optionalServerList.push(clusterId);
339
+ });
340
+ });
341
+ optionalServerList.forEach((clusterId) => {
342
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
343
+ });
344
+ this.addClusterServerFromList(endpoint, optionalServerList);
345
+ return endpoint;
346
+ }
212
347
  /**
213
348
  * Adds a child endpoint with one or more device types with the required cluster servers and the specified cluster servers.
214
349
  * If the child endpoint is not already present in the childEndpoints, it will be added.
@@ -216,30 +351,60 @@ export class MatterbridgeEndpoint extends Endpoint {
216
351
  *
217
352
  * @param {string} endpointName - The name of the new enpoint to add.
218
353
  * @param {AtLeastOne<DeviceTypeDefinition>} deviceTypes - The device types to add.
219
- * @param {ClusterId[]} includeServerList - The list of cluster IDs to include.
220
- * @returns {Endpoint} - The child endpoint that was found or added.
354
+ * @param {ClusterId[]} [includeServerList=[]] - The list of cluster IDs to include.
355
+ * @param {EndpointOptions} [options={}] - The options for the device.
356
+ * @param {boolean} [debug=false] - Whether to enable debug logging.
357
+ * @returns {MatterbridgeEndpoint} - The child endpoint that was found or added.
221
358
  */
222
- addChildDeviceTypeWithClusterServer(endpointName, deviceTypes, includeServerList) {
223
- /*
359
+ addChildDeviceTypeWithClusterServer(endpointName, deviceTypes, includeServerList = [], options = {}, debug = false) {
224
360
  this.log.debug(`addChildDeviceTypeWithClusterServer: ${CYAN}${endpointName}${db}`);
225
- let child = this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName);
361
+ let child = this.getChildEndpointByName(endpointName);
226
362
  if (!child) {
227
- child = new Endpoint(deviceTypes, { uniqueStorageKey: endpointName });
228
- child.addFixedLabel('endpointName', endpointName);
363
+ child = new MatterbridgeEndpoint(deviceTypes[0], { uniqueStorageKey: endpointName }, debug);
229
364
  }
230
365
  deviceTypes.forEach((deviceType) => {
231
- this.log.debug(`- with deviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`);
232
- deviceType.requiredServerClusters.forEach((clusterId) => {
233
- if (!includeServerList.includes(clusterId)) includeServerList.push(clusterId);
234
- });
366
+ this.log.debug(`- with deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
367
+ deviceType.requiredServerClusters.forEach((clusterId) => {
368
+ if (!includeServerList.includes(clusterId))
369
+ includeServerList.push(clusterId);
370
+ });
235
371
  });
236
372
  includeServerList.forEach((clusterId) => {
237
- this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
373
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
238
374
  });
239
375
  this.addClusterServerFromList(child, includeServerList);
240
- this.addChildEndpoint(child);
376
+ if (this.lifecycle.isInstalled) {
377
+ this.log.debug(`- with lifecycle installed`);
378
+ this.add(child);
379
+ }
380
+ else {
381
+ this.log.debug(`- with lifecycle NOT installed`);
382
+ this.parts.add(child);
383
+ }
241
384
  return child;
242
- */
385
+ }
386
+ /**
387
+ * Retrieves a child endpoint by its name.
388
+ *
389
+ * @param {string} endpointName - The name of the endpoint to retrieve.
390
+ * @returns {Endpoint | undefined} The child endpoint with the specified name, or undefined if not found.
391
+ */
392
+ getChildEndpointByName(endpointName) {
393
+ return this.parts.find((part) => part.id === endpointName);
394
+ }
395
+ getChildEndpoint(endpointNumber) {
396
+ return this.parts.find((part) => part.number === endpointNumber);
397
+ }
398
+ getChildEndpoints() {
399
+ return Array.from(this.parts);
400
+ }
401
+ getDeviceTypes() {
402
+ return Array.from(this.deviceTypes.values());
403
+ }
404
+ setDeviceTypes(deviceTypes) {
405
+ deviceTypes.forEach((deviceType) => {
406
+ this.addDeviceType(deviceType);
407
+ });
243
408
  }
244
409
  getClusterServer(cluster) {
245
410
  const clusterServer = this.clusterServers.get(cluster.id);
@@ -254,17 +419,28 @@ export class MatterbridgeEndpoint extends Endpoint {
254
419
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
420
  const options = {};
256
421
  for (const attribute of Object.values(cluster.attributes)) {
422
+ // console.error('Attribute:', (attribute as any).id, (attribute as any).name, (attribute as any).value);
257
423
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
424
  if (attribute.id < 0xfff0) {
259
- // No issue here for value, as cluster here is just a definition without getter setter
260
425
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
426
  options[attribute.name] = attribute.value;
262
427
  }
263
428
  }
264
- this.log.debug(`addClusterServer: ${hk}${cluster.id}${db}-${hk}${getClusterNameById(cluster.id)}${db} with options: ${debugStringify(options)}${rs}`);
265
- const behavior = MatterbridgeEndpoint.getBehaviourTypeFromClusterServerId(cluster.id);
266
- this.behaviors.require(behavior, options);
267
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
429
+ this.log.debug(`addClusterServer: ${hk}${'0x' + cluster.id.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(cluster.id)}${db} with options: ${debugStringify(options)}${rs}`);
430
+ let type = undefined;
431
+ if (cluster.id === SwitchCluster.id && cluster.isEventSupportedByName('multiPressComplete'))
432
+ type = 'MomentarySwitch';
433
+ if (cluster.id === SwitchCluster.id && cluster.isEventSupportedByName('switchLatched'))
434
+ type = 'LatchingSwitch';
435
+ if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('wiredCurrentType'))
436
+ type = 'WiredPowerSource';
437
+ if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('batReplacementDescription'))
438
+ type = 'BatteryReplaceablePowerSource';
439
+ if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('batChargeState'))
440
+ type = 'BatteryRechargeablePowerSource';
441
+ const behavior = MatterbridgeEndpoint.getBehaviourTypeFromClusterServerId(cluster.id, type);
442
+ if (cluster.id !== BasicInformationCluster.id)
443
+ this.behaviors.require(behavior, options);
268
444
  this.clusterServers.set(cluster.id, cluster);
269
445
  }
270
446
  /**
@@ -279,21 +455,18 @@ export class MatterbridgeEndpoint extends Endpoint {
279
455
  endpoint.addClusterServer(this.getDefaultIdentifyClusterServer());
280
456
  if (includeServerList.includes(Groups.Cluster.id))
281
457
  endpoint.addClusterServer(this.getDefaultGroupsClusterServer());
282
- // if (includeServerList.includes(Scenes.Cluster.id)) endpoint.addClusterServer(this.getDefaultScenesClusterServer());
283
458
  if (includeServerList.includes(OnOff.Cluster.id))
284
459
  endpoint.addClusterServer(this.getDefaultOnOffClusterServer());
285
460
  if (includeServerList.includes(LevelControl.Cluster.id))
286
461
  endpoint.addClusterServer(this.getDefaultLevelControlClusterServer());
287
462
  if (includeServerList.includes(ColorControl.Cluster.id))
288
- endpoint.addClusterServer(this.getDefaultCompleteColorControlClusterServer());
463
+ endpoint.addClusterServer(this.getDefaultColorControlClusterServer());
289
464
  if (includeServerList.includes(Switch.Cluster.id))
290
465
  endpoint.addClusterServer(this.getDefaultSwitchClusterServer());
291
466
  if (includeServerList.includes(DoorLock.Cluster.id))
292
467
  endpoint.addClusterServer(this.getDefaultDoorLockClusterServer());
293
468
  if (includeServerList.includes(Thermostat.Cluster.id))
294
469
  endpoint.addClusterServer(this.getDefaultThermostatClusterServer());
295
- if (includeServerList.includes(TimeSynchronization.Cluster.id))
296
- endpoint.addClusterServer(this.getDefaultTimeSyncClusterServer());
297
470
  if (includeServerList.includes(WindowCovering.Cluster.id))
298
471
  endpoint.addClusterServer(this.getDefaultWindowCoveringClusterServer());
299
472
  if (includeServerList.includes(FanControl.Cluster.id))
@@ -349,20 +522,38 @@ export class MatterbridgeEndpoint extends Endpoint {
349
522
  // if (includeServerList.includes(DeviceEnergyManagement.Cluster.id)) endpoint.addClusterServer(this.getDefaultDeviceEnergyManagementClusterServer());
350
523
  // if (includeServerList.includes(DeviceEnergyManagementMode.Cluster.id)) endpoint.addClusterServer(this.getDefaultDeviceEnergyManagementModeClusterServer());
351
524
  }
352
- /**
353
- * From here copy paste from MatterbridgeDevice
354
- */
355
- /**
356
- * Retrieves a child endpoint by its name.
357
- *
358
- * @param {string} endpointName - The name of the endpoint to retrieve.
359
- * @returns {Endpoint | undefined} The child endpoint with the specified name, or undefined if not found.
360
- */
361
- /*
362
- getChildEndpointByName(endpointName: string): Endpoint | undefined {
363
- return this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName);
525
+ async addFixedLabel(label, value) {
526
+ if (!this.clusterServers.get(FixedLabelCluster.id)) {
527
+ this.addClusterServer(ClusterServer(FixedLabelCluster, {
528
+ labelList: [{ label, value }],
529
+ }, {}));
530
+ return;
531
+ }
532
+ const labelList = (this.getAttribute(FixedLabelCluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
533
+ labelList.push({ label, value });
534
+ await this.setAttribute(FixedLabelCluster.id, 'labelList', labelList, this.log);
535
+ }
536
+ async addUserLabel(label, value) {
537
+ if (!this.clusterServers.get(UserLabelCluster.id)) {
538
+ this.addClusterServer(ClusterServer(UserLabelCluster, {
539
+ labelList: [{ label, value }],
540
+ }, {}));
541
+ return;
542
+ }
543
+ const labelList = (this.getAttribute(UserLabelCluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
544
+ labelList.push({ label, value });
545
+ await this.setAttribute(UserLabelCluster.id, 'labelList', labelList, this.log);
546
+ }
547
+ capitalizeFirstLetter(name) {
548
+ if (!name)
549
+ return name;
550
+ return name.charAt(0).toUpperCase() + name.slice(1);
551
+ }
552
+ lowercaseFirstLetter(name) {
553
+ if (!name)
554
+ return name;
555
+ return name.charAt(0).toLowerCase() + name.slice(1);
364
556
  }
365
- */
366
557
  /**
367
558
  * Retrieves the value of the specified attribute from the given endpoint and cluster.
368
559
  *
@@ -372,33 +563,30 @@ export class MatterbridgeEndpoint extends Endpoint {
372
563
  * @param {Endpoint} [endpoint] - Optional the child endpoint to retrieve the attribute from.
373
564
  * @returns {any} The value of the attribute, or undefined if the attribute is not found.
374
565
  */
375
- /*
376
- getAttribute(clusterId: ClusterId, attribute: string, log?: AnsiLogger, endpoint?: Endpoint): any {
377
- if (!endpoint) endpoint = this as Endpoint;
378
-
379
- const clusterServer = endpoint.getClusterServerById(clusterId);
380
- if (!clusterServer) {
381
- log?.error(`getAttribute error: Cluster ${clusterId} not found on endpoint ${endpoint.name}:${endpoint.number}`);
382
- return undefined;
383
- }
384
- const capitalizedAttributeName = attribute.charAt(0).toUpperCase() + attribute.slice(1);
385
- if (!clusterServer.isAttributeSupportedByName(attribute) && !clusterServer.isAttributeSupportedByName(capitalizedAttributeName)) {
386
- if (log) log.error(`getAttribute error: Attribute ${attribute} not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
387
- return undefined;
388
- }
389
- // Find the getter method
390
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
391
- if (!(clusterServer as any)[`get${capitalizedAttributeName}Attribute`]) {
392
- log?.error(`getAttribute error: Getter get${capitalizedAttributeName}Attribute not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
393
- return undefined;
394
- }
395
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
396
- const getter = (clusterServer as any)[`get${capitalizedAttributeName}Attribute`] as () => {};
397
- const value = getter();
398
- log?.info(`${db}Get endpoint ${or}${endpoint.name}:${endpoint.number}${db} attribute ${hk}${clusterServer.name}.${capitalizedAttributeName}${db} value ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
399
- return value;
566
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
567
+ getAttribute(clusterId, attribute, log, endpoint) {
568
+ if (!endpoint)
569
+ endpoint = this;
570
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
571
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
572
+ log?.error(`getAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
573
+ return undefined;
574
+ }
575
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
576
+ const state = endpoint.state;
577
+ if (!(clusterName in state)) {
578
+ log?.error(`getAttribute error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
579
+ return undefined;
580
+ }
581
+ attribute = this.lowercaseFirstLetter(attribute);
582
+ if (!(attribute in state[clusterName])) {
583
+ log?.error(`getAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
584
+ return undefined;
585
+ }
586
+ const value = state[clusterName][attribute];
587
+ log?.info(`${db}Get endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${this.capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} value ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
588
+ return value;
400
589
  }
401
- */
402
590
  /**
403
591
  * Sets the value of an attribute on a cluster server endpoint.
404
592
  *
@@ -408,46 +596,36 @@ export class MatterbridgeEndpoint extends Endpoint {
408
596
  * @param {AnsiLogger} [log] - (Optional) The logger to use for logging errors and information.
409
597
  * @param {Endpoint} [endpoint] - (Optional) The endpoint to set the attribute on. If not provided, the attribute will be set on the current endpoint.
410
598
  */
411
- /*
412
- setAttribute(clusterId: ClusterId, attribute: string, value: any, log?: AnsiLogger, endpoint?: Endpoint): boolean {
413
- if (!endpoint) endpoint = this as Endpoint;
414
-
415
- const clusterServer = endpoint.getClusterServerById(clusterId);
416
- if (!clusterServer) {
417
- log?.error(`setAttribute error: Cluster ${clusterId} not found on endpoint ${endpoint.name}:${endpoint.number}`);
418
- return false;
419
- }
420
- const capitalizedAttributeName = attribute.charAt(0).toUpperCase() + attribute.slice(1);
421
- if (!clusterServer.isAttributeSupportedByName(attribute) && !clusterServer.isAttributeSupportedByName(capitalizedAttributeName)) {
422
- if (log) log.error(`setAttribute error: Attribute ${attribute} not found on Cluster ${clusterId} on endpoint ${endpoint.name}:${endpoint.number}`);
423
- return false;
424
- }
425
- // Find the getter method
426
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
427
- if (!(clusterServer as any)[`get${capitalizedAttributeName}Attribute`]) {
428
- log?.error(`setAttribute error: Getter get${capitalizedAttributeName}Attribute not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
429
- return false;
430
- }
431
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
432
- const getter = (clusterServer as any)[`get${capitalizedAttributeName}Attribute`] as () => {};
433
- // Find the setter method
434
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
435
- if (!(clusterServer as any)[`set${capitalizedAttributeName}Attribute`]) {
436
- log?.error(`setAttribute error: Setter set${capitalizedAttributeName}Attribute not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
437
- return false;
438
- }
439
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
440
- const setter = (clusterServer as any)[`set${capitalizedAttributeName}Attribute`] as (value: any) => {};
441
- const oldValue = getter();
442
- setter(value);
443
- log?.info(
444
- `${db}Set endpoint ${or}${endpoint.name}:${endpoint.number}${db} attribute ${hk}${clusterServer.name}.${capitalizedAttributeName}${db} ` +
445
- `from ${YELLOW}${typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
446
- `to ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`,
447
- );
448
- return true;
449
- }
450
- */
599
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
600
+ async setAttribute(clusterId, attribute, value, log, endpoint) {
601
+ if (!endpoint)
602
+ endpoint = this;
603
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
604
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
605
+ log?.error(`setAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
606
+ return false;
607
+ }
608
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
609
+ const state = endpoint.state;
610
+ if (!(clusterName in state)) {
611
+ log?.error(`setAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
612
+ return false;
613
+ }
614
+ attribute = this.lowercaseFirstLetter(attribute);
615
+ if (!(attribute in state[clusterName])) {
616
+ log?.error(`setAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
617
+ return false;
618
+ }
619
+ let oldValue = state[clusterName][attribute];
620
+ if (typeof oldValue === 'object')
621
+ oldValue = deepCopy(oldValue);
622
+ await endpoint.setStateOf(endpoint.behaviors.supported[clusterName], { [attribute]: value });
623
+ // await endpoint.set({ [clusterName]: { [attribute]: value } });
624
+ log?.info(`${db}Set endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${this.capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
625
+ `from ${YELLOW}${typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
626
+ `to ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
627
+ return true;
628
+ }
451
629
  /**
452
630
  * Subscribes to an attribute on a cluster.
453
631
  *
@@ -458,58 +636,50 @@ export class MatterbridgeEndpoint extends Endpoint {
458
636
  * @param {Endpoint} endpoint - (Optional) The endpoint to subscribe the attribute on. If not provided, the current endpoint will be used.
459
637
  * @returns A boolean indicating whether the subscription was successful.
460
638
  */
461
- /*
462
- subscribeAttribute(clusterId: ClusterId, attribute: string, listener: (newValue: any, oldValue: any) => void, log?: AnsiLogger, endpoint?: Endpoint): boolean {
463
- if (!endpoint) endpoint = this as Endpoint;
464
-
465
- const clusterServer = endpoint.getClusterServerById(clusterId);
466
- if (!clusterServer) {
467
- log?.error(`subscribeAttribute error: Cluster ${clusterId} not found on endpoint ${endpoint.name}:${endpoint.number}`);
468
- return false;
469
- }
470
- const capitalizedAttributeName = attribute.charAt(0).toUpperCase() + attribute.slice(1);
471
- if (!clusterServer.isAttributeSupportedByName(attribute) && !clusterServer.isAttributeSupportedByName(capitalizedAttributeName)) {
472
- if (log) log.error(`subscribeAttribute error: Attribute ${attribute} not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
473
- return false;
474
- }
475
- // Find the subscribe method
476
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
477
- if (!(clusterServer as any)[`subscribe${capitalizedAttributeName}Attribute`]) {
478
- log?.error(`subscribeAttribute error: subscribe${capitalizedAttributeName}Attribute not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
479
- return false;
480
- }
481
- // Subscribe to the attribute
482
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
483
- const subscribe = (clusterServer as any)[`subscribe${capitalizedAttributeName}Attribute`] as (listener: (newValue: any, oldValue: any) => void) => {};
484
- subscribe(listener);
485
- log?.info(`${db}Subscribe endpoint ${or}${endpoint.name}:${endpoint.number}${db} attribute ${hk}${clusterServer.name}.${capitalizedAttributeName}${db}`);
486
- return true;
487
- }
488
- */
489
639
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
490
- addCommandHandler(clusterId, command, handler) {
491
- const clusterServer = this.getClusterServerById(clusterId);
492
- if (!clusterServer) {
493
- this.log.error(`addCommandHandler error: Cluster ${clusterId} not found on endpoint ${this.id}:${this.number}`);
640
+ subscribeAttribute(clusterId, attribute, listener, log, endpoint) {
641
+ if (!endpoint)
642
+ endpoint = this;
643
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
644
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
645
+ const events = endpoint.events;
646
+ if (!(clusterName in events)) {
647
+ log?.error(`subscribeAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
648
+ return false;
649
+ }
650
+ attribute = this.lowercaseFirstLetter(attribute) + '$Changed';
651
+ if (!(attribute in events[clusterName])) {
652
+ log?.error(`subscribeAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
494
653
  return false;
495
654
  }
496
- if (!clusterServer.isCommandSupportedByName(command)) {
497
- this.log.error(`addCommandHandler error: Command ${command} not found on Cluster ${clusterServer.name} on endpoint ${this.id}:${this.number}`);
655
+ events[clusterName][attribute].on(listener);
656
+ log?.info(`${db}Subscribe endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${this.capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db}`);
657
+ return true;
658
+ }
659
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
660
+ async triggerEvent(clusterId, event, payload, log, endpoint) {
661
+ if (!endpoint)
662
+ endpoint = this;
663
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
664
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
665
+ log?.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
498
666
  return false;
499
667
  }
500
- // Find the command
501
668
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
502
- const commands = clusterServer.commands;
503
- for (const [key, value] of Object.entries(commands)) {
504
- // console.log(`Key "${key}": ${debugStringify(value)}`);
505
- if (key === command) {
506
- value.handler = handler;
507
- this.log.info(`${db}Command handler added for endpoint ${or}${this.id}:${this.number}${db} ${hk}${clusterServer.name}.${command}${db}`);
508
- return true;
509
- }
669
+ const events = endpoint.events;
670
+ if (!(clusterName in events) || !(event in events[clusterName])) {
671
+ log?.error(`triggerEvent ${hk}${event}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
672
+ return false;
510
673
  }
511
- this.log.error(`Command handler not found for endpoint ${or}${this.id}:${this.number}${er} ${hk}${clusterServer.name}.${command}${er}`);
512
- return false;
674
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
675
+ // @ts-ignore
676
+ await endpoint.act((agent) => agent[clusterName].events[event].emit(payload, agent.context));
677
+ log?.info(`${db}Trigger event ${hk}${this.capitalizeFirstLetter(clusterName)}${db}.${hk}${event}${db} with ${debugStringify(payload)}${db} on endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} `);
678
+ return true;
679
+ }
680
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
681
+ addCommandHandler(command, handler) {
682
+ this.commandHandler.addHandler(command, handler);
513
683
  }
514
684
  /**
515
685
  * Serializes the Matterbridge device into a serialized object.
@@ -517,64 +687,69 @@ export class MatterbridgeEndpoint extends Endpoint {
517
687
  * @param pluginName - The name of the plugin.
518
688
  * @returns The serialized Matterbridge device object.
519
689
  */
520
- /*
521
- serialize(): SerializedMatterbridgeDevice | undefined {
522
- if (!this.serialNumber || !this.deviceName || !this.uniqueId) return;
523
- const cluster = this.getClusterServer(BasicInformationCluster) ?? this.getClusterServer(BridgedDeviceBasicInformationCluster);
524
- if (!cluster) return;
525
- const serialized: SerializedMatterbridgeDevice = {
526
- pluginName: this.plugin ?? 'Unknown',
527
- serialNumber: this.serialNumber,
528
- deviceName: this.deviceName,
529
- uniqueId: this.uniqueId,
530
- productName: cluster.attributes.productName?.getLocal(),
531
- vendorId: cluster.attributes.vendorId?.getLocal(),
532
- vendorName: cluster.attributes.vendorName?.getLocal(),
533
- deviceTypes: this.getDeviceTypes(),
534
- endpoint: this.number,
535
- endpointName: this.name,
536
- clusterServersId: [],
537
- };
538
- this.getAllClusterServers().forEach((clusterServer) => {
539
- serialized.clusterServersId.push(clusterServer.id);
540
- });
541
- return serialized;
542
- }
543
- */
690
+ serialize() {
691
+ return undefined;
692
+ /*
693
+ if (!this.serialNumber || !this.deviceName || !this.uniqueId) return;
694
+ const cluster = this.getClusterServer(BasicInformationCluster) ?? this.getClusterServer(BridgedDeviceBasicInformationCluster);
695
+ if (!cluster) return;
696
+ const serialized: SerializedMatterbridgeDevice = {
697
+ pluginName: this.plugin ?? 'Unknown',
698
+ serialNumber: this.serialNumber,
699
+ deviceName: this.deviceName,
700
+ uniqueId: this.uniqueId,
701
+ productName: cluster.attributes.productName?.getLocal(),
702
+ vendorId: cluster.attributes.vendorId?.getLocal(),
703
+ vendorName: cluster.attributes.vendorName?.getLocal(),
704
+ deviceTypes: Array.from(this.deviceTypes.values()),
705
+ endpoint: this.number,
706
+ endpointName: this.id,
707
+ clusterServersId: [],
708
+ };
709
+ this.getAllClusterServers().forEach((clusterServer) => {
710
+ serialized.clusterServersId.push(clusterServer.id);
711
+ });
712
+ return serialized;
713
+ */
714
+ }
544
715
  /**
545
716
  * Deserializes the device into a serialized object.
546
717
  *
547
718
  * @returns The deserialized MatterbridgeDevice.
548
719
  */
549
- /*
550
- static deserialize(serializedDevice: SerializedMatterbridgeDevice): MatterbridgeDevice {
551
- const device = new MatterbridgeDevice(serializedDevice.deviceTypes);
552
- device.serialNumber = serializedDevice.serialNumber;
553
- device.deviceName = serializedDevice.deviceName;
554
- device.uniqueId = serializedDevice.uniqueId;
555
- for (const clusterId of serializedDevice.clusterServersId) {
556
- if (clusterId === BasicInformationCluster.id)
557
- device.createDefaultBasicInformationClusterServer(
558
- serializedDevice.deviceName,
559
- serializedDevice.serialNumber,
560
- serializedDevice.vendorId ?? 0xfff1,
561
- serializedDevice.vendorName ?? 'Matterbridge',
562
- serializedDevice.productId ?? 0x8000,
563
- serializedDevice.productName ?? 'Matterbridge device',
564
- );
565
- else if (clusterId === BridgedDeviceBasicInformationCluster.id)
566
- device.createDefaultBridgedDeviceBasicInformationClusterServer(
567
- serializedDevice.deviceName,
568
- serializedDevice.serialNumber,
569
- serializedDevice.vendorId ?? 0xfff1,
570
- serializedDevice.vendorName ?? 'Matterbridge',
571
- serializedDevice.productName ?? 'Matterbridge device',
572
- );
573
- else device.addClusterServerFromList(device, [clusterId]);
574
- }
575
- return device;
576
- }
577
- */
720
+ static deserialize(serializedDevice) {
721
+ return undefined;
722
+ /*
723
+ const device = new MatterbridgeDevice(serializedDevice.deviceTypes);
724
+ device.serialNumber = serializedDevice.serialNumber;
725
+ device.deviceName = serializedDevice.deviceName;
726
+ device.uniqueId = serializedDevice.uniqueId;
727
+ for (const clusterId of serializedDevice.clusterServersId) {
728
+ if (clusterId === BasicInformationCluster.id)
729
+ device.createDefaultBasicInformationClusterServer(
730
+ serializedDevice.deviceName,
731
+ serializedDevice.serialNumber,
732
+ serializedDevice.vendorId ?? 0xfff1,
733
+ serializedDevice.vendorName ?? 'Matterbridge',
734
+ serializedDevice.productId ?? 0x8000,
735
+ serializedDevice.productName ?? 'Matterbridge device',
736
+ );
737
+ else if (clusterId === BridgedDeviceBasicInformationCluster.id)
738
+ device.createDefaultBridgedDeviceBasicInformationClusterServer(
739
+ serializedDevice.deviceName,
740
+ serializedDevice.serialNumber,
741
+ serializedDevice.vendorId ?? 0xfff1,
742
+ serializedDevice.vendorName ?? 'Matterbridge',
743
+ serializedDevice.productName ?? 'Matterbridge device',
744
+ );
745
+ else device.addClusterServerFromList(device, [clusterId]);
746
+ }
747
+ return device;
748
+ */
749
+ }
750
+ /**
751
+ * From here copy paste from MatterbridgeDevice
752
+ */
578
753
  /**
579
754
  * Get a default IdentifyCluster server.
580
755
  */
@@ -676,6 +851,18 @@ export class MatterbridgeEndpoint extends Endpoint {
676
851
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
677
852
  */
678
853
  getDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productId, productName, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
854
+ this.log.logName = deviceName;
855
+ this.deviceName = deviceName;
856
+ this.serialNumber = serialNumber;
857
+ this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName);
858
+ this.productId = productId;
859
+ this.productName = productName;
860
+ this.vendorId = vendorId;
861
+ this.vendorName = vendorName;
862
+ this.softwareVersion = softwareVersion;
863
+ this.softwareVersionString = softwareVersionString;
864
+ this.hardwareVersion = hardwareVersion;
865
+ this.hardwareVersionString = hardwareVersionString;
679
866
  return ClusterServer(BasicInformationCluster, {
680
867
  dataModelRevision: 1,
681
868
  location: 'XX',
@@ -717,10 +904,8 @@ export class MatterbridgeEndpoint extends Endpoint {
717
904
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
718
905
  */
719
906
  createDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productId, productName, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
720
- this.deviceName = deviceName;
721
- this.serialNumber = serialNumber;
722
- this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName);
723
907
  if (MatterbridgeEndpoint.bridgeMode === 'bridge') {
908
+ this.addDeviceType(bridgedNode);
724
909
  this.createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
725
910
  return;
726
911
  }
@@ -740,6 +925,18 @@ export class MatterbridgeEndpoint extends Endpoint {
740
925
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
741
926
  */
742
927
  getDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
928
+ this.log.logName = deviceName;
929
+ this.deviceName = deviceName;
930
+ this.serialNumber = serialNumber;
931
+ this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName);
932
+ this.productId = undefined;
933
+ this.productName = productName;
934
+ this.vendorId = vendorId;
935
+ this.vendorName = vendorName;
936
+ this.softwareVersion = softwareVersion;
937
+ this.softwareVersionString = softwareVersionString;
938
+ this.hardwareVersion = hardwareVersion;
939
+ this.hardwareVersionString = hardwareVersionString;
743
940
  return ClusterServer(BridgedDeviceBasicInformationCluster, {
744
941
  vendorId: vendorId !== undefined ? VendorId(vendorId) : undefined, // 4874
745
942
  vendorName: vendorName.slice(0, 32),
@@ -774,9 +971,6 @@ export class MatterbridgeEndpoint extends Endpoint {
774
971
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
775
972
  */
776
973
  createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
777
- this.deviceName = deviceName;
778
- this.serialNumber = serialNumber;
779
- this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName);
780
974
  this.addClusterServer(this.getDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString));
781
975
  }
782
976
  /**
@@ -821,7 +1015,7 @@ export class MatterbridgeEndpoint extends Endpoint {
821
1015
  getDefaultElectricalPowerMeasurementClusterServer(voltage = null, current = null, power = null, frequency = null) {
822
1016
  return ClusterServer(ElectricalPowerMeasurementCluster.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), {
823
1017
  powerMode: ElectricalPowerMeasurement.PowerMode.Ac,
824
- numberOfMeasurementTypes: 3,
1018
+ numberOfMeasurementTypes: 4,
825
1019
  accuracy: [
826
1020
  {
827
1021
  measurementType: MeasurementType.Voltage,
@@ -858,42 +1052,6 @@ export class MatterbridgeEndpoint extends Endpoint {
858
1052
  frequency: frequency,
859
1053
  }, {}, {});
860
1054
  }
861
- /**
862
- * Creates a default Dummy Thread Network Diagnostics Cluster server.
863
- * @deprecated This method is deprecated and is only used for testing.
864
- *
865
- * @remarks
866
- * This method adds a cluster server used only to give the networkName to Eve app.
867
- *
868
- * @returns void
869
- */
870
- createDefaultDummyThreadNetworkDiagnosticsClusterServer() {
871
- this.addClusterServer(ClusterServer(ThreadNetworkDiagnosticsCluster.with(ThreadNetworkDiagnostics.Feature.PacketCounts, ThreadNetworkDiagnostics.Feature.ErrorCounts), {
872
- channel: 1,
873
- routingRole: ThreadNetworkDiagnostics.RoutingRole.Router,
874
- networkName: 'MyMatterThread',
875
- panId: 0,
876
- extendedPanId: 0,
877
- meshLocalPrefix: null,
878
- neighborTable: [],
879
- routeTable: [],
880
- partitionId: null,
881
- weighting: null,
882
- dataVersion: null,
883
- stableDataVersion: null,
884
- leaderRouterId: null,
885
- securityPolicy: null,
886
- channelPage0Mask: null,
887
- operationalDatasetComponents: null,
888
- overrunCount: 0,
889
- activeNetworkFaultsList: [],
890
- }, {
891
- resetCounts: async (data) => {
892
- this.log.debug('Matter command: resetCounts');
893
- await this.commandHandler.executeHandler('resetCounts', data);
894
- },
895
- }, {}));
896
- }
897
1055
  /**
898
1056
  * Get a default OnOff cluster server.
899
1057
  *
@@ -921,6 +1079,7 @@ export class MatterbridgeEndpoint extends Endpoint {
921
1079
  * Creates a default OnOff cluster server.
922
1080
  *
923
1081
  * @param onOff - The initial state of the OnOff cluster (default: false).
1082
+ * @returns {void}
924
1083
  */
925
1084
  createDefaultOnOffClusterServer(onOff = false) {
926
1085
  this.addClusterServer(this.getDefaultOnOffClusterServer(onOff));
@@ -928,12 +1087,17 @@ export class MatterbridgeEndpoint extends Endpoint {
928
1087
  /**
929
1088
  * Get a default level control cluster server.
930
1089
  *
931
- * @param currentLevel - The current level (default: 0).
1090
+ * @param currentLevel - The current level (default: 254).
1091
+ * @param minLevel - The minimum level (default: 1).
1092
+ * @param maxLevel - The maximum level (default: 254).
1093
+ * @param onLevel - The on level (default: null).
932
1094
  */
933
- getDefaultLevelControlClusterServer(currentLevel = 0) {
1095
+ getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel = null) {
934
1096
  return ClusterServer(LevelControlCluster.with(LevelControl.Feature.OnOff), {
935
1097
  currentLevel,
936
- onLevel: 0,
1098
+ minLevel,
1099
+ maxLevel,
1100
+ onLevel,
937
1101
  options: {
938
1102
  executeIfOff: false,
939
1103
  coupleColorTempToLevel: false,
@@ -970,10 +1134,13 @@ export class MatterbridgeEndpoint extends Endpoint {
970
1134
  /**
971
1135
  * Creates a default level control cluster server.
972
1136
  *
973
- * @param currentLevel - The current level (default: 0).
1137
+ * @param currentLevel - The current level (default: 254).
1138
+ * @param minLevel - The minimum level (default: 1).
1139
+ * @param maxLevel - The maximum level (default: 254).
1140
+ * @param onLevel - The on level (default: null).
974
1141
  */
975
- createDefaultLevelControlClusterServer(currentLevel = 0) {
976
- this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel));
1142
+ createDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel = null) {
1143
+ this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel, minLevel, maxLevel, onLevel));
977
1144
  }
978
1145
  /**
979
1146
  * Get a default color control cluster server.
@@ -986,7 +1153,7 @@ export class MatterbridgeEndpoint extends Endpoint {
986
1153
  * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds.
987
1154
  * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds.
988
1155
  */
989
- getDefaultCompleteColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
1156
+ getDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
990
1157
  return ClusterServer(ColorControlCluster.with(ColorControl.Feature.Xy, ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), {
991
1158
  colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation,
992
1159
  enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation,
@@ -1002,6 +1169,8 @@ export class MatterbridgeEndpoint extends Endpoint {
1002
1169
  colorTemperatureMireds,
1003
1170
  colorTempPhysicalMinMireds,
1004
1171
  colorTempPhysicalMaxMireds,
1172
+ coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
1173
+ startUpColorTemperatureMireds: null,
1005
1174
  }, {
1006
1175
  moveToColor: async (data) => {
1007
1176
  this.log.debug('Matter command: moveToColor request:', data.request, 'attributes.currentX:', data.attributes.currentX.getLocal(), 'attributes.currentY:', data.attributes.currentY.getLocal());
@@ -1063,14 +1232,15 @@ export class MatterbridgeEndpoint extends Endpoint {
1063
1232
  * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds.
1064
1233
  * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds.
1065
1234
  */
1066
- createDefaultCompleteColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
1067
- this.addClusterServer(this.getDefaultCompleteColorControlClusterServer(currentX, currentY, currentHue, currentSaturation, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds));
1235
+ createDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
1236
+ this.addClusterServer(this.getDefaultColorControlClusterServer(currentX, currentY, currentHue, currentSaturation, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds));
1068
1237
  }
1238
+ isColorControlConfigured = false;
1069
1239
  /**
1070
1240
  * Configures the color control cluster for a device.
1071
1241
  *
1072
- * @remark This method must be called only after creating the cluster with getDefaultCompleteColorControlClusterServer or createDefaultCompleteColorControlClusterServer
1073
- * and before starting the matter server.
1242
+ * @remark This method must be called only after creating the cluster with getDefaultColorControlClusterServer or createDefaultColorControlClusterServer
1243
+ * and before starting the matter node.
1074
1244
  *
1075
1245
  * @param {boolean} hueSaturation - A boolean indicating whether the device supports hue and saturation control.
1076
1246
  * @param {boolean} xy - A boolean indicating whether the device supports XY control.
@@ -1078,15 +1248,27 @@ export class MatterbridgeEndpoint extends Endpoint {
1078
1248
  * @param {ColorControl.ColorMode} colorMode - An optional parameter specifying the color mode of the device.
1079
1249
  * @param {Endpoint} endpoint - An optional parameter specifying the endpoint to configure. If not provided, the device endpoint will be used.
1080
1250
  */
1081
- configureColorControlCluster(hueSaturation, xy, colorTemperature, colorMode, endpoint) {
1251
+ async configureColorControlCluster(hueSaturation, xy, colorTemperature, colorMode, endpoint) {
1082
1252
  if (!endpoint)
1083
1253
  endpoint = this;
1084
- endpoint.getClusterServer(ColorControlCluster)?.setFeatureMapAttribute({ hueSaturation, enhancedHue: false, colorLoop: false, xy, colorTemperature });
1085
- endpoint.getClusterServer(ColorControlCluster)?.setColorCapabilitiesAttribute({ hueSaturation, enhancedHue: false, colorLoop: false, xy, colorTemperature });
1086
- if (colorMode !== undefined && colorMode >= 0 && colorMode <= 2) {
1087
- endpoint.getClusterServer(ColorControlCluster)?.setColorModeAttribute(colorMode);
1088
- endpoint.getClusterServer(ColorControlCluster)?.setEnhancedColorModeAttribute(colorMode);
1254
+ if (this.isColorControlConfigured)
1255
+ return;
1256
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
1257
+ this.log.debug(`**configureColorControlCluster() delaying for endpoint construction ${endpoint.construction.status}`);
1258
+ setTimeout(async () => {
1259
+ await endpoint.configureColorControlCluster(hueSaturation, xy, colorTemperature, colorMode, endpoint);
1260
+ this.isColorControlConfigured = true;
1261
+ }, 500);
1262
+ return;
1263
+ }
1264
+ this.log.debug(`**configureColorControlCluster()`);
1265
+ await endpoint.setAttribute(ColorControlCluster.id, 'featureMap', { hueSaturation, enhancedHue: false, colorLoop: false, xy, colorTemperature }, this.log, endpoint);
1266
+ await endpoint.setAttribute(ColorControlCluster.id, 'colorCapabilities', { hueSaturation, enhancedHue: false, colorLoop: false, xy, colorTemperature }, this.log, endpoint);
1267
+ if (isValidNumber(colorMode, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, ColorControl.ColorMode.ColorTemperatureMireds)) {
1268
+ await endpoint.setAttribute(ColorControlCluster.id, 'colorMode', colorMode, this.log, endpoint);
1269
+ await endpoint.setAttribute(ColorControlCluster.id, 'enhancedColorMode', colorMode, this.log, endpoint);
1089
1270
  }
1271
+ this.isColorControlConfigured = true;
1090
1272
  }
1091
1273
  /**
1092
1274
  * Configures the color control mode for the device.
@@ -1094,18 +1276,18 @@ export class MatterbridgeEndpoint extends Endpoint {
1094
1276
  * @param {ColorControl.ColorMode} colorMode - The color mode to set.
1095
1277
  * @param {Endpoint} endpoint - The optional endpoint to configure. If not provided, the method will configure the current endpoint.
1096
1278
  */
1097
- configureColorControlMode(colorMode, endpoint) {
1279
+ async configureColorControlMode(colorMode, endpoint) {
1098
1280
  if (!endpoint)
1099
1281
  endpoint = this;
1100
- if (colorMode !== undefined && colorMode >= ColorControl.ColorMode.CurrentHueAndCurrentSaturation && colorMode <= ColorControl.ColorMode.ColorTemperatureMireds) {
1101
- endpoint.getClusterServer(ColorControlCluster)?.setColorModeAttribute(colorMode);
1102
- endpoint.getClusterServer(ColorControlCluster)?.setEnhancedColorModeAttribute(colorMode);
1282
+ if (isValidNumber(colorMode, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, ColorControl.ColorMode.ColorTemperatureMireds)) {
1283
+ await endpoint.setAttribute(ColorControlCluster.id, 'colorMode', colorMode, this.log, endpoint);
1284
+ await endpoint.setAttribute(ColorControlCluster.id, 'enhancedColorMode', colorMode, this.log, endpoint);
1103
1285
  }
1104
1286
  }
1105
1287
  /**
1106
1288
  * Get a default window covering cluster server.
1107
1289
  *
1108
- * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0.
1290
+ * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0. Matter uses 10000 = fully closed 0 = fully opened.
1109
1291
  */
1110
1292
  getDefaultWindowCoveringClusterServer(positionPercent100ths) {
1111
1293
  return ClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
@@ -1124,8 +1306,6 @@ export class MatterbridgeEndpoint extends Endpoint {
1124
1306
  mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
1125
1307
  targetPositionLiftPercent100ths: positionPercent100ths ?? 0, // 0 Fully open 10000 fully closed
1126
1308
  currentPositionLiftPercent100ths: positionPercent100ths ?? 0, // 0 Fully open 10000 fully closed
1127
- // installedClosedLimitLift: 10000,
1128
- // installedOpenLimitLift: 0,
1129
1309
  }, {
1130
1310
  upOrOpen: async (data) => {
1131
1311
  this.log.debug('Matter command: upOrOpen');
@@ -1149,7 +1329,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1149
1329
  /**
1150
1330
  * Creates a default window covering cluster server.
1151
1331
  *
1152
- * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0.
1332
+ * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0. Matter uses 10000 = fully closed 0 = fully opened.
1153
1333
  */
1154
1334
  createDefaultWindowCoveringClusterServer(positionPercent100ths) {
1155
1335
  this.addClusterServer(this.getDefaultWindowCoveringClusterServer(positionPercent100ths));
@@ -1158,22 +1338,19 @@ export class MatterbridgeEndpoint extends Endpoint {
1158
1338
  * Sets the window covering target position as the current position and stops the movement.
1159
1339
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1160
1340
  */
1161
- setWindowCoveringTargetAsCurrentAndStopped(endpoint) {
1341
+ async setWindowCoveringTargetAsCurrentAndStopped(endpoint) {
1162
1342
  if (!endpoint)
1163
1343
  endpoint = this;
1164
- const windowCoveringCluster = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift));
1165
- if (windowCoveringCluster) {
1166
- const position = windowCoveringCluster.getCurrentPositionLiftPercent100thsAttribute();
1167
- if (position !== null) {
1168
- windowCoveringCluster.setTargetPositionLiftPercent100thsAttribute(position);
1169
- windowCoveringCluster.setOperationalStatusAttribute({
1170
- global: WindowCovering.MovementStatus.Stopped,
1171
- lift: WindowCovering.MovementStatus.Stopped,
1172
- tilt: WindowCovering.MovementStatus.Stopped,
1173
- });
1174
- }
1175
- this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`);
1344
+ const position = endpoint.getAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', this.log, endpoint); // windowCoveringCluster.getCurrentPositionLiftPercent100thsAttribute();
1345
+ if (position !== null) {
1346
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', position, this.log, endpoint);
1347
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'operationalStatus', {
1348
+ global: WindowCovering.MovementStatus.Stopped,
1349
+ lift: WindowCovering.MovementStatus.Stopped,
1350
+ tilt: WindowCovering.MovementStatus.Stopped,
1351
+ }, this.log, endpoint);
1176
1352
  }
1353
+ this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`);
1177
1354
  }
1178
1355
  /**
1179
1356
  * Sets the current and target status of a window covering.
@@ -1182,19 +1359,16 @@ export class MatterbridgeEndpoint extends Endpoint {
1182
1359
  * @param {WindowCovering.MovementStatus} status - The movement status of the window covering.
1183
1360
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1184
1361
  */
1185
- setWindowCoveringCurrentTargetStatus(current, target, status, endpoint) {
1362
+ async setWindowCoveringCurrentTargetStatus(current, target, status, endpoint) {
1186
1363
  if (!endpoint)
1187
1364
  endpoint = this;
1188
- const windowCoveringCluster = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift));
1189
- if (windowCoveringCluster) {
1190
- windowCoveringCluster.setCurrentPositionLiftPercent100thsAttribute(current);
1191
- windowCoveringCluster.setTargetPositionLiftPercent100thsAttribute(target);
1192
- windowCoveringCluster.setOperationalStatusAttribute({
1193
- global: status,
1194
- lift: status,
1195
- tilt: status,
1196
- });
1197
- }
1365
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', current, this.log, endpoint);
1366
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', target, this.log, endpoint);
1367
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'operationalStatus', {
1368
+ global: status,
1369
+ lift: status,
1370
+ tilt: status,
1371
+ }, this.log, endpoint);
1198
1372
  this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${current}, targetPositionLiftPercent100ths: ${target} and operationalStatus: ${status}.`);
1199
1373
  }
1200
1374
  /**
@@ -1202,13 +1376,14 @@ export class MatterbridgeEndpoint extends Endpoint {
1202
1376
  * @param {WindowCovering.MovementStatus} status - The movement status to set.
1203
1377
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1204
1378
  */
1205
- setWindowCoveringStatus(status, endpoint) {
1379
+ async setWindowCoveringStatus(status, endpoint) {
1206
1380
  if (!endpoint)
1207
1381
  endpoint = this;
1208
- const windowCovering = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift));
1209
- if (!windowCovering)
1210
- return;
1211
- windowCovering.setOperationalStatusAttribute({ global: status, lift: status, tilt: status });
1382
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'operationalStatus', {
1383
+ global: status,
1384
+ lift: status,
1385
+ tilt: status,
1386
+ }, this.log, endpoint);
1212
1387
  this.log.debug(`Set WindowCovering operationalStatus: ${status}`);
1213
1388
  }
1214
1389
  /**
@@ -1220,10 +1395,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1220
1395
  getWindowCoveringStatus(endpoint) {
1221
1396
  if (!endpoint)
1222
1397
  endpoint = this;
1223
- const windowCovering = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift));
1224
- if (!windowCovering)
1225
- return undefined;
1226
- const status = windowCovering.getOperationalStatusAttribute();
1398
+ const status = endpoint.getAttribute(WindowCoveringCluster.id, 'operationalStatus', this.log, endpoint);
1227
1399
  this.log.debug(`Get WindowCovering operationalStatus: ${status.global}`);
1228
1400
  return status.global;
1229
1401
  }
@@ -1233,14 +1405,11 @@ export class MatterbridgeEndpoint extends Endpoint {
1233
1405
  * @param position - The position to set, specified as a number.
1234
1406
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1235
1407
  */
1236
- setWindowCoveringTargetAndCurrentPosition(position, endpoint) {
1408
+ async setWindowCoveringTargetAndCurrentPosition(position, endpoint) {
1237
1409
  if (!endpoint)
1238
1410
  endpoint = this;
1239
- const windowCovering = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift));
1240
- if (!windowCovering)
1241
- return;
1242
- windowCovering.setCurrentPositionLiftPercent100thsAttribute(position);
1243
- windowCovering.setTargetPositionLiftPercent100thsAttribute(position);
1411
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', position, this.log, endpoint);
1412
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', position, this.log, endpoint);
1244
1413
  this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${position} and targetPositionLiftPercent100ths: ${position}.`);
1245
1414
  }
1246
1415
  /**
@@ -1341,7 +1510,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1341
1510
  * @param {Endpoint} endpoint - The endpoint on which to trigger the event (default the device endpoint).
1342
1511
  * @returns {void}
1343
1512
  */
1344
- triggerSwitchEvent(event, log, endpoint) {
1513
+ async triggerSwitchEvent(event, log, endpoint) {
1345
1514
  if (!endpoint)
1346
1515
  endpoint = this;
1347
1516
  if (['Single', 'Double', 'Long'].includes(event)) {
@@ -1355,38 +1524,38 @@ export class MatterbridgeEndpoint extends Endpoint {
1355
1524
  return false;
1356
1525
  }
1357
1526
  if (event === 'Single') {
1358
- cluster.setCurrentPositionAttribute(1);
1359
- cluster.triggerInitialPressEvent({ newPosition: 1 });
1360
- cluster.setCurrentPositionAttribute(0);
1361
- cluster.triggerShortReleaseEvent({ previousPosition: 1 });
1362
- cluster.setCurrentPositionAttribute(0);
1363
- cluster.triggerMultiPressCompleteEvent({ previousPosition: 1, totalNumberOfPressesCounted: 1 });
1527
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1528
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1529
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1530
+ endpoint.triggerEvent(cluster.id, 'shortRelease', { previousPosition: 1 }, log);
1531
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1532
+ endpoint.triggerEvent(cluster.id, 'multiPressComplete', { previousPosition: 1, totalNumberOfPressesCounted: 1 }, log);
1364
1533
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.SinglePress${db}`);
1365
1534
  }
1366
1535
  if (event === 'Double') {
1367
- cluster.setCurrentPositionAttribute(1);
1368
- cluster.triggerInitialPressEvent({ newPosition: 1 });
1369
- cluster.setCurrentPositionAttribute(0);
1370
- cluster.triggerShortReleaseEvent({ previousPosition: 1 });
1371
- cluster.setCurrentPositionAttribute(1);
1372
- cluster.triggerInitialPressEvent({ newPosition: 1 });
1373
- cluster.triggerMultiPressOngoingEvent({ newPosition: 1, currentNumberOfPressesCounted: 2 });
1374
- cluster.setCurrentPositionAttribute(0);
1375
- cluster.triggerShortReleaseEvent({ previousPosition: 1 });
1376
- cluster.triggerMultiPressCompleteEvent({ previousPosition: 1, totalNumberOfPressesCounted: 2 });
1536
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1537
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1538
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1539
+ endpoint.triggerEvent(cluster.id, 'shortRelease', { previousPosition: 1 }, log);
1540
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1541
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1542
+ endpoint.triggerEvent(cluster.id, 'multiPressOngoing', { newPosition: 1, currentNumberOfPressesCounted: 2 }, log);
1543
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1544
+ endpoint.triggerEvent(cluster.id, 'shortRelease', { previousPosition: 1 }, log);
1545
+ endpoint.triggerEvent(cluster.id, 'multiPressComplete', { previousPosition: 1, totalNumberOfPressesCounted: 2 }, log);
1377
1546
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.DoublePress${db}`);
1378
1547
  }
1379
1548
  if (event === 'Long') {
1380
- cluster.setCurrentPositionAttribute(1);
1381
- cluster.triggerInitialPressEvent({ newPosition: 1 });
1382
- cluster.triggerLongPressEvent({ newPosition: 1 });
1383
- cluster.setCurrentPositionAttribute(0);
1384
- cluster.triggerLongReleaseEvent({ previousPosition: 1 });
1549
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1550
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1551
+ endpoint.triggerEvent(cluster.id, 'longPress', { newPosition: 1 }, log);
1552
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1553
+ endpoint.triggerEvent(cluster.id, 'longRelease', { previousPosition: 1 }, log);
1385
1554
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.LongPress${db}`);
1386
1555
  }
1387
1556
  }
1388
1557
  if (['Press', 'Release'].includes(event)) {
1389
- const cluster = endpoint.getClusterServer(Switch.Complete);
1558
+ const cluster = endpoint.getClusterServer(SwitchCluster.with(Switch.Feature.LatchingSwitch));
1390
1559
  if (!cluster || !cluster.getFeatureMapAttribute().latchingSwitch) {
1391
1560
  log?.error(`triggerSwitchEvent ${event} error: Switch cluster with LatchingSwitch not found on endpoint ${endpoint.id}:${endpoint.number}`);
1392
1561
  return false;
@@ -1396,17 +1565,13 @@ export class MatterbridgeEndpoint extends Endpoint {
1396
1565
  return false;
1397
1566
  }
1398
1567
  if (event === 'Press') {
1399
- cluster.setCurrentPositionAttribute(1);
1400
- log?.info(`${db}Update endpoint ${or}${endpoint.id}:${endpoint.number}${db} attribute ${hk}${cluster.name}.CurrentPosition${db} to ${YELLOW}1${db}`);
1401
- if (cluster.triggerSwitchLatchedEvent)
1402
- cluster.triggerSwitchLatchedEvent({ newPosition: 1 });
1568
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1569
+ endpoint.triggerEvent(cluster.id, 'switchLatched', { newPosition: 1 }, log);
1403
1570
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.Press${db}`);
1404
1571
  }
1405
1572
  if (event === 'Release') {
1406
- cluster.setCurrentPositionAttribute(0);
1407
- log?.info(`${db}Update endpoint ${or}${endpoint.id}:${endpoint.number}${db} attribute ${hk}${cluster.name}.CurrentPosition${db} to ${YELLOW}0${db}`);
1408
- if (cluster.triggerSwitchLatchedEvent)
1409
- cluster.triggerSwitchLatchedEvent({ newPosition: 0 });
1573
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1574
+ endpoint.triggerEvent(cluster.id, 'switchLatched', { newPosition: 0 }, log);
1410
1575
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.Release${db}`);
1411
1576
  }
1412
1577
  }
@@ -1438,10 +1603,12 @@ export class MatterbridgeEndpoint extends Endpoint {
1438
1603
  /**
1439
1604
  * Creates a default mode select cluster server.
1440
1605
  *
1441
- * @remarks
1442
- * This method adds a cluster server for a mode select cluster with default settings.
1443
- *
1606
+ * @param description - The description of the cluster server.
1607
+ * @param supportedModes - The supported modes for the cluster server.
1608
+ * @param currentMode - The current mode of the cluster server. Defaults to 0.
1609
+ * @param startUpMode - The startup mode of the cluster server. Defaults to 0.
1444
1610
  * @param endpoint - The endpoint to add the cluster server to. Defaults to `this` if not provided.
1611
+ *
1445
1612
  */
1446
1613
  createDefaultModeSelectClusterServer(description, supportedModes, currentMode = 0, startUpMode = 0, endpoint) {
1447
1614
  if (!endpoint)
@@ -1493,7 +1660,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1493
1660
  /**
1494
1661
  * Get a default flow measurement cluster server.
1495
1662
  *
1496
- * @param measuredValue - The measured value of the temperature.
1663
+ * @param measuredValue - The measured value of the flow in 10 x m/h.
1497
1664
  */
1498
1665
  getDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1499
1666
  return ClusterServer(FlowMeasurementCluster, {
@@ -1506,7 +1673,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1506
1673
  /**
1507
1674
  * Creates a default flow measurement cluster server.
1508
1675
  *
1509
- * @param measuredValue - The measured value of the temperature.
1676
+ * @param measuredValue - The measured value of the flow in 10 x m/h.
1510
1677
  */
1511
1678
  createDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1512
1679
  this.addClusterServer(this.getDefaultFlowMeasurementClusterServer(measuredValue));
@@ -1514,7 +1681,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1514
1681
  /**
1515
1682
  * Get a default temperature measurement cluster server.
1516
1683
  *
1517
- * @param measuredValue - The measured value of the temperature.
1684
+ * @param measuredValue - The measured value of the temperature x 100.
1518
1685
  */
1519
1686
  getDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1520
1687
  return ClusterServer(TemperatureMeasurementCluster, {
@@ -1527,7 +1694,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1527
1694
  /**
1528
1695
  * Creates a default temperature measurement cluster server.
1529
1696
  *
1530
- * @param measuredValue - The measured value of the temperature.
1697
+ * @param measuredValue - The measured value of the temperature x 100.
1531
1698
  */
1532
1699
  createDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1533
1700
  this.addClusterServer(this.getDefaultTemperatureMeasurementClusterServer(measuredValue));
@@ -1535,7 +1702,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1535
1702
  /**
1536
1703
  * Get a default RelativeHumidityMeasurementCluster server.
1537
1704
  *
1538
- * @param measuredValue - The measured value of the relative humidity.
1705
+ * @param measuredValue - The measured value of the relative humidity x 100.
1539
1706
  */
1540
1707
  getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1541
1708
  return ClusterServer(RelativeHumidityMeasurementCluster, {
@@ -1548,7 +1715,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1548
1715
  /**
1549
1716
  * Creates a default RelativeHumidityMeasurementCluster server.
1550
1717
  *
1551
- * @param measuredValue - The measured value of the relative humidity.
1718
+ * @param measuredValue - The measured value of the relative humidity x 100.
1552
1719
  */
1553
1720
  createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1554
1721
  this.addClusterServer(this.getDefaultRelativeHumidityMeasurementClusterServer(measuredValue));
@@ -1769,6 +1936,80 @@ export class MatterbridgeEndpoint extends Endpoint {
1769
1936
  createDefaultTvocMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) {
1770
1937
  this.addClusterServer(this.getDefaultTvocMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium));
1771
1938
  }
1939
+ /**
1940
+ * Get a default heating thermostat cluster server with the specified parameters.
1941
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
1942
+ * @param {number} [occupiedHeatingSetpoint] - The occupied heating setpoint value in degrees Celsius. Defaults to 21°.
1943
+ * @param {number} [minHeatSetpointLimit] - The minimum heat setpoint limit value. Defaults to 0°.
1944
+ * @param {number} [maxHeatSetpointLimit] - The maximum heat setpoint limit value. Defaults to 50°.
1945
+ * @returns {ThermostatClusterServer} A default thermostat cluster server configured with the specified parameters.
1946
+ */
1947
+ getDefaultHeatingThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50) {
1948
+ return ClusterServer(ThermostatCluster.with(Thermostat.Feature.Heating), {
1949
+ localTemperature: localTemperature * 100,
1950
+ systemMode: Thermostat.SystemMode.Heat,
1951
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.HeatingOnly,
1952
+ // Thermostat.Feature.Heating
1953
+ occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
1954
+ minHeatSetpointLimit: minHeatSetpointLimit * 100,
1955
+ maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1956
+ absMinHeatSetpointLimit: minHeatSetpointLimit * 100,
1957
+ absMaxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1958
+ }, {
1959
+ setpointRaiseLower: async (data) => {
1960
+ this.log.debug('Matter command: setpointRaiseLower', data.request);
1961
+ await this.commandHandler.executeHandler('setpointRaiseLower', data);
1962
+ },
1963
+ }, {});
1964
+ }
1965
+ /**
1966
+ * Creates and adds a default heating thermostat cluster server to the device.
1967
+ *
1968
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
1969
+ * @param {number} [occupiedHeatingSetpoint] - The occupied heating setpoint value in degrees Celsius. Defaults to 21°.
1970
+ * @param {number} [minHeatSetpointLimit] - The minimum heat setpoint limit value. Defaults to 0°.
1971
+ * @param {number} [maxHeatSetpointLimit] - The maximum heat setpoint limit value. Defaults to 50°.
1972
+ */
1973
+ createDefaultHeatingThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 25, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50) {
1974
+ this.addClusterServer(this.getDefaultHeatingThermostatClusterServer(localTemperature, occupiedHeatingSetpoint, minHeatSetpointLimit, maxHeatSetpointLimit));
1975
+ }
1976
+ /**
1977
+ * Get a default cooling thermostat cluster server with the specified parameters.
1978
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
1979
+ * @param {number} [occupiedCoolingSetpoint] - The occupied cooling setpoint value in degrees Celsius. Defaults to 25°.
1980
+ * @param {number} [minCoolSetpointLimit] - The minimum cool setpoint limit value. Defaults to 0°.
1981
+ * @param {number} [maxCoolSetpointLimit] - The maximum cool setpoint limit value. Defaults to 50°.
1982
+ * @returns {ThermostatClusterServer} A default thermostat cluster server configured with the specified parameters.
1983
+ */
1984
+ getDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
1985
+ return ClusterServer(ThermostatCluster.with(Thermostat.Feature.Cooling), {
1986
+ localTemperature: localTemperature * 100,
1987
+ systemMode: Thermostat.SystemMode.Cool,
1988
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingOnly,
1989
+ // Thermostat.Feature.Cooling
1990
+ occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
1991
+ minCoolSetpointLimit: minCoolSetpointLimit * 100,
1992
+ maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
1993
+ absMinCoolSetpointLimit: minCoolSetpointLimit * 100,
1994
+ absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
1995
+ }, {
1996
+ setpointRaiseLower: async (data) => {
1997
+ this.log.debug('Matter command: setpointRaiseLower', data.request);
1998
+ await this.commandHandler.executeHandler('setpointRaiseLower', data);
1999
+ },
2000
+ }, {});
2001
+ }
2002
+ /**
2003
+ * Creates and adds a default cooling thermostat cluster server to the device.
2004
+ *
2005
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
2006
+ * @param {number} [occupiedCoolingSetpoint] - The occupied cooling setpoint value in degrees Celsius. Defaults to 25°.
2007
+ * @param {number} [minCoolSetpointLimit] - The minimum cool setpoint limit value. Defaults to 0°.
2008
+ * @param {number} [maxCoolSetpointLimit] - The maximum cool setpoint limit value. Defaults to 50°.
2009
+ */
2010
+ createDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
2011
+ this.addClusterServer(this.getDefaultCoolingThermostatClusterServer(localTemperature, occupiedCoolingSetpoint, minCoolSetpointLimit, maxCoolSetpointLimit));
2012
+ }
1772
2013
  /**
1773
2014
  * Get a default thermostat cluster server with the specified parameters.
1774
2015
  *
@@ -1785,19 +2026,22 @@ export class MatterbridgeEndpoint extends Endpoint {
1785
2026
  getDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
1786
2027
  return ClusterServer(ThermostatCluster.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode), {
1787
2028
  localTemperature: localTemperature * 100,
2029
+ systemMode: Thermostat.SystemMode.Auto,
2030
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
2031
+ // Thermostat.Feature.Heating
1788
2032
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
1789
- occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
1790
2033
  minHeatSetpointLimit: minHeatSetpointLimit * 100,
1791
2034
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1792
2035
  absMinHeatSetpointLimit: minHeatSetpointLimit * 100,
1793
2036
  absMaxHeatSetpointLimit: maxHeatSetpointLimit * 100,
2037
+ // Thermostat.Feature.Cooling
2038
+ occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
1794
2039
  minCoolSetpointLimit: minCoolSetpointLimit * 100,
1795
2040
  maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
1796
2041
  absMinCoolSetpointLimit: minCoolSetpointLimit * 100,
1797
2042
  absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
2043
+ // Thermostat.Feature.AutoMode
1798
2044
  minSetpointDeadBand: minSetpointDeadBand * 100,
1799
- systemMode: Thermostat.SystemMode.Off,
1800
- controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
1801
2045
  thermostatRunningMode: Thermostat.ThermostatRunningMode.Off,
1802
2046
  }, {
1803
2047
  setpointRaiseLower: async (data) => {
@@ -1821,46 +2065,6 @@ export class MatterbridgeEndpoint extends Endpoint {
1821
2065
  createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
1822
2066
  this.addClusterServer(this.getDefaultThermostatClusterServer(localTemperature, occupiedHeatingSetpoint, occupiedCoolingSetpoint, minSetpointDeadBand, minHeatSetpointLimit, maxHeatSetpointLimit, minCoolSetpointLimit, maxCoolSetpointLimit));
1823
2067
  }
1824
- /**
1825
- * Get a default dummy time sync cluster server. Only needed to create a thermostat.
1826
- */
1827
- getDefaultTimeSyncClusterServer() {
1828
- return ClusterServer(TimeSynchronizationCluster.with(TimeSynchronization.Feature.TimeZone), {
1829
- utcTime: null,
1830
- granularity: TimeSynchronization.Granularity.NoTimeGranularity,
1831
- timeZone: [{ offset: 0, validAt: 0 }],
1832
- dstOffset: [],
1833
- localTime: null,
1834
- timeZoneDatabase: TimeSynchronization.TimeZoneDatabase.None,
1835
- timeZoneListMaxSize: 1,
1836
- dstOffsetListMaxSize: 1,
1837
- }, {
1838
- setTimeZone: async (data) => {
1839
- this.log.debug('Matter command: setTimeZone', data.request);
1840
- await this.commandHandler.executeHandler('setTimeZone', data);
1841
- return { dstOffsetsRequired: false };
1842
- },
1843
- setDstOffset: async (data) => {
1844
- this.log.debug('Matter command: setDstOffset', data.request);
1845
- await this.commandHandler.executeHandler('setDstOffset', data);
1846
- },
1847
- setUtcTime: async (data) => {
1848
- this.log.debug('Matter command: setUtcTime', data.request);
1849
- await this.commandHandler.executeHandler('setUtcTime', data);
1850
- },
1851
- }, {
1852
- dstTableEmpty: true,
1853
- dstStatus: true,
1854
- timeZoneStatus: true,
1855
- timeFailure: true,
1856
- });
1857
- }
1858
- /**
1859
- * Creates a default dummy time sync cluster server. Only needed to create a thermostat.
1860
- */
1861
- createDefaultTimeSyncClusterServer() {
1862
- this.addClusterServer(this.getDefaultTimeSyncClusterServer());
1863
- }
1864
2068
  /**
1865
2069
  * Returns the default SmokeCOAlarm Cluster Server.
1866
2070
  *