node-red-contrib-knx-ultimate 6.2.2 → 6.2.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/nodes/knxUltimateAI.js +7 -1
  3. package/nodes/knxUltimateHueAreaMotion.html +15 -4
  4. package/nodes/knxUltimateHueBattery.html +15 -4
  5. package/nodes/knxUltimateHueButton.html +15 -4
  6. package/nodes/knxUltimateHueCameraMotion.html +15 -4
  7. package/nodes/knxUltimateHueContactSensor.html +15 -4
  8. package/nodes/knxUltimateHueController.html +1386 -0
  9. package/nodes/knxUltimateHueController.js +28 -0
  10. package/nodes/knxUltimateHueHumiditySensor.html +15 -4
  11. package/nodes/knxUltimateHueLight.html +15 -4
  12. package/nodes/knxUltimateHueLightSensor.html +15 -4
  13. package/nodes/knxUltimateHueMotion.html +15 -4
  14. package/nodes/knxUltimateHuePlug.html +15 -4
  15. package/nodes/knxUltimateHueScene.html +18 -7
  16. package/nodes/knxUltimateHueTapDial.html +15 -4
  17. package/nodes/knxUltimateHueTemperatureSensor.html +15 -4
  18. package/nodes/knxUltimateHueZigbeeConnectivity.html +15 -4
  19. package/nodes/knxUltimateHuedevice_software_update.html +15 -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 +669 -0
  26. package/nodes/locales/de/knxUltimateHueController.json +48 -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 +672 -0
  43. package/nodes/locales/en/knxUltimateHueController.json +48 -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 +672 -0
  60. package/nodes/locales/es/knxUltimateHueController.json +48 -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 +672 -0
  77. package/nodes/locales/fr/knxUltimateHueController.json +48 -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 +672 -0
  94. package/nodes/locales/it/knxUltimateHueController.json +48 -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 +674 -0
  111. package/nodes/locales/zh-CN/knxUltimateHueController.json +48 -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 +199 -0
  140. package/resources/hueControllerMigrationDialog.js +189 -0
  141. package/resources/hueControllerProfiles.js +9290 -0
@@ -0,0 +1,352 @@
1
+ // Canonical private runtime profile for HUE Controller: scene.
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 max-len */
5
+ const dptlib = require('knxultimate').dptlib// require('knxultimate').dptlib;
6
+ const loggerClass = require('../../sysLogger')
7
+ module.exports = function (RED) {
8
+ function knxUltimateHueScene (config) {
9
+ RED.nodes.createNode(this, config)
10
+ const node = this
11
+ node.serverKNX = RED.nodes.getNode(config.server) || undefined
12
+ node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
13
+ node.topic = node.name
14
+ node.name = config.name === undefined ? 'Hue' : config.name
15
+ node.dpt = ''
16
+ node.notifyreadrequest = false
17
+ node.notifyreadrequestalsorespondtobus = 'false'
18
+ node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
19
+ node.notifyresponse = false
20
+ node.notifywrite = true
21
+ node.initialread = true
22
+ node.listenallga = true // Don't remove
23
+ node.outputtype = 'write'
24
+ node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
25
+ node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
26
+ node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
27
+ node.passthrough = 'no'
28
+ node.formatmultiplyvalue = 1
29
+ node.formatnegativevalue = 'leave'
30
+ node.formatdecimalsvalue = 2
31
+ node.hueDevice = config.hueDevice
32
+ node.initializingAtStart = false
33
+ try {
34
+ const baseLogLevel = (node.serverKNX && node.serverKNX.loglevel) ? node.serverKNX.loglevel : 'error'
35
+ node.sysLogger = new loggerClass({ loglevel: baseLogLevel, setPrefix: node.type + ' <' + (node.name || node.id || '') + '>' })
36
+ } catch (error) { console.log(error.stack) }
37
+ // Multi scene
38
+ config.GAsceneMulti = config.GAsceneMulti === undefined ? '' : config.GAsceneMulti
39
+ config.namesceneMulti = config.namesceneMulti === undefined ? '' : config.namesceneMulti
40
+ config.dptsceneMulti = config.dptsceneMulti === undefined ? '' : config.dptsceneMulti
41
+ config.rules = config.rules === undefined ? [] : config.rules
42
+ config.selectedModeTabNumber = config.selectedModeTabNumber === undefined ? 0 : Number(config.selectedModeTabNumber) // Transform as number
43
+
44
+ const pushStatus = (status) => {
45
+ if (!status) return
46
+ const provider = node.serverKNX
47
+ if (provider && typeof provider.applyStatusUpdate === 'function') {
48
+ provider.applyStatusUpdate(node, status)
49
+ } else {
50
+ node.status(status)
51
+ }
52
+ }
53
+
54
+ const updateStatus = (status) => {
55
+ if (!status) return
56
+ pushStatus(status)
57
+ }
58
+
59
+ const formatTs = (date) => {
60
+ const d = date instanceof Date ? date : new Date(date)
61
+ const provider = node.serverKNX
62
+ if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
63
+ return `${d.getDate()}, ${d.toLocaleTimeString()}`
64
+ }
65
+
66
+ const safeSendToKNX = (telegram, context = 'write') => {
67
+ try {
68
+ if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
69
+ const now = new Date()
70
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
71
+ return
72
+ }
73
+ node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
74
+ } catch (error) {
75
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
76
+ }
77
+ }
78
+
79
+ // Used to call the status update from the config node.
80
+ node.setNodeStatus = ({
81
+ fill, shape, text, payload
82
+ }) => {
83
+ try {
84
+ if (payload === undefined) payload = ''
85
+ const dDate = new Date()
86
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
87
+ node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
88
+ updateStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
89
+ } catch (error) { }
90
+ }
91
+ // Used to call the status update from the HUE config node.
92
+ node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
93
+ try {
94
+ if (payload === undefined) payload = ''
95
+ const dDate = new Date()
96
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
97
+ node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
98
+ updateStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
99
+ } catch (error) { }
100
+ }
101
+
102
+ let hueSceneReady = false
103
+ let pendingHueSceneSnapshot = null
104
+ const needsSceneReady = () => Number(config.selectedModeTabNumber) === 0 && config.hueDevice !== undefined && config.hueDevice !== ''
105
+
106
+ const ensureHueSceneReady = async ({ forceRefresh = false } = {}) => {
107
+ if (!needsSceneReady()) {
108
+ hueSceneReady = true
109
+ return true
110
+ }
111
+ if (!node.serverHue || typeof node.serverHue.getHueResourceSnapshot !== 'function') {
112
+ hueSceneReady = false
113
+ return false
114
+ }
115
+ if (pendingHueSceneSnapshot) {
116
+ try {
117
+ return await pendingHueSceneSnapshot
118
+ } catch (error) {
119
+ return false
120
+ }
121
+ }
122
+ pendingHueSceneSnapshot = (async () => {
123
+ try {
124
+ const resource = await node.serverHue.getHueResourceSnapshot(config.hueDevice, { forceRefresh })
125
+ if (!resource) {
126
+ hueSceneReady = false
127
+ node.setNodeStatusHue({
128
+ fill: 'red',
129
+ shape: 'ring',
130
+ text: 'Hue scene not found',
131
+ payload: ''
132
+ })
133
+ return false
134
+ }
135
+ hueSceneReady = true
136
+ try {
137
+ node.handleSendHUE(resource)
138
+ } catch (error) {
139
+ RED.log.debug(`knxUltimateHueScene: ensureHueSceneReady handleSendHUE error ${error.message}`)
140
+ }
141
+ return true
142
+ } catch (error) {
143
+ hueSceneReady = false
144
+ RED.log.warn(`knxUltimateHueScene: ensureHueSceneReady error ${error.message}`)
145
+ node.setNodeStatusHue({
146
+ fill: 'red',
147
+ shape: 'ring',
148
+ text: `Sync failed ${error.message}`,
149
+ payload: ''
150
+ })
151
+ return false
152
+ } finally {
153
+ pendingHueSceneSnapshot = null
154
+ }
155
+ })()
156
+ return pendingHueSceneSnapshot
157
+ }
158
+
159
+ const processKnxSceneCommand = (msg) => {
160
+ let state = {}
161
+ if (Number(config.selectedModeTabNumber) === 0) {
162
+ try {
163
+ switch (msg.knx.destination) {
164
+ case config.GAscene: {
165
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptscene))
166
+ if (config.dptscene.startsWith('1.')) {
167
+ if (msg.payload === true) {
168
+ state = { recall: { action: config.hueSceneRecallType } }
169
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene')
170
+ } else {
171
+ (async () => {
172
+ try {
173
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'stopScene')
174
+ } catch (error) {
175
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`KNXUltimateHUEConfig: stopScene: ${error.message}`)
176
+ }
177
+ })()
178
+ }
179
+ } else if (Number(config.valscene) === msg.payload.scenenumber && msg.payload.save_recall === 0) {
180
+ state = { recall: { action: config.hueSceneRecallType } }
181
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene')
182
+ }
183
+ node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload })
184
+ break
185
+ }
186
+ default:
187
+ break
188
+ }
189
+ } catch (error) {
190
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX->HUE single: ${error.message} (${formatTs(new Date())})` })
191
+ }
192
+ } else if (Number(config.selectedModeTabNumber) === 1) {
193
+ try {
194
+ if (config.GAsceneMulti !== undefined && config.dptsceneMulti !== undefined && config.rules.length !== 0 && config.GAsceneMulti === msg.knx.destination) {
195
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptsceneMulti))
196
+ config.rules.forEach((row) => {
197
+ if (row.rowRuleKNXSceneNumber !== undefined &&
198
+ row.rowRuleHUESceneID !== undefined &&
199
+ row.rowRuleRecallAs !== undefined &&
200
+ Number(row.rowRuleKNXSceneNumber) === Number(msg.payload.scenenumber) &&
201
+ Number(msg.payload.save_recall) === 0 &&
202
+ node.serverHue.hueManager !== undefined) {
203
+ state = { recall: { action: row.rowRuleRecallAs } }
204
+ node.serverHue.hueManager.writeHueQueueAdd(row.rowRuleHUESceneID, state, 'setScene')
205
+ node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload })
206
+ }
207
+ })
208
+ }
209
+ } catch (error) {
210
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX->HUE multi: ${error.message} (${formatTs(new Date())})` })
211
+ }
212
+ }
213
+ }
214
+
215
+ // This function is called by the knx-hue config node
216
+ node.handleSend = (msg) => {
217
+ if (needsSceneReady() && !hueSceneReady) {
218
+ node.setNodeStatusHue({
219
+ fill: 'yellow',
220
+ shape: 'ring',
221
+ text: 'Syncing with HUE bridge',
222
+ payload: ''
223
+ })
224
+ const clonedMsg = RED.util.cloneMessage(msg)
225
+ ensureHueSceneReady({ forceRefresh: true }).then((ready) => {
226
+ if (ready) processKnxSceneCommand(clonedMsg)
227
+ }).catch(() => { /* handled in ensure */ })
228
+ return
229
+ }
230
+ processKnxSceneCommand(msg)
231
+ }
232
+
233
+ node.handleSendHUE = (_event) => {
234
+ try {
235
+ if (Number(config.selectedModeTabNumber) === 0 && config.hueDevice !== undefined && _event.id === config.hueDevice) {
236
+ // Single mode
237
+ const knxMsgPayload = {}
238
+ knxMsgPayload.topic = config.GAsceneStatus
239
+ knxMsgPayload.dpt = config.dptsceneStatus
240
+ if (_event.hasOwnProperty('status') && _event.status.hasOwnProperty('active')) {
241
+ knxMsgPayload.payload = _event.status.active !== 'inactive'
242
+ // Send to KNX bus
243
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
244
+ safeSendToKNX({
245
+ grpaddr: knxMsgPayload.topic,
246
+ payload: knxMsgPayload.payload,
247
+ dpt: knxMsgPayload.dpt,
248
+ outputtype: 'write'
249
+ }, 'write')
250
+ }
251
+ updateStatus({
252
+ fill: 'blue',
253
+ shape: 'dot',
254
+ text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})`
255
+ })
256
+ // Output the msg to the flow
257
+ node.send(_event)
258
+ }
259
+ } else if (Number(config.selectedModeTabNumber) === 1 && config.rules !== undefined) {
260
+ // Multi mode
261
+ config.rules.forEach(row => {
262
+ if (row.rowRuleHUESceneID !== undefined && _event.id === row.rowRuleHUESceneID) {
263
+ // Output the msg to the flow
264
+ node.send(_event)
265
+ }
266
+ })
267
+ }
268
+ } catch (error) {
269
+ updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
270
+ }
271
+ }
272
+
273
+ // On each deploy, unsubscribe+resubscribe
274
+ if (node.serverKNX) {
275
+ node.serverKNX.removeClient(node)
276
+ node.serverKNX.addClient(node)
277
+ }
278
+ if (node.serverHue) {
279
+ node.serverHue.removeClient(node)
280
+ node.serverHue.addClient(node)
281
+ ensureHueSceneReady({ forceRefresh: true })
282
+ }
283
+
284
+ const processFlowSceneCommand = (msg) => {
285
+ try {
286
+ const state = RED.util.cloneMessage(msg)
287
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene')
288
+ const statusValue = state.payload !== undefined ? state.payload : state
289
+ node.setNodeStatusHue({
290
+ fill: 'green',
291
+ shape: 'dot',
292
+ text: '->HUE',
293
+ payload: statusValue
294
+ })
295
+ } catch (error) {
296
+ node.setNodeStatusHue({
297
+ fill: 'red',
298
+ shape: 'dot',
299
+ text: '->HUE',
300
+ payload: error.message
301
+ })
302
+ throw error
303
+ }
304
+ }
305
+
306
+ node.on('input', (msg, send, done) => {
307
+ const finalize = (error) => {
308
+ if (done) done(error)
309
+ }
310
+ if (needsSceneReady() && !hueSceneReady) {
311
+ node.setNodeStatusHue({
312
+ fill: 'yellow',
313
+ shape: 'ring',
314
+ text: 'Syncing with HUE bridge',
315
+ payload: ''
316
+ })
317
+ const clonedMsg = RED.util.cloneMessage(msg)
318
+ ensureHueSceneReady({ forceRefresh: true }).then((ready) => {
319
+ if (ready) {
320
+ try {
321
+ processFlowSceneCommand(clonedMsg)
322
+ finalize()
323
+ } catch (error) {
324
+ finalize(error)
325
+ }
326
+ } else {
327
+ finalize(new Error('Hue scene initialization failed'))
328
+ }
329
+ }).catch((error) => {
330
+ finalize(error)
331
+ })
332
+ return
333
+ }
334
+ try {
335
+ processFlowSceneCommand(msg)
336
+ finalize()
337
+ } catch (error) {
338
+ finalize(error)
339
+ }
340
+ })
341
+ node.on('close', function (done) {
342
+ if (node.serverKNX) {
343
+ node.serverKNX.removeClient(node)
344
+ }
345
+ if (node.serverHue) {
346
+ node.serverHue.removeClient(node)
347
+ }
348
+ done()
349
+ })
350
+ }
351
+ RED.nodes.registerType('knxUltimateHueScene', knxUltimateHueScene)
352
+ }
@@ -0,0 +1,187 @@
1
+ // Canonical private runtime profile for HUE Controller: temperature.
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
+ module.exports = function (RED) {
5
+ function knxUltimateHueTemperatureSensor (config) {
6
+ RED.nodes.createNode(this, config)
7
+ const node = this
8
+ node.serverKNX = RED.nodes.getNode(config.server) || undefined
9
+ node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
10
+ node.topic = node.name
11
+ node.name = config.name === undefined ? 'Hue' : config.name
12
+ node.dpt = ''
13
+ node.notifyreadrequest = true
14
+ node.notifyreadrequestalsorespondtobus = 'false'
15
+ node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
16
+ node.notifyresponse = false
17
+ node.notifywrite = true
18
+ node.initialread = true
19
+ node.listenallga = true // Don't remove
20
+ node.outputtype = 'write'
21
+ node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
22
+ node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
23
+ node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
24
+ node.passthrough = 'no'
25
+ node.formatmultiplyvalue = 1
26
+ node.formatnegativevalue = 'leave'
27
+ node.formatdecimalsvalue = 2
28
+ node.hueDevice = config.hueDevice
29
+ node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
30
+ node.currentDeviceValue = 0
31
+ const pinsSetting = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes' || config.enableNodePINS === true)
32
+ node.enableNodePINS = pinsSetting ? 'yes' : 'no'
33
+ node.outputs = pinsSetting ? 1 : 0
34
+ if (!node.serverKNX && node.outputs === 0) {
35
+ node.enableNodePINS = 'yes'
36
+ node.outputs = 1
37
+ }
38
+
39
+ const pushStatus = (status) => {
40
+ if (!status) return
41
+ const provider = node.serverKNX
42
+ if (provider && typeof provider.applyStatusUpdate === 'function') {
43
+ provider.applyStatusUpdate(node, status)
44
+ } else {
45
+ node.status(status)
46
+ }
47
+ }
48
+
49
+ const updateStatus = (status) => {
50
+ if (!status) return
51
+ pushStatus(status)
52
+ }
53
+
54
+ const formatTs = (date) => {
55
+ const d = date instanceof Date ? date : new Date(date)
56
+ const provider = node.serverKNX
57
+ if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
58
+ return `${d.getDate()}, ${d.toLocaleTimeString()}`
59
+ }
60
+
61
+ // Used to call the status update from the config node.
62
+ node.setNodeStatus = ({
63
+ fill, shape, text, payload
64
+ }) => {
65
+ try {
66
+ if (payload === undefined) payload = ''
67
+ const dDate = new Date()
68
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
69
+ node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
70
+ pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
71
+ } catch (error) { }
72
+ }
73
+ // Used to call the status update from the HUE config node.
74
+ node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
75
+ try {
76
+ if (payload === undefined) payload = ''
77
+ const dDate = new Date()
78
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
79
+ node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
80
+ pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
81
+ } catch (error) { }
82
+ }
83
+
84
+ // This function is called by the knx-ultimate config node, to output a msg.payload.
85
+ node.handleSend = (msg) => {
86
+ // Respond to KNX read telegram, by sending the current value as response telegram.
87
+ if (msg.knx.event === 'GroupValue_Read') {
88
+ switch (msg.knx.destination) {
89
+ case config.GAtemperaturesensor:
90
+ // To the KNX bus wires
91
+ node.sendResponseToKNX(node.currentDeviceValue)
92
+ break
93
+ default:
94
+ break
95
+ }
96
+ }
97
+ }
98
+
99
+ const safeSendToKNX = (telegram, context = 'write') => {
100
+ try {
101
+ if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
102
+ const now = new Date()
103
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
104
+ return
105
+ }
106
+ node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
107
+ } catch (error) {
108
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
109
+ }
110
+ }
111
+
112
+ node.handleSendHUE = (_event) => {
113
+ try {
114
+ if (_event.id === config.hueDevice) {
115
+ const knxMsgPayload = {}
116
+ knxMsgPayload.topic = config.GAtemperaturesensor
117
+ knxMsgPayload.dpt = config.dpttemperaturesensor
118
+
119
+ if (_event.hasOwnProperty('temperature') && _event.temperature.hasOwnProperty('temperature')) {
120
+ knxMsgPayload.payload = _event.temperature.temperature
121
+ // Send to KNX bus
122
+ if (knxMsgPayload.topic) {
123
+ safeSendToKNX({
124
+ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write'
125
+ }, 'write')
126
+ }
127
+ node.currentDeviceValue = knxMsgPayload.payload
128
+
129
+ updateStatus({ fill: 'green', shape: 'dot', text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})` })
130
+
131
+ // Setup the output msg
132
+ knxMsgPayload.name = node.name
133
+ knxMsgPayload.event = 'temperature'
134
+
135
+ // Send payload
136
+ knxMsgPayload.rawEvent = _event
137
+ node.send(knxMsgPayload)
138
+ node.setNodeStatusHue({
139
+ fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload
140
+ })
141
+ }
142
+ }
143
+ } catch (error) {
144
+ updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
145
+ }
146
+ }
147
+
148
+ node.sendResponseToKNX = (_level) => {
149
+ const knxMsgPayload = {}
150
+ knxMsgPayload.topic = config.GAtemperaturesensor
151
+ knxMsgPayload.dpt = config.dpttemperaturesensor
152
+
153
+ knxMsgPayload.payload = _level
154
+ // Send to KNX bus
155
+ if (knxMsgPayload.topic) {
156
+ safeSendToKNX({
157
+ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response'
158
+ }, 'response')
159
+ }
160
+ }
161
+
162
+ // On each deploy, unsubscribe+resubscribe
163
+ if (node.serverKNX) {
164
+ node.serverKNX.removeClient(node)
165
+ node.serverKNX.addClient(node)
166
+ }
167
+ if (node.serverHue) {
168
+ node.serverHue.removeClient(node)
169
+ node.serverHue.addClient(node)
170
+ }
171
+
172
+ node.on('input', (msg) => {
173
+
174
+ })
175
+
176
+ node.on('close', (done) => {
177
+ if (node.serverKNX) {
178
+ node.serverKNX.removeClient(node)
179
+ }
180
+ if (node.serverHue) {
181
+ node.serverHue.removeClient(node)
182
+ }
183
+ done()
184
+ })
185
+ }
186
+ RED.nodes.registerType('knxUltimateHueTemperatureSensor', knxUltimateHueTemperatureSensor)
187
+ }