matterbridge 1.6.1 → 1.6.3-dev.1

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 (139) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README-DEV.md +0 -4
  3. package/README-NGINX.md +63 -0
  4. package/README.md +7 -3
  5. package/dist/cli.js +15 -7
  6. package/dist/cli.js.map +1 -1
  7. package/dist/deviceManager.d.ts +1 -1
  8. package/dist/deviceManager.d.ts.map +1 -1
  9. package/dist/deviceManager.js +1 -1
  10. package/dist/deviceManager.js.map +1 -1
  11. package/dist/index.d.ts +9 -9
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +23 -10
  14. package/dist/index.js.map +1 -1
  15. package/dist/matter/export.d.ts +5 -0
  16. package/dist/matter/export.d.ts.map +1 -0
  17. package/dist/matter/export.js +5 -0
  18. package/dist/matter/export.js.map +1 -0
  19. package/dist/matterbridge.d.ts +12 -7
  20. package/dist/matterbridge.d.ts.map +1 -1
  21. package/dist/matterbridge.js +133 -83
  22. package/dist/matterbridge.js.map +1 -1
  23. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -1
  24. package/dist/matterbridgeAccessoryPlatform.js.map +1 -1
  25. package/dist/matterbridgeBehaviors.d.ts +1123 -0
  26. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  27. package/dist/matterbridgeBehaviors.js +281 -0
  28. package/dist/matterbridgeBehaviors.js.map +1 -0
  29. package/dist/matterbridgeDevice.d.ts +2089 -1511
  30. package/dist/matterbridgeDevice.d.ts.map +1 -1
  31. package/dist/matterbridgeDevice.js +243 -209
  32. package/dist/matterbridgeDevice.js.map +1 -1
  33. package/dist/matterbridgeDeviceTypes.d.ts +65 -0
  34. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  35. package/dist/matterbridgeDeviceTypes.js +307 -0
  36. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  37. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -1
  38. package/dist/matterbridgeDynamicPlatform.js.map +1 -1
  39. package/dist/matterbridgeEdge.d.ts +19 -20
  40. package/dist/matterbridgeEdge.d.ts.map +1 -1
  41. package/dist/matterbridgeEdge.js +505 -102
  42. package/dist/matterbridgeEdge.js.map +1 -1
  43. package/dist/matterbridgeEndpoint.d.ts +3554 -2297
  44. package/dist/matterbridgeEndpoint.d.ts.map +1 -1
  45. package/dist/matterbridgeEndpoint.js +709 -476
  46. package/dist/matterbridgeEndpoint.js.map +1 -1
  47. package/dist/matterbridgePlatform.d.ts +4 -3
  48. package/dist/matterbridgePlatform.d.ts.map +1 -1
  49. package/dist/matterbridgePlatform.js +10 -2
  50. package/dist/matterbridgePlatform.js.map +1 -1
  51. package/dist/matterbridgeTypes.d.ts +7 -8
  52. package/dist/matterbridgeTypes.d.ts.map +1 -1
  53. package/dist/matterbridgeTypes.js.map +1 -1
  54. package/dist/matterbridgeWebsocket.d.ts +1 -1
  55. package/dist/matterbridgeWebsocket.d.ts.map +1 -1
  56. package/dist/matterbridgeWebsocket.js +5 -3
  57. package/dist/matterbridgeWebsocket.js.map +1 -1
  58. package/dist/pluginManager.d.ts.map +1 -1
  59. package/dist/pluginManager.js +16 -7
  60. package/dist/pluginManager.js.map +1 -1
  61. package/dist/utils/colorUtils.js +1 -1
  62. package/dist/utils/colorUtils.js.map +1 -1
  63. package/dist/utils/utils.d.ts +21 -0
  64. package/dist/utils/utils.d.ts.map +1 -1
  65. package/dist/utils/utils.js +49 -3
  66. package/dist/utils/utils.js.map +1 -1
  67. package/frontend/build/asset-manifest.json +62 -6
  68. package/frontend/build/index.html +1 -1
  69. package/frontend/build/static/css/main.823e08b6.css +2 -0
  70. package/frontend/build/static/css/main.823e08b6.css.map +1 -0
  71. package/frontend/build/static/js/main.a14c87e7.js +115 -0
  72. package/frontend/build/static/js/{main.045d08f7.js.LICENSE.txt → main.a14c87e7.js.LICENSE.txt} +3 -3
  73. package/frontend/build/static/js/main.a14c87e7.js.map +1 -0
  74. package/frontend/build/static/media/roboto-cyrillic-300-normal.1b79538ccd585c259996.woff2 +0 -0
  75. package/frontend/build/static/media/roboto-cyrillic-300-normal.5f077fd7b977d1715acf.woff +0 -0
  76. package/frontend/build/static/media/roboto-cyrillic-400-normal.5d2930082227d172f62c.woff +0 -0
  77. package/frontend/build/static/media/roboto-cyrillic-400-normal.a9e19870cf6c4b973427.woff2 +0 -0
  78. package/frontend/build/static/media/roboto-cyrillic-500-normal.0ae2428323939af5e1ad.woff2 +0 -0
  79. package/frontend/build/static/media/roboto-cyrillic-500-normal.dd7bc8a52c6c70c5a3f5.woff +0 -0
  80. package/frontend/build/static/media/roboto-cyrillic-700-normal.3f6e1548bd5175a8c342.woff +0 -0
  81. package/frontend/build/static/media/roboto-cyrillic-700-normal.4fdfc29a10e7d4b7c527.woff2 +0 -0
  82. package/frontend/build/static/media/roboto-cyrillic-ext-300-normal.795dbc8140e3fef82983.woff +0 -0
  83. package/frontend/build/static/media/roboto-cyrillic-ext-300-normal.80947a31d23c70204b47.woff2 +0 -0
  84. package/frontend/build/static/media/roboto-cyrillic-ext-400-normal.135d076fa32aa0b4d105.woff +0 -0
  85. package/frontend/build/static/media/roboto-cyrillic-ext-400-normal.5cec61a21cc20180fbe1.woff2 +0 -0
  86. package/frontend/build/static/media/roboto-cyrillic-ext-500-normal.6de16332fda843a3dc3d.woff2 +0 -0
  87. package/frontend/build/static/media/roboto-cyrillic-ext-500-normal.c0a0638f90b31d6454ba.woff +0 -0
  88. package/frontend/build/static/media/roboto-cyrillic-ext-700-normal.4750292c47fa2bc6ac1a.woff2 +0 -0
  89. package/frontend/build/static/media/roboto-cyrillic-ext-700-normal.ca247189fc12d00de361.woff +0 -0
  90. package/frontend/build/static/media/roboto-greek-300-normal.285f3e6261d8eb20417d.woff2 +0 -0
  91. package/frontend/build/static/media/roboto-greek-300-normal.889beddda1c9bd9f97df.woff +0 -0
  92. package/frontend/build/static/media/roboto-greek-400-normal.160a791a8e4f46bca3cc.woff +0 -0
  93. package/frontend/build/static/media/roboto-greek-400-normal.2c32b1315be61477013a.woff2 +0 -0
  94. package/frontend/build/static/media/roboto-greek-500-normal.60810e07c7b0273013aa.woff +0 -0
  95. package/frontend/build/static/media/roboto-greek-500-normal.f95e757c5483310f9c11.woff2 +0 -0
  96. package/frontend/build/static/media/roboto-greek-700-normal.77dd370f2001e184ba0d.woff2 +0 -0
  97. package/frontend/build/static/media/roboto-greek-700-normal.df87b053fae3d7ad5f7a.woff +0 -0
  98. package/frontend/build/static/media/roboto-greek-ext-300-normal.b590dbe5c639944366d1.woff +0 -0
  99. package/frontend/build/static/media/roboto-greek-ext-300-normal.d6049cb54aa6fbe14c42.woff2 +0 -0
  100. package/frontend/build/static/media/roboto-greek-ext-400-normal.16eb83b4a3b1ea994243.woff +0 -0
  101. package/frontend/build/static/media/roboto-greek-ext-400-normal.1df4abad55796d11a0c8.woff2 +0 -0
  102. package/frontend/build/static/media/roboto-greek-ext-500-normal.4a96ba31abcce0f5d52b.woff2 +0 -0
  103. package/frontend/build/static/media/roboto-greek-ext-500-normal.fd28d9c008bf3af1bed7.woff +0 -0
  104. package/frontend/build/static/media/roboto-greek-ext-700-normal.2dd6febad11502dec6a6.woff2 +0 -0
  105. package/frontend/build/static/media/roboto-greek-ext-700-normal.4abdc9fff4507f17d726.woff +0 -0
  106. package/frontend/build/static/media/roboto-latin-300-normal.b850f1ff581ea232fac9.woff2 +0 -0
  107. package/frontend/build/static/media/roboto-latin-300-normal.c4bc0593c9954d79cb3a.woff +0 -0
  108. package/frontend/build/static/media/roboto-latin-400-normal.047a7839f69b209db815.woff +0 -0
  109. package/frontend/build/static/media/roboto-latin-400-normal.297d48e1b5a10c0831a9.woff2 +0 -0
  110. package/frontend/build/static/media/roboto-latin-500-normal.68d40d6d01c6f85d24ba.woff +0 -0
  111. package/frontend/build/static/media/roboto-latin-500-normal.7077203b1982951ecf76.woff2 +0 -0
  112. package/frontend/build/static/media/roboto-latin-700-normal.4535474e1cf8598695ad.woff2 +0 -0
  113. package/frontend/build/static/media/roboto-latin-700-normal.9f6a16a7770c87b2042b.woff +0 -0
  114. package/frontend/build/static/media/roboto-latin-ext-300-normal.14982a9e4857a93b6dce.woff +0 -0
  115. package/frontend/build/static/media/roboto-latin-ext-300-normal.97cbc447d4a8d41a9543.woff2 +0 -0
  116. package/frontend/build/static/media/roboto-latin-ext-400-normal.27da5b36b6d3a16f53f4.woff +0 -0
  117. package/frontend/build/static/media/roboto-latin-ext-400-normal.2eeae187764baf05867d.woff2 +0 -0
  118. package/frontend/build/static/media/roboto-latin-ext-500-normal.06c30711d588145a4541.woff +0 -0
  119. package/frontend/build/static/media/roboto-latin-ext-500-normal.9a18d7bb9ff7a6af7b32.woff2 +0 -0
  120. package/frontend/build/static/media/roboto-latin-ext-700-normal.18841836e391d39e83a8.woff2 +0 -0
  121. package/frontend/build/static/media/roboto-latin-ext-700-normal.3c5bcdd0e69c4c3ffafe.woff +0 -0
  122. package/frontend/build/static/media/roboto-vietnamese-300-normal.c96b16e5c05c7b7c3e89.woff2 +0 -0
  123. package/frontend/build/static/media/roboto-vietnamese-300-normal.f5e7cea32756dfe7af40.woff +0 -0
  124. package/frontend/build/static/media/roboto-vietnamese-400-normal.0dc97c66f9b542d6fa17.woff +0 -0
  125. package/frontend/build/static/media/roboto-vietnamese-400-normal.d3f8e26d6c27de8102b6.woff2 +0 -0
  126. package/frontend/build/static/media/roboto-vietnamese-500-normal.090fabef926bdc0e9b9f.woff2 +0 -0
  127. package/frontend/build/static/media/roboto-vietnamese-500-normal.23b7b8a2524d2d4b637b.woff +0 -0
  128. package/frontend/build/static/media/roboto-vietnamese-700-normal.0a79a9fabfc32e33f360.woff2 +0 -0
  129. package/frontend/build/static/media/roboto-vietnamese-700-normal.35ed0597568ff6f19c16.woff +0 -0
  130. package/npm-shrinkwrap.json +117 -36
  131. package/package.json +8 -3
  132. package/dist/matterbridgeController.d.ts +0 -24
  133. package/dist/matterbridgeController.d.ts.map +0 -1
  134. package/dist/matterbridgeController.js +0 -386
  135. package/dist/matterbridgeController.js.map +0 -1
  136. package/frontend/build/static/css/main.1cf003ae.css +0 -2
  137. package/frontend/build/static/css/main.1cf003ae.css.map +0 -1
  138. package/frontend/build/static/js/main.045d08f7.js +0 -3
  139. package/frontend/build/static/js/main.045d08f7.js.map +0 -1
@@ -21,81 +21,122 @@
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.
71
- * @param {EndpointOptions} [options={}] - The options for the device.
72
- */
73
- constructor(definition, options = {}) {
74
- // Convert the DeviceTypeDefinition to a EndpointType.Options
77
+ * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The DeviceTypeDefinition(s) of the endpoint.
78
+ * @param {MatterbridgeEndpointOptions} [options={}] - The options for the device.
79
+ */
80
+ constructor(definition, options = {}, debug = false) {
81
+ // Get the first DeviceTypeDefinition
82
+ let firstDefinition;
83
+ if (Array.isArray(definition))
84
+ firstDefinition = definition[0];
85
+ else
86
+ firstDefinition = definition;
87
+ // Convert the first DeviceTypeDefinition to an EndpointType.Options
75
88
  const deviceTypeDefinitionV8 = {
76
- name: definition.name.replace('-', '_'),
77
- deviceType: definition.code,
78
- deviceRevision: definition.revision,
79
- deviceClass: definition.deviceClass,
89
+ name: firstDefinition.name.replace('-', '_'),
90
+ deviceType: firstDefinition.code,
91
+ deviceRevision: firstDefinition.revision,
92
+ deviceClass: firstDefinition.deviceClass.toLowerCase(),
80
93
  requirements: {
81
94
  server: {
82
- mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(definition.requiredServerClusters)),
83
- optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(definition.optionalServerClusters)),
95
+ mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(firstDefinition.requiredServerClusters)),
96
+ optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(firstDefinition.optionalServerClusters)),
84
97
  },
85
98
  client: {
86
- mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(definition.requiredClientClusters)),
87
- optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(definition.optionalClientClusters)),
99
+ mandatory: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(firstDefinition.requiredClientClusters)),
100
+ optional: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterClientIds(firstDefinition.optionalClientClusters)),
88
101
  },
89
102
  },
90
- behaviors: SupportedBehaviors(...MatterbridgeEndpoint.getBehaviourTypesFromClusterServerIds(definition.requiredServerClusters)),
103
+ behaviors: options.tagList ? SupportedBehaviors(DescriptorServer.with(Descriptor.Feature.TagList)) : {},
91
104
  };
92
105
  const endpointV8 = MutableEndpoint(deviceTypeDefinitionV8);
106
+ // Convert the options to an Endpoint.Options
107
+ // [{ mfgCode: null, namespaceId: 0x07, tag: 1, label: 'Switch1' }]
108
+ // endpoint = endpoint.enable({features: { tagList: true }});
93
109
  const optionsV8 = {
94
- id: options.uniqueStorageKey,
110
+ id: options.uniqueStorageKey?.replace(/[ .]/g, ''),
111
+ number: options.endpointId,
112
+ descriptor: options.tagList ? { tagList: options.tagList } : undefined,
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
114
  };
96
115
  super(endpointV8, optionsV8);
97
- this.log = new AnsiLogger({ logName: 'MatterbridgeDevice', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: true });
98
- this.deviceTypes.set(definition.code, definition);
116
+ this.uniqueStorageKey = options.uniqueStorageKey;
117
+ this.tagList = options.tagList;
118
+ // Update the endpoint
119
+ this.log = new AnsiLogger({ logName: 'MatterbridgeEndpoint', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: debug === true ? "debug" /* LogLevel.DEBUG */ : MatterbridgeEndpoint.logLevel });
120
+ this.log.debug(`${YELLOW}new${db} MatterbridgeEndpoint: ${zb}${'0x' + firstDefinition.code.toString(16).padStart(4, '0')}${db}-${zb}${firstDefinition.name}${db} ` +
121
+ `id: ${CYAN}${options.uniqueStorageKey}${db} number: ${CYAN}${options.endpointId}${db} taglist: ${CYAN}${options.tagList ? debugStringify(options.tagList) : 'undefined'}${db}`);
122
+ this.deviceTypes.set(firstDefinition.code, firstDefinition);
123
+ // Add the other device types to the descriptor server
124
+ if (Array.isArray(definition)) {
125
+ definition.forEach((deviceType) => {
126
+ this.addDeviceType(deviceType);
127
+ });
128
+ }
129
+ // Add MatterbridgeBehavior with MatterbridgeBehaviorDevice
130
+ this.behaviors.require(MatterbridgeBehavior, { deviceCommand: new MatterbridgeBehaviorDevice(this.log, this.commandHandler, undefined) });
131
+ }
132
+ /**
133
+ * Loads an instance of the MatterbridgeDevice class.
134
+ *
135
+ * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The DeviceTypeDefinition(s) of the device.
136
+ * @returns MatterbridgeDevice instance.
137
+ */
138
+ static async loadInstance(definition, options = {}, debug = false) {
139
+ return new MatterbridgeEndpoint(definition, options, debug);
99
140
  }
100
141
  static getBehaviourTypesFromClusterServerIds(clusterServerList) {
101
142
  // Map Server ClusterId to Behavior.Type
@@ -113,57 +154,97 @@ export class MatterbridgeEndpoint extends Endpoint {
113
154
  });
114
155
  return behaviorTypes;
115
156
  }
116
- static getBehaviourTypeFromClusterServerId(clusterId) {
157
+ static getBehaviourTypeFromClusterServerId(clusterId, type) {
117
158
  // Map ClusterId to Behavior.Type
118
159
  if (clusterId === Identify.Cluster.id)
119
- return IdentifyServer;
160
+ return MatterbridgeIdentifyServer;
120
161
  if (clusterId === Groups.Cluster.id)
121
162
  return GroupsServer;
122
- // if (clusterId === Scenes.Cluster.id) return ScenesServer;
123
163
  if (clusterId === OnOff.Cluster.id)
124
- return OnOffServer;
164
+ return MatterbridgeOnOffServer;
125
165
  if (clusterId === LevelControl.Cluster.id)
126
- return LevelControlServer;
166
+ return MatterbridgeLevelControlServer;
127
167
  if (clusterId === ColorControl.Cluster.id)
128
- return ColorControlServer.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.Xy, ColorControl.Feature.ColorTemperature);
168
+ return MatterbridgeColorControlServer;
129
169
  if (clusterId === DoorLock.Cluster.id)
130
- return DoorLockServer;
170
+ return MatterbridgeDoorLockServer;
131
171
  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);
172
+ return MatterbridgeThermostatServer;
135
173
  if (clusterId === WindowCovering.Cluster.id)
136
- return WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift);
174
+ return MatterbridgeWindowCoveringServer;
137
175
  if (clusterId === FanControl.Cluster.id)
138
- return FanControlServer.with(FanControl.Feature.MultiSpeed, FanControl.Feature.Auto, FanControl.Feature.Step);
176
+ return MatterbridgeFanControlServer;
177
+ if (clusterId === Switch.Cluster.id && type === 'MomentarySwitch')
178
+ return SwitchServer.with('MomentarySwitch', 'MomentarySwitchRelease', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress');
179
+ if (clusterId === Switch.Cluster.id && type === 'LatchingSwitch')
180
+ return SwitchServer.with('LatchingSwitch');
139
181
  if (clusterId === TemperatureMeasurement.Cluster.id)
140
182
  return TemperatureMeasurementServer;
141
183
  if (clusterId === RelativeHumidityMeasurement.Cluster.id)
142
184
  return RelativeHumidityMeasurementServer;
143
185
  if (clusterId === PressureMeasurement.Cluster.id)
144
- return PressureMeasurementServer.with(PressureMeasurement.Feature.Extended);
186
+ return PressureMeasurementServer;
145
187
  if (clusterId === FlowMeasurement.Cluster.id)
146
188
  return FlowMeasurementServer;
147
189
  if (clusterId === BooleanState.Cluster.id)
148
190
  return BooleanStateServer;
149
191
  if (clusterId === BooleanStateConfiguration.Cluster.id)
150
- return BooleanStateConfigurationServer;
192
+ return MatterbridgeBooleanStateConfigurationServer;
151
193
  if (clusterId === OccupancySensing.Cluster.id)
152
194
  return OccupancySensingServer;
153
195
  if (clusterId === IlluminanceMeasurement.Cluster.id)
154
196
  return IlluminanceMeasurementServer;
197
+ if (clusterId === SmokeCoAlarm.Cluster.id)
198
+ return SmokeCoAlarmServer.with(SmokeCoAlarm.Feature.SmokeAlarm, SmokeCoAlarm.Feature.CoAlarm);
199
+ if (clusterId === AirQuality.Cluster.id)
200
+ return AirQualityServer.with(AirQuality.Feature.Fair, AirQuality.Feature.Moderate, AirQuality.Feature.VeryPoor, AirQuality.Feature.ExtremelyPoor);
201
+ if (clusterId === CarbonMonoxideConcentrationMeasurement.Cluster.id)
202
+ return CarbonMonoxideConcentrationMeasurementServer.with('NumericMeasurement');
203
+ if (clusterId === CarbonDioxideConcentrationMeasurement.Cluster.id)
204
+ return CarbonDioxideConcentrationMeasurementServer.with('NumericMeasurement');
205
+ if (clusterId === NitrogenDioxideConcentrationMeasurement.Cluster.id)
206
+ return NitrogenDioxideConcentrationMeasurementServer.with('NumericMeasurement');
207
+ if (clusterId === OzoneConcentrationMeasurement.Cluster.id)
208
+ return OzoneConcentrationMeasurementServer.with('NumericMeasurement');
209
+ if (clusterId === FormaldehydeConcentrationMeasurement.Cluster.id)
210
+ return FormaldehydeConcentrationMeasurementServer.with('NumericMeasurement');
211
+ if (clusterId === Pm1ConcentrationMeasurement.Cluster.id)
212
+ return Pm1ConcentrationMeasurementServer.with('NumericMeasurement');
213
+ if (clusterId === Pm25ConcentrationMeasurement.Cluster.id)
214
+ return Pm25ConcentrationMeasurementServer.with('NumericMeasurement');
215
+ if (clusterId === Pm10ConcentrationMeasurement.Cluster.id)
216
+ return Pm10ConcentrationMeasurementServer.with('NumericMeasurement');
217
+ if (clusterId === RadonConcentrationMeasurement.Cluster.id)
218
+ return RadonConcentrationMeasurementServer.with('NumericMeasurement');
219
+ if (clusterId === TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id)
220
+ return TotalVolatileOrganicCompoundsConcentrationMeasurementServer.with('NumericMeasurement');
221
+ if (clusterId === ModeSelect.Cluster.id)
222
+ return ModeSelectServer;
223
+ if (clusterId === UserLabel.Cluster.id)
224
+ return UserLabelServer;
225
+ if (clusterId === FixedLabel.Cluster.id)
226
+ return FixedLabelServer;
227
+ if (clusterId === PowerTopology.Cluster.id)
228
+ return PowerTopologyServer.with('TreeTopology');
229
+ if (clusterId === ElectricalPowerMeasurement.Cluster.id)
230
+ return ElectricalPowerMeasurementServer.with('AlternatingCurrent');
231
+ if (clusterId === ElectricalEnergyMeasurement.Cluster.id)
232
+ return ElectricalEnergyMeasurementServer.with('ImportedEnergy', 'ExportedEnergy', 'CumulativeEnergy');
233
+ if (clusterId === PowerSource.Cluster.id && type === 'WiredPowerSource')
234
+ return PowerSourceServer.with(PowerSource.Feature.Wired);
235
+ if (clusterId === PowerSource.Cluster.id && type === 'BatteryReplaceablePowerSource')
236
+ return PowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Replaceable);
237
+ if (clusterId === PowerSource.Cluster.id && type === 'BatteryRechargeablePowerSource')
238
+ return PowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable);
239
+ if (clusterId === BasicInformation.Cluster.id)
240
+ return BasicInformationServer;
155
241
  if (clusterId === BridgedDeviceBasicInformation.Cluster.id)
156
242
  return BridgedDeviceBasicInformationServer;
157
- return IdentifyServer;
243
+ return MatterbridgeIdentifyServer;
158
244
  }
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);
245
+ static getBehaviourTypeFromClusterClientId(clusterId) {
246
+ // Map ClusterId to Behavior.Type
247
+ return IdentifyBehavior;
167
248
  }
168
249
  /**
169
250
  * Adds a device type to the list of device types.
@@ -174,16 +255,24 @@ export class MatterbridgeEndpoint extends Endpoint {
174
255
  addDeviceType(deviceType) {
175
256
  if (!this.deviceTypes.has(deviceType.code)) {
176
257
  // Keep the Matterbridge internal map
177
- this.log.debug(`addDeviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`);
258
+ this.log.debug(`addDeviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
178
259
  this.deviceTypes.set(deviceType.code, deviceType);
179
260
  // Add the device types to the descriptor server
180
261
  const deviceTypeList = Array.from(this.deviceTypes.values()).map((dt) => ({
181
262
  deviceType: dt.code,
182
263
  revision: dt.revision,
183
264
  }));
184
- this.behaviors.require(DescriptorServer, {
185
- deviceTypeList,
186
- });
265
+ if (this.tagList) {
266
+ this.behaviors.require(DescriptorServer.with(Descriptor.Feature.TagList), {
267
+ tagList: this.tagList,
268
+ deviceTypeList,
269
+ });
270
+ }
271
+ else {
272
+ this.behaviors.require(DescriptorServer, {
273
+ deviceTypeList,
274
+ });
275
+ }
187
276
  }
188
277
  }
189
278
  /**
@@ -195,20 +284,64 @@ export class MatterbridgeEndpoint extends Endpoint {
195
284
  addDeviceTypeWithClusterServer(deviceTypes, includeServerList) {
196
285
  this.log.debug('addDeviceTypeWithClusterServer:');
197
286
  deviceTypes.forEach((deviceType) => {
198
- this.log.debug(`- with deviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`);
287
+ this.log.debug(`- with deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
199
288
  deviceType.requiredServerClusters.forEach((clusterId) => {
200
289
  if (!includeServerList.includes(clusterId))
201
290
  includeServerList.push(clusterId);
202
291
  });
203
292
  });
204
293
  includeServerList.forEach((clusterId) => {
205
- this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
294
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
206
295
  });
207
296
  deviceTypes.forEach((deviceType) => {
208
297
  this.addDeviceType(deviceType);
209
298
  });
210
299
  this.addClusterServerFromList(this, includeServerList);
211
300
  }
301
+ /**
302
+ * Adds the required cluster servers (only if they are not present) for the device types of the specified endpoint.
303
+ *
304
+ * @param {MatterbridgeEndpoint} endpoint - The endpoint to add the required cluster servers to.
305
+ * @returns {MatterbridgeEndpoint} The updated endpoint with the required cluster servers added.
306
+ */
307
+ addRequiredClusterServers(endpoint) {
308
+ const requiredServerList = [];
309
+ this.log.debug(`addRequiredClusterServer for ${CYAN}${endpoint.id}${db}`);
310
+ endpoint.getDeviceTypes().forEach((deviceType) => {
311
+ this.log.debug(`- for deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
312
+ deviceType.requiredServerClusters.forEach((clusterId) => {
313
+ if (!requiredServerList.includes(clusterId) && !endpoint.getClusterServerById(clusterId))
314
+ requiredServerList.push(clusterId);
315
+ });
316
+ });
317
+ requiredServerList.forEach((clusterId) => {
318
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
319
+ });
320
+ this.addClusterServerFromList(endpoint, requiredServerList);
321
+ return endpoint;
322
+ }
323
+ /**
324
+ * Adds the optional cluster servers (only if they are not present) for the device types of the specified endpoint.
325
+ *
326
+ * @param {MatterbridgeEndpoint} endpoint - The endpoint to add the required cluster servers to.
327
+ * @returns {MatterbridgeEndpoint} The updated endpoint with the required cluster servers added.
328
+ */
329
+ addOptionalClusterServers(endpoint) {
330
+ const optionalServerList = [];
331
+ this.log.debug(`addRequiredClusterServer for ${CYAN}${endpoint.id}${db}`);
332
+ endpoint.getDeviceTypes().forEach((deviceType) => {
333
+ this.log.debug(`- for deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
334
+ deviceType.optionalServerClusters.forEach((clusterId) => {
335
+ if (!optionalServerList.includes(clusterId) && !endpoint.getClusterServerById(clusterId))
336
+ optionalServerList.push(clusterId);
337
+ });
338
+ });
339
+ optionalServerList.forEach((clusterId) => {
340
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
341
+ });
342
+ this.addClusterServerFromList(endpoint, optionalServerList);
343
+ return endpoint;
344
+ }
212
345
  /**
213
346
  * Adds a child endpoint with one or more device types with the required cluster servers and the specified cluster servers.
214
347
  * If the child endpoint is not already present in the childEndpoints, it will be added.
@@ -216,30 +349,71 @@ export class MatterbridgeEndpoint extends Endpoint {
216
349
  *
217
350
  * @param {string} endpointName - The name of the new enpoint to add.
218
351
  * @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.
352
+ * @param {ClusterId[]} [includeServerList=[]] - The list of cluster IDs to include.
353
+ * @param {EndpointOptions} [options={}] - The options for the device.
354
+ * @param {boolean} [debug=false] - Whether to enable debug logging.
355
+ * @returns {MatterbridgeEndpoint} - The child endpoint that was found or added.
221
356
  */
222
- addChildDeviceTypeWithClusterServer(endpointName, deviceTypes, includeServerList) {
223
- /*
357
+ addChildDeviceTypeWithClusterServer(endpointName, deviceTypes, includeServerList = [], options = {}, debug = false) {
224
358
  this.log.debug(`addChildDeviceTypeWithClusterServer: ${CYAN}${endpointName}${db}`);
225
- let child = this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName);
359
+ let child = this.getChildEndpointByName(endpointName);
226
360
  if (!child) {
227
- child = new Endpoint(deviceTypes, { uniqueStorageKey: endpointName });
228
- child.addFixedLabel('endpointName', endpointName);
361
+ if ('tagList' in options) {
362
+ for (const tag of options.tagList) {
363
+ this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`);
364
+ }
365
+ child = new MatterbridgeEndpoint(deviceTypes[0], { uniqueStorageKey: endpointName, taglist: options.tagList }, debug);
366
+ }
367
+ else {
368
+ child = new MatterbridgeEndpoint(deviceTypes[0], { uniqueStorageKey: endpointName }, debug);
369
+ }
229
370
  }
230
371
  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
- });
372
+ this.log.debug(`- with deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
373
+ deviceType.requiredServerClusters.forEach((clusterId) => {
374
+ if (!includeServerList.includes(clusterId))
375
+ includeServerList.push(clusterId);
376
+ });
235
377
  });
236
378
  includeServerList.forEach((clusterId) => {
237
- this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
379
+ this.log.debug(`- with cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
238
380
  });
239
381
  this.addClusterServerFromList(child, includeServerList);
240
- this.addChildEndpoint(child);
382
+ if (this.lifecycle.isInstalled) {
383
+ this.log.debug(`- with lifecycle installed`);
384
+ this.add(child);
385
+ }
386
+ else {
387
+ this.log.debug(`- with lifecycle NOT installed`);
388
+ this.parts.add(child);
389
+ }
241
390
  return child;
242
- */
391
+ }
392
+ /**
393
+ * Retrieves a child endpoint by its name.
394
+ *
395
+ * @param {string} endpointName - The name of the endpoint to retrieve.
396
+ * @returns {Endpoint | undefined} The child endpoint with the specified name, or undefined if not found.
397
+ */
398
+ getChildEndpointByName(endpointName) {
399
+ return this.parts.find((part) => part.id === endpointName);
400
+ }
401
+ getChildEndpoint(endpointNumber) {
402
+ return this.parts.find((part) => part.number === endpointNumber);
403
+ }
404
+ getChildEndpoints() {
405
+ return Array.from(this.parts);
406
+ }
407
+ getDeviceTypes() {
408
+ return Array.from(this.deviceTypes.values());
409
+ }
410
+ setDeviceTypes(deviceTypes) {
411
+ deviceTypes.forEach((deviceType) => {
412
+ this.addDeviceType(deviceType);
413
+ });
414
+ }
415
+ hasClusterServer(cluster) {
416
+ return this.clusterServers.has(cluster.id);
243
417
  }
244
418
  getClusterServer(cluster) {
245
419
  const clusterServer = this.clusterServers.get(cluster.id);
@@ -250,21 +424,35 @@ export class MatterbridgeEndpoint extends Endpoint {
250
424
  getClusterServerById(clusterId) {
251
425
  return this.clusterServers.get(clusterId);
252
426
  }
427
+ addTagList(endpoint, mfgCode, namespaceId, tag, label) {
428
+ // Do nothing here
429
+ }
253
430
  addClusterServer(cluster) {
254
431
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
432
  const options = {};
256
433
  for (const attribute of Object.values(cluster.attributes)) {
434
+ // console.error('Attribute:', (attribute as any).id, (attribute as any).name, (attribute as any).value);
257
435
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
436
  if (attribute.id < 0xfff0) {
259
- // No issue here for value, as cluster here is just a definition without getter setter
260
437
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
438
  options[attribute.name] = attribute.value;
262
439
  }
263
440
  }
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
441
+ this.log.debug(`addClusterServer: ${hk}${'0x' + cluster.id.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(cluster.id)}${db} with options: ${debugStringify(options)}${rs}`);
442
+ let type = undefined;
443
+ if (cluster.id === SwitchCluster.id && cluster.isEventSupportedByName('multiPressComplete'))
444
+ type = 'MomentarySwitch';
445
+ if (cluster.id === SwitchCluster.id && cluster.isEventSupportedByName('switchLatched'))
446
+ type = 'LatchingSwitch';
447
+ if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('wiredCurrentType'))
448
+ type = 'WiredPowerSource';
449
+ if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('batReplacementDescription'))
450
+ type = 'BatteryReplaceablePowerSource';
451
+ if (cluster.id === PowerSourceCluster.id && cluster.isAttributeSupportedByName('batChargeState'))
452
+ type = 'BatteryRechargeablePowerSource';
453
+ const behavior = MatterbridgeEndpoint.getBehaviourTypeFromClusterServerId(cluster.id, type);
454
+ if (cluster.id !== BasicInformationCluster.id)
455
+ this.behaviors.require(behavior, options);
268
456
  this.clusterServers.set(cluster.id, cluster);
269
457
  }
270
458
  /**
@@ -279,21 +467,18 @@ export class MatterbridgeEndpoint extends Endpoint {
279
467
  endpoint.addClusterServer(this.getDefaultIdentifyClusterServer());
280
468
  if (includeServerList.includes(Groups.Cluster.id))
281
469
  endpoint.addClusterServer(this.getDefaultGroupsClusterServer());
282
- // if (includeServerList.includes(Scenes.Cluster.id)) endpoint.addClusterServer(this.getDefaultScenesClusterServer());
283
470
  if (includeServerList.includes(OnOff.Cluster.id))
284
471
  endpoint.addClusterServer(this.getDefaultOnOffClusterServer());
285
472
  if (includeServerList.includes(LevelControl.Cluster.id))
286
473
  endpoint.addClusterServer(this.getDefaultLevelControlClusterServer());
287
474
  if (includeServerList.includes(ColorControl.Cluster.id))
288
- endpoint.addClusterServer(this.getDefaultCompleteColorControlClusterServer());
475
+ endpoint.addClusterServer(this.getDefaultColorControlClusterServer());
289
476
  if (includeServerList.includes(Switch.Cluster.id))
290
477
  endpoint.addClusterServer(this.getDefaultSwitchClusterServer());
291
478
  if (includeServerList.includes(DoorLock.Cluster.id))
292
479
  endpoint.addClusterServer(this.getDefaultDoorLockClusterServer());
293
480
  if (includeServerList.includes(Thermostat.Cluster.id))
294
481
  endpoint.addClusterServer(this.getDefaultThermostatClusterServer());
295
- if (includeServerList.includes(TimeSynchronization.Cluster.id))
296
- endpoint.addClusterServer(this.getDefaultTimeSyncClusterServer());
297
482
  if (includeServerList.includes(WindowCovering.Cluster.id))
298
483
  endpoint.addClusterServer(this.getDefaultWindowCoveringClusterServer());
299
484
  if (includeServerList.includes(FanControl.Cluster.id))
@@ -349,56 +534,71 @@ export class MatterbridgeEndpoint extends Endpoint {
349
534
  // if (includeServerList.includes(DeviceEnergyManagement.Cluster.id)) endpoint.addClusterServer(this.getDefaultDeviceEnergyManagementClusterServer());
350
535
  // if (includeServerList.includes(DeviceEnergyManagementMode.Cluster.id)) endpoint.addClusterServer(this.getDefaultDeviceEnergyManagementModeClusterServer());
351
536
  }
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);
537
+ async addFixedLabel(label, value) {
538
+ if (!this.clusterServers.get(FixedLabelCluster.id)) {
539
+ this.addClusterServer(ClusterServer(FixedLabelCluster, {
540
+ labelList: [{ label, value }],
541
+ }, {}));
542
+ return;
543
+ }
544
+ const labelList = (this.getAttribute(FixedLabelCluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
545
+ labelList.push({ label, value });
546
+ await this.setAttribute(FixedLabelCluster.id, 'labelList', labelList, this.log);
547
+ }
548
+ async addUserLabel(label, value) {
549
+ if (!this.clusterServers.get(UserLabelCluster.id)) {
550
+ this.addClusterServer(ClusterServer(UserLabelCluster, {
551
+ labelList: [{ label, value }],
552
+ }, {}));
553
+ return;
554
+ }
555
+ const labelList = (this.getAttribute(UserLabelCluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
556
+ labelList.push({ label, value });
557
+ await this.setAttribute(UserLabelCluster.id, 'labelList', labelList, this.log);
558
+ }
559
+ capitalizeFirstLetter(name) {
560
+ if (!name)
561
+ return name;
562
+ return name.charAt(0).toUpperCase() + name.slice(1);
563
+ }
564
+ lowercaseFirstLetter(name) {
565
+ if (!name)
566
+ return name;
567
+ return name.charAt(0).toLowerCase() + name.slice(1);
364
568
  }
365
- */
366
569
  /**
367
570
  * Retrieves the value of the specified attribute from the given endpoint and cluster.
368
571
  *
369
572
  * @param {ClusterId} clusterId - The ID of the cluster to retrieve the attribute from.
370
573
  * @param {string} attribute - The name of the attribute to retrieve.
371
574
  * @param {AnsiLogger} [log] - Optional logger for error and info messages.
372
- * @param {Endpoint} [endpoint] - Optional the child endpoint to retrieve the attribute from.
575
+ * @param {MatterbridgeEndpoint} [endpoint] - Optional the child endpoint to retrieve the attribute from.
373
576
  * @returns {any} The value of the attribute, or undefined if the attribute is not found.
374
577
  */
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;
578
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
579
+ getAttribute(clusterId, attribute, log, endpoint) {
580
+ if (!endpoint)
581
+ endpoint = this;
582
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
583
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
584
+ this.log.error(`getAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
585
+ return undefined;
586
+ }
587
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
588
+ const state = endpoint.state;
589
+ if (!(clusterName in state)) {
590
+ this.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}`);
591
+ return undefined;
592
+ }
593
+ attribute = this.lowercaseFirstLetter(attribute);
594
+ if (!(attribute in state[clusterName])) {
595
+ this.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}`);
596
+ return undefined;
597
+ }
598
+ const value = state[clusterName][attribute];
599
+ 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}`);
600
+ return value;
400
601
  }
401
- */
402
602
  /**
403
603
  * Sets the value of an attribute on a cluster server endpoint.
404
604
  *
@@ -406,110 +606,109 @@ export class MatterbridgeEndpoint extends Endpoint {
406
606
  * @param {string} attribute - The name of the attribute.
407
607
  * @param {any} value - The value to set for the attribute.
408
608
  * @param {AnsiLogger} [log] - (Optional) The logger to use for logging errors and information.
409
- * @param {Endpoint} [endpoint] - (Optional) The endpoint to set the attribute on. If not provided, the attribute will be set on the current endpoint.
410
- */
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
- */
609
+ * @param {MatterbridgeEndpoint} [endpoint] - (Optional) The endpoint to set the attribute on. If not provided, the attribute will be set on the current endpoint.
610
+ * @returns {Promise<boolean>} - A promise that resolves to a boolean indicating whether the attribute was successfully set.
611
+ */
612
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
613
+ async setAttribute(clusterId, attribute, value, log, endpoint) {
614
+ if (!endpoint)
615
+ endpoint = this;
616
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
617
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
618
+ this.log.error(`setAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
619
+ return false;
620
+ }
621
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
622
+ const state = endpoint.state;
623
+ if (!(clusterName in state)) {
624
+ this.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}`);
625
+ return false;
626
+ }
627
+ attribute = this.lowercaseFirstLetter(attribute);
628
+ if (!(attribute in state[clusterName])) {
629
+ this.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}`);
630
+ return false;
631
+ }
632
+ let oldValue = state[clusterName][attribute];
633
+ if (typeof oldValue === 'object')
634
+ oldValue = deepCopy(oldValue);
635
+ await endpoint.setStateOf(endpoint.behaviors.supported[clusterName], { [attribute]: value });
636
+ // await endpoint.set({ [clusterName]: { [attribute]: value } });
637
+ log?.info(`${db}Set endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${this.capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
638
+ `from ${YELLOW}${typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
639
+ `to ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
640
+ return true;
641
+ }
451
642
  /**
452
643
  * Subscribes to an attribute on a cluster.
453
644
  *
454
645
  * @param {ClusterId} clusterId - The ID of the cluster.
455
646
  * @param {string} attribute - The name of the attribute to subscribe to.
456
647
  * @param {(newValue: any, oldValue: any) => void} listener - A callback function that will be called when the attribute value changes.
457
- * @param {AnsiLogger} log - (Optional) An AnsiLogger instance for logging errors and information.
458
- * @param {Endpoint} endpoint - (Optional) The endpoint to subscribe the attribute on. If not provided, the current endpoint will be used.
459
- * @returns A boolean indicating whether the subscription was successful.
460
- */
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
- */
648
+ * @param {AnsiLogger} [log] - Optional logger for logging errors and information.
649
+ * @param {MatterbridgeEndpoint} [endpoint] - Optional endpoint to subscribe the attribute on. Defaults to the current endpoint.
650
+ * @returns {boolean} - A boolean indicating whether the subscription was successful.
651
+ */
489
652
  // 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}`);
653
+ subscribeAttribute(clusterId, attribute, listener, log, endpoint) {
654
+ if (!endpoint)
655
+ endpoint = this;
656
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
657
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
658
+ const events = endpoint.events;
659
+ if (!(clusterName in events)) {
660
+ this.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}`);
494
661
  return false;
495
662
  }
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}`);
663
+ attribute = this.lowercaseFirstLetter(attribute) + '$Changed';
664
+ if (!(attribute in events[clusterName])) {
665
+ this.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}`);
666
+ return false;
667
+ }
668
+ events[clusterName][attribute].on(listener);
669
+ log?.info(`${db}Subscribe endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${this.capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db}`);
670
+ return true;
671
+ }
672
+ /**
673
+ * Triggers an event on the specified cluster.
674
+ *
675
+ * @param {ClusterId} clusterId - The ID of the cluster.
676
+ * @param {string} event - The name of the event to trigger.
677
+ * @param {Record<string, boolean | number | bigint | string | object | undefined | null>} payload - The payload to pass to the event.
678
+ * @param {AnsiLogger} [log] - Optional logger for logging information.
679
+ * @param {MatterbridgeEndpoint} [endpoint] - Optional endpoint to trigger the event on. Defaults to the current endpoint.
680
+ * @returns {Promise<boolean>} - A promise that resolves to a boolean indicating whether the event was successfully triggered.
681
+ */
682
+ async triggerEvent(clusterId, event, payload, log, endpoint) {
683
+ if (!endpoint)
684
+ endpoint = this;
685
+ const clusterName = this.lowercaseFirstLetter(getClusterNameById(clusterId));
686
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
687
+ this.log.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${endpoint.id}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
498
688
  return false;
499
689
  }
500
- // Find the command
501
690
  // 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
- }
691
+ const events = endpoint.events;
692
+ if (!(clusterName in events) || !(event in events[clusterName])) {
693
+ this.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}`);
694
+ return false;
510
695
  }
511
- this.log.error(`Command handler not found for endpoint ${or}${this.id}:${this.number}${er} ${hk}${clusterServer.name}.${command}${er}`);
512
- return false;
696
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
697
+ // @ts-ignore
698
+ await endpoint.act((agent) => agent[clusterName].events[event].emit(payload, agent.context));
699
+ 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} `);
700
+ return true;
701
+ }
702
+ /**
703
+ * Adds a command handler for the specified command.
704
+ *
705
+ * @param {keyof MatterbridgeEndpointCommands} command - The command to add the handler for.
706
+ * @param {(data: any) => void} handler - The handler function to execute when the command is received.
707
+ * @returns {void}
708
+ */
709
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
710
+ addCommandHandler(command, handler) {
711
+ this.commandHandler.addHandler(command, handler);
513
712
  }
514
713
  /**
515
714
  * Serializes the Matterbridge device into a serialized object.
@@ -517,64 +716,69 @@ export class MatterbridgeEndpoint extends Endpoint {
517
716
  * @param pluginName - The name of the plugin.
518
717
  * @returns The serialized Matterbridge device object.
519
718
  */
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
- */
719
+ serialize() {
720
+ return undefined;
721
+ /*
722
+ if (!this.serialNumber || !this.deviceName || !this.uniqueId) return;
723
+ const cluster = this.getClusterServer(BasicInformationCluster) ?? this.getClusterServer(BridgedDeviceBasicInformationCluster);
724
+ if (!cluster) return;
725
+ const serialized: SerializedMatterbridgeDevice = {
726
+ pluginName: this.plugin ?? 'Unknown',
727
+ serialNumber: this.serialNumber,
728
+ deviceName: this.deviceName,
729
+ uniqueId: this.uniqueId,
730
+ productName: cluster.attributes.productName?.getLocal(),
731
+ vendorId: cluster.attributes.vendorId?.getLocal(),
732
+ vendorName: cluster.attributes.vendorName?.getLocal(),
733
+ deviceTypes: Array.from(this.deviceTypes.values()),
734
+ endpoint: this.number,
735
+ endpointName: this.id,
736
+ clusterServersId: [],
737
+ };
738
+ this.getAllClusterServers().forEach((clusterServer) => {
739
+ serialized.clusterServersId.push(clusterServer.id);
740
+ });
741
+ return serialized;
742
+ */
743
+ }
544
744
  /**
545
745
  * Deserializes the device into a serialized object.
546
746
  *
547
747
  * @returns The deserialized MatterbridgeDevice.
548
748
  */
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
- */
749
+ static deserialize(serializedDevice) {
750
+ return undefined;
751
+ /*
752
+ const device = new MatterbridgeDevice(serializedDevice.deviceTypes);
753
+ device.serialNumber = serializedDevice.serialNumber;
754
+ device.deviceName = serializedDevice.deviceName;
755
+ device.uniqueId = serializedDevice.uniqueId;
756
+ for (const clusterId of serializedDevice.clusterServersId) {
757
+ if (clusterId === BasicInformationCluster.id)
758
+ device.createDefaultBasicInformationClusterServer(
759
+ serializedDevice.deviceName,
760
+ serializedDevice.serialNumber,
761
+ serializedDevice.vendorId ?? 0xfff1,
762
+ serializedDevice.vendorName ?? 'Matterbridge',
763
+ serializedDevice.productId ?? 0x8000,
764
+ serializedDevice.productName ?? 'Matterbridge device',
765
+ );
766
+ else if (clusterId === BridgedDeviceBasicInformationCluster.id)
767
+ device.createDefaultBridgedDeviceBasicInformationClusterServer(
768
+ serializedDevice.deviceName,
769
+ serializedDevice.serialNumber,
770
+ serializedDevice.vendorId ?? 0xfff1,
771
+ serializedDevice.vendorName ?? 'Matterbridge',
772
+ serializedDevice.productName ?? 'Matterbridge device',
773
+ );
774
+ else device.addClusterServerFromList(device, [clusterId]);
775
+ }
776
+ return device;
777
+ */
778
+ }
779
+ /**
780
+ * From here copy paste from MatterbridgeDevice
781
+ */
578
782
  /**
579
783
  * Get a default IdentifyCluster server.
580
784
  */
@@ -676,6 +880,18 @@ export class MatterbridgeEndpoint extends Endpoint {
676
880
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
677
881
  */
678
882
  getDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productId, productName, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
883
+ this.log.logName = deviceName;
884
+ this.deviceName = deviceName;
885
+ this.serialNumber = serialNumber;
886
+ this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName);
887
+ this.productId = productId;
888
+ this.productName = productName;
889
+ this.vendorId = vendorId;
890
+ this.vendorName = vendorName;
891
+ this.softwareVersion = softwareVersion;
892
+ this.softwareVersionString = softwareVersionString;
893
+ this.hardwareVersion = hardwareVersion;
894
+ this.hardwareVersionString = hardwareVersionString;
679
895
  return ClusterServer(BasicInformationCluster, {
680
896
  dataModelRevision: 1,
681
897
  location: 'XX',
@@ -717,10 +933,8 @@ export class MatterbridgeEndpoint extends Endpoint {
717
933
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
718
934
  */
719
935
  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
936
  if (MatterbridgeEndpoint.bridgeMode === 'bridge') {
937
+ this.addDeviceType(bridgedNode);
724
938
  this.createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
725
939
  return;
726
940
  }
@@ -740,6 +954,18 @@ export class MatterbridgeEndpoint extends Endpoint {
740
954
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
741
955
  */
742
956
  getDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0') {
957
+ this.log.logName = deviceName;
958
+ this.deviceName = deviceName;
959
+ this.serialNumber = serialNumber;
960
+ this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName);
961
+ this.productId = undefined;
962
+ this.productName = productName;
963
+ this.vendorId = vendorId;
964
+ this.vendorName = vendorName;
965
+ this.softwareVersion = softwareVersion;
966
+ this.softwareVersionString = softwareVersionString;
967
+ this.hardwareVersion = hardwareVersion;
968
+ this.hardwareVersionString = hardwareVersionString;
743
969
  return ClusterServer(BridgedDeviceBasicInformationCluster, {
744
970
  vendorId: vendorId !== undefined ? VendorId(vendorId) : undefined, // 4874
745
971
  vendorName: vendorName.slice(0, 32),
@@ -774,9 +1000,6 @@ export class MatterbridgeEndpoint extends Endpoint {
774
1000
  * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'.
775
1001
  */
776
1002
  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
1003
  this.addClusterServer(this.getDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString));
781
1004
  }
782
1005
  /**
@@ -821,7 +1044,7 @@ export class MatterbridgeEndpoint extends Endpoint {
821
1044
  getDefaultElectricalPowerMeasurementClusterServer(voltage = null, current = null, power = null, frequency = null) {
822
1045
  return ClusterServer(ElectricalPowerMeasurementCluster.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), {
823
1046
  powerMode: ElectricalPowerMeasurement.PowerMode.Ac,
824
- numberOfMeasurementTypes: 3,
1047
+ numberOfMeasurementTypes: 4,
825
1048
  accuracy: [
826
1049
  {
827
1050
  measurementType: MeasurementType.Voltage,
@@ -858,42 +1081,6 @@ export class MatterbridgeEndpoint extends Endpoint {
858
1081
  frequency: frequency,
859
1082
  }, {}, {});
860
1083
  }
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
1084
  /**
898
1085
  * Get a default OnOff cluster server.
899
1086
  *
@@ -921,6 +1108,7 @@ export class MatterbridgeEndpoint extends Endpoint {
921
1108
  * Creates a default OnOff cluster server.
922
1109
  *
923
1110
  * @param onOff - The initial state of the OnOff cluster (default: false).
1111
+ * @returns {void}
924
1112
  */
925
1113
  createDefaultOnOffClusterServer(onOff = false) {
926
1114
  this.addClusterServer(this.getDefaultOnOffClusterServer(onOff));
@@ -928,12 +1116,17 @@ export class MatterbridgeEndpoint extends Endpoint {
928
1116
  /**
929
1117
  * Get a default level control cluster server.
930
1118
  *
931
- * @param currentLevel - The current level (default: 0).
1119
+ * @param currentLevel - The current level (default: 254).
1120
+ * @param minLevel - The minimum level (default: 0).
1121
+ * @param maxLevel - The maximum level (default: 254).
1122
+ * @param onLevel - The on level (default: null).
932
1123
  */
933
- getDefaultLevelControlClusterServer(currentLevel = 0) {
1124
+ getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 0, maxLevel = 254, onLevel = null) {
934
1125
  return ClusterServer(LevelControlCluster.with(LevelControl.Feature.OnOff), {
935
1126
  currentLevel,
936
- onLevel: 0,
1127
+ minLevel,
1128
+ maxLevel,
1129
+ onLevel,
937
1130
  options: {
938
1131
  executeIfOff: false,
939
1132
  coupleColorTempToLevel: false,
@@ -970,10 +1163,13 @@ export class MatterbridgeEndpoint extends Endpoint {
970
1163
  /**
971
1164
  * Creates a default level control cluster server.
972
1165
  *
973
- * @param currentLevel - The current level (default: 0).
1166
+ * @param currentLevel - The current level (default: 254).
1167
+ * @param minLevel - The minimum level (default: 0).
1168
+ * @param maxLevel - The maximum level (default: 254).
1169
+ * @param onLevel - The on level (default: null).
974
1170
  */
975
- createDefaultLevelControlClusterServer(currentLevel = 0) {
976
- this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel));
1171
+ createDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 0, maxLevel = 254, onLevel = null) {
1172
+ this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel, minLevel, maxLevel, onLevel));
977
1173
  }
978
1174
  /**
979
1175
  * Get a default color control cluster server.
@@ -986,7 +1182,7 @@ export class MatterbridgeEndpoint extends Endpoint {
986
1182
  * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds.
987
1183
  * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds.
988
1184
  */
989
- getDefaultCompleteColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
1185
+ getDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
990
1186
  return ClusterServer(ColorControlCluster.with(ColorControl.Feature.Xy, ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), {
991
1187
  colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation,
992
1188
  enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation,
@@ -1002,6 +1198,8 @@ export class MatterbridgeEndpoint extends Endpoint {
1002
1198
  colorTemperatureMireds,
1003
1199
  colorTempPhysicalMinMireds,
1004
1200
  colorTempPhysicalMaxMireds,
1201
+ coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
1202
+ startUpColorTemperatureMireds: null,
1005
1203
  }, {
1006
1204
  moveToColor: async (data) => {
1007
1205
  this.log.debug('Matter command: moveToColor request:', data.request, 'attributes.currentX:', data.attributes.currentX.getLocal(), 'attributes.currentY:', data.attributes.currentY.getLocal());
@@ -1063,14 +1261,15 @@ export class MatterbridgeEndpoint extends Endpoint {
1063
1261
  * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds.
1064
1262
  * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds.
1065
1263
  */
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));
1264
+ createDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
1265
+ this.addClusterServer(this.getDefaultColorControlClusterServer(currentX, currentY, currentHue, currentSaturation, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds));
1068
1266
  }
1267
+ isColorControlConfigured = false;
1069
1268
  /**
1070
1269
  * Configures the color control cluster for a device.
1071
1270
  *
1072
- * @remark This method must be called only after creating the cluster with getDefaultCompleteColorControlClusterServer or createDefaultCompleteColorControlClusterServer
1073
- * and before starting the matter server.
1271
+ * @remark This method must be called only after creating the cluster with getDefaultColorControlClusterServer or createDefaultColorControlClusterServer
1272
+ * and before starting the matter node.
1074
1273
  *
1075
1274
  * @param {boolean} hueSaturation - A boolean indicating whether the device supports hue and saturation control.
1076
1275
  * @param {boolean} xy - A boolean indicating whether the device supports XY control.
@@ -1078,15 +1277,27 @@ export class MatterbridgeEndpoint extends Endpoint {
1078
1277
  * @param {ColorControl.ColorMode} colorMode - An optional parameter specifying the color mode of the device.
1079
1278
  * @param {Endpoint} endpoint - An optional parameter specifying the endpoint to configure. If not provided, the device endpoint will be used.
1080
1279
  */
1081
- configureColorControlCluster(hueSaturation, xy, colorTemperature, colorMode, endpoint) {
1280
+ async configureColorControlCluster(hueSaturation, xy, colorTemperature, colorMode, endpoint) {
1082
1281
  if (!endpoint)
1083
1282
  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);
1283
+ if (this.isColorControlConfigured)
1284
+ return;
1285
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
1286
+ this.log.debug(`**configureColorControlCluster() delaying for endpoint construction ${endpoint.construction.status}`);
1287
+ setTimeout(async () => {
1288
+ await endpoint.configureColorControlCluster(hueSaturation, xy, colorTemperature, colorMode, endpoint);
1289
+ this.isColorControlConfigured = true;
1290
+ }, 500);
1291
+ return;
1089
1292
  }
1293
+ this.log.debug(`**configureColorControlCluster()`);
1294
+ await endpoint.setAttribute(ColorControlCluster.id, 'featureMap', { hueSaturation, enhancedHue: false, colorLoop: false, xy, colorTemperature }, this.log, endpoint);
1295
+ await endpoint.setAttribute(ColorControlCluster.id, 'colorCapabilities', { hueSaturation, enhancedHue: false, colorLoop: false, xy, colorTemperature }, this.log, endpoint);
1296
+ if (isValidNumber(colorMode, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, ColorControl.ColorMode.ColorTemperatureMireds)) {
1297
+ await endpoint.setAttribute(ColorControlCluster.id, 'colorMode', colorMode, this.log, endpoint);
1298
+ await endpoint.setAttribute(ColorControlCluster.id, 'enhancedColorMode', colorMode, this.log, endpoint);
1299
+ }
1300
+ this.isColorControlConfigured = true;
1090
1301
  }
1091
1302
  /**
1092
1303
  * Configures the color control mode for the device.
@@ -1094,18 +1305,18 @@ export class MatterbridgeEndpoint extends Endpoint {
1094
1305
  * @param {ColorControl.ColorMode} colorMode - The color mode to set.
1095
1306
  * @param {Endpoint} endpoint - The optional endpoint to configure. If not provided, the method will configure the current endpoint.
1096
1307
  */
1097
- configureColorControlMode(colorMode, endpoint) {
1308
+ async configureColorControlMode(colorMode, endpoint) {
1098
1309
  if (!endpoint)
1099
1310
  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);
1311
+ if (isValidNumber(colorMode, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, ColorControl.ColorMode.ColorTemperatureMireds)) {
1312
+ await endpoint.setAttribute(ColorControlCluster.id, 'colorMode', colorMode, this.log, endpoint);
1313
+ await endpoint.setAttribute(ColorControlCluster.id, 'enhancedColorMode', colorMode, this.log, endpoint);
1103
1314
  }
1104
1315
  }
1105
1316
  /**
1106
1317
  * Get a default window covering cluster server.
1107
1318
  *
1108
- * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0.
1319
+ * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0. Matter uses 10000 = fully closed 0 = fully opened.
1109
1320
  */
1110
1321
  getDefaultWindowCoveringClusterServer(positionPercent100ths) {
1111
1322
  return ClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
@@ -1124,8 +1335,6 @@ export class MatterbridgeEndpoint extends Endpoint {
1124
1335
  mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
1125
1336
  targetPositionLiftPercent100ths: positionPercent100ths ?? 0, // 0 Fully open 10000 fully closed
1126
1337
  currentPositionLiftPercent100ths: positionPercent100ths ?? 0, // 0 Fully open 10000 fully closed
1127
- // installedClosedLimitLift: 10000,
1128
- // installedOpenLimitLift: 0,
1129
1338
  }, {
1130
1339
  upOrOpen: async (data) => {
1131
1340
  this.log.debug('Matter command: upOrOpen');
@@ -1149,7 +1358,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1149
1358
  /**
1150
1359
  * Creates a default window covering cluster server.
1151
1360
  *
1152
- * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0.
1361
+ * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0. Matter uses 10000 = fully closed 0 = fully opened.
1153
1362
  */
1154
1363
  createDefaultWindowCoveringClusterServer(positionPercent100ths) {
1155
1364
  this.addClusterServer(this.getDefaultWindowCoveringClusterServer(positionPercent100ths));
@@ -1158,22 +1367,19 @@ export class MatterbridgeEndpoint extends Endpoint {
1158
1367
  * Sets the window covering target position as the current position and stops the movement.
1159
1368
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1160
1369
  */
1161
- setWindowCoveringTargetAsCurrentAndStopped(endpoint) {
1370
+ async setWindowCoveringTargetAsCurrentAndStopped(endpoint) {
1162
1371
  if (!endpoint)
1163
1372
  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.`);
1373
+ const position = endpoint.getAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', this.log, endpoint); // windowCoveringCluster.getCurrentPositionLiftPercent100thsAttribute();
1374
+ if (position !== null) {
1375
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', position, this.log, endpoint);
1376
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'operationalStatus', {
1377
+ global: WindowCovering.MovementStatus.Stopped,
1378
+ lift: WindowCovering.MovementStatus.Stopped,
1379
+ tilt: WindowCovering.MovementStatus.Stopped,
1380
+ }, this.log, endpoint);
1176
1381
  }
1382
+ this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`);
1177
1383
  }
1178
1384
  /**
1179
1385
  * Sets the current and target status of a window covering.
@@ -1182,19 +1388,16 @@ export class MatterbridgeEndpoint extends Endpoint {
1182
1388
  * @param {WindowCovering.MovementStatus} status - The movement status of the window covering.
1183
1389
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1184
1390
  */
1185
- setWindowCoveringCurrentTargetStatus(current, target, status, endpoint) {
1391
+ async setWindowCoveringCurrentTargetStatus(current, target, status, endpoint) {
1186
1392
  if (!endpoint)
1187
1393
  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
- }
1394
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', current, this.log, endpoint);
1395
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', target, this.log, endpoint);
1396
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'operationalStatus', {
1397
+ global: status,
1398
+ lift: status,
1399
+ tilt: status,
1400
+ }, this.log, endpoint);
1198
1401
  this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${current}, targetPositionLiftPercent100ths: ${target} and operationalStatus: ${status}.`);
1199
1402
  }
1200
1403
  /**
@@ -1202,13 +1405,14 @@ export class MatterbridgeEndpoint extends Endpoint {
1202
1405
  * @param {WindowCovering.MovementStatus} status - The movement status to set.
1203
1406
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1204
1407
  */
1205
- setWindowCoveringStatus(status, endpoint) {
1408
+ async setWindowCoveringStatus(status, endpoint) {
1206
1409
  if (!endpoint)
1207
1410
  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 });
1411
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'operationalStatus', {
1412
+ global: status,
1413
+ lift: status,
1414
+ tilt: status,
1415
+ }, this.log, endpoint);
1212
1416
  this.log.debug(`Set WindowCovering operationalStatus: ${status}`);
1213
1417
  }
1214
1418
  /**
@@ -1220,10 +1424,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1220
1424
  getWindowCoveringStatus(endpoint) {
1221
1425
  if (!endpoint)
1222
1426
  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();
1427
+ const status = endpoint.getAttribute(WindowCoveringCluster.id, 'operationalStatus', this.log, endpoint);
1227
1428
  this.log.debug(`Get WindowCovering operationalStatus: ${status.global}`);
1228
1429
  return status.global;
1229
1430
  }
@@ -1233,14 +1434,11 @@ export class MatterbridgeEndpoint extends Endpoint {
1233
1434
  * @param position - The position to set, specified as a number.
1234
1435
  * @param {Endpoint} endpoint - The endpoint on which to set the window covering (default the device endpoint).
1235
1436
  */
1236
- setWindowCoveringTargetAndCurrentPosition(position, endpoint) {
1437
+ async setWindowCoveringTargetAndCurrentPosition(position, endpoint) {
1237
1438
  if (!endpoint)
1238
1439
  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);
1440
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', position, this.log, endpoint);
1441
+ await endpoint.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', position, this.log, endpoint);
1244
1442
  this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${position} and targetPositionLiftPercent100ths: ${position}.`);
1245
1443
  }
1246
1444
  /**
@@ -1341,7 +1539,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1341
1539
  * @param {Endpoint} endpoint - The endpoint on which to trigger the event (default the device endpoint).
1342
1540
  * @returns {void}
1343
1541
  */
1344
- triggerSwitchEvent(event, log, endpoint) {
1542
+ async triggerSwitchEvent(event, log, endpoint) {
1345
1543
  if (!endpoint)
1346
1544
  endpoint = this;
1347
1545
  if (['Single', 'Double', 'Long'].includes(event)) {
@@ -1355,38 +1553,38 @@ export class MatterbridgeEndpoint extends Endpoint {
1355
1553
  return false;
1356
1554
  }
1357
1555
  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 });
1556
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1557
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1558
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1559
+ endpoint.triggerEvent(cluster.id, 'shortRelease', { previousPosition: 1 }, log);
1560
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1561
+ endpoint.triggerEvent(cluster.id, 'multiPressComplete', { previousPosition: 1, totalNumberOfPressesCounted: 1 }, log);
1364
1562
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.SinglePress${db}`);
1365
1563
  }
1366
1564
  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 });
1565
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1566
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1567
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1568
+ endpoint.triggerEvent(cluster.id, 'shortRelease', { previousPosition: 1 }, log);
1569
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1570
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1571
+ endpoint.triggerEvent(cluster.id, 'multiPressOngoing', { newPosition: 1, currentNumberOfPressesCounted: 2 }, log);
1572
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1573
+ endpoint.triggerEvent(cluster.id, 'shortRelease', { previousPosition: 1 }, log);
1574
+ endpoint.triggerEvent(cluster.id, 'multiPressComplete', { previousPosition: 1, totalNumberOfPressesCounted: 2 }, log);
1377
1575
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.DoublePress${db}`);
1378
1576
  }
1379
1577
  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 });
1578
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1579
+ endpoint.triggerEvent(cluster.id, 'initialPress', { newPosition: 1 }, log);
1580
+ endpoint.triggerEvent(cluster.id, 'longPress', { newPosition: 1 }, log);
1581
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1582
+ endpoint.triggerEvent(cluster.id, 'longRelease', { previousPosition: 1 }, log);
1385
1583
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.LongPress${db}`);
1386
1584
  }
1387
1585
  }
1388
1586
  if (['Press', 'Release'].includes(event)) {
1389
- const cluster = endpoint.getClusterServer(Switch.Complete);
1587
+ const cluster = endpoint.getClusterServer(SwitchCluster.with(Switch.Feature.LatchingSwitch));
1390
1588
  if (!cluster || !cluster.getFeatureMapAttribute().latchingSwitch) {
1391
1589
  log?.error(`triggerSwitchEvent ${event} error: Switch cluster with LatchingSwitch not found on endpoint ${endpoint.id}:${endpoint.number}`);
1392
1590
  return false;
@@ -1396,17 +1594,13 @@ export class MatterbridgeEndpoint extends Endpoint {
1396
1594
  return false;
1397
1595
  }
1398
1596
  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 });
1597
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 1, log);
1598
+ endpoint.triggerEvent(cluster.id, 'switchLatched', { newPosition: 1 }, log);
1403
1599
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.Press${db}`);
1404
1600
  }
1405
1601
  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 });
1602
+ await endpoint.setAttribute(cluster.id, 'currentPosition', 0, log);
1603
+ endpoint.triggerEvent(cluster.id, 'switchLatched', { newPosition: 0 }, log);
1410
1604
  log?.info(`${db}Trigger endpoint ${or}${endpoint.id}:${endpoint.number}${db} event ${hk}${cluster.name}.Release${db}`);
1411
1605
  }
1412
1606
  }
@@ -1438,10 +1632,12 @@ export class MatterbridgeEndpoint extends Endpoint {
1438
1632
  /**
1439
1633
  * Creates a default mode select cluster server.
1440
1634
  *
1441
- * @remarks
1442
- * This method adds a cluster server for a mode select cluster with default settings.
1443
- *
1635
+ * @param description - The description of the cluster server.
1636
+ * @param supportedModes - The supported modes for the cluster server.
1637
+ * @param currentMode - The current mode of the cluster server. Defaults to 0.
1638
+ * @param startUpMode - The startup mode of the cluster server. Defaults to 0.
1444
1639
  * @param endpoint - The endpoint to add the cluster server to. Defaults to `this` if not provided.
1640
+ *
1445
1641
  */
1446
1642
  createDefaultModeSelectClusterServer(description, supportedModes, currentMode = 0, startUpMode = 0, endpoint) {
1447
1643
  if (!endpoint)
@@ -1493,7 +1689,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1493
1689
  /**
1494
1690
  * Get a default flow measurement cluster server.
1495
1691
  *
1496
- * @param measuredValue - The measured value of the temperature.
1692
+ * @param measuredValue - The measured value of the flow in 10 x m/h.
1497
1693
  */
1498
1694
  getDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1499
1695
  return ClusterServer(FlowMeasurementCluster, {
@@ -1506,7 +1702,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1506
1702
  /**
1507
1703
  * Creates a default flow measurement cluster server.
1508
1704
  *
1509
- * @param measuredValue - The measured value of the temperature.
1705
+ * @param measuredValue - The measured value of the flow in 10 x m/h.
1510
1706
  */
1511
1707
  createDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1512
1708
  this.addClusterServer(this.getDefaultFlowMeasurementClusterServer(measuredValue));
@@ -1514,7 +1710,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1514
1710
  /**
1515
1711
  * Get a default temperature measurement cluster server.
1516
1712
  *
1517
- * @param measuredValue - The measured value of the temperature.
1713
+ * @param measuredValue - The measured value of the temperature x 100.
1518
1714
  */
1519
1715
  getDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1520
1716
  return ClusterServer(TemperatureMeasurementCluster, {
@@ -1527,7 +1723,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1527
1723
  /**
1528
1724
  * Creates a default temperature measurement cluster server.
1529
1725
  *
1530
- * @param measuredValue - The measured value of the temperature.
1726
+ * @param measuredValue - The measured value of the temperature x 100.
1531
1727
  */
1532
1728
  createDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1533
1729
  this.addClusterServer(this.getDefaultTemperatureMeasurementClusterServer(measuredValue));
@@ -1535,7 +1731,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1535
1731
  /**
1536
1732
  * Get a default RelativeHumidityMeasurementCluster server.
1537
1733
  *
1538
- * @param measuredValue - The measured value of the relative humidity.
1734
+ * @param measuredValue - The measured value of the relative humidity x 100.
1539
1735
  */
1540
1736
  getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1541
1737
  return ClusterServer(RelativeHumidityMeasurementCluster, {
@@ -1548,7 +1744,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1548
1744
  /**
1549
1745
  * Creates a default RelativeHumidityMeasurementCluster server.
1550
1746
  *
1551
- * @param measuredValue - The measured value of the relative humidity.
1747
+ * @param measuredValue - The measured value of the relative humidity x 100.
1552
1748
  */
1553
1749
  createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1554
1750
  this.addClusterServer(this.getDefaultRelativeHumidityMeasurementClusterServer(measuredValue));
@@ -1769,6 +1965,80 @@ export class MatterbridgeEndpoint extends Endpoint {
1769
1965
  createDefaultTvocMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) {
1770
1966
  this.addClusterServer(this.getDefaultTvocMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium));
1771
1967
  }
1968
+ /**
1969
+ * Get a default heating thermostat cluster server with the specified parameters.
1970
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
1971
+ * @param {number} [occupiedHeatingSetpoint] - The occupied heating setpoint value in degrees Celsius. Defaults to 21°.
1972
+ * @param {number} [minHeatSetpointLimit] - The minimum heat setpoint limit value. Defaults to 0°.
1973
+ * @param {number} [maxHeatSetpointLimit] - The maximum heat setpoint limit value. Defaults to 50°.
1974
+ * @returns {ThermostatClusterServer} A default thermostat cluster server configured with the specified parameters.
1975
+ */
1976
+ getDefaultHeatingThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50) {
1977
+ return ClusterServer(ThermostatCluster.with(Thermostat.Feature.Heating), {
1978
+ localTemperature: localTemperature * 100,
1979
+ systemMode: Thermostat.SystemMode.Heat,
1980
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.HeatingOnly,
1981
+ // Thermostat.Feature.Heating
1982
+ occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
1983
+ minHeatSetpointLimit: minHeatSetpointLimit * 100,
1984
+ maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1985
+ absMinHeatSetpointLimit: minHeatSetpointLimit * 100,
1986
+ absMaxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1987
+ }, {
1988
+ setpointRaiseLower: async (data) => {
1989
+ this.log.debug('Matter command: setpointRaiseLower', data.request);
1990
+ await this.commandHandler.executeHandler('setpointRaiseLower', data);
1991
+ },
1992
+ }, {});
1993
+ }
1994
+ /**
1995
+ * Creates and adds a default heating thermostat cluster server to the device.
1996
+ *
1997
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
1998
+ * @param {number} [occupiedHeatingSetpoint] - The occupied heating setpoint value in degrees Celsius. Defaults to 21°.
1999
+ * @param {number} [minHeatSetpointLimit] - The minimum heat setpoint limit value. Defaults to 0°.
2000
+ * @param {number} [maxHeatSetpointLimit] - The maximum heat setpoint limit value. Defaults to 50°.
2001
+ */
2002
+ createDefaultHeatingThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 25, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50) {
2003
+ this.addClusterServer(this.getDefaultHeatingThermostatClusterServer(localTemperature, occupiedHeatingSetpoint, minHeatSetpointLimit, maxHeatSetpointLimit));
2004
+ }
2005
+ /**
2006
+ * Get a default cooling thermostat cluster server with the specified parameters.
2007
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
2008
+ * @param {number} [occupiedCoolingSetpoint] - The occupied cooling setpoint value in degrees Celsius. Defaults to 25°.
2009
+ * @param {number} [minCoolSetpointLimit] - The minimum cool setpoint limit value. Defaults to 0°.
2010
+ * @param {number} [maxCoolSetpointLimit] - The maximum cool setpoint limit value. Defaults to 50°.
2011
+ * @returns {ThermostatClusterServer} A default thermostat cluster server configured with the specified parameters.
2012
+ */
2013
+ getDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
2014
+ return ClusterServer(ThermostatCluster.with(Thermostat.Feature.Cooling), {
2015
+ localTemperature: localTemperature * 100,
2016
+ systemMode: Thermostat.SystemMode.Cool,
2017
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingOnly,
2018
+ // Thermostat.Feature.Cooling
2019
+ occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
2020
+ minCoolSetpointLimit: minCoolSetpointLimit * 100,
2021
+ maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
2022
+ absMinCoolSetpointLimit: minCoolSetpointLimit * 100,
2023
+ absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
2024
+ }, {
2025
+ setpointRaiseLower: async (data) => {
2026
+ this.log.debug('Matter command: setpointRaiseLower', data.request);
2027
+ await this.commandHandler.executeHandler('setpointRaiseLower', data);
2028
+ },
2029
+ }, {});
2030
+ }
2031
+ /**
2032
+ * Creates and adds a default cooling thermostat cluster server to the device.
2033
+ *
2034
+ * @param {number} [localTemperature] - The local temperature value in degrees Celsius. Defaults to 23°.
2035
+ * @param {number} [occupiedCoolingSetpoint] - The occupied cooling setpoint value in degrees Celsius. Defaults to 25°.
2036
+ * @param {number} [minCoolSetpointLimit] - The minimum cool setpoint limit value. Defaults to 0°.
2037
+ * @param {number} [maxCoolSetpointLimit] - The maximum cool setpoint limit value. Defaults to 50°.
2038
+ */
2039
+ createDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
2040
+ this.addClusterServer(this.getDefaultCoolingThermostatClusterServer(localTemperature, occupiedCoolingSetpoint, minCoolSetpointLimit, maxCoolSetpointLimit));
2041
+ }
1772
2042
  /**
1773
2043
  * Get a default thermostat cluster server with the specified parameters.
1774
2044
  *
@@ -1785,19 +2055,22 @@ export class MatterbridgeEndpoint extends Endpoint {
1785
2055
  getDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
1786
2056
  return ClusterServer(ThermostatCluster.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode), {
1787
2057
  localTemperature: localTemperature * 100,
2058
+ systemMode: Thermostat.SystemMode.Auto,
2059
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
2060
+ // Thermostat.Feature.Heating
1788
2061
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
1789
- occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
1790
2062
  minHeatSetpointLimit: minHeatSetpointLimit * 100,
1791
2063
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1792
2064
  absMinHeatSetpointLimit: minHeatSetpointLimit * 100,
1793
2065
  absMaxHeatSetpointLimit: maxHeatSetpointLimit * 100,
2066
+ // Thermostat.Feature.Cooling
2067
+ occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
1794
2068
  minCoolSetpointLimit: minCoolSetpointLimit * 100,
1795
2069
  maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
1796
2070
  absMinCoolSetpointLimit: minCoolSetpointLimit * 100,
1797
2071
  absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
2072
+ // Thermostat.Feature.AutoMode
1798
2073
  minSetpointDeadBand: minSetpointDeadBand * 100,
1799
- systemMode: Thermostat.SystemMode.Off,
1800
- controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
1801
2074
  thermostatRunningMode: Thermostat.ThermostatRunningMode.Off,
1802
2075
  }, {
1803
2076
  setpointRaiseLower: async (data) => {
@@ -1821,46 +2094,6 @@ export class MatterbridgeEndpoint extends Endpoint {
1821
2094
  createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50) {
1822
2095
  this.addClusterServer(this.getDefaultThermostatClusterServer(localTemperature, occupiedHeatingSetpoint, occupiedCoolingSetpoint, minSetpointDeadBand, minHeatSetpointLimit, maxHeatSetpointLimit, minCoolSetpointLimit, maxCoolSetpointLimit));
1823
2096
  }
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
2097
  /**
1865
2098
  * Returns the default SmokeCOAlarm Cluster Server.
1866
2099
  *