node-red-contrib-knx-ultimate 6.2.2 → 6.2.3-beta.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 (141) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/nodes/knxUltimateAI.js +7 -1
  3. package/nodes/knxUltimateHueAreaMotion.html +16 -4
  4. package/nodes/knxUltimateHueBattery.html +16 -4
  5. package/nodes/knxUltimateHueButton.html +16 -4
  6. package/nodes/knxUltimateHueCameraMotion.html +16 -4
  7. package/nodes/knxUltimateHueContactSensor.html +16 -4
  8. package/nodes/knxUltimateHueController.html +1259 -0
  9. package/nodes/knxUltimateHueController.js +28 -0
  10. package/nodes/knxUltimateHueHumiditySensor.html +16 -4
  11. package/nodes/knxUltimateHueLight.html +16 -4
  12. package/nodes/knxUltimateHueLightSensor.html +16 -4
  13. package/nodes/knxUltimateHueMotion.html +16 -4
  14. package/nodes/knxUltimateHuePlug.html +16 -4
  15. package/nodes/knxUltimateHueScene.html +19 -7
  16. package/nodes/knxUltimateHueTapDial.html +16 -4
  17. package/nodes/knxUltimateHueTemperatureSensor.html +16 -4
  18. package/nodes/knxUltimateHueZigbeeConnectivity.html +16 -4
  19. package/nodes/knxUltimateHuedevice_software_update.html +16 -4
  20. package/nodes/locales/de/knxUltimateHueAreaMotion.html +1 -1
  21. package/nodes/locales/de/knxUltimateHueBattery.html +1 -0
  22. package/nodes/locales/de/knxUltimateHueButton.html +1 -0
  23. package/nodes/locales/de/knxUltimateHueCameraMotion.html +1 -0
  24. package/nodes/locales/de/knxUltimateHueContactSensor.html +1 -0
  25. package/nodes/locales/de/knxUltimateHueController.html +668 -0
  26. package/nodes/locales/de/knxUltimateHueController.json +47 -0
  27. package/nodes/locales/de/knxUltimateHueHumiditySensor.html +1 -0
  28. package/nodes/locales/de/knxUltimateHueLight.html +1 -0
  29. package/nodes/locales/de/knxUltimateHueLightSensor.html +1 -0
  30. package/nodes/locales/de/knxUltimateHueMotion.html +1 -0
  31. package/nodes/locales/de/knxUltimateHuePlug.html +1 -0
  32. package/nodes/locales/de/knxUltimateHueScene.html +1 -0
  33. package/nodes/locales/de/knxUltimateHueTapDial.html +1 -0
  34. package/nodes/locales/de/knxUltimateHueTemperatureSensor.html +1 -0
  35. package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.html +1 -0
  36. package/nodes/locales/de/knxUltimateHuedevice_software_update.html +1 -0
  37. package/nodes/locales/en/knxUltimateHueAreaMotion.html +1 -1
  38. package/nodes/locales/en/knxUltimateHueBattery.html +1 -0
  39. package/nodes/locales/en/knxUltimateHueButton.html +1 -0
  40. package/nodes/locales/en/knxUltimateHueCameraMotion.html +1 -0
  41. package/nodes/locales/en/knxUltimateHueContactSensor.html +1 -0
  42. package/nodes/locales/en/knxUltimateHueController.html +671 -0
  43. package/nodes/locales/en/knxUltimateHueController.json +47 -0
  44. package/nodes/locales/en/knxUltimateHueHumiditySensor.html +1 -0
  45. package/nodes/locales/en/knxUltimateHueLight.html +1 -0
  46. package/nodes/locales/en/knxUltimateHueLightSensor.html +1 -0
  47. package/nodes/locales/en/knxUltimateHueMotion.html +1 -0
  48. package/nodes/locales/en/knxUltimateHuePlug.html +1 -0
  49. package/nodes/locales/en/knxUltimateHueScene.html +1 -0
  50. package/nodes/locales/en/knxUltimateHueTapDial.html +1 -0
  51. package/nodes/locales/en/knxUltimateHueTemperatureSensor.html +1 -0
  52. package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.html +1 -0
  53. package/nodes/locales/en/knxUltimateHuedevice_software_update.html +1 -0
  54. package/nodes/locales/es/knxUltimateHueAreaMotion.html +1 -1
  55. package/nodes/locales/es/knxUltimateHueBattery.html +1 -0
  56. package/nodes/locales/es/knxUltimateHueButton.html +1 -0
  57. package/nodes/locales/es/knxUltimateHueCameraMotion.html +1 -0
  58. package/nodes/locales/es/knxUltimateHueContactSensor.html +1 -0
  59. package/nodes/locales/es/knxUltimateHueController.html +671 -0
  60. package/nodes/locales/es/knxUltimateHueController.json +47 -0
  61. package/nodes/locales/es/knxUltimateHueHumiditySensor.html +1 -0
  62. package/nodes/locales/es/knxUltimateHueLight.html +1 -0
  63. package/nodes/locales/es/knxUltimateHueLightSensor.html +1 -0
  64. package/nodes/locales/es/knxUltimateHueMotion.html +1 -0
  65. package/nodes/locales/es/knxUltimateHuePlug.html +1 -0
  66. package/nodes/locales/es/knxUltimateHueScene.html +1 -0
  67. package/nodes/locales/es/knxUltimateHueTapDial.html +1 -0
  68. package/nodes/locales/es/knxUltimateHueTemperatureSensor.html +1 -0
  69. package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.html +1 -0
  70. package/nodes/locales/es/knxUltimateHuedevice_software_update.html +1 -0
  71. package/nodes/locales/fr/knxUltimateHueAreaMotion.html +1 -1
  72. package/nodes/locales/fr/knxUltimateHueBattery.html +1 -0
  73. package/nodes/locales/fr/knxUltimateHueButton.html +1 -0
  74. package/nodes/locales/fr/knxUltimateHueCameraMotion.html +1 -0
  75. package/nodes/locales/fr/knxUltimateHueContactSensor.html +1 -0
  76. package/nodes/locales/fr/knxUltimateHueController.html +671 -0
  77. package/nodes/locales/fr/knxUltimateHueController.json +47 -0
  78. package/nodes/locales/fr/knxUltimateHueHumiditySensor.html +1 -0
  79. package/nodes/locales/fr/knxUltimateHueLight.html +1 -0
  80. package/nodes/locales/fr/knxUltimateHueLightSensor.html +1 -0
  81. package/nodes/locales/fr/knxUltimateHueMotion.html +1 -0
  82. package/nodes/locales/fr/knxUltimateHuePlug.html +1 -0
  83. package/nodes/locales/fr/knxUltimateHueScene.html +1 -0
  84. package/nodes/locales/fr/knxUltimateHueTapDial.html +1 -0
  85. package/nodes/locales/fr/knxUltimateHueTemperatureSensor.html +1 -0
  86. package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.html +1 -0
  87. package/nodes/locales/fr/knxUltimateHuedevice_software_update.html +1 -0
  88. package/nodes/locales/it/knxUltimateHueAreaMotion.html +1 -1
  89. package/nodes/locales/it/knxUltimateHueBattery.html +1 -0
  90. package/nodes/locales/it/knxUltimateHueButton.html +1 -0
  91. package/nodes/locales/it/knxUltimateHueCameraMotion.html +1 -0
  92. package/nodes/locales/it/knxUltimateHueContactSensor.html +1 -0
  93. package/nodes/locales/it/knxUltimateHueController.html +671 -0
  94. package/nodes/locales/it/knxUltimateHueController.json +47 -0
  95. package/nodes/locales/it/knxUltimateHueHumiditySensor.html +1 -0
  96. package/nodes/locales/it/knxUltimateHueLight.html +1 -0
  97. package/nodes/locales/it/knxUltimateHueLightSensor.html +1 -0
  98. package/nodes/locales/it/knxUltimateHueMotion.html +1 -0
  99. package/nodes/locales/it/knxUltimateHuePlug.html +1 -0
  100. package/nodes/locales/it/knxUltimateHueScene.html +1 -0
  101. package/nodes/locales/it/knxUltimateHueTapDial.html +1 -0
  102. package/nodes/locales/it/knxUltimateHueTemperatureSensor.html +1 -0
  103. package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.html +1 -0
  104. package/nodes/locales/it/knxUltimateHuedevice_software_update.html +1 -0
  105. package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.html +1 -1
  106. package/nodes/locales/zh-CN/knxUltimateHueBattery.html +1 -0
  107. package/nodes/locales/zh-CN/knxUltimateHueButton.html +1 -0
  108. package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.html +1 -0
  109. package/nodes/locales/zh-CN/knxUltimateHueContactSensor.html +1 -0
  110. package/nodes/locales/zh-CN/knxUltimateHueController.html +673 -0
  111. package/nodes/locales/zh-CN/knxUltimateHueController.json +47 -0
  112. package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.html +1 -0
  113. package/nodes/locales/zh-CN/knxUltimateHueLight.html +1 -0
  114. package/nodes/locales/zh-CN/knxUltimateHueLightSensor.html +1 -0
  115. package/nodes/locales/zh-CN/knxUltimateHueMotion.html +1 -0
  116. package/nodes/locales/zh-CN/knxUltimateHuePlug.html +1 -0
  117. package/nodes/locales/zh-CN/knxUltimateHueScene.html +1 -0
  118. package/nodes/locales/zh-CN/knxUltimateHueTapDial.html +1 -0
  119. package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.html +1 -0
  120. package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.html +1 -0
  121. package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.html +1 -0
  122. package/nodes/utils/hueControllerProfileAdapter.js +104 -0
  123. package/nodes/utils/hueControllerProfiles/runtime/area_motion.js +221 -0
  124. package/nodes/utils/hueControllerProfiles/runtime/button.js +295 -0
  125. package/nodes/utils/hueControllerProfiles/runtime/camera_motion.js +221 -0
  126. package/nodes/utils/hueControllerProfiles/runtime/contact.js +191 -0
  127. package/nodes/utils/hueControllerProfiles/runtime/device_power.js +185 -0
  128. package/nodes/utils/hueControllerProfiles/runtime/device_software_update.js +185 -0
  129. package/nodes/utils/hueControllerProfiles/runtime/humidity.js +204 -0
  130. package/nodes/utils/hueControllerProfiles/runtime/light.js +1583 -0
  131. package/nodes/utils/hueControllerProfiles/runtime/light_level.js +180 -0
  132. package/nodes/utils/hueControllerProfiles/runtime/motion.js +185 -0
  133. package/nodes/utils/hueControllerProfiles/runtime/plug.js +344 -0
  134. package/nodes/utils/hueControllerProfiles/runtime/relative_rotary.js +271 -0
  135. package/nodes/utils/hueControllerProfiles/runtime/scene.js +352 -0
  136. package/nodes/utils/hueControllerProfiles/runtime/temperature.js +187 -0
  137. package/nodes/utils/hueControllerProfiles/runtime/zigbee_connectivity.js +185 -0
  138. package/package.json +5 -2
  139. package/resources/hueControllerMigration.js +338 -0
  140. package/resources/hueControllerMigrationDialog.js +235 -0
  141. package/resources/hueControllerProfiles.js +9290 -0
@@ -0,0 +1,1583 @@
1
+ // Canonical private runtime profile for HUE Controller: light.
2
+ // This module is shipped with the Controller but is intentionally absent from
3
+ // package.json, so Node-RED never registers it as a separate palette node.
4
+ /* eslint-disable no-param-reassign */
5
+ /* eslint-disable camelcase */
6
+ /* eslint-disable max-len */
7
+ /* eslint-disable no-lonely-if */
8
+ const cloneDeep = require('lodash/cloneDeep')
9
+ const dptlib = require('knxultimate').dptlib
10
+ const hueColorConverter = require('../../colorManipulators/hueColorConverter')
11
+
12
+ module.exports = function (RED) {
13
+ function knxUltimateHueLight (config) {
14
+ RED.nodes.createNode(this, config)
15
+ const node = this
16
+ node.serverKNX = RED.nodes.getNode(config.server) || undefined
17
+ node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
18
+
19
+ // Convert for backward compatibility
20
+ if (config.nameLightKelvinDIM === undefined) {
21
+ config.nameLightKelvinDIM = config.nameLightHSV
22
+ config.GALightKelvinDIM = config.GALightHSV
23
+ config.dptLightKelvinDIM = config.dptLightHSV
24
+
25
+ config.nameLightKelvinPercentage = config.nameLightHSVPercentage
26
+ config.GALightKelvinPercentage = config.GALightHSVPercentage
27
+ config.dptLightKelvinPercentage = config.dptLightHSVPercentage
28
+
29
+ config.nameLightKelvinPercentageState = config.nameLightHSVState
30
+ config.GALightKelvinPercentageState = config.GALightHSVState
31
+ config.dptLightKelvinPercentageState = config.dptLightHSVState
32
+ }
33
+
34
+ node.topic = node.name
35
+ node.name = config.name === undefined ? 'Hue' : config.name
36
+ node.outputtopic = node.name
37
+ node.dpt = ''
38
+ node.notifyreadrequest = true
39
+ node.notifyreadrequestalsorespondtobus = 'false'
40
+ node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
41
+ node.notifyresponse = false
42
+ node.notifywrite = true
43
+ node.initialread = true
44
+ node.listenallga = true // Don't remove
45
+ node.outputtype = 'write'
46
+ node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
47
+ node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
48
+ node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
49
+ node.passthrough = 'no'
50
+ node.formatmultiplyvalue = 1
51
+ node.formatnegativevalue = 'leave'
52
+ node.formatdecimalsvalue = 2
53
+ node.currentHUEDevice = undefined // At start, this value is filled by a call to HUE api. It stores a value representing the current light status.
54
+ node.HUEDeviceWhileDaytime = null// This retains the HUE device status while daytime, to be restored after nighttime elapsed.
55
+ node.HUELightsBelongingToGroupWhileDaytime = null // Array contains all light belonging to the grouped_light (if grouped_light is selected)
56
+ node.DayTime = true
57
+ node.isGrouped_light = config.hueDevice.split('#')[1] === 'grouped_light'
58
+ node.hueDevice = config.hueDevice.split('#')[0]
59
+ node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
60
+ config.specifySwitchOnBrightness = (config.specifySwitchOnBrightness === undefined || config.specifySwitchOnBrightness === '') ? 'temperature' : config.specifySwitchOnBrightness
61
+ config.specifySwitchOnBrightnessNightTime = (config.specifySwitchOnBrightnessNightTime === undefined || config.specifySwitchOnBrightnessNightTime === '') ? 'no' : config.specifySwitchOnBrightnessNightTime
62
+ config.colorAtSwitchOnDayTime = (config.colorAtSwitchOnDayTime === '' || config.colorAtSwitchOnDayTime === undefined) ? '{ "kelvin":3000, "brightness":100 }' : config.colorAtSwitchOnDayTime
63
+ config.colorAtSwitchOnNightTime = (config.colorAtSwitchOnNightTime === '' || config.colorAtSwitchOnNightTime === undefined) ? '{ "kelvin":2700, "brightness":20 }' : config.colorAtSwitchOnNightTime
64
+ config.colorAtSwitchOnDayTime = config.colorAtSwitchOnDayTime.replace('geen', 'green')
65
+ config.colorAtSwitchOnNightTime = config.colorAtSwitchOnNightTime.replace('geen', 'green')
66
+ config.dimSpeed = (config.dimSpeed === undefined || config.dimSpeed === '') ? 5000 : Number(config.dimSpeed)
67
+ config.HSVDimSpeed = (config.HSVDimSpeed === undefined || config.HSVDimSpeed === '') ? 5000 : Number(config.HSVDimSpeed)
68
+ config.invertDimTunableWhiteDirection = config.invertDimTunableWhiteDirection !== undefined
69
+ config.restoreDayMode = config.restoreDayMode === undefined ? 'no' : config.restoreDayMode // no or setDayByFastSwitchLightSingle or setDayByFastSwitchLightALL
70
+ config.updateLocalStateFromKNXWrite = config.updateLocalStateFromKNXWrite === true || config.updateLocalStateFromKNXWrite === 'true' // Starting from v 4.1.31
71
+ node.timerCheckForFastLightSwitch = null
72
+ config.invertDayNight = config.invertDayNight === undefined ? false : config.invertDayNight
73
+ node.HSVObject = null // { h, s, v };// Store the current light calculated HSV
74
+
75
+ // Transform HEX in RGB and stringified json in json oblects.
76
+ if (config.colorAtSwitchOnDayTime.indexOf('#') !== -1) {
77
+ // Transform to rgb.
78
+ try {
79
+ config.colorAtSwitchOnDayTime = hueColorConverter.ColorConverter.hexRgb(config.colorAtSwitchOnDayTime.replace('#', ''))
80
+ } catch (error) {
81
+ config.colorAtSwitchOnDayTime = { kelvin: 3000, brightness: 100 }
82
+ }
83
+ } else {
84
+ try {
85
+ config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnDayTime)
86
+ } catch (error) {
87
+ RED.log.error(`knxUltimateHueLight: config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnDayTime): ${error.message} : ${error.stack || ''} `)
88
+ config.colorAtSwitchOnDayTime = ''
89
+ }
90
+ }
91
+ // Same thing, but with night color
92
+ if (config.colorAtSwitchOnNightTime.indexOf('#') !== -1) {
93
+ // Transform to rgb.
94
+ try {
95
+ config.colorAtSwitchOnNightTime = hueColorConverter.ColorConverter.hexRgb(config.colorAtSwitchOnNightTime.replace('#', ''))
96
+ } catch (error) {
97
+ config.colorAtSwitchOnNightTime = { kelvin: 2700, brightness: 20 }
98
+ }
99
+ } else {
100
+ try {
101
+ config.colorAtSwitchOnNightTime = JSON.parse(config.colorAtSwitchOnNightTime)
102
+ } catch (error) {
103
+ RED.log.error(`knxUltimateHueLight: config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnNightTime): ${error.message} : ${error.stack || ''} `)
104
+ config.colorAtSwitchOnNightTime = ''
105
+ }
106
+ }
107
+
108
+ const formatTs = (date) => {
109
+ const d = date instanceof Date ? date : new Date(date)
110
+ const provider = node.serverKNX
111
+ if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
112
+ return `${d.getDate()}, ${d.toLocaleTimeString()}`
113
+ }
114
+
115
+ node.syncCurrentHUEDeviceFromKNXState = function syncCurrentHUEDeviceFromKNXState (_state) { // Starting from v 4.1.31
116
+ if (config.updateLocalStateFromKNXWrite !== true) return // Starting from v 4.1.31
117
+ if (_state === undefined || _state === null || typeof _state !== 'object') return // Starting from v 4.1.31
118
+ if (node.currentHUEDevice === undefined || node.currentHUEDevice === null) return // Starting from v 4.1.31
119
+ if (_state.on !== undefined && typeof _state.on.on === 'boolean') { // Starting from v 4.1.31
120
+ if (node.currentHUEDevice.on === undefined || node.currentHUEDevice.on === null) node.currentHUEDevice.on = {} // Starting from v 4.1.31
121
+ node.currentHUEDevice.on.on = _state.on.on // Starting from v 4.1.31
122
+ } // Starting from v 4.1.31
123
+ if (_state.dimming !== undefined && _state.dimming !== null && _state.dimming.brightness !== undefined) { // Starting from v 4.1.31
124
+ if (node.currentHUEDevice.dimming === undefined || node.currentHUEDevice.dimming === null) node.currentHUEDevice.dimming = {} // Starting from v 4.1.31
125
+ node.currentHUEDevice.dimming.brightness = _state.dimming.brightness // Starting from v 4.1.31
126
+ } // Starting from v 4.1.31
127
+ if (_state.color !== undefined && _state.color !== null && _state.color.xy !== undefined) { // Starting from v 4.1.31
128
+ if (node.currentHUEDevice.color === undefined || node.currentHUEDevice.color === null) node.currentHUEDevice.color = {} // Starting from v 4.1.31
129
+ node.currentHUEDevice.color.xy = cloneDeep(_state.color.xy) // Starting from v 4.1.31
130
+ } // Starting from v 4.1.31
131
+ if (_state.color_temperature !== undefined && _state.color_temperature !== null && _state.color_temperature.mirek !== undefined) { // Starting from v 4.1.31
132
+ if (node.currentHUEDevice.color_temperature === undefined || node.currentHUEDevice.color_temperature === null) node.currentHUEDevice.color_temperature = {} // Starting from v 4.1.31
133
+ node.currentHUEDevice.color_temperature.mirek = _state.color_temperature.mirek // Starting from v 4.1.31
134
+ } // Starting from v 4.1.31
135
+ } // Starting from v 4.1.31
136
+
137
+ // Used to call the status update from the config node.
138
+ node.setNodeStatus = ({
139
+ fill, shape, text, payload
140
+ }) => {
141
+ try {
142
+ if (node.currentHUEDevice?.on?.on === true) { fill = 'blue'; shape = 'dot' } else { fill = 'blue'; shape = 'ring' };
143
+ if (payload === undefined) payload = ''
144
+ const dDate = new Date()
145
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
146
+ node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
147
+ node.status({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
148
+ } catch (error) { }
149
+ }
150
+ // Used to call the status update from the HUE config node.
151
+ node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
152
+ try {
153
+ if (node.currentHUEDevice?.on?.on === true) { fill = 'blue'; shape = 'dot' } else { fill = 'blue'; shape = 'ring' };
154
+ if (payload === undefined) payload = ''
155
+ const dDate = new Date()
156
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
157
+ node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
158
+ node.status({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
159
+ } catch (error) { }
160
+ }
161
+
162
+ node.writeHueState = function writeHueState (_state) {
163
+ const defaultOperation = node.isGrouped_light === false ? 'setLight' : 'setGroupedLight'
164
+ const isGroupedLightOff = node.isGrouped_light === true && node.currentHUEDevice?.on?.on === false
165
+ const stateKeys = _state && typeof _state === 'object' ? Object.keys(_state) : []
166
+ const presetKeys = ['dimming', 'color', 'color_temperature', 'gradient']
167
+ const actionableKeys = ['dimming', ...presetKeys]
168
+ const hasActionablePayload = stateKeys.some((key) => actionableKeys.includes(key))
169
+ const mustPresetGroupedLightChildren = isGroupedLightOff &&
170
+ stateKeys.some((key) => presetKeys.includes(key))
171
+
172
+ if (!mustPresetGroupedLightChildren) {
173
+ node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, _state, defaultOperation)
174
+ return
175
+ }
176
+
177
+ (async () => {
178
+ try {
179
+ const groupLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
180
+ RED.log.debug(`knxUltimateHueLight: preset grouped_light children before group on. Group=${node.hueDevice} lights=${Array.isArray(groupLights) ? groupLights.length : 0}`)
181
+ let hasWrittenAtLeastOneLight = false
182
+ for (let index = 0; index < groupLights.length; index++) {
183
+ const light = groupLights[index]
184
+ if (!light?.id) continue
185
+ const lightState = {}
186
+ let hasActionableStateForLight = false
187
+
188
+ if (_state.dimming !== undefined && light.dimming !== undefined) {
189
+ lightState.dimming = cloneDeep(_state.dimming)
190
+ hasActionableStateForLight = true
191
+ }
192
+ if (_state.color !== undefined && light.color !== undefined) {
193
+ lightState.color = cloneDeep(_state.color)
194
+ hasActionableStateForLight = true
195
+ }
196
+ if (_state.color_temperature !== undefined && light.color_temperature !== undefined) {
197
+ lightState.color_temperature = cloneDeep(_state.color_temperature)
198
+ hasActionableStateForLight = true
199
+ }
200
+ if (_state.gradient !== undefined && light.gradient !== undefined) {
201
+ lightState.gradient = cloneDeep(_state.gradient)
202
+ hasActionableStateForLight = true
203
+ }
204
+
205
+ if (!hasActionableStateForLight) continue
206
+ node.serverHue.hueManager.writeHueQueueAdd(light.id, lightState, 'setLight')
207
+ hasWrittenAtLeastOneLight = true
208
+ }
209
+
210
+ if (!hasWrittenAtLeastOneLight || !hasActionablePayload) {
211
+ node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, _state, defaultOperation)
212
+ return
213
+ }
214
+
215
+ if (_state?.on?.on !== true) return
216
+
217
+ const groupedLightState = { on: cloneDeep(_state.on) }
218
+ if (_state.dynamics !== undefined) groupedLightState.dynamics = cloneDeep(_state.dynamics)
219
+ RED.log.debug(`knxUltimateHueLight: turning on grouped_light after children preset. Group=${node.hueDevice}`)
220
+ node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, groupedLightState, defaultOperation)
221
+ } catch (error) {
222
+ RED.log.debug(`knxUltimateHueLight: node.writeHueState fallback to grouped_light write: ${error.message}`)
223
+ node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, _state, defaultOperation)
224
+ }
225
+ })()
226
+ }
227
+
228
+ node.deleteHueStateQueue = function deleteHueStateQueue () {
229
+ node.serverHue.hueManager.deleteHueQueue(node.hueDevice)
230
+ if (node.isGrouped_light !== true) return;
231
+
232
+ (async () => {
233
+ try {
234
+ const groupLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
235
+ for (let index = 0; index < groupLights.length; index++) {
236
+ const light = groupLights[index]
237
+ if (!light?.id) continue
238
+ node.serverHue.hueManager.deleteHueQueue(light.id)
239
+ }
240
+ } catch (error) {
241
+ RED.log.debug(`knxUltimateHueLight: node.deleteHueStateQueue: ${error.message}`)
242
+ }
243
+ })()
244
+ }
245
+
246
+ function getRandomIntInclusive (min, max) {
247
+ min = Math.ceil(min)
248
+ max = Math.floor(max)
249
+ return Math.floor(Math.random() * (max - min + 1) + min) // The maximum is inclusive and the minimum is inclusive
250
+ }
251
+
252
+ // This function is called by the hue-config.js
253
+ node.handleSend = (msg) => {
254
+ if (node.currentHUEDevice === undefined && node.serverHue?.linkStatus === 'connected') {
255
+ node.setNodeStatusHue({
256
+ fill: 'yellow',
257
+ shape: 'ring',
258
+ text: 'Initializing. Please wait.',
259
+ payload: ''
260
+ })
261
+ return
262
+ }
263
+ if (msg.knx.event !== 'GroupValue_Read' && node.currentHUEDevice !== undefined) {
264
+ let state = {}
265
+ try {
266
+ switch (msg.knx.destination) {
267
+ case config.GALightSwitch:
268
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
269
+
270
+ // 15/05/2024 Supergiovane: check the Override to Day option
271
+ // config.restoreDayMode can be: no or setDayByFastSwitchLightSingle or setDayByFastSwitchLightALL
272
+ // ----------------------------------------------------------
273
+ if (config.restoreDayMode === 'setDayByFastSwitchLightSingle' || config.restoreDayMode === 'setDayByFastSwitchLightALL') {
274
+ if (node.DayTime === false) {
275
+ if (msg.payload === true) {
276
+ if (node.timerCheckForFastLightSwitch === null) {
277
+ node.timerCheckForFastLightSwitch = setTimeout(() => {
278
+ node.DayTime = false
279
+ RED.log.debug('knxUltimateHueLight: node.timerCheckForFastLightSwitch: set daytime to false after node.timerCheckForFastLightSwitch elapsed')
280
+ node.timerCheckForFastLightSwitch = null
281
+ }, 10000) // 10 seconds
282
+ } else {
283
+ if (config.restoreDayMode === 'setDayByFastSwitchLightALL') {
284
+ // Turn off the Day/Night group address
285
+ if (config.GADaylightSensor !== undefined && config.GADaylightSensor !== '') {
286
+ if (node.timerCheckForFastLightSwitch !== null) { clearTimeout(node.timerCheckForFastLightSwitch); node.timerCheckForFastLightSwitch = null }
287
+ RED.log.debug(`knxUltimateHueLight: node.timerCheckForFastLightSwitch: set daytime the group address ${config.GADaylightSensor}`)
288
+ node.serverKNX.sendKNXTelegramToKNXEngine({
289
+ grpaddr: config.GADaylightSensor,
290
+ payload: config.invertDayNight === false,
291
+ dpt: config.dptDaylightSensor,
292
+ outputtype: 'write',
293
+ nodecallerid: node.id
294
+ })
295
+ }
296
+ }
297
+ node.DayTime = true
298
+ RED.log.debug('knxUltimateHueLight: node.timerCheckForFastLightSwitch: set daytime to true')
299
+ }
300
+ }
301
+ }
302
+ }
303
+ // ----------------------------------------------------------
304
+
305
+ if (msg.payload === true) {
306
+ // From HUE Api core concepts:
307
+ // If you try and control multiple conflicting parameters at once e.g. {"color": {"xy": {"x":0.5,"y":0.5}}, "color_temperature": {"mirek": 250}}
308
+ // the lights can only physically do one, for this we apply the rule that xy beats ct. Simple.
309
+ // color_temperature.mirek: color temperature in mirek is null when the light color is not in the ct spectrum
310
+ if ((node.DayTime === true && config.specifySwitchOnBrightness === 'no') && ((node.isGrouped_light === false && node.HUEDeviceWhileDaytime !== null) || (node.isGrouped_light === true && node.HUELightsBelongingToGroupWhileDaytime !== null))) {
311
+ if (node.isGrouped_light === false && node.HUEDeviceWhileDaytime !== null) {
312
+ // The DayNight has switched into day, so restore the previous light status
313
+ state = { on: { on: true }, dimming: node.HUEDeviceWhileDaytime.dimming, color: node.HUEDeviceWhileDaytime.color, color_temperature: node.HUEDeviceWhileDaytime.color_temperature }
314
+ if (node.HUEDeviceWhileDaytime.color_temperature !== undefined && node.HUEDeviceWhileDaytime.color_temperature.mirek === null) delete state.color_temperature // Otherwise the lamp will not turn on due to an error. color_temperature.mirek: color temperature in mirek is null when the light color is not in the ct spectrum
315
+ node.writeHueState(state)
316
+ node.setNodeStatusHue({
317
+ fill: 'green',
318
+ shape: 'dot',
319
+ text: 'KNX->HUE',
320
+ payload: 'Restore light status'
321
+ })
322
+ node.HUEDeviceWhileDaytime = null // Nullize the object.
323
+ } else if (node.isGrouped_light === true && node.HUELightsBelongingToGroupWhileDaytime !== null) {
324
+ // The DayNight has switched into day, so restore the previous light state, belonging to the group
325
+ let bAtLeastOneIsOn = false
326
+ for (let index = 0; index < node.HUELightsBelongingToGroupWhileDaytime.length; index++) { // Ensure, at least 1 lamp was on, otherwise turn all lamps on
327
+ const element = node.HUELightsBelongingToGroupWhileDaytime[index].light[0]
328
+ if (element.on.on === true) {
329
+ bAtLeastOneIsOn = true
330
+ break
331
+ }
332
+ }
333
+ for (let index = 0; index < node.HUELightsBelongingToGroupWhileDaytime.length; index++) {
334
+ const element = node.HUELightsBelongingToGroupWhileDaytime[index].light[0]
335
+ if (bAtLeastOneIsOn === true) {
336
+ state = { on: element.on, dimming: element.dimming, color: element.color, color_temperature: element.color_temperature }
337
+ } else {
338
+ // Failsafe all on
339
+ state = { on: { on: true }, dimming: element.dimming, color: element.color, color_temperature: element.color_temperature }
340
+ }
341
+ if (element.color_temperature !== undefined && element.color_temperature.mirek === null) delete state.color_temperature // Otherwise the lamp will not turn on due to an error. color_temperature.mirek: color temperature in mirek is null when the light color is not in the ct spectrum
342
+ node.serverHue.hueManager.writeHueQueueAdd(element.id, state, 'setLight')
343
+ }
344
+ node.setNodeStatusHue({
345
+ fill: 'green',
346
+ shape: 'dot',
347
+ text: 'KNX->HUE',
348
+ payload: "Resuming all group's light"
349
+ })
350
+ node.HUELightsBelongingToGroupWhileDaytime = null // Nullize the object.
351
+ return
352
+ }
353
+ } else {
354
+ let colorChoosen
355
+ let temperatureChoosen
356
+ let brightnessChoosen
357
+ // The light must support the temperature (in this case, colorAtSwitchOnNightTime is an object {kelvin:xx, brightness:yy})
358
+ if (node.currentHUEDevice.color_temperature !== undefined) {
359
+ if (node.DayTime === true && config.specifySwitchOnBrightness === 'temperature') {
360
+ temperatureChoosen = config.colorAtSwitchOnDayTime.kelvin
361
+ } else if (node.DayTime === false && config.enableDayNightLighting === 'temperature') {
362
+ temperatureChoosen = config.colorAtSwitchOnNightTime.kelvin
363
+ }
364
+ }
365
+ if (node.currentHUEDevice.dimming !== undefined) {
366
+ // Check wether the user selected specific brightness at switch on (in this case, colorAtSwitchOnNightTime is an object {kelvin:xx, brightness:yy})
367
+ if (node.DayTime === true && config.specifySwitchOnBrightness === 'temperature') {
368
+ brightnessChoosen = config.colorAtSwitchOnDayTime.brightness
369
+ } else if (node.DayTime === false && config.enableDayNightLighting === 'temperature') {
370
+ brightnessChoosen = config.colorAtSwitchOnNightTime.brightness
371
+ }
372
+ }
373
+ if (node.currentHUEDevice.color !== undefined) {
374
+ // Check wether the user selected specific color at switch on (in this case, colorAtSwitchOnDayTime is a text with HTML web color)
375
+ if (node.DayTime === true && config.specifySwitchOnBrightness === 'yes') {
376
+ colorChoosen = config.colorAtSwitchOnDayTime
377
+ } else if (node.DayTime === false && config.enableDayNightLighting === 'yes') {
378
+ colorChoosen = config.colorAtSwitchOnNightTime
379
+ }
380
+ }
381
+ // Create the HUE command
382
+ if (colorChoosen !== undefined) {
383
+ // Now we have a jColorChoosen. Proceed illuminating the light
384
+ let gamut = null
385
+ if (node.currentHUEDevice.color.gamut !== undefined) {
386
+ gamut = node.currentHUEDevice.color.gamut
387
+ }
388
+ const dretXY = hueColorConverter.ColorConverter.calculateXYFromRGB(colorChoosen.red, colorChoosen.green, colorChoosen.blue, gamut)
389
+ const dbright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(colorChoosen.red, colorChoosen.green, colorChoosen.blue)
390
+ node.currentHUEDevice.dimming.brightness = Math.round(dbright, 0)
391
+ if (node.currentHUEDevice.color !== undefined) node.currentHUEDevice.color.xy = dretXY // 26/03/2024
392
+ node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness)
393
+ state = dbright > 0 ? { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } } : { on: { on: false } }
394
+ // state = { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } };
395
+ } else if (temperatureChoosen !== undefined) {
396
+ // Kelvin
397
+ const mirek = hueColorConverter.ColorConverter.kelvinToMirek(temperatureChoosen)
398
+ node.currentHUEDevice.color_temperature.mirek = mirek
399
+ node.currentHUEDevice.dimming.brightness = brightnessChoosen
400
+ node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness)
401
+ // Kelvin temp
402
+ state = brightnessChoosen > 0 ? { on: { on: true }, dimming: { brightness: brightnessChoosen }, color_temperature: { mirek } } : { on: { on: false } }
403
+ // state = { on: { on: true }, dimming: { brightness: brightnessChoosen }, color_temperature: { mirek: mirek } };
404
+ } else if (brightnessChoosen !== undefined) {
405
+ state = brightnessChoosen > 0 ? { on: { on: true }, dimming: { brightness: brightnessChoosen } } : { on: { on: false } }
406
+ // state = { on: { on: true }, dimming: { brightness: brightnessChoosen } };
407
+ } else {
408
+ state = { on: { on: true } }
409
+ }
410
+ }
411
+ } else {
412
+ // Stop color cycle
413
+ if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
414
+ // Stop Blinking
415
+ if (node.timerBlink !== undefined) clearInterval(node.timerBlink)
416
+ state = { on: { on: false } }
417
+ }
418
+
419
+ node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
420
+ node.writeHueState(state)
421
+ node.setNodeStatusHue({
422
+ fill: 'green',
423
+ shape: 'dot',
424
+ text: 'KNX->HUE',
425
+ payload: state
426
+ })
427
+ break
428
+ case config.GALightDIM:
429
+ // { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
430
+ // { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
431
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
432
+ node.hueDimming(msg.payload.decr_incr, msg.payload.data, config.dimSpeed)
433
+ node.setNodeStatusHue({
434
+ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: JSON.stringify(msg.payload)
435
+ })
436
+ break
437
+ case config.GALightHSV_H_DIM:
438
+ // { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
439
+ // { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
440
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
441
+ node.hueDimmingHSV_H(msg.payload.decr_incr, msg.payload.data, config.HSVDimSpeed)
442
+ node.setNodeStatusHue({
443
+ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: JSON.stringify(msg.payload)
444
+ })
445
+ break
446
+ case config.GALightHSV_S_DIM:
447
+ // { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
448
+ // { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
449
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
450
+ node.hueDimmingHSV_S(msg.payload.decr_incr, msg.payload.data, config.HSVDimSpeed)
451
+ node.setNodeStatusHue({
452
+ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: JSON.stringify(msg.payload)
453
+ })
454
+ break
455
+ case config.GALightKelvin:
456
+ let retMirek
457
+ let kelvinValue = 0
458
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvin))
459
+ if (config.dptLightKelvin === '7.600') {
460
+ if (msg.payload > 6535) msg.payload = 6535
461
+ if (msg.payload < 2000) msg.payload = 2000
462
+ kelvinValue = msg.payload// hueColorConverter.ColorConverter.scale(msg.payload, [0, 65535], [2000, 6535]);
463
+ retMirek = hueColorConverter.ColorConverter.kelvinToMirek(kelvinValue)
464
+ } else if (config.dptLightKelvin === '9.002') {
465
+ // Relative temperature in Kelvin. Use HUE scale.
466
+ if (msg.payload > 6535) msg.payload = 6535
467
+ if (msg.payload < 2000) msg.payload = 2000
468
+ retMirek = hueColorConverter.ColorConverter.kelvinToMirek(msg.payload)
469
+ }
470
+ state = { color_temperature: { mirek: retMirek } }
471
+ node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
472
+ node.writeHueState(state)
473
+ node.setNodeStatusHue({
474
+ fill: 'green',
475
+ shape: 'dot',
476
+ text: 'KNX->HUE',
477
+ payload: kelvinValue
478
+ })
479
+ break
480
+ case config.GADaylightSensor:
481
+ node.DayTime = Boolean(dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptDaylightSensor)))
482
+ if (config.invertDayNight === true) node.DayTime = !node.DayTime
483
+ if (config.specifySwitchOnBrightness === 'no') {
484
+ // This retains the HUE device status while daytime, to be restored after nighttime elapsed. https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/298
485
+ if (node.DayTime === false) {
486
+ if (node.isGrouped_light === false) node.HUEDeviceWhileDaytime = cloneDeep(node.currentHUEDevice) // DayTime has switched to false: save the currentHUEDevice into the HUEDeviceWhileDaytime
487
+ if (node.isGrouped_light === true) {
488
+ (async () => {
489
+ try {
490
+ const retLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
491
+ node.HUELightsBelongingToGroupWhileDaytime = cloneDeep(retLights) // DayTime has switched to false: save the lights belonging to the group into the HUELightsBelongingToGroupWhileDaytime array
492
+ } catch (error) { /* empty */ }
493
+ })()
494
+ }
495
+ }
496
+ } else {
497
+ node.HUEDeviceWhileDaytime = null
498
+ node.HUELightsBelongingToGroupWhileDaytime = null
499
+ }
500
+ node.setNodeStatusHue({
501
+ fill: 'green',
502
+ shape: 'dot',
503
+ text: 'KNX->HUE Daytime',
504
+ payload: node.DayTime
505
+ })
506
+
507
+ break
508
+ case config.GALightKelvinDIM:
509
+ if (config.dptLightKelvinDIM === '3.007') {
510
+ // MDT smartbutton will dim the color temperature
511
+ // { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
512
+ // { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
513
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvinDIM))
514
+ node.hueDimmingTunableWhite(msg.payload.decr_incr, msg.payload.data, 5000)
515
+ node.setNodeStatusHue({
516
+ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: JSON.stringify(msg.payload)
517
+ })
518
+ }
519
+ break
520
+ case config.GALightKelvinPercentage:
521
+ if (config.dptLightKelvinPercentage === '5.001') {
522
+ // 0-100% tunable white
523
+ msg.payload = 100 - dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvinPercentage))
524
+ // msg.payload = msg.payload <= 0 ? 1 : msg.payload
525
+ const retMirek = hueColorConverter.ColorConverter.scale(msg.payload, [0, 100], [153, 500])
526
+ msg.payload = retMirek
527
+ state = { color_temperature: { mirek: msg.payload } }
528
+ node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
529
+ node.writeHueState(state)
530
+ node.setNodeStatusHue({
531
+ fill: 'green',
532
+ shape: 'dot',
533
+ text: 'KNX->HUE',
534
+ payload: state
535
+ })
536
+ }
537
+ break
538
+ case config.GALightBrightness:
539
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
540
+ if (typeof msg.payload !== 'number' || Number.isNaN(msg.payload)) throw new Error('Invalid KNX brightness payload')
541
+ state = { dimming: { brightness: msg.payload } }
542
+ if (msg.payload === 0) {
543
+ state.on = { on: false }
544
+ } else if (node.currentHUEDevice !== undefined && node.currentHUEDevice.on?.on === true) {
545
+ state.on = { on: true }
546
+ }
547
+ node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
548
+ node.writeHueState(state)
549
+ node.setNodeStatusHue({
550
+ fill: 'green',
551
+ shape: 'dot',
552
+ text: 'KNX->HUE',
553
+ payload: state
554
+ })
555
+ break
556
+ case config.GALightColor:
557
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColor))
558
+ let gamut = null
559
+ if (
560
+ node.currentHUEDevice !== undefined &&
561
+ node.currentHUEDevice.color !== undefined &&
562
+ node.currentHUEDevice.color.gamut !== undefined
563
+ ) {
564
+ gamut = node.currentHUEDevice.color.gamut
565
+ }
566
+ const retXY = hueColorConverter.ColorConverter.calculateXYFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue, gamut)
567
+ const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(msg.payload.red, msg.payload.green, msg.payload.blue)
568
+ state = { dimming: { brightness: bright }, color: { xy: retXY } }
569
+ if (node.currentHUEDevice === undefined) {
570
+ // Grouped light
571
+ state.on = { on: bright > 0 }
572
+ } else {
573
+ // Light
574
+ if (node.currentHUEDevice.on.on === false && bright > 0) state.on = { on: true }
575
+ if (node.currentHUEDevice.on.on === true && bright === 0) state = { on: { on: false }, dimming: { brightness: bright } }
576
+ }
577
+ node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
578
+ node.writeHueState(state)
579
+ node.setNodeStatusHue({
580
+ fill: 'green',
581
+ shape: 'dot',
582
+ text: 'KNX->HUE',
583
+ payload: state
584
+ })
585
+ break
586
+ case config.GALightBlink:
587
+ const gaVal = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBlink))
588
+ if (gaVal) {
589
+ node.timerBlink = setInterval(() => {
590
+ if (node.blinkValue === undefined) node.blinkValue = true
591
+ node.blinkValue = !node.blinkValue
592
+ msg.payload = node.blinkValue
593
+ // state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
594
+ state = msg.payload === true
595
+ ? { on: { on: true }, dimming: { brightness: 100 }, dynamics: { duration: 0 } }
596
+ : { on: { on: false }, dimming: { brightness: 0 }, dynamics: { duration: 0 } }
597
+ node.writeHueState(state)
598
+ }, 1500)
599
+ } else {
600
+ if (node.timerBlink !== undefined) clearInterval(node.timerBlink)
601
+ node.writeHueState({ on: { on: false } })
602
+ }
603
+ node.setNodeStatusHue({
604
+ fill: 'green',
605
+ shape: 'dot',
606
+ text: 'KNX->HUE',
607
+ payload: gaVal
608
+ })
609
+ break
610
+ case config.GALightColorCycle:
611
+ {
612
+ if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
613
+ const gaValColorCycle = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColorCycle))
614
+ if (gaValColorCycle === true) {
615
+ node.writeHueState({ on: { on: true } })
616
+ node.timerColorCycle = setInterval(() => {
617
+ try {
618
+ const red = getRandomIntInclusive(0, 255)
619
+ const green = getRandomIntInclusive(0, 255)
620
+ const blue = getRandomIntInclusive(0, 255)
621
+ let gamut = null
622
+ if (
623
+ node.currentHUEDevice !== undefined &&
624
+ node.currentHUEDevice.color !== undefined &&
625
+ node.currentHUEDevice.color.gamut !== undefined
626
+ ) {
627
+ gamut = node.currentHUEDevice.color.gamut
628
+ }
629
+ const retXY = hueColorConverter.ColorConverter.calculateXYFromRGB(red, green, blue, gamut)
630
+ const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(red, green, blue)
631
+ state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } }
632
+ node.writeHueState(state)
633
+ } catch (error) { }
634
+ }, 10000)
635
+ } else {
636
+ if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
637
+ node.writeHueState({ on: { on: false } })
638
+ }
639
+ node.setNodeStatusHue({
640
+ fill: 'green',
641
+ shape: 'dot',
642
+ text: 'KNX->HUE',
643
+ payload: gaValColorCycle
644
+ })
645
+ }
646
+ break
647
+ default:
648
+ break
649
+ }
650
+ } catch (error) {
651
+ node.status({
652
+ fill: 'red',
653
+ shape: 'dot',
654
+ text: `KNX->HUE errorRead ${error.message} (${formatTs(new Date())})`
655
+ })
656
+ RED.log.error(`knxUltimateHueLight: node.handleSend: if (msg.knx.event !== "GroupValue_Read"): ${error.message} : ${error.stack || ''} `)
657
+ }
658
+ }
659
+
660
+ // I must respond to query requests (read request) sent from the KNX BUS
661
+ try {
662
+ if (msg.knx.event === 'GroupValue_Read' && node.currentHUEDevice !== undefined) {
663
+ const zeroBrightnessWhenOff = (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')
664
+ let ret
665
+ switch (msg.knx.destination) {
666
+ case config.GALightState:
667
+ ret = node.currentHUEDevice.on.on
668
+ if (ret !== undefined) node.updateKNXLightState(ret, 'response')
669
+ break
670
+ case config.GALightColorState:
671
+ ret = node.currentHUEDevice.color.xy
672
+ if (ret !== undefined) node.updateKNXLightColorState(node.currentHUEDevice.color, 'response')
673
+ break
674
+ case config.GALightKelvinPercentageState:
675
+ // The kelvin level belongs to the group defice, so i don't need to get the first light in the collection (if the device is a grouped_light)
676
+ ret = node.currentHUEDevice.color_temperature.mirek
677
+ if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, 'response')
678
+ // if (node.isGrouped_light === false) {
679
+ // ret = node.currentHUEDevice.color_temperature.mirek;
680
+ // if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
681
+ // } else {
682
+ // (async () => {
683
+ // try {
684
+ // // Find the first light in the collection, having the color_temperature capabilities
685
+ // const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
686
+ // for (let index = 0; index < devices.length; index++) {
687
+ // const element = devices[index];
688
+ // if (element.light[0].color_temperature !== undefined) {
689
+ // ret = element.light[0].color_temperature.mirek;
690
+ // break;
691
+ // }
692
+ // }
693
+ // if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
694
+ // } catch (error) { /* empty */ }
695
+ // })();
696
+ // }
697
+ break
698
+ case config.GALightBrightnessState:
699
+ // Hue keeps the last brightness even when the light is OFF. If the user enabled "brightness status -> 0 on HUE off",
700
+ // keep returning 0 while OFF to avoid the KNX status jumping back to the last/start value after some time/read-requests.
701
+ if (zeroBrightnessWhenOff === true && node.currentHUEDevice?.on?.on === false) {
702
+ ret = 0
703
+ } else {
704
+ ret = node.currentHUEDevice.dimming.brightness
705
+ }
706
+ if (ret !== undefined) node.updateKNXBrightnessState(ret, 'response')
707
+ break
708
+ case config.GALightKelvinState:
709
+ // The kelvin level belongs to the group defice, so i don't need to get the first light in the collection (if the device is a grouped_light)
710
+ ret = node.currentHUEDevice.color_temperature.mirek
711
+ if (ret !== undefined) node.updateKNXLightKelvinState(ret, 'response')
712
+ // if (node.isGrouped_light === false) {
713
+ // ret = node.currentHUEDevice.color_temperature.mirek;
714
+ // if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
715
+ // } else {
716
+ // (async () => {
717
+ // try {
718
+ // // Find the first light in the collection, having the color_temperature capabilities
719
+ // const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
720
+ // for (let index = 0; index < devices.length; index++) {
721
+ // const element = devices[index];
722
+ // if (element.light[0].color_temperature !== undefined) {
723
+ // ret = element.light[0].color_temperature.mirek;
724
+ // break;
725
+ // }
726
+ // }
727
+ // if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
728
+ // } catch (error) { /* empty */ }
729
+ // })();
730
+ // }
731
+ break
732
+ default:
733
+ break
734
+ }
735
+ }
736
+ } catch (error) {
737
+ node.status({
738
+ fill: 'red',
739
+ shape: 'dot',
740
+ text: `KNX->HUE error :-( ${error.message} (${formatTs(new Date())})`
741
+ })
742
+ RED.log.error(`knxUltimateHueLight: node.handleSend: if (msg.knx.event === "GroupValue_Read" && node.currentHUEDevice !== undefined): ${error.message} : ${error.stack || ''} `)
743
+ }
744
+ }
745
+
746
+ // Start dimming
747
+ // ***********************************************************
748
+ node.hueDimming = function hueDimming (_KNXaction, _KNXbrightness_Direction, _dimSpeedInMillisecs = undefined) {
749
+ // 31/10/2023 after many attempts to use dimming_delta function of the HueApeV2, loosing days of my life, without a decent success, will use the standard dimming calculations
750
+ // i decide to go to the "step brightness" way.
751
+ try {
752
+ let hueTelegram = {}
753
+ let numStep = 10 // Steps from 0 to 100 by 10
754
+ const extendedConf = {}
755
+
756
+ if (_KNXbrightness_Direction === 0 && _KNXaction === 0) {
757
+ // STOP DIM
758
+ if (node.timerStepDim !== undefined) clearInterval(node.timerStepDim)
759
+ node.brightnessStep = null
760
+ node.deleteHueStateQueue() // Clear dimming queue.
761
+ return
762
+ }
763
+
764
+ // 26/03/2024 set the extended configuration as well, because the light was off (so i've been unable to set it up elsewhere)
765
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
766
+ // if (node.currentHUEDevice.color !== undefined) extendedConf.color = { xy: node.currentHUEDevice.color.xy }; // DO NOT ADD THE COLOR, BECAUSE THE COLOR HAS ALSO THE BRIGHTNESS, SO ALL THE DATA NEEDED TO BE SWITCHED ON CORRECLY
767
+ if (node.currentHUEDevice.color_temperature !== undefined) extendedConf.color_temperature = { mirek: node.currentHUEDevice.color_temperature.mirek }
768
+ }
769
+
770
+ // If i'm dimming up while the light is off, start the dim with the initial brightness set to zero
771
+ if (_KNXbrightness_Direction > 0 && _KNXaction === 1 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
772
+ node.brightnessStep = null
773
+ node.currentHUEDevice.dimming.brightness = 0
774
+ }
775
+
776
+ // Set the actual brightness to start with
777
+ if (node.brightnessStep === null || node.brightnessStep === undefined) node.brightnessStep = node.currentHUEDevice.dimming.brightness !== undefined ? node.currentHUEDevice.dimming.brightness : 50
778
+ node.brightnessStep = Math.ceil(Number(node.brightnessStep))
779
+
780
+ // We have also minDimLevelLight and maxDimLevelLight to take care of.
781
+ let minDimLevelLight
782
+ if (config.minDimLevelLight === undefined) {
783
+ minDimLevelLight = 10
784
+ } else if (config.minDimLevelLight === 'useHueLightLevel') {
785
+ minDimLevelLight = node.currentHUEDevice.dimming.min_dim_level === undefined ? 10 : node.currentHUEDevice.dimming.min_dim_level
786
+ } else {
787
+ minDimLevelLight = Number(config.minDimLevelLight)
788
+ }
789
+ const maxDimLevelLight = config.maxDimLevelLight === undefined ? 100 : Number(config.maxDimLevelLight)
790
+
791
+ // Set the speed
792
+ _dimSpeedInMillisecs /= numStep
793
+ numStep = Math.round((maxDimLevelLight - minDimLevelLight) / numStep, 0)
794
+
795
+ if (_KNXbrightness_Direction > 0 && _KNXaction === 1) {
796
+ // DIM UP
797
+ if (node.timerStepDim !== undefined) clearInterval(node.timerStepDim)
798
+ node.timerStepDim = setInterval(() => {
799
+ node.updateKNXBrightnessState(node.brightnessStep) // Unnecessary, but necessary to set the KNX Status in real time.
800
+ node.brightnessStep += numStep
801
+ if (node.brightnessStep > maxDimLevelLight) node.brightnessStep = maxDimLevelLight
802
+ hueTelegram = { dimming: { brightness: node.brightnessStep }, dynamics: { duration: _dimSpeedInMillisecs + 500 } } // + is to avoid ladder effect
803
+ // Switch on the light if off
804
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
805
+ hueTelegram.on = { on: true }
806
+ Object.assign(hueTelegram, extendedConf) // 26/03/2024 add extended conf
807
+ }
808
+ // console.log(hueTelegram)
809
+ node.writeHueState(hueTelegram)
810
+ if (node.brightnessStep >= maxDimLevelLight) clearInterval(node.timerStepDim)
811
+ }, _dimSpeedInMillisecs)
812
+ }
813
+ if (_KNXbrightness_Direction > 0 && _KNXaction === 0) {
814
+ if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
815
+ // DIM DOWN
816
+ if (node.timerStepDim !== undefined) clearInterval(node.timerStepDim)
817
+ node.timerStepDim = setInterval(() => {
818
+ node.updateKNXBrightnessState(node.brightnessStep) // Unnecessary, but necessary to set the KNX Status in real time.
819
+ node.brightnessStep -= numStep
820
+ if (node.brightnessStep < minDimLevelLight) node.brightnessStep = minDimLevelLight
821
+ hueTelegram = { dimming: { brightness: node.brightnessStep }, dynamics: { duration: _dimSpeedInMillisecs + 500 } }// + 100 is to avoid ladder effect
822
+ // Switch off the light if on
823
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStep === 0) {
824
+ hueTelegram.on = { on: false }
825
+ }
826
+ node.writeHueState(hueTelegram)
827
+ if (node.brightnessStep <= minDimLevelLight) clearInterval(node.timerStepDim)
828
+ }, _dimSpeedInMillisecs)
829
+ }
830
+ } catch (error) { }
831
+ }
832
+ // ***********************************************************
833
+
834
+ // Start dimming tunable white
835
+ // mirek: required(integer minimum: 153, maximum: 500)
836
+ // ***********************************************************
837
+ node.hueDimmingTunableWhite = function hueDimmingTunableWhite (_KNXaction, _KNXbrightness_DirectionTunableWhite, _dimSpeedInMillisecsTunableWhite = undefined) {
838
+ // 23/23/2023 after many attempts to use dimming_delta function of the HueApeV2, loosing days of my life, without a decent success, will use the standard dimming calculations
839
+ // i decide to go to the "step brightness" way.
840
+ try {
841
+ if (_KNXbrightness_DirectionTunableWhite === 0 && _KNXaction === 0) {
842
+ // STOP DIM
843
+ if (node.timerStepDimTunableWhite !== undefined) clearInterval(node.timerStepDimTunableWhite)
844
+ node.brightnessStepTunableWhite = null
845
+ node.deleteHueStateQueue() // Clear dimming queue.
846
+ return
847
+ }
848
+
849
+ let numStepTunableWhite = 10 // Steps from 153 to 500
850
+ if (config.invertDimTunableWhiteDirection === true) {
851
+ if (_KNXaction === 1) { _KNXaction = 0 } else { _KNXaction = 1 }
852
+ }
853
+
854
+ // Set the actual brightness to start with
855
+ if (node.brightnessStepTunableWhite === null || node.brightnessStepTunableWhite === undefined) node.brightnessStepTunableWhite = node.currentHUEDevice.color_temperature.mirek || 372
856
+ node.brightnessStepTunableWhite = Math.ceil(Number(node.brightnessStepTunableWhite))
857
+
858
+ // Set the speed
859
+ _dimSpeedInMillisecsTunableWhite = Math.ceil(_dimSpeedInMillisecsTunableWhite / numStepTunableWhite)
860
+
861
+ const minDimLevelLightTunableWhite = 153
862
+ const maxDimLevelLightTunableWhite = 500
863
+ // numStepTunableWhite = hueColorConverter.ColorConverter.scale(numStepTunableWhite, [0, 100], [minDimLevelLightTunableWhite, maxDimLevelLightTunableWhite]);
864
+ // numStepTunableWhite = hueColorConverter.ColorConverter.scale(numStepTunableWhite, [node.brightnessStepTunableWhite, maxDimLevelLightTunableWhite], [node.brightnessStepTunableWhite, maxDimLevelLightTunableWhite]);
865
+ numStepTunableWhite = Math.round((maxDimLevelLightTunableWhite - minDimLevelLightTunableWhite) / numStepTunableWhite, 0)
866
+
867
+ if (_KNXbrightness_DirectionTunableWhite > 0 && _KNXaction === 1) {
868
+ // DIM UP
869
+ if (node.timerStepDimTunableWhite !== undefined) clearInterval(node.timerStepDimTunableWhite)
870
+ node.timerStepDimTunableWhite = setInterval(() => {
871
+ node.updateKNXLightKelvinPercentageState(node.brightnessStepTunableWhite) // Unnecessary, but necessary to set the KNX Status in real time.
872
+ node.brightnessStepTunableWhite += numStepTunableWhite // *2 to speed up the things
873
+ if (node.brightnessStepTunableWhite > maxDimLevelLightTunableWhite) node.brightnessStepTunableWhite = maxDimLevelLightTunableWhite
874
+ const hueTelegram = { color_temperature: { mirek: node.brightnessStepTunableWhite }, dynamics: { duration: _dimSpeedInMillisecsTunableWhite } }
875
+ // Switch on the light if off
876
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
877
+ hueTelegram.on = { on: true }
878
+ }
879
+ node.writeHueState(hueTelegram)
880
+ if (node.brightnessStepTunableWhite >= maxDimLevelLightTunableWhite) clearInterval(node.timerStepDimTunableWhite)
881
+ }, _dimSpeedInMillisecsTunableWhite)
882
+ }
883
+ if (_KNXbrightness_DirectionTunableWhite > 0 && _KNXaction === 0) {
884
+ if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
885
+ // DIM DOWN
886
+ if (node.timerStepDimTunableWhite !== undefined) clearInterval(node.timerStepDimTunableWhite)
887
+ node.timerStepDimTunableWhite = setInterval(() => {
888
+ node.updateKNXLightKelvinPercentageState(node.brightnessStepTunableWhite) // Unnecessary, but necessary to set the KNX Status in real time.
889
+ node.brightnessStepTunableWhite -= numStepTunableWhite // *2 to speed up the things
890
+ if (node.brightnessStepTunableWhite < minDimLevelLightTunableWhite) node.brightnessStepTunableWhite = minDimLevelLightTunableWhite
891
+ const hueTelegram = { color_temperature: { mirek: node.brightnessStepTunableWhite }, dynamics: { duration: _dimSpeedInMillisecsTunableWhite } }
892
+ // Switch off the light if on
893
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStepTunableWhite === 0) {
894
+ hueTelegram.on = { on: false }
895
+ }
896
+ node.writeHueState(hueTelegram)
897
+ if (node.brightnessStepTunableWhite <= minDimLevelLightTunableWhite) clearInterval(node.timerStepDimTunableWhite)
898
+ }, _dimSpeedInMillisecsTunableWhite)
899
+ }
900
+ } catch (error) { }
901
+ }
902
+ // ***********************************************************
903
+
904
+ /**
905
+ * Starts dimming / stop dimming of the HUE
906
+ * @param {number} _KNXaction 1 for start, 0 for stop
907
+ * @param {number} _KNXbrightness_DirectionHSV_H 1 for up, 0 for down
908
+ * @param {number} _dimSpeedInMillisecsHSV Speed time in milliseconds
909
+ * @returns {}
910
+ */
911
+ node.hueDimmingHSV_H = function hueDimmingHSV_H (_KNXaction, _KNXbrightness_DirectionHSV_H, _dimSpeedInMillisecsHSV = undefined) {
912
+ // After many attempts to use dimming_delta function of the HueApiV2, loosing days of my life, without a decent success, will use the standard dimming calculations
913
+ // i decide to go to the "step brightness" way.
914
+ try {
915
+ if (_KNXbrightness_DirectionHSV_H === 0 && _KNXaction === 0) {
916
+ // STOP DIM
917
+ if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
918
+ node.deleteHueStateQueue() // Clear dimming queue.
919
+ return
920
+ }
921
+
922
+ let xyBrightnessToHsv
923
+ if (node.currentHUEDevice.color !== undefined && node.currentHUEDevice.color.xy !== undefined) {
924
+ // Get the XY + brightness from the lamp and transform it into HSV
925
+ // xyBrightnessToHsv = hueColorConverter.ColorConverter.xyBrightnessToHsv(node.currentHUEDevice.color.xy.x, node.currentHUEDevice.color.xy.y, node.currentHUEDevice.dimming.brightness);
926
+ xyBrightnessToHsv = hueColorConverter.ColorConverter.xyBrightnessToHsv(hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.x, [0, 1], [0, 100]), hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.y, [0, 1], [0, 100]), node.currentHUEDevice.dimming.brightness)
927
+ } else {
928
+ return // Something wrong here.
929
+ }
930
+ let numStepHSV_H = 10 // Steps from 0 to 100
931
+
932
+ // Set the actual brightness to start with
933
+ if (node.brightnessStepHSV_H === null || node.brightnessStepHSV_H === undefined) node.brightnessStepHSV_H = node.currentHUEDevice.dimming.brightness
934
+ node.brightnessStepHSV_H = Math.ceil(Number(node.brightnessStepHSV_H))
935
+
936
+ const minDimLevelLightHSV_H = 1
937
+ const maxDimLevelLightHSV_H = 100
938
+ numStepHSV_H = Math.round((maxDimLevelLightHSV_H - minDimLevelLightHSV_H) / numStepHSV_H, 0)
939
+ // Set the speed
940
+ _dimSpeedInMillisecsHSV = Math.ceil(_dimSpeedInMillisecsHSV / (maxDimLevelLightHSV_H / numStepHSV_H))
941
+
942
+ if (_KNXbrightness_DirectionHSV_H > 0 && _KNXaction === 1) {
943
+ // DIM UP
944
+ if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
945
+ node.timerStepDimHSV_H = setInterval(() => {
946
+ node.deleteHueStateQueue() // Clear dimming queue.
947
+ node.brightnessStepHSV_H += numStepHSV_H
948
+ if (node.brightnessStepHSV_H > maxDimLevelLightHSV_H) node.brightnessStepHSV_H = maxDimLevelLightHSV_H
949
+ // node.updateKNXLightHSV_H_State(node.brightnessStepHSV_H); // Unnecessary, but necessary to set the KNX Status in real time.
950
+ xyBrightnessToHsv.h = node.brightnessStepHSV_H
951
+ const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
952
+ const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } } // Add some ms, to prevent the "ladder" step effect.
953
+ // Switch on the light if off
954
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
955
+ hueTelegram.on = { on: true }
956
+ }
957
+ node.currentHUEDevice.color.xy = hsvToXYBri// Unnecessary, but necessary to set the KNX Status in real time.
958
+ node.updateKNXLightColorState(node.currentHUEDevice.color)
959
+ node.writeHueState(hueTelegram)
960
+ if (node.brightnessStepHSV_H >= maxDimLevelLightHSV_H) {
961
+ if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
962
+ }
963
+ }, _dimSpeedInMillisecsHSV)
964
+ }
965
+ if (_KNXbrightness_DirectionHSV_H > 0 && _KNXaction === 0) {
966
+ if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
967
+ // DIM DOWN
968
+ if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
969
+ node.timerStepDimHSV_H = setInterval(() => {
970
+ node.deleteHueStateQueue() // Clear dimming queue.
971
+ node.brightnessStepHSV_H -= numStepHSV_H // *2 to speed up the things
972
+ if (node.brightnessStepHSV_H < minDimLevelLightHSV_H) node.brightnessStepHSV_H = minDimLevelLightHSV_H
973
+ // node.updateKNXLightHSV_H_State(node.brightnessStepHSV_H); // Unnecessary, but necessary to set the KNX Status in real time.
974
+ // Produco l'RGB
975
+ xyBrightnessToHsv.h = node.brightnessStepHSV_H
976
+ const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
977
+ const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } }
978
+ // Switch off the light if on
979
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStepHSV_H === 0) {
980
+ hueTelegram.on = { on: false }
981
+ }
982
+ node.currentHUEDevice.color.xy = hsvToXYBri// Unnecessary, but necessary to set the KNX Status in real time.
983
+ node.updateKNXLightColorState(node.currentHUEDevice.color)
984
+ node.writeHueState(hueTelegram)
985
+ if (node.brightnessStepHSV_H <= minDimLevelLightHSV_H) {
986
+ if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
987
+ }
988
+ }, _dimSpeedInMillisecsHSV)
989
+ }
990
+ } catch (error) { /* empty */ }
991
+ }
992
+ /**
993
+ * Starts dimming / stop dimming of the Saturation
994
+ * @param {number} _KNXaction 1 for start, 0 for stop
995
+ * @param {number} _KNXbrightness_DirectionHSV_S 1 for up, 0 for down
996
+ * @param {number} _dimSpeedInMillisecsHSV Speed time in milliseconds
997
+ * @returns {}
998
+ */
999
+ node.hueDimmingHSV_S = function hueDimmingHSV_S (_KNXaction, _KNXbrightness_DirectionHSV_S, _dimSpeedInMillisecsHSV = undefined) {
1000
+ // After many attempts to use dimming_delta function of the HueApiV2, loosing days of my life, without a decent success, will use the standard dimming calculations
1001
+ // i decide to go to the "step brightness" way.
1002
+ try {
1003
+ if (_KNXbrightness_DirectionHSV_S === 0 && _KNXaction === 0) {
1004
+ // STOP DIM
1005
+ if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
1006
+ node.deleteHueStateQueue() // Clear dimming queue.
1007
+ return
1008
+ }
1009
+
1010
+ let xyBrightnessToHsv
1011
+ if (node.currentHUEDevice.color !== undefined && node.currentHUEDevice.color.xy !== undefined) {
1012
+ // Get the XY + brightness from the lamp and transform it into HSV
1013
+ xyBrightnessToHsv = hueColorConverter.ColorConverter.xyBrightnessToHsv(hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.x, [0, 1], [0, 100]), hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.y, [0, 1], [0, 100]), node.currentHUEDevice.dimming.brightness)
1014
+ } else {
1015
+ return // Something wrong here.
1016
+ }
1017
+ let numStepHSV_S = 10 // Steps from 0 to 100
1018
+
1019
+ // Set the actual brightness to start with
1020
+ if (node.brightnessStepHSV_S === null || node.brightnessStepHSV_S === undefined) node.brightnessStepHSV_S = node.currentHUEDevice.dimming.brightness
1021
+ node.brightnessStepHSV_S = Math.ceil(Number(node.brightnessStepHSV_S))
1022
+
1023
+ const minDimLevelLightHSV_S = 1
1024
+ const maxDimLevelLightHSV_S = 100
1025
+ numStepHSV_S = Math.round((maxDimLevelLightHSV_S - minDimLevelLightHSV_S) / numStepHSV_S, 0)
1026
+ // Set the speed
1027
+ _dimSpeedInMillisecsHSV = Math.ceil(_dimSpeedInMillisecsHSV / (maxDimLevelLightHSV_S / numStepHSV_S))
1028
+
1029
+ if (_KNXbrightness_DirectionHSV_S > 0 && _KNXaction === 1) {
1030
+ // DIM UP
1031
+ if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
1032
+ node.timerStepDimHSV_S = setInterval(() => {
1033
+ node.deleteHueStateQueue() // Clear dimming queue.
1034
+ node.brightnessStepHSV_S += numStepHSV_S
1035
+ if (node.brightnessStepHSV_S > maxDimLevelLightHSV_S) node.brightnessStepHSV_S = maxDimLevelLightHSV_S
1036
+ // node.updateKNXLightHSV_S_State(node.brightnessStepHSV_S); // Unnecessary, but necessary to set the KNX Status in real time.
1037
+ xyBrightnessToHsv.s = node.brightnessStepHSV_S
1038
+ const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
1039
+ const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } } // Add some ms, to prevent the "ladder" step effect.
1040
+ // Switch on the light if off
1041
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
1042
+ hueTelegram.on = { on: true }
1043
+ }
1044
+ node.currentHUEDevice.color.xy = cloneDeep(hsvToXYBri)// Unnecessary, but necessary to set the KNX Status in real time.
1045
+ node.updateKNXLightColorState(node.currentHUEDevice.color)
1046
+ node.writeHueState(hueTelegram)
1047
+ if (node.brightnessStepHSV_S >= maxDimLevelLightHSV_S) {
1048
+ if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
1049
+ }
1050
+ }, _dimSpeedInMillisecsHSV)
1051
+ }
1052
+ if (_KNXbrightness_DirectionHSV_S > 0 && _KNXaction === 0) {
1053
+ if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
1054
+ // DIM DOWN
1055
+ if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
1056
+ node.timerStepDimHSV_S = setInterval(() => {
1057
+ node.deleteHueStateQueue() // Clear dimming queue.
1058
+ node.brightnessStepHSV_S -= numStepHSV_S // *2 to speed up the things
1059
+ if (node.brightnessStepHSV_S < minDimLevelLightHSV_S) node.brightnessStepHSV_S = minDimLevelLightHSV_S
1060
+ // node.updateKNXLightHSV_S_State(node.brightnessStepHSV_S); // Unnecessary, but necessary to set the KNX Status in real time.
1061
+ // Produco l'RGB
1062
+ xyBrightnessToHsv.s = node.brightnessStepHSV_S
1063
+ const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
1064
+ const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } }
1065
+ // Switch off the light if on
1066
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStepHSV_S === 0) {
1067
+ hueTelegram.on = { on: false }
1068
+ }
1069
+ node.currentHUEDevice.color.xy = cloneDeep(hsvToXYBri)// Unnecessary, but necessary to set the KNX Status in real time.
1070
+ node.updateKNXLightColorState(node.currentHUEDevice.color)
1071
+ node.writeHueState(hueTelegram)
1072
+ if (node.brightnessStepHSV_S <= minDimLevelLightHSV_S) {
1073
+ if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
1074
+ }
1075
+ }, _dimSpeedInMillisecsHSV)
1076
+ }
1077
+ } catch (error) { /* empty */ }
1078
+ }
1079
+ // ***********************************************************
1080
+
1081
+ node.handleSendHUE = async (_event) => {
1082
+ if (_event === undefined) return
1083
+ if (_event.type !== 'grouped_light' && _event.type !== 'light') return
1084
+
1085
+ // !!!! >>> if the node is a grouped_light, the only values required, thus present, by HUE apis are "on" and "dimming".
1086
+ // !!!! >>> For all others properties like for example color and tunable white, i must get the data from one of the child lights.
1087
+
1088
+ // (async () => {
1089
+ try {
1090
+ if (node.currentHUEDevice === undefined || node.serverHue === null || node.serverHue === undefined) {
1091
+ node.setNodeStatusHue({
1092
+ fill: 'red',
1093
+ shape: 'ring',
1094
+ text: "Rejected HUE light settings. I'm still not ready...",
1095
+ payload: ''
1096
+ })
1097
+ return
1098
+ }
1099
+
1100
+ const receivedHUEObject = cloneDeep(_event)
1101
+
1102
+ // #region "CALCULATE COLOR AND COLOR TEMPERATURE OF THE LIGHTS BELONGING TO THE GROUPED_LIGHT"
1103
+ // Check wether the incoming _event belongs to a light belonging to the group_light
1104
+ if (node.isGrouped_light === true && receivedHUEObject.type === 'light') {
1105
+ // Handling of not by HUE handled Color Temperature and ColorXY
1106
+ let modifiedLight
1107
+ const groupChilds = []
1108
+ let AverageColorsXYBrightnessAndTemperature // Average color xy and color temp if the node is a grouped light.
1109
+ // If the current node is a grouped lights, i must check wether the receivedHUEObject (containing a light) belongs to the node lights collection.
1110
+ // Find all the lights in the collection, having the color_temperature capabilities, belonging to the group.
1111
+ const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
1112
+ if (devices.length === 0) {
1113
+ devices.push(receivedHUEObject)
1114
+ }
1115
+
1116
+ for (let index = 0; index < devices.length; index++) {
1117
+ const element = devices[index]
1118
+ if (receivedHUEObject.id === element.id) {
1119
+ modifiedLight = element
1120
+ // The dimming is not necessary, beacause the HUE API already sends a group_light event with the average brightness //if (receivedHUEObject.dimming !== undefined) modifiedLight.dimming = { brightness: receivedHUEObject.dimming.brightness };
1121
+ if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined) modifiedLight.color = receivedHUEObject.color
1122
+ if (receivedHUEObject.color_temperature !== undefined) modifiedLight.color_temperature = receivedHUEObject.color_temperature
1123
+ groupChilds.push(modifiedLight)
1124
+ } else {
1125
+ // Simply append the light
1126
+ if ((element.color_temperature !== undefined && element.color_temperature.mirek !== undefined) ||
1127
+ (element.color !== undefined && element.color.xy !== undefined)) {
1128
+ groupChilds.push(element)
1129
+ }
1130
+ }
1131
+ }
1132
+
1133
+ // Use the arithmetic average of color xy, brightness and color temperature "averageColorXYandKelvinOfGroupedLights"
1134
+ if (groupChilds !== undefined) AverageColorsXYBrightnessAndTemperature = await node.serverHue.getAverageColorsXYBrightnessAndTemperature(groupChilds)
1135
+
1136
+ // Set the new values based on average calculated above
1137
+ // CHECK FIRST THE COLOR_TEMPERATURE, because it can be undefined, because the current selected color
1138
+ // is out of the mirek range, so it cannot be represented with the colore temperature.
1139
+ if (receivedHUEObject.color_temperature !== undefined && AverageColorsXYBrightnessAndTemperature.mirek !== undefined && receivedHUEObject.color_temperature.mirel_valid === true) {
1140
+ receivedHUEObject.color_temperature.mirek = AverageColorsXYBrightnessAndTemperature.mirek
1141
+ node.currentHUEDevice.color_temperature = { mirek: AverageColorsXYBrightnessAndTemperature.mirek }
1142
+ node.updateKNXLightKelvinPercentageState(receivedHUEObject.color_temperature.mirek)
1143
+ node.updateKNXLightKelvinState(receivedHUEObject.color_temperature.mirek)
1144
+ } else if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined && AverageColorsXYBrightnessAndTemperature.x !== undefined) {
1145
+ receivedHUEObject.color.xy.x = AverageColorsXYBrightnessAndTemperature.x
1146
+ receivedHUEObject.color.xy.y = AverageColorsXYBrightnessAndTemperature.y
1147
+ node.currentHUEDevice.color = { xy: { x: AverageColorsXYBrightnessAndTemperature.x, y: AverageColorsXYBrightnessAndTemperature.y } }
1148
+ node.updateKNXLightColorState(receivedHUEObject.color)
1149
+ }
1150
+ // The dimming is not necessary, beacause the HUE API already sends a group_light event with the average brightness
1151
+ // if (receivedHUEObject.dimming !== undefined && AverageColorsXYBrightnessAndTemperature.brightness !== undefined) {
1152
+ // receivedHUEObject.dimming = { brightness: AverageColorsXYBrightnessAndTemperature.brightness };
1153
+ // }
1154
+ // --------------------------------------------------------------------------------------------------
1155
+ if (modifiedLight !== undefined && modifiedLight.metadata !== undefined && modifiedLight.metadata.name !== undefined) {
1156
+ receivedHUEObject.lightName = modifiedLight.metadata.name
1157
+ }
1158
+ node.send(receivedHUEObject)
1159
+ return
1160
+ }
1161
+ // #endregion
1162
+
1163
+ if (receivedHUEObject.id === node.hueDevice) {
1164
+ // Output the msg to the flow
1165
+ node.send(receivedHUEObject)
1166
+
1167
+ // // DEBUG testing enable/disable HTML UI Tabs
1168
+ // delete _event.dimming;
1169
+ // delete _event.color;
1170
+ // delete _event.color_temperature;
1171
+ // delete _event.color_temperature_delta;
1172
+
1173
+ if (receivedHUEObject.on !== undefined) {
1174
+ node.updateKNXLightState(receivedHUEObject.on.on)
1175
+ // In case of switch off, set the dim to zero
1176
+ if (receivedHUEObject.on.on === false && (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')) {
1177
+ node.updateKNXBrightnessState(0)
1178
+ if (receivedHUEObject.dimming !== undefined) delete receivedHUEObject.dimming // Remove event.dimming, because has beem handled by this function and i don't want the function below to take care of it.
1179
+ } else if (receivedHUEObject.on.on === true && node.currentHUEDevice.on.on === false) {
1180
+ // Turn on: update the dimming KNX Status value as well, but only if we have a reliable value.
1181
+ // For grouped_light, the bridge exposes an aggregated brightness value via /resource/grouped_light/{id}.
1182
+ let brightVal
1183
+ if (receivedHUEObject.dimming !== undefined && receivedHUEObject.dimming.brightness !== undefined) {
1184
+ brightVal = receivedHUEObject.dimming.brightness
1185
+ } else if (node.isGrouped_light === true && typeof node.serverHue?.getHueResourceSnapshot === 'function' && config.GALightBrightnessState) {
1186
+ try {
1187
+ const snapshot = await node.serverHue.getHueResourceSnapshot(node.hueDevice, { forceRefresh: true })
1188
+ if (snapshot?.dimming?.brightness !== undefined) brightVal = snapshot.dimming.brightness
1189
+ } catch (error) { /* empty */ }
1190
+ } else if (node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness !== undefined) {
1191
+ brightVal = node.currentHUEDevice.dimming.brightness
1192
+ }
1193
+ if (brightVal !== undefined) node.updateKNXBrightnessState(brightVal)
1194
+ }
1195
+ node.currentHUEDevice.on.on = receivedHUEObject.on.on
1196
+ }
1197
+
1198
+ if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined) { // fixed https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/287
1199
+ node.updateKNXLightColorState(receivedHUEObject.color)
1200
+ node.currentHUEDevice.color = receivedHUEObject.color
1201
+ }
1202
+
1203
+ if (receivedHUEObject.dimming !== undefined && receivedHUEObject.dimming.brightness !== undefined) {
1204
+ const zeroBrightnessWhenOff = (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')
1205
+ // Once upon n a time, the light transmit the brightness value of 0.39.
1206
+ // To avoid wrongly turn light state on, exit
1207
+ if (receivedHUEObject.dimming.brightness < 1) receivedHUEObject.dimming.brightness = 0
1208
+ // If the light is OFF and the user wants KNX brightness status to stay 0 while OFF,
1209
+ // don't propagate Hue's cached brightness (usually the last/boot brightness) back to KNX.
1210
+ if (zeroBrightnessWhenOff === true && node.currentHUEDevice?.on?.on === false && (receivedHUEObject.on === undefined || receivedHUEObject.on.on === false)) {
1211
+ if (node.currentHUEDevice.dimming !== undefined) node.currentHUEDevice.dimming.brightness = receivedHUEObject.dimming.brightness
1212
+ } else if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && receivedHUEObject.dimming.brightness === 0) {
1213
+ // Do nothing, because the light is off and the dimming also is 0
1214
+ } else {
1215
+ if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && (receivedHUEObject.on === undefined || (receivedHUEObject.on !== undefined && receivedHUEObject.on.on === true))) node.updateKNXLightState(receivedHUEObject.dimming.brightness > 0)
1216
+ node.updateKNXBrightnessState(receivedHUEObject.dimming.brightness)
1217
+ // If the brightness reaches zero, the hue lamp "on" property must be set to zero as well
1218
+ if (receivedHUEObject.dimming.brightness === 0 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true) {
1219
+ node.writeHueState({ on: { on: false } })
1220
+ node.currentHUEDevice.on.on = false
1221
+ }
1222
+ node.currentHUEDevice.dimming.brightness = receivedHUEObject.dimming.brightness
1223
+ }
1224
+ }
1225
+
1226
+ if (receivedHUEObject.color_temperature !== undefined && receivedHUEObject.color_temperature.mirek !== undefined) {
1227
+ node.updateKNXLightKelvinPercentageState(receivedHUEObject.color_temperature.mirek)
1228
+ node.updateKNXLightKelvinState(receivedHUEObject.color_temperature.mirek)
1229
+ node.currentHUEDevice.color_temperature.mirek = receivedHUEObject.color_temperature.mirek
1230
+ }
1231
+ }
1232
+ } catch (error) {
1233
+ node.status({
1234
+ fill: 'red',
1235
+ shape: 'dot',
1236
+ text: `HUE->KNX error ${node.id} ${error.message}. Seee Log`
1237
+ })
1238
+ RED.log.error(`knxUltimateHueLight: node.handleSendHUE = (_event): ${error.stack}`)
1239
+ }
1240
+ // })();
1241
+ }
1242
+
1243
+ // Leave the name after "function", to avoid <anonymous function> in the stack trace, in caso of errors.
1244
+ node.updateKNXBrightnessState = function updateKNXBrightnessState (_value, _outputtype = 'write') {
1245
+ if (config.GALightBrightnessState !== undefined && config.GALightBrightnessState !== '') {
1246
+ const knxMsgPayload = {}
1247
+ knxMsgPayload.topic = config.GALightBrightnessState
1248
+ knxMsgPayload.dpt = config.dptLightBrightnessState
1249
+ knxMsgPayload.payload = _value
1250
+ // Send to KNX bus
1251
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1252
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1253
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1254
+ grpaddr: knxMsgPayload.topic,
1255
+ payload: knxMsgPayload.payload,
1256
+ dpt: knxMsgPayload.dpt,
1257
+ outputtype: _outputtype,
1258
+ nodecallerid: node.id
1259
+ })
1260
+ }
1261
+ }
1262
+ node.setNodeStatusHue({
1263
+ fill: 'blue',
1264
+ shape: 'ring',
1265
+ text: 'HUE->KNX Bright',
1266
+ payload: knxMsgPayload.payload
1267
+ })
1268
+ }
1269
+ }
1270
+
1271
+ node.updateKNXLightState = function updateKNXLightState (_value, _outputtype = 'write') {
1272
+ try {
1273
+ const knxMsgPayload = {}
1274
+ knxMsgPayload.topic = config.GALightState
1275
+ knxMsgPayload.dpt = config.dptLightState
1276
+ knxMsgPayload.payload = _value
1277
+ if (config.GALightState !== undefined && config.GALightState !== '') {
1278
+ // Check not to have already sent the value
1279
+ // Send to KNX bus
1280
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1281
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1282
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1283
+ grpaddr: knxMsgPayload.topic,
1284
+ payload: knxMsgPayload.payload,
1285
+ dpt: knxMsgPayload.dpt,
1286
+ outputtype: _outputtype,
1287
+ nodecallerid: node.id
1288
+ })
1289
+ }
1290
+ }
1291
+ node.setNodeStatusHue({
1292
+ fill: 'blue',
1293
+ shape: 'ring',
1294
+ text: 'HUE->KNX On/Off',
1295
+ payload: knxMsgPayload.payload
1296
+ })
1297
+ }
1298
+ } catch (error) {
1299
+ /* empty */
1300
+ }
1301
+ }
1302
+
1303
+ node.updateKNXLightKelvinPercentageState = function updateKNXLightKelvinPercentageState (_value, _outputtype = 'write') {
1304
+ if (config.GALightKelvinPercentageState !== undefined && config.GALightKelvinPercentageState !== '') {
1305
+ const knxMsgPayload = {}
1306
+ knxMsgPayload.topic = config.GALightKelvinPercentageState
1307
+ knxMsgPayload.dpt = config.dptLightKelvinPercentageState
1308
+ if (config.dptLightKelvinPercentageState === '5.001') {
1309
+ const retPercent = hueColorConverter.ColorConverter.scale(_value, [153, 500], [0, 100])
1310
+ knxMsgPayload.payload = 100 - retPercent
1311
+ }
1312
+ // Send to KNX bus
1313
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1314
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1315
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1316
+ grpaddr: knxMsgPayload.topic,
1317
+ payload: knxMsgPayload.payload,
1318
+ dpt: knxMsgPayload.dpt,
1319
+ outputtype: _outputtype,
1320
+ nodecallerid: node.id
1321
+ })
1322
+ }
1323
+ }
1324
+ node.setNodeStatusHue({
1325
+ fill: 'blue',
1326
+ shape: 'ring',
1327
+ text: 'HUE->KNX Tunable White',
1328
+ payload: knxMsgPayload.payload
1329
+ })
1330
+ }
1331
+ }
1332
+
1333
+ // /**
1334
+ // * Update the internal HSV variable status and send the valuge in % to the KNX BUS
1335
+ // * @param {number} _value 0-100%
1336
+ // * @param {string} _outputtype "write" is the default KNX command
1337
+ // * * @returns {}
1338
+ // */
1339
+ // node.updateKNXLightHSV_H_State = function updateKNXLightHSV_H_State(_value, _outputtype = "write") {
1340
+ // if (config.GALightHSV_H_State !== undefined && config.GALightHSV_H_State !== "") {
1341
+ // const knxMsgPayload = {};
1342
+ // knxMsgPayload.topic = config.GALightHSV_H_State;
1343
+ // knxMsgPayload.dpt = config.dptLightHSV_H_State;
1344
+ // if (config.dptLightHSV_H_State === "5.001") {
1345
+ // knxMsgPayload.payload = 100 - _value;
1346
+ // }
1347
+ // // Send to KNX bus
1348
+ // if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined) {
1349
+ // if (node.serverKNX !== null && node.serverKNX !== undefined) {
1350
+ // node.serverKNX.sendKNXTelegramToKNXEngine({
1351
+ // grpaddr: knxMsgPayload.topic,
1352
+ // payload: knxMsgPayload.payload,
1353
+ // dpt: knxMsgPayload.dpt,
1354
+ // outputtype: _outputtype || "write",
1355
+ // nodecallerid: node.id,
1356
+ // });
1357
+ // }
1358
+ // }
1359
+ // node.setNodeStatusHue({
1360
+ // fill: "blue",
1361
+ // shape: "ring",
1362
+ // text: "HUE->KNX HSV (H)",
1363
+ // payload: knxMsgPayload.payload,
1364
+ // });
1365
+ // }
1366
+ // };
1367
+
1368
+ // /**
1369
+ // * Update the internal HSV variable status and send the valuge in % to the KNX BUS
1370
+ // * @param {number} _value 0-100%
1371
+ // * @param {string} _outputtype "write" is the default KNX command
1372
+ // * @returns {}
1373
+ // */
1374
+ // node.updateKNXLightHSV_S_State = function updateKNXLightHSV_S_State(_value, _outputtype = "write") {
1375
+ // if (config.GALightHSV_S_State !== undefined && config.GALightHSV_S_State !== "") {
1376
+ // const knxMsgPayload = {};
1377
+ // knxMsgPayload.topic = config.GALightHSV_S_State;
1378
+ // knxMsgPayload.dpt = config.dptLightHSV_S_State;
1379
+ // if (config.dptLightHSV_S_State === "5.001") {
1380
+ // knxMsgPayload.payload = _value;
1381
+ // }
1382
+ // // Send to KNX bus
1383
+ // if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined) {
1384
+ // if (node.serverKNX !== null && node.serverKNX !== undefined) {
1385
+ // node.serverKNX.sendKNXTelegramToKNXEngine({
1386
+ // grpaddr: knxMsgPayload.topic,
1387
+ // payload: knxMsgPayload.payload,
1388
+ // dpt: knxMsgPayload.dpt,
1389
+ // outputtype: _outputtype || "write",
1390
+ // nodecallerid: node.id,
1391
+ // });
1392
+ // }
1393
+ // }
1394
+ // node.setNodeStatusHue({
1395
+ // fill: "blue",
1396
+ // shape: "ring",
1397
+ // text: "HUE->KNX HSV (H)",
1398
+ // payload: knxMsgPayload.payload,
1399
+ // });
1400
+ // }
1401
+ // };
1402
+
1403
+ /**
1404
+ * Update the KNC colors and HSV states group addresses
1405
+ * @param {object} _value {xy:{x,y}} in 0-1 scale
1406
+ * @param {string} _outputtype "write" is the default KNX command
1407
+ * @returns {}
1408
+ */
1409
+ node.updateKNXLightColorState = function updateKNXLightColorState (_value, _outputtype = 'write') {
1410
+ if (config.GALightColorState !== undefined && config.GALightColorState !== '') {
1411
+ if (_value.xy === undefined || _value.xy.x === undefined) return
1412
+ const knxMsgPayload = {}
1413
+ knxMsgPayload.topic = config.GALightColorState
1414
+ knxMsgPayload.dpt = config.dptLightColorState
1415
+ try {
1416
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(
1417
+ _value.xy.x,
1418
+ _value.xy.y,
1419
+ node.currentHUEDevice !== undefined && node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness === undefined ? node.currentHUEDevice.dimming.brightness : 100
1420
+ )
1421
+ knxMsgPayload.payload = { red: knxMsgPayload.payload.r, green: knxMsgPayload.payload.g, blue: knxMsgPayload.payload.b }
1422
+ // Send to KNX bus
1423
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1424
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1425
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1426
+ grpaddr: knxMsgPayload.topic,
1427
+ payload: knxMsgPayload.payload,
1428
+ dpt: knxMsgPayload.dpt,
1429
+ outputtype: _outputtype,
1430
+ nodecallerid: node.id
1431
+ })
1432
+ }
1433
+ }
1434
+ node.setNodeStatusHue({
1435
+ fill: 'blue',
1436
+ shape: 'ring',
1437
+ text: 'HUE->KNX Color',
1438
+ payload: knxMsgPayload.payload
1439
+ })
1440
+ } catch (error) { }
1441
+ }
1442
+ if (config.GALightHSV_S_State !== undefined && config.GALightHSV_S_State !== '') {
1443
+ const knxMsgPayload = {}
1444
+ knxMsgPayload.topic = config.GALightHSV_S_State
1445
+ knxMsgPayload.dpt = config.dptLightHSV_S_State
1446
+ if (config.dptLightHSV_S_State === '5.001') {
1447
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBrightnessToHsv(_value.xy.x, _value.xy.y, node.currentHUEDevice !== undefined && node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness === undefined ? node.currentHUEDevice.dimming.brightness : 100, false)
1448
+ knxMsgPayload.payload = knxMsgPayload.payload.s
1449
+ }
1450
+ // Send to KNX bus
1451
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1452
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1453
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1454
+ grpaddr: knxMsgPayload.topic,
1455
+ payload: knxMsgPayload.payload,
1456
+ dpt: knxMsgPayload.dpt,
1457
+ outputtype: _outputtype || 'write',
1458
+ nodecallerid: node.id
1459
+ })
1460
+ }
1461
+ }
1462
+ node.setNodeStatusHue({
1463
+ fill: 'blue',
1464
+ shape: 'ring',
1465
+ text: 'HUE->KNX HSV (S)',
1466
+ payload: knxMsgPayload.payload
1467
+ })
1468
+ }
1469
+ if (config.GALightHSV_H_State !== undefined && config.GALightHSV_H_State !== '') {
1470
+ const knxMsgPayload = {}
1471
+ knxMsgPayload.topic = config.GALightHSV_H_State
1472
+ knxMsgPayload.dpt = config.dptLightHSV_H_State
1473
+ if (config.dptLightHSV_H_State === '5.001') {
1474
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBrightnessToHsv(_value.xy.x, _value.xy.y, node.currentHUEDevice !== undefined && node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness === undefined ? node.currentHUEDevice.dimming.brightness : 100, false)
1475
+ knxMsgPayload.payload = 100 - knxMsgPayload.payload.h
1476
+ }
1477
+ // Send to KNX bus
1478
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1479
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1480
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1481
+ grpaddr: knxMsgPayload.topic,
1482
+ payload: knxMsgPayload.payload,
1483
+ dpt: knxMsgPayload.dpt,
1484
+ outputtype: _outputtype || 'write',
1485
+ nodecallerid: node.id
1486
+ })
1487
+ }
1488
+ }
1489
+ node.setNodeStatusHue({
1490
+ fill: 'blue',
1491
+ shape: 'ring',
1492
+ text: 'HUE->KNX HSV (H)',
1493
+ payload: knxMsgPayload.payload
1494
+ })
1495
+ }
1496
+ }
1497
+
1498
+ node.updateKNXLightKelvinState = function updateKNXLightKelvinState (_value, _outputtype = 'write') {
1499
+ if (config.GALightKelvinState !== undefined && config.GALightKelvinState !== '') {
1500
+ const knxMsgPayload = {}
1501
+ const kelvinValue = 0
1502
+ knxMsgPayload.topic = config.GALightKelvinState
1503
+ knxMsgPayload.dpt = config.dptLightKelvinState
1504
+ if (config.dptLightKelvinState === '7.600') {
1505
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value)
1506
+ // knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(kelvinValue, [2000, 6535], [0, 65535]);
1507
+ } else if (config.dptLightKelvinState === '9.002') {
1508
+ knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value)
1509
+ }
1510
+ // Send to KNX bus
1511
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
1512
+ if (node.serverKNX !== null && node.serverKNX !== undefined) {
1513
+ node.serverKNX.sendKNXTelegramToKNXEngine({
1514
+ grpaddr: knxMsgPayload.topic,
1515
+ payload: knxMsgPayload.payload,
1516
+ dpt: knxMsgPayload.dpt,
1517
+ outputtype: _outputtype,
1518
+ nodecallerid: node.id
1519
+ })
1520
+ }
1521
+
1522
+ node.setNodeStatusHue({
1523
+ fill: 'blue',
1524
+ shape: 'ring',
1525
+ text: 'HUE->KNX Kelvin',
1526
+ payload: knxMsgPayload.payload
1527
+ })
1528
+ }
1529
+ }
1530
+ }
1531
+
1532
+ // On each deploy, unsubscribe+resubscribe
1533
+ if (node.serverKNX) {
1534
+ node.serverKNX.removeClient(node)
1535
+ node.serverKNX.addClient(node)
1536
+ }
1537
+ if (node.serverHue) {
1538
+ try {
1539
+ node.serverHue.removeClient(node)
1540
+ node.serverHue.addClient(node)
1541
+ } catch (error) {
1542
+ RED.log.error('knxUltimateHueLight: if (node.serverKNX): ' + error.message)
1543
+ }
1544
+ }
1545
+
1546
+ node.on('input', (msg, send, done) => {
1547
+ try {
1548
+ const state = RED.util.cloneMessage(msg)
1549
+ node.writeHueState(state)
1550
+ node.setNodeStatusHue({
1551
+ fill: 'green',
1552
+ shape: 'dot',
1553
+ text: '->HUE',
1554
+ payload: 'Flow msg.'
1555
+ })
1556
+ } catch (error) {
1557
+ node.setNodeStatusHue({
1558
+ fill: 'red',
1559
+ shape: 'dot',
1560
+ text: '->HUE',
1561
+ payload: error.message
1562
+ })
1563
+ }
1564
+ // Once finished, call 'done'.
1565
+ // This call is wrapped in a check that 'done' exists
1566
+ // so the node will work in earlier versions of Node-RED (<1.0)
1567
+ if (done) {
1568
+ done()
1569
+ }
1570
+ })
1571
+
1572
+ node.on('close', (done) => {
1573
+ if (node.serverKNX) {
1574
+ node.serverKNX.removeClient(node)
1575
+ }
1576
+ if (node.serverHue) {
1577
+ node.serverHue.removeClient(node)
1578
+ }
1579
+ done()
1580
+ })
1581
+ }
1582
+ RED.nodes.registerType('knxUltimateHueLight', knxUltimateHueLight)
1583
+ }