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,1386 @@
1
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/11f26b4500.js"></script>
2
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
3
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerProfiles.js"></script>
4
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerMigration.js"></script>
5
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/hueControllerMigrationDialog.js"></script>
6
+
7
+ <script type="text/javascript">
8
+ (function () {
9
+ // Editor architecture
10
+ // -------------------
11
+ // HUE Controller is registered once, but hosts fifteen private editors. The
12
+ // resource loaded above owns their JavaScript, HTML and translations; this
13
+ // wrapper only selects a profile and bridges its lifecycle to one Node-RED
14
+ // dialog. In particular, nothing below queries RED.nodes for a legacy type.
15
+ //
16
+ // Keep this browser-facing catalog aligned with the canonical build catalog
17
+ // in scripts/hue-controller-profiles/catalog.js. Tests guard that alignment,
18
+ // including the independent runtime map.
19
+ const PROFILE_TYPES = Object.freeze({
20
+ light: { nodeType: 'knxUltimateHueLight', label: 'HUE Light / grouped light' },
21
+ plug: { nodeType: 'knxUltimateHuePlug', label: 'HUE Plug / outlet' },
22
+ button: { nodeType: 'knxUltimateHueButton', label: 'HUE Button' },
23
+ relative_rotary: { nodeType: 'knxUltimateHueTapDial', label: 'HUE Tap dial' },
24
+ motion: { nodeType: 'knxUltimateHueMotion', label: 'HUE Motion' },
25
+ area_motion: { nodeType: 'knxUltimateHueAreaMotion', label: 'HUE Area motion' },
26
+ camera_motion: { nodeType: 'knxUltimateHueCameraMotion', label: 'HUE Camera motion' },
27
+ contact: { nodeType: 'knxUltimateHueContactSensor', label: 'HUE Contact' },
28
+ light_level: { nodeType: 'knxUltimateHueLightSensor', label: 'HUE Light level' },
29
+ temperature: { nodeType: 'knxUltimateHueTemperatureSensor', label: 'HUE Temperature' },
30
+ humidity: { nodeType: 'knxUltimateHueHumiditySensor', label: 'HUE Humidity' },
31
+ scene: { nodeType: 'knxUltimateHueScene', label: 'HUE Scene' },
32
+ device_power: { nodeType: 'knxUltimateHueBattery', label: 'HUE Battery' },
33
+ zigbee_connectivity: { nodeType: 'knxUltimateHueZigbeeConnectivity', label: 'HUE Zigbee connectivity' },
34
+ device_software_update: { nodeType: 'knxUltimateHuedevice_software_update', label: 'HUE Device software update' }
35
+ });
36
+ // Node-RED 4.x uses `_ADD_` both for the visible "none" option (when
37
+ // gateways exist) and for "Add new..." (when none exist).
38
+ const KNX_EMPTY_SERVER_VALUES = new Set(['', 'none', '_add_', '__none__']);
39
+ const TUTORIALS_URL = 'https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E';
40
+ const profileBundle = window.KNXUltimateHueControllerProfiles;
41
+
42
+ const getProfileDefinition = (controllerType) => {
43
+ // The bundle captures a private definition through a RED facade. It does
44
+ // not register that definition and its presentation metadata is inert.
45
+ if (!profileBundle || typeof profileBundle.getDefinition !== 'function') return undefined;
46
+ return profileBundle.getDefinition(controllerType, RED);
47
+ };
48
+
49
+ const focusProfileHelp = (controllerType) => {
50
+ // Node-RED renders localized help asynchronously after opening the dialog.
51
+ // A few bounded retries focus the matching function without leaving a
52
+ // timer running for the lifetime of the editor.
53
+ const anchorId = `hue-controller-help-${controllerType}`;
54
+ let attempts = 0;
55
+ const focus = () => {
56
+ const marker = document.getElementById(anchorId);
57
+ if (marker && typeof marker.scrollIntoView === 'function') {
58
+ marker.scrollIntoView({ block: 'start' });
59
+ return;
60
+ }
61
+ attempts += 1;
62
+ if (attempts < 4) setTimeout(focus, 100);
63
+ };
64
+ setTimeout(focus, 0);
65
+ };
66
+
67
+ const buildDefaults = () => {
68
+ // Node-RED persists only keys declared at registration time. Build the
69
+ // union once so changing function inside the dialog never drops fields
70
+ // belonging to the newly selected profile.
71
+ const defaults = {
72
+ // Keep config-node references explicit. Node-RED must see their `type`
73
+ // while this node is registered or it renders plain text inputs instead
74
+ // of managed config-node selectors.
75
+ server: { value: '', type: 'knxUltimate-config', required: false },
76
+ serverHue: { value: '', type: 'hue-config', required: true },
77
+ name: { value: '' },
78
+ hueControllerType: { value: 'light', required: true },
79
+ inputs: { value: 0 },
80
+ outputs: { value: 0 }
81
+ };
82
+ Object.keys(PROFILE_TYPES).forEach((controllerType) => {
83
+ const definition = getProfileDefinition(controllerType);
84
+ Object.entries(definition?.defaults || {}).forEach(([key, property]) => {
85
+ // HUE Controller starts as a light, so the first profile wins for
86
+ // shared keys. Clone the descriptor because Node-RED may decorate it.
87
+ if (!Object.prototype.hasOwnProperty.call(defaults, key)) defaults[key] = { ...property };
88
+ });
89
+ });
90
+ // Dynamic pins must always be persisted by this node, regardless of whether
91
+ // the selected legacy editor originally used fixed or configurable pins.
92
+ defaults.inputs = { value: 0 };
93
+ defaults.outputs = { value: 0 };
94
+ return defaults;
95
+ };
96
+
97
+ const setFieldValue = ($field, value) => {
98
+ // Property keys come from the private definition, never from CSS classes:
99
+ // jQuery UI mutates classes when it upgrades fields into widgets.
100
+ if (!$field.length) return;
101
+ if ($field.is(':checkbox')) {
102
+ $field.prop('checked', value === true || value === 'true' || value === 'yes');
103
+ } else if ($field.is(':radio')) {
104
+ $field.filter(`[value="${String(value).replace(/"/g, '\\"')}"]`).prop('checked', true);
105
+ } else if (value !== undefined && value !== null) {
106
+ $field.val(typeof value === 'object' ? JSON.stringify(value) : value);
107
+ }
108
+ };
109
+
110
+ const readFieldValue = ($field, currentValue) => {
111
+ // Mirror setFieldValue for the untouched-save round trip. Missing elements
112
+ // retain their current value instead of silently becoming undefined.
113
+ if (!$field.length) return currentValue;
114
+ if ($field.is(':checkbox')) return $field.prop('checked');
115
+ if ($field.is(':radio')) return $field.filter(':checked').val();
116
+ return $field.val();
117
+ };
118
+
119
+ const translateProfileEditor = ($container, controllerType, nodeType) => {
120
+ // Injected fragments miss Node-RED's initial i18n pass and belong to the
121
+ // original profile namespace. Resolve them against the private dictionaries;
122
+ // the bundle applies the active-locale, English and RED._ fallbacks.
123
+ $container.find('[data-i18n]').each(function () {
124
+ const $element = $(this);
125
+ const translationSpec = String($element.attr('data-i18n') || '').trim();
126
+ if (!translationSpec) return;
127
+
128
+ const attributeMatch = translationSpec.match(/^\[([^\]]+)](.+)$/);
129
+ const attributeName = attributeMatch ? attributeMatch[1] : undefined;
130
+ const translationKey = attributeMatch ? attributeMatch[2] : translationSpec;
131
+ const qualifiedKey = translationKey.includes(':')
132
+ ? translationKey
133
+ : `node-red-contrib-knx-ultimate/${nodeType}:${translationKey}`;
134
+ let translated;
135
+ try {
136
+ translated = profileBundle && typeof profileBundle.translate === 'function'
137
+ ? profileBundle.translate(controllerType, qualifiedKey, RED)
138
+ : RED._(qualifiedKey);
139
+ } catch (error) { /* empty */ }
140
+ if (!translated || translated === qualifiedKey || translated === translationKey) return;
141
+
142
+ if (attributeName) $element.attr(attributeName, translated);
143
+ else $element.text(translated);
144
+ // The fragment belongs to the selected legacy node namespace. Removing
145
+ // the marker prevents the controller namespace from blanking it later.
146
+ $element.removeAttr('data-i18n');
147
+ });
148
+ };
149
+
150
+ const ensureControllerEditorStyle = () => {
151
+ // Styles are scoped to the mounted fragment so no legacy editor or other
152
+ // Node-RED dialog is affected. Insert them once per browser page.
153
+ if ($('#knxUltimateHueControllerEditorStyle').length) return;
154
+ $('head').append(`
155
+ <style id="knxUltimateHueControllerEditorStyle">
156
+ /* Migration is an occasional but important action, so give it a
157
+ strong warning-orange treatment that remains legible in both the
158
+ light and dark Node-RED editor themes. */
159
+ #hue-controller-migrate-legacy-flow.red-ui-button {
160
+ background: #d97706 !important;
161
+ border-color: #b45309 !important;
162
+ color: #ffffff !important;
163
+ font-weight: 700;
164
+ box-shadow: 0 2px 7px rgba(146, 64, 14, 0.35);
165
+ }
166
+ #hue-controller-migrate-legacy-flow.red-ui-button:hover {
167
+ background: #b45309 !important;
168
+ border-color: #92400e !important;
169
+ }
170
+ #hue-controller-migrate-legacy-flow.red-ui-button:active {
171
+ background: #92400e !important;
172
+ }
173
+ #hue-controller-migrate-legacy-flow.red-ui-button:focus-visible {
174
+ outline: 3px solid #fbbf24;
175
+ outline-offset: 2px;
176
+ }
177
+ #hue-controller-profile-editor.hue-controller-no-knx .hue-knx-section,
178
+ #hue-controller-profile-editor.hue-controller-light-profile.hue-controller-no-knx #tabs {
179
+ display: none !important;
180
+ }
181
+ /* Every profile inherited slightly different fixed KNX widths. Mark
182
+ actual DPT rows after mounting and compact only their DPT/Name
183
+ controls. No wrapping keeps one mapping on one visual line, while the
184
+ small flex allowance lets Name contract on narrow editor trays. */
185
+ #hue-controller-profile-editor .hue-controller-knx-mapping-row {
186
+ display: flex;
187
+ align-items: center;
188
+ flex-wrap: nowrap !important;
189
+ }
190
+ #hue-controller-profile-editor .hue-controller-knx-mapping-row > label {
191
+ flex-shrink: 0;
192
+ }
193
+ #hue-controller-profile-editor .hue-controller-knx-mapping-row > select[id^="node-input-dpt"] {
194
+ box-sizing: border-box;
195
+ width: 105px !important;
196
+ min-width: 90px !important;
197
+ max-width: 105px !important;
198
+ flex: 0 1 105px !important;
199
+ }
200
+ #hue-controller-profile-editor .hue-controller-knx-mapping-row > input[type="text"][id^="node-input-name"] {
201
+ box-sizing: border-box;
202
+ width: 115px !important;
203
+ min-width: 70px !important;
204
+ max-width: 115px !important;
205
+ flex: 0 1 115px !important;
206
+ }
207
+ </style>
208
+ `);
209
+ };
210
+
211
+ const migrationText = (node, key, fallback, values) => {
212
+ try {
213
+ const translated = node._(`knxUltimateHueController.${key}`, values);
214
+ if (translated && translated !== `knxUltimateHueController.${key}`) return translated;
215
+ } catch (error) { /* empty */ }
216
+ return fallback;
217
+ };
218
+
219
+ const editorContainsLegacyHueNodes = () => {
220
+ // This read-only scan controls visibility only. The converter itself
221
+ // remains deliberately isolated from the open editor and still accepts
222
+ // only JSON explicitly pasted by the user.
223
+ const migrationApi = window.KNXUltimateHueControllerMigration;
224
+ if (!migrationApi || typeof migrationApi.isLegacyHueNode !== 'function') return false;
225
+ if (!RED.nodes || typeof RED.nodes.eachNode !== 'function') return false;
226
+ let found = false;
227
+ try {
228
+ RED.nodes.eachNode((node) => {
229
+ if (!found && migrationApi.isLegacyHueNode(node)) found = true;
230
+ });
231
+ } catch (error) {
232
+ // If an older/custom editor cannot enumerate nodes, hide the action:
233
+ // showing it without evidence of a migration target would be noisier.
234
+ return false;
235
+ }
236
+ return found;
237
+ };
238
+
239
+ const openLegacyMigrationDialog = (node) => {
240
+ const migrationApi = window.KNXUltimateHueControllerMigration;
241
+ if (!migrationApi || typeof migrationApi.convertLegacyHueFlowJson !== 'function') {
242
+ RED.notify(migrationText(node, 'migration_unavailable', 'The HUE migration tool is unavailable. Restart Node-RED after updating the package.'), 'error');
243
+ return () => { };
244
+ }
245
+
246
+ const $dialog = $('<div class="hue-controller-migration-dialog"></div>').appendTo('body');
247
+ $('<div class="hue-controller-migration-note"></div>')
248
+ .text(migrationText(node, 'migration_privacy', 'Paste an exported Node-RED flow. Conversion happens locally and does not modify the open flow.'))
249
+ .css({ marginBottom: '8px' })
250
+ .appendTo($dialog);
251
+ $('<div class="hue-controller-migration-credentials"></div>')
252
+ .text(migrationText(node, 'migration_credentials_notice', 'Node-RED deliberately excludes keys and passwords from exported flows. The converter cannot read or recreate them.'))
253
+ .css({ marginBottom: '12px', padding: '8px 10px', borderLeft: '4px solid #d79b00', background: '#fff8df' })
254
+ .appendTo($dialog);
255
+ $('<label for="hue-controller-migration-input"></label>')
256
+ .text(migrationText(node, 'migration_input', 'Original flow JSON'))
257
+ .css({ display: 'block', fontWeight: 'bold', marginBottom: '4px' })
258
+ .appendTo($dialog);
259
+ const $input = $('<textarea id="hue-controller-migration-input"></textarea>')
260
+ .attr('placeholder', migrationText(node, 'migration_input_placeholder', 'Paste the complete Node-RED flow export here'))
261
+ .css({ width: '100%', height: '190px', boxSizing: 'border-box', fontFamily: 'monospace', resize: 'vertical' })
262
+ .appendTo($dialog);
263
+ const $reuseConfigsRow = $('<div></div>')
264
+ .css({ margin: '10px 0' })
265
+ .appendTo($dialog);
266
+ const $reuseConfigs = $('<input type="checkbox" id="hue-controller-migration-reuse-configs">')
267
+ .prop('checked', true)
268
+ .appendTo($reuseConfigsRow);
269
+ $('<label for="hue-controller-migration-reuse-configs"></label>')
270
+ .text(` ${migrationText(node, 'migration_reuse_configs', 'Reuse existing Hue and KNX config nodes (recommended)')}`)
271
+ .css({ display: 'inline', fontWeight: 'bold' })
272
+ .appendTo($reuseConfigsRow);
273
+ $('<div></div>')
274
+ .text(migrationText(node, 'migration_reuse_configs_help', 'Keep this selected when importing back into the same Node-RED installation. Referenced config nodes are omitted from the result, so their stored credentials remain untouched.'))
275
+ .css({ margin: '4px 0 0 22px', color: '#666' })
276
+ .appendTo($reuseConfigsRow);
277
+ const $toolbar = $('<div></div>')
278
+ .css({ display: 'flex', gap: '8px', alignItems: 'center', margin: '10px 0' })
279
+ .appendTo($dialog);
280
+ const $convert = $('<button type="button" class="red-ui-button"></button>')
281
+ .append($('<i class="fa fa-exchange"></i>'))
282
+ .append(document.createTextNode(` ${migrationText(node, 'migration_convert', 'Convert')}`))
283
+ .appendTo($toolbar);
284
+ const $copy = $('<button type="button" class="red-ui-button"></button>')
285
+ .append($('<i class="fa fa-clipboard"></i>'))
286
+ .append(document.createTextNode(` ${migrationText(node, 'migration_copy', 'Copy converted flow')}`))
287
+ .prop('disabled', true)
288
+ .appendTo($toolbar);
289
+ const $status = $('<span class="hue-controller-migration-status"></span>')
290
+ .css({ marginLeft: '4px' })
291
+ .appendTo($toolbar);
292
+ $('<label for="hue-controller-migration-output"></label>')
293
+ .text(migrationText(node, 'migration_output', 'Converted flow JSON'))
294
+ .css({ display: 'block', fontWeight: 'bold', marginBottom: '4px' })
295
+ .appendTo($dialog);
296
+ const $output = $('<textarea id="hue-controller-migration-output" readonly></textarea>')
297
+ .css({ width: '100%', height: '230px', boxSizing: 'border-box', fontFamily: 'monospace', resize: 'vertical' })
298
+ .appendTo($dialog);
299
+
300
+ $convert.on('click.knxUltimateHueControllerMigration', () => {
301
+ try {
302
+ const result = migrationApi.convertLegacyHueFlowJson($input.val(), {
303
+ reuseConfigNodes: $reuseConfigs.prop('checked')
304
+ });
305
+ $output.val(result.json).scrollTop(0);
306
+ $copy.prop('disabled', false);
307
+ if (result.convertedCount === 0) {
308
+ $status.text(migrationText(node, 'migration_none', 'No legacy HUE nodes were found.')).css('color', '#a15c00');
309
+ } else {
310
+ let summary = migrationText(node, 'migration_summary', '{{count}} legacy HUE nodes converted.')
311
+ .replace('{{count}}', String(result.convertedCount));
312
+ let statusColor = '#1b7d33';
313
+ if (result.omittedConfigCount > 0) {
314
+ summary += ` ${migrationText(node, 'migration_summary_reused', '{{count}} referenced config nodes will be reused with their existing credentials.')
315
+ .replace('{{count}}', String(result.omittedConfigCount))}`;
316
+ } else if (!$reuseConfigs.prop('checked')) {
317
+ summary += ` ${migrationText(node, 'migration_configs_included_warning', 'Config nodes are included without exported credentials; enter their keys and passwords after import.')}`;
318
+ statusColor = '#a15c00';
319
+ }
320
+ $status.text(summary).css('color', statusColor);
321
+ }
322
+ } catch (error) {
323
+ $output.val('');
324
+ $copy.prop('disabled', true);
325
+ const message = error && error.message ? error.message : String(error);
326
+ $status
327
+ .text(`${migrationText(node, 'migration_invalid', 'Invalid flow JSON:')} ${message}`)
328
+ .css('color', '#b00020');
329
+ }
330
+ });
331
+
332
+ $copy.on('click.knxUltimateHueControllerMigration', () => {
333
+ const convertedJson = $output.val();
334
+ if (!convertedJson) return;
335
+ $copy.prop('disabled', true);
336
+ migrationApi.copyTextToClipboard(convertedJson, {
337
+ navigator: window.navigator,
338
+ document: window.document
339
+ }).then(() => {
340
+ RED.notify(migrationText(node, 'migration_copy_success', 'Converted flow copied.'), 'success');
341
+ }).catch(() => {
342
+ const outputElement = $output.get(0);
343
+ if (outputElement) {
344
+ outputElement.focus();
345
+ outputElement.select();
346
+ if (typeof outputElement.setSelectionRange === 'function') {
347
+ outputElement.setSelectionRange(0, convertedJson.length);
348
+ }
349
+ }
350
+ RED.notify(migrationText(node, 'migration_copy_failed', 'Clipboard access was blocked. The converted JSON has been selected; copy it manually.'), 'error');
351
+ }).finally(() => {
352
+ $copy.prop('disabled', false);
353
+ });
354
+ });
355
+
356
+ let closed = false;
357
+ const closeDialog = () => {
358
+ if (closed) return;
359
+ closed = true;
360
+ $convert.off('.knxUltimateHueControllerMigration');
361
+ $copy.off('.knxUltimateHueControllerMigration');
362
+ try { $dialog.dialog('destroy'); } catch (error) { /* empty */ }
363
+ $dialog.remove();
364
+ };
365
+
366
+ $dialog.dialog({
367
+ modal: true,
368
+ width: Math.min(920, Math.max(620, $(window).width() - 80)),
369
+ height: Math.min(760, Math.max(600, $(window).height() - 60)),
370
+ title: migrationText(node, 'migration_title', 'Convert legacy HUE flow'),
371
+ close: closeDialog,
372
+ buttons: [{
373
+ text: migrationText(node, 'migration_close', 'Close'),
374
+ click() { $(this).dialog('close'); }
375
+ }]
376
+ });
377
+ setTimeout(() => $input.trigger('focus'), 0);
378
+ return closeDialog;
379
+ };
380
+
381
+ RED.nodes.registerType('knxUltimateHueController', {
382
+ category: 'KNX Ultimate HUE',
383
+ color: '#C0C7E9',
384
+ defaults: buildDefaults(),
385
+ // catalogDefaults is consumed by the KNX AI catalog. It intentionally
386
+ // describes only the stable public surface; the real persistence contract
387
+ // remains the complete `defaults` union above.
388
+ catalogDefaults: {
389
+ server: { type: 'knxUltimate-config' },
390
+ serverHue: { type: 'hue-config' },
391
+ name: {},
392
+ hueControllerType: {},
393
+ hueDevice: {},
394
+ enableNodePINS: {},
395
+ inputs: {},
396
+ outputs: {}
397
+ },
398
+ inputs: 0,
399
+ outputs: 0,
400
+ icon: 'node-hue-icon.svg',
401
+ label() {
402
+ const profile = PROFILE_TYPES[this.hueControllerType] || PROFILE_TYPES.light;
403
+ return this.name || `HUE Controller · ${profile.label}`;
404
+ },
405
+ paletteLabel: 'HUE Controller',
406
+ oneditprepare() {
407
+ try { RED.sidebar.show('help'); } catch (error) { /* empty */ }
408
+ ensureControllerEditorStyle();
409
+ const controllerNode = this;
410
+ // activeProfile/legacyContext describe the currently mounted function.
411
+ // Drafts preserve unsaved edits when the user switches away and back;
412
+ // the migration closer prevents detached jQuery dialogs and handlers.
413
+ let activeProfile;
414
+ let legacyContext;
415
+ let closeMigrationDialog = () => { };
416
+ const profileDrafts = {};
417
+
418
+ const hasLegacyHueNodes = editorContainsLegacyHueNodes();
419
+ const $migrationButton = $('#hue-controller-migrate-legacy-flow');
420
+ $('#hue-controller-migrate-legacy-flow-row').toggle(hasLegacyHueNodes);
421
+ $migrationButton.off('.knxUltimateHueControllerMigration');
422
+ if (hasLegacyHueNodes) {
423
+ $migrationButton.on('click.knxUltimateHueControllerMigration', () => {
424
+ closeMigrationDialog();
425
+ closeMigrationDialog = openLegacyMigrationDialog(controllerNode);
426
+ });
427
+ }
428
+
429
+ const cloneEditorValue = (value) => {
430
+ // Profile defaults may contain arrays or objects. Clone them before
431
+ // drafting so editing one function cannot mutate shared definitions.
432
+ if (value === undefined || value === null || typeof value !== 'object') return value;
433
+ try { return JSON.parse(JSON.stringify(value)); } catch (error) { return value; }
434
+ };
435
+
436
+ const collectCurrentEditor = () => {
437
+ // Collect before unmounting. Exact keys come from the selected private
438
+ // definition, avoiding broad DOM scans and widget-added CSS classes.
439
+ if (!activeProfile || !legacyContext) return;
440
+ const definition = getProfileDefinition(controllerNode.hueControllerType);
441
+ const draft = {};
442
+ Object.keys(definition?.defaults || {}).forEach((key) => {
443
+ const value = readFieldValue($(`#node-input-${key}`), controllerNode[key]);
444
+ controllerNode[key] = value;
445
+ legacyContext[key] = value;
446
+ draft[key] = cloneEditorValue(value);
447
+ });
448
+ profileDrafts[controllerNode.hueControllerType] = draft;
449
+ if (Number.isFinite(Number(legacyContext.inputs))) controllerNode.inputs = Number(legacyContext.inputs);
450
+ if (Number.isFinite(Number(legacyContext.outputs))) controllerNode.outputs = Number(legacyContext.outputs);
451
+ };
452
+
453
+ const disposeCurrentEditor = () => {
454
+ // A profile can attach handlers or allocate editor-only state. Reuse
455
+ // its cancel hook when changing function, then remove the two handler
456
+ // namespaces that belong specifically to this wrapper.
457
+ if (!activeProfile || !legacyContext) return;
458
+ $('#node-input-server').off('.knxUltimateHueControllerLightVisibility');
459
+ $('#node-input-server').off('.knxUltimateHueControllerKnxVisibility');
460
+ const definition = getProfileDefinition(controllerNode.hueControllerType);
461
+ if (definition && typeof definition.oneditcancel === 'function') {
462
+ definition.oneditcancel.call(legacyContext);
463
+ }
464
+ };
465
+
466
+ const prepareProfileValues = (controllerType, definition) => {
467
+ // Returning to a profile restores its in-dialog draft. First visits use
468
+ // that profile's defaults, except shared gateway selections which must
469
+ // survive every function change.
470
+ const draft = profileDrafts[controllerType];
471
+ Object.entries(definition?.defaults || {}).forEach(([key, property]) => {
472
+ if (draft && Object.prototype.hasOwnProperty.call(draft, key)) {
473
+ controllerNode[key] = cloneEditorValue(draft[key]);
474
+ } else if (key !== 'server' && key !== 'serverHue') {
475
+ controllerNode[key] = cloneEditorValue(property.value);
476
+ }
477
+ });
478
+ };
479
+
480
+ const renderProfileEditor = (controllerType, skipCollect) => {
481
+ // Controlled lifecycle transition:
482
+ // collect old -> dispose old -> mount private HTML -> translate ->
483
+ // restore values -> execute the selected profile's oneditprepare.
484
+ if (skipCollect !== true) collectCurrentEditor();
485
+ disposeCurrentEditor();
486
+ const profile = PROFILE_TYPES[controllerType] || PROFILE_TYPES.light;
487
+ const definition = getProfileDefinition(controllerType);
488
+ const template = profileBundle && typeof profileBundle.getTemplate === 'function'
489
+ ? profileBundle.getTemplate(controllerType)
490
+ : undefined;
491
+ const $container = $('#hue-controller-profile-editor');
492
+ $container.toggleClass('hue-controller-light-profile', controllerType === 'light');
493
+
494
+ if (!definition || !template) {
495
+ $container.html('<div class="hue-controller-editor-error">The selected Hue editor is unavailable. Restart Node-RED after updating the package.</div>');
496
+ return;
497
+ }
498
+
499
+ activeProfile = profile;
500
+ controllerNode.hueControllerType = controllerType;
501
+ // Some dedicated definitions used fixed pins, others persisted them.
502
+ // Normalize both forms into per-instance numeric Controller pins.
503
+ if (!definition.defaults?.inputs) controllerNode.inputs = Number(definition.inputs || 0);
504
+ if (!definition.defaults?.outputs) controllerNode.outputs = Number(definition.outputs || 0);
505
+ controllerNode.inputs = Number.isFinite(Number(controllerNode.inputs))
506
+ ? Number(controllerNode.inputs)
507
+ : Number(definition.inputs || 0);
508
+ controllerNode.outputs = Number.isFinite(Number(controllerNode.outputs))
509
+ ? Number(controllerNode.outputs)
510
+ : Number(definition.outputs || 0);
511
+
512
+ const $profileContent = $('<div></div>').html(template);
513
+ // The unified editor owns the single playlist link at the top.
514
+ $profileContent.find(`a[href="${TUTORIALS_URL}"]`).each(function () {
515
+ $(this).closest('.form-row').remove();
516
+ });
517
+ // This notice belongs only to the dedicated legacy node editor. The
518
+ // current editor is already HUE Controller, so do not repeat it here.
519
+ $profileContent.find('.hue-legacy-controller-notice').remove();
520
+ // Configuration-node fields must exist before oneditprepare so Node-RED
521
+ // can turn them into its managed selectors. The unified template owns
522
+ // those two fixed fields; remove the duplicate legacy rows.
523
+ $profileContent.find('#node-input-server, #node-input-serverHue').each(function () {
524
+ $(this).closest('.form-row').remove();
525
+ });
526
+ $container.empty().append($profileContent.contents());
527
+ // Node-RED translates the static dialog before oneditprepare. This
528
+ // fragment belongs to another node type, so qualify every key with the
529
+ // selected profile's namespace instead of the controller namespace.
530
+ translateProfileEditor($container, controllerType, profile.nodeType);
531
+ // A row containing a DPT selector is a KNX mapping row regardless of
532
+ // which private profile supplied it. Tagging by the strict field ID
533
+ // avoids coupling the Controller layout to legacy CSS class names.
534
+ $container.find('select[id^="node-input-dpt"]').each(function () {
535
+ $(this).closest('.form-row').addClass('hue-controller-knx-mapping-row');
536
+ });
537
+ Object.keys(definition.defaults || {}).forEach((key) => {
538
+ setFieldValue($(`#node-input-${key}`), controllerNode[key]);
539
+ });
540
+
541
+ // The inherited context lets mature callbacks read the Controller's
542
+ // properties while isolating their temporary mutations. Never assign
543
+ // `legacyContext.type`: that could save the node under a legacy type.
544
+ legacyContext = Object.create(controllerNode);
545
+ legacyContext._ = definition._;
546
+ legacyContext.inputs = controllerNode.inputs;
547
+ legacyContext.outputs = controllerNode.outputs;
548
+ // Light deliberately receives the real Hue config-node selection.
549
+ // Its private editor owns the bounded readiness poll and displays the
550
+ // hourglass until the bridge resources are available or it times out.
551
+ if (typeof definition.oneditprepare === 'function') {
552
+ definition.oneditprepare.call(legacyContext);
553
+ }
554
+ // Profile guidance is already available in the node help. Keeping it
555
+ // out of the embedded editor makes every device profile more compact.
556
+ $container.find('.form-tips').remove();
557
+
558
+ const updateControllerKnxVisibility = () => {
559
+ // Node-RED has used several sentinel values for an empty config-node
560
+ // selector. Normalize them before deciding whether KNX rows exist.
561
+ const serverValue = $('#node-input-server').val();
562
+ const normalizedServerValue = serverValue === undefined || serverValue === null
563
+ ? ''
564
+ : String(serverValue).trim().toLowerCase();
565
+ const knxDisabled = KNX_EMPTY_SERVER_VALUES.has(normalizedServerValue);
566
+ $container.toggleClass('hue-controller-no-knx', knxDisabled);
567
+ };
568
+ updateControllerKnxVisibility();
569
+ $('#node-input-server')
570
+ .off('.knxUltimateHueControllerKnxVisibility')
571
+ .on('change.knxUltimateHueControllerKnxVisibility', updateControllerKnxVisibility);
572
+ focusProfileHelp(controllerType);
573
+ };
574
+
575
+ const selectedType = PROFILE_TYPES[this.hueControllerType] ? this.hueControllerType : 'light';
576
+ $('#node-input-hueControllerType')
577
+ .val(selectedType)
578
+ .off('.knxUltimateHueController')
579
+ .on('change.knxUltimateHueController', function () {
580
+ const nextType = PROFILE_TYPES[$(this).val()] ? $(this).val() : 'light';
581
+ const nextDefinition = getProfileDefinition(nextType);
582
+ collectCurrentEditor();
583
+ prepareProfileValues(nextType, nextDefinition);
584
+ // A deliberate function change adopts that function's normal pin shape.
585
+ controllerNode.inputs = Number(nextDefinition?.inputs || 0);
586
+ controllerNode.outputs = Number(nextDefinition?.outputs || 0);
587
+ renderProfileEditor(nextType, true);
588
+ });
589
+
590
+ this._hueControllerCollectEditor = collectCurrentEditor;
591
+ this._hueControllerGetLegacyContext = () => legacyContext;
592
+ this._hueControllerGetProfile = () => activeProfile;
593
+ this._hueControllerCloseMigration = () => closeMigrationDialog();
594
+ renderProfileEditor(selectedType);
595
+ },
596
+ oneditsave() {
597
+ // Preserve lifecycle order: close auxiliary UI, collect mounted fields,
598
+ // let the profile normalize them, then copy values and dynamic pins back
599
+ // onto the actual node that Node-RED will serialize.
600
+ if (typeof this._hueControllerCloseMigration === 'function') this._hueControllerCloseMigration();
601
+ if (typeof this._hueControllerCollectEditor === 'function') this._hueControllerCollectEditor();
602
+ const profile = typeof this._hueControllerGetProfile === 'function'
603
+ ? this._hueControllerGetProfile()
604
+ : undefined;
605
+ const legacyContext = typeof this._hueControllerGetLegacyContext === 'function'
606
+ ? this._hueControllerGetLegacyContext()
607
+ : undefined;
608
+ const definition = profile ? getProfileDefinition(this.hueControllerType) : undefined;
609
+ if (definition && typeof definition.oneditsave === 'function') {
610
+ definition.oneditsave.call(legacyContext || this);
611
+ }
612
+ if (legacyContext) {
613
+ Object.keys(definition?.defaults || {}).forEach((key) => {
614
+ if (Object.prototype.hasOwnProperty.call(legacyContext, key)) this[key] = legacyContext[key];
615
+ });
616
+ if (Number.isFinite(Number(legacyContext.inputs))) this.inputs = Number(legacyContext.inputs);
617
+ if (Number.isFinite(Number(legacyContext.outputs))) this.outputs = Number(legacyContext.outputs);
618
+ }
619
+ delete this._hueControllerCollectEditor;
620
+ delete this._hueControllerGetLegacyContext;
621
+ delete this._hueControllerGetProfile;
622
+ delete this._hueControllerCloseMigration;
623
+ },
624
+ oneditcancel() {
625
+ // Cancel releases profile-owned editor state but deliberately does not
626
+ // collect or persist the values currently visible in the dialog.
627
+ if (typeof this._hueControllerCloseMigration === 'function') this._hueControllerCloseMigration();
628
+ const profile = typeof this._hueControllerGetProfile === 'function'
629
+ ? this._hueControllerGetProfile()
630
+ : undefined;
631
+ const legacyContext = typeof this._hueControllerGetLegacyContext === 'function'
632
+ ? this._hueControllerGetLegacyContext()
633
+ : undefined;
634
+ const definition = profile ? getProfileDefinition(this.hueControllerType) : undefined;
635
+ if (definition && typeof definition.oneditcancel === 'function') {
636
+ definition.oneditcancel.call(legacyContext || this);
637
+ }
638
+ delete this._hueControllerCloseMigration;
639
+ }
640
+ });
641
+ }());
642
+ </script>
643
+
644
+ <script type="text/html" data-template-name="knxUltimateHueController">
645
+ <div class="form-row" style="margin-bottom:14px;">
646
+ <span style="color:#ff0000"><i class="fa fa-youtube"></i></span>&nbsp;
647
+ <a target="_blank" rel="noopener noreferrer" href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E"><b>KNX-Ultimate video tutorials (YouTube playlist)</b></a>
648
+ </div>
649
+ <div class="form-row" id="hue-controller-migrate-legacy-flow-row" style="display:none; margin-bottom:14px;">
650
+ <button type="button" id="hue-controller-migrate-legacy-flow" class="red-ui-button" data-i18n="[title]knxUltimateHueController.migration_button_title">
651
+ <i class="fa fa-exchange"></i>
652
+ <span data-i18n="knxUltimateHueController.migration_button"></span>
653
+ </button>
654
+ </div>
655
+ <div class="form-row">
656
+ <label for="node-input-server">
657
+ <i class="fa fa-server"></i>
658
+ <span data-i18n="common.knx_gw"></span>
659
+ </label>
660
+ <input type="text" id="node-input-server">
661
+ </div>
662
+ <div class="form-row">
663
+ <label for="node-input-serverHue">
664
+ <i class="fa fa-circle"></i>
665
+ <span data-i18n="common.hue_bridge"></span>
666
+ </label>
667
+ <input type="text" id="node-input-serverHue">
668
+ </div>
669
+ <div class="form-row">
670
+ <label for="node-input-hueControllerType" style="width:160px;">
671
+ <i class="fa fa-cubes"></i>
672
+ <span data-i18n="knxUltimateHueController.device_function"></span>
673
+ </label>
674
+ <select id="node-input-hueControllerType" style="width:33.333%;">
675
+ <option value="light" data-i18n="knxUltimateHueController.types.light"></option>
676
+ <option value="plug" data-i18n="knxUltimateHueController.types.plug"></option>
677
+ <option value="button" data-i18n="knxUltimateHueController.types.button"></option>
678
+ <option value="relative_rotary" data-i18n="knxUltimateHueController.types.relative_rotary"></option>
679
+ <option value="motion" data-i18n="knxUltimateHueController.types.motion"></option>
680
+ <option value="area_motion" data-i18n="knxUltimateHueController.types.area_motion"></option>
681
+ <option value="camera_motion" data-i18n="knxUltimateHueController.types.camera_motion"></option>
682
+ <option value="contact" data-i18n="knxUltimateHueController.types.contact"></option>
683
+ <option value="light_level" data-i18n="knxUltimateHueController.types.light_level"></option>
684
+ <option value="temperature" data-i18n="knxUltimateHueController.types.temperature"></option>
685
+ <option value="humidity" data-i18n="knxUltimateHueController.types.humidity"></option>
686
+ <option value="scene" data-i18n="knxUltimateHueController.types.scene"></option>
687
+ <option value="device_power" data-i18n="knxUltimateHueController.types.device_power"></option>
688
+ <option value="zigbee_connectivity" data-i18n="knxUltimateHueController.types.zigbee_connectivity"></option>
689
+ <option value="device_software_update" data-i18n="knxUltimateHueController.types.device_software_update"></option>
690
+ </select>
691
+ </div>
692
+ <div id="hue-controller-profile-editor"></div>
693
+ <br/>
694
+ <br/>
695
+ <br/>
696
+ <br/>
697
+ </script>
698
+
699
+ <script type="text/markdown" data-help-name="knxUltimateHueController">
700
+ # HUE Controller
701
+
702
+ [**KNX-Ultimate video tutorials (YouTube playlist)**](https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E)
703
+
704
+ The unified controller for Hue API v2 resources. Choose the device function and configure the same KNX mapping and behaviour already available in the established dedicated Hue node.
705
+
706
+ The controller supports lights and grouped lights, plugs, buttons, Tap dial, motion resources, contacts, light level, temperature, humidity, scenes, battery, Zigbee connectivity, and device software update resources.
707
+
708
+ ## Convert a legacy HUE flow
709
+
710
+ Use **Convert legacy HUE flow**, paste a complete Node-RED flow export, and press **Convert**. The conversion runs only in the browser: it does not inspect or modify the open flow. The returned JSON replaces every supported legacy HUE node with HUE Controller while preserving IDs, configuration references, properties, positions, and wires. Copy the result and import it into Node-RED, preferably as a new flow for comparison before removing the original.
711
+
712
+ Node-RED intentionally excludes credentials from flow exports. When importing into the same Node-RED installation, keep **Reuse existing Hue and KNX config nodes** selected: referenced `hue-config` and `knxUltimate-config` objects are omitted from the converted JSON, while their IDs remain on the converted nodes so the existing configurations retain their keys and passwords. Clear the option only for a different installation, where credentials must be entered again after import.
713
+
714
+ **Copy converted flow** uses the browser Clipboard API and falls back to a temporary selected textarea when direct clipboard access is unavailable. If the browser blocks both methods, the output JSON is selected so it can be copied manually.
715
+
716
+ ## Compatibility and loop protection
717
+
718
+ Each function uses its private mature runtime contract. Hue events remain status updates and are never reflected to Hue or KNX as new commands unless the selected function explicitly maps them to a KNX output. The Controller owns its Light implementation without modifying the original legacy Hue Light node.
719
+
720
+ Dedicated legacy HUE nodes remain registered for existing flows, but are frozen and receive no new features or maintenance updates. Node-RED hides their special `deprecated` category from the palette, while existing instances remain editable, deployable, lighter than HUE Controller and visibly marked `(deprecated)` on the canvas. They also show a migration notice at the top of their editor. Use HUE Controller for new work or migrate them with the paste-in converter.
721
+
722
+ When no **KNX Gateway** is selected (including **none** and **Add new...**), the KNX mapping fields are hidden. Hue resource selection and flow-only options remain available.
723
+
724
+ For a single light, the **Dim**, **Tunable White**, **RGB/HSV**, and native-effects sections are enabled from the live capabilities reported by the selected Hue API v2 light resource. A light that exposes `dimming`, `color_temperature`, or `color` therefore shows the corresponding KNX mappings automatically.
725
+
726
+ While the Hue Bridge is still loading its resources, the Light editor displays a spinning hourglass and polls readiness every 500 ms. The wait is bounded: after about 10 seconds the editor is released, the mapping tabs remain hidden, and a localized error asks you to check the bridge configuration, deploy, and retry. Closing, saving, or changing function always cancels the pending timer.
727
+
728
+ Changing **Device function** changes the visible editor and the node pin layout. After saving, reopen the node to verify the selected resource and mappings.
729
+
730
+ KNX mapping rows keep GA, DPT and Name on one line. DPT selectors use a compact fixed range, while Name fields are shorter and may contract further on a narrow editor tray without changing their stored text.
731
+
732
+ <!-- HUE_CONTROLLER_PROFILE_HELP_START -->
733
+
734
+ ## Device function
735
+
736
+ - [Light / grouped light (`light`)](#hue-controller-help-light)
737
+ - [Plug / outlet (`plug`)](#hue-controller-help-plug)
738
+ - [Button (`button`)](#hue-controller-help-button)
739
+ - [Tap dial (`relative_rotary`)](#hue-controller-help-relative_rotary)
740
+ - [Motion (`motion`)](#hue-controller-help-motion)
741
+ - [Area motion (`area_motion`)](#hue-controller-help-area_motion)
742
+ - [Camera motion (`camera_motion`)](#hue-controller-help-camera_motion)
743
+ - [Contact (`contact`)](#hue-controller-help-contact)
744
+ - [Light level (`light_level`)](#hue-controller-help-light_level)
745
+ - [Temperature (`temperature`)](#hue-controller-help-temperature)
746
+ - [Humidity (`humidity`)](#hue-controller-help-humidity)
747
+ - [Scene (`scene`)](#hue-controller-help-scene)
748
+ - [Battery (`device_power`)](#hue-controller-help-device_power)
749
+ - [Zigbee connectivity (`zigbee_connectivity`)](#hue-controller-help-zigbee_connectivity)
750
+ - [Device software update (`device_software_update`)](#hue-controller-help-device_software_update)
751
+
752
+ <span id="hue-controller-help-light" data-hue-controller-type="light"></span>
753
+
754
+ ## Light / grouped light (`light`)
755
+
756
+ <p>This node controls Philips Hue lights (single or grouped) and maps their commands/states to KNX.</p>
757
+
758
+ **General**
759
+
760
+ |Property|Description|
761
+ |--|--|
762
+ | KNX GW | Select the KNX gateway to be used |
763
+ | Hue Bridge | Select the Hue Bridge to be used |
764
+ | Name | Hue light or grouped light to use (autocomplete while typing).|
765
+
766
+ <br/>
767
+
768
+ **Locate device**
769
+
770
+ Use the `Locate` button (play icon) to start a Hue identify session for the selected resource. While the session is active the button switches to a stop icon and the bridge makes the light — or every light in the grouped resource — blink once per second. Press the button again to stop immediately; otherwise the session ends automatically after 10 minutes.
771
+
772
+ <br/>
773
+
774
+ **OPTIONS**
775
+
776
+ Here you can link KNX Group Addresses to the available Hue commands/states.<br/>
777
+ Start typing in the GA field (name or Group Address); suggestions appear while you type.
778
+
779
+ **Switch**
780
+
781
+ |Property|Description|
782
+ |--|--|
783
+ | Control | This GA is used to turn on/off the Hue light via a boolean KNX value true/false|
784
+ | Status | Link this to the light's switch status group address|
785
+
786
+ <br/>
787
+
788
+ **Dim**
789
+
790
+ |Property|Description|
791
+ |--|--|
792
+ | Control dim | Relative DIM of the Hue light. You can set the dimming speed in the **Behaviour** tab. |
793
+ | Control % | Changes the absolute Hue light's brightness (0-100%)|
794
+ | Status % | Link this to the light's brightness status KNX group address |
795
+ | Dim Speed (ms) | Dimming speed in milliseconds. Applies to both the light brightness and the tunable-white datapoints. Calculated over the 0%→100% range. |
796
+ | Min Dim brightness | Tha Minimum brightness that the lamp can reach. For example, if you are dimming the light down, the light will stop dimming at the specified brightness %. |
797
+ | Max Dim brightness | Tha Maximum brightness that the lamp can reach. For example, if you are dimming the light up, the light will stop dimming at the specified brightness %. |
798
+
799
+ <br/>
800
+
801
+ **Tunable White**
802
+
803
+ |Property|Description|
804
+ |--|--|
805
+ | Control dim | Change white temperature using DPT 3.007 dimming. Speed is set in the **Behaviour** tab.|
806
+ | Control % | Change white temperature using DPT 5.001. 0 = full warm, 100 = full cold.|
807
+ | Status %| Temperature status GA. DPT 5.001 absolute value: 0 = full warm, 100 = full cold.|
808
+ | Control kelvin | **DPT 7.600: ** set temperature in Kelvin using the KNX range 2000-6535 (converted to Hue mirek).<br/>**DPT 9.002:** set temperature using Hue range 2000-6535 K (Ambiance starts at 2200 K). Conversions may introduce small deviations.|
809
+ | Status kelvin | **DPT 7.600: ** read temperature in Kelvin using KNX range 2000-6535 (converted from Hue).<br/>**DPT 9.002:** read temperature using Hue range 2000-6535 K (Ambiance starts at 2200 K). Conversions may introduce small deviations. |
810
+ | Invert dim direction | Inverts the DIM direction. |
811
+ <br/>
812
+
813
+ **RGB/HSV**
814
+
815
+ |Property|Description|
816
+ |--|--|
817
+ | **RGB section** ||
818
+ | Control rgb| Change color using RGB triplet (r,g,b). Gamut correction is handled. Sending a color turns the light on and sets color/brightness (perceptual). Sending r,g,b = 0 turns the light off. |
819
+ | Status rgb | The light's color status group address. Accepted datapoint is RGB triplet (r,g,b)|
820
+ | **HSV section** ||
821
+ | Color H dim | Cycle through HSV hue using DPT 3.007 dimming. Speed is set in the **Behaviour** tab.|
822
+ | Status H %| Status of the HSV chromatic circle.|
823
+ | Control S dim | Changes the light's color saturation, using DPT 3.007 dimming. You can set the dimming speed in the **_Behaviour_** tab.|
824
+ | Status S %| The light color saturation status group address.|
825
+ | Dim Speed (ms) | The dimming speed, in Milliseconds, from bottom to top scale. |
826
+
827
+ For controlling the HSV "V” (brightness), use the standard controls under the **Dim** tab.
828
+
829
+ <br/>
830
+
831
+ **Effects**
832
+
833
+ _Non-Hue basic effects_
834
+
835
+ |Property|Description|
836
+ |--|--|
837
+ | Blink | _true_ Blink the light, _false_ Stop blinking. Blinks the light on and off. Useful for signalling. Works with all Hue lights. |
838
+ | Color Cycle | _true_ start cycle, _false_ Stop cycle. Randomly changes the Hue light's color at regular interval. Works with all Hue lights having color capabilities. The color effect will start 10 seconds after set. |
839
+
840
+ _Hue native effects_
841
+
842
+ Use the **Hue native effects** table to map your KNX values to the effects supported by the selected light (for example `candle`, `fireplace`, `prism`). Each row links a KNX value (boolean, numeric or textual, depending on the datapoint you pick) with a Hue effect. On the KNX side you can:
843
+
844
+ - send the mapped value to activate that effect;
845
+ - optionally provide a status Group Address: the node emits the mapped value whenever the Hue bridge reports an effect change; if no mapping exists the raw effect name is sent (requires a textual DPT such as 16.xxx).
846
+
847
+ <br/>
848
+
849
+ **Behaviour**
850
+
851
+ | Property | Description |
852
+ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
853
+ | Read status at startup | Read the Hue light status at node-red's startup or node-red's full deploy, and send that status to the KNX BUS |
854
+ | KNX Brightness Status | Updates the KNX brightness group address status, whenever the Hue lamp is switched ON/OFF. The options are **When Hue light is Off send 0%. When Hue On, restore previous value (Default KNX behaviour) ** and**Leave as is (default Hue behaviour) ** . If you have KNX dimmer with brightness status, like MDT, the suggested option is _**When Hue light is Off send 0%. When Hue On, restore previous value (Default KNX behaviour)** _ |
855
+ | Update local cached Hue state from KNX bus writes | Advanced option, enabled by default. When enabled, writes arriving from the KNX bus also update the node's local cached Hue state immediately, without waiting for feedback/events from the Hue bridge. This gives faster local reactions and more consistent immediate KNX read responses, especially while the light or grouped light is OFF. Disable it if you prefer the cache to follow only real feedback/events from the Hue bridge. |
856
+ | Switch on behaviour | It sets the behaviour of your lights when switched on. You can choose from differents behaviours.<br/> **Select color: ** the light will be switched on with the color of your choice. To change color, just CLICK on the color selector (under the _Select color_ control).<br/>**Select temperature and brightness: ** the light will be switched on with the temperature (Kelvin) and brightness (0-100) of your choice.<br/>**None:** the light will retain its last status. In case you've enable the night lighting, after the night time ends, the lamp will resume the color/temperature/brightness state set at day time. |
857
+ | Night Lighting | It allows to set a particular light color/brightness at nighttime. The options are the same as the daytime. You could select either a temperature/brightness or color. A cozy temperature of 2700 Kelvin, with a brightness of 10% or 20%, is a good choice for bathroom's night light.|
858
+ | Day/Night | Select the group address used to set the day/night behaviour. The group address value is _true_ if daytime, _false_ if nighttime. |
859
+ | Invert day/night value | Invert the values of _Day/Night_ group address. Default value is **unchecked** . |
860
+ | Read status at startup | Read the status at startup and emit the event to the KNX bus at startup/reconnection. (Default "no")|
861
+ | Force day mode | You can force the day mode by manually switching the light as described here: **Switch to DAY mode by rapid switching the ligth off then on (This light only) ** does what described and acts only on this light.**Switch to DAY mode by rapid switching the ligth off then on (apply yo ALL light nodes)** acts to ALL Light nodes, by setting the Day/Night group address to Day mode. |
862
+ | Node Input/Output PINs | Hide or show the input/output PINs. Input/output PINS allow the node to accept msg input from the flow and send msg output to the flow. Input msg must follow the Hue API v.2 Standards. This is an example msg, that turns on the light: <code>msg.on = {"on":true}</code>. Please refer to the [official Hue Api page](https://developers.meethue.com/develop/hue-api-v2/api-reference/#resource_light__id__put) |
863
+
864
+ ##### Note
865
+
866
+ The Dimming function works in **KNX mode `start` and`stop` ** . To start dimming, send only one "start" KNX telegram. To stop dimming, send a "stop" KNX telegram. Please**remember that** , when you set your wall swiches properties.
867
+
868
+ <br/>
869
+
870
+ ---
871
+
872
+ <span id="hue-controller-help-plug" data-hue-controller-type="plug"></span>
873
+
874
+ ## Plug / outlet (`plug`)
875
+
876
+ ### Hue Plug / Outlet
877
+
878
+ #### Overview
879
+
880
+ The Hue Plug node links a Philips Hue smart plug (service `plug`) with KNX group addresses so you can control power and track the state directly from the BUS.
881
+
882
+ - Supports **on/off control** and **status feedback**.
883
+ - Optional mapping of the Hue `power_state` (on / standby).
884
+ - Can expose Node-RED input/output pins to forward Hue events to flows or send advanced API payloads.
885
+
886
+ #### Configuration
887
+
888
+ |Field|Description|
889
+ |--|--|
890
+ | KNX GW | KNX gateway used for telegrams |
891
+ | Hue Bridge | Configured Hue Bridge |
892
+ | Name | Select the Hue plug from the autocomplete list |
893
+ | Control | KNX GA for on/off commands (boolean DPT) |
894
+ | Status | GA for the on/off feedback coming from Hue |
895
+ | Power state | Optional GA mirroring Hue `power_state` (boolean/text) |
896
+ | Read status at startup | When enabled, the node emits the current plug state on deploy/connection |
897
+ | Node Input/Output PINs | Enable Node-RED input/output pins. Input expects Hue API payloads (e.g. `{ on: { on: true } }`). Output forwards every Hue event. |
898
+
899
+ #### KNX Mapping Tips
900
+
901
+ - Use a boolean datapoint (e.g. DPT 1.001) for both command and status.
902
+ - If you expose `power_state`, map it to a boolean GA (true = `on`, false = `standby`).
903
+ - For read requests (`GroupValue_Read`) the node returns the last cached Hue value.
904
+
905
+ #### Flow Integration
906
+
907
+ When _Node I/O pins_ are enabled:
908
+
909
+ - **Input:** send Hue v2 payloads to perform advanced actions (e.g. `msg.on = { on: true }`).
910
+ - **Output:** receive an event object `{ payload: boolean, on, power_state, rawEvent }` whenever Hue reports a change.
911
+
912
+ #### Hue API Reference
913
+
914
+ The node uses `/resource/plug/{id}` over HTTPS. Status changes are delivered via the Hue event stream and cached for KNX read responses.
915
+
916
+ ---
917
+
918
+ <span id="hue-controller-help-button" data-hue-controller-type="button"></span>
919
+
920
+ ## Button (`button`)
921
+
922
+ <p>The Hue Button node maps Hue button events to KNX group addresses and exposes the same events on its flow output via <code>button.button_report.event</code>.</p>
923
+
924
+ Start typing in the GA field (name or Group Address) to link the KNX GA; devices appear while you type.
925
+
926
+ **General**
927
+
928
+ |Property|Description|
929
+ |--|--|
930
+ | KNX GW | Select the KNX gateway to be used |
931
+ | Hue Bridge | Select the Hue Bridge to be used |
932
+ | Hue Button | Hue button to be used (autocomplete while typing) |
933
+
934
+ **Switch**
935
+
936
+ |Property|Description|
937
+ |--|--|
938
+ | Switch | GA triggered by <code>short\_release</code> (quick press/release). |
939
+ | Status GA | Optional feedback GA when <em>Toggle values</em> is enabled to keep the internal toggle state aligned with other actuators. |
940
+
941
+ **Dim**
942
+
943
+ |Property|Description|
944
+ |--|--|
945
+ | Dim | GA used during <code>long\_press</code>/<code>repeat</code> events for dimming (typically DPT 3.007). |
946
+
947
+ **Behaviour**
948
+
949
+ |Property|Description|
950
+ |--|--|
951
+ | Toggle values on each event | If enabled, the node alternates between <code>true/false</code> and up/down dimming payloads. |
952
+ | Switch payload | Payload sent to KNX/flow when Toggle values is disabled. |
953
+ | Dim payload | Direction sent to KNX/flow when Toggle values is disabled. |
954
+
955
+ ##### Outputs
956
+
957
+ 1. Standard output
958
+ : `msg.payload` carries the boolean (or dim object) sent to KNX; `msg.event` is the Hue event string (e.g. `short_release`, `repeat`).
959
+
960
+ ##### Details
961
+
962
+ `msg.event` mirrors `button.button_report.event`. The original Hue event is exposed in `msg.rawEvent`. Use the optional Status GA to keep the toggle state in sync with wall switches or other controllers.
963
+
964
+ ---
965
+
966
+ <span id="hue-controller-help-relative_rotary" data-hue-controller-type="relative_rotary"></span>
967
+
968
+ ## Tap dial (`relative_rotary`)
969
+
970
+ The **Hue Tap Dial** node maps the rotary service of the Hue Tap Dial to KNX and forwards the raw Hue events to your flow. Use the refresh icon beside the device field after pairing a new dial on the bridge.
971
+
972
+ ##### Tabs
973
+
974
+ - **Mapping** - select the KNX GA and DPT used for the rotation events. Supported datapoints: DPT 3.007 (relative dim), DPT 5.001 (absolute level 0-100 %) and DPT 232.600 (vendor colour control).
975
+ - **Behaviour** - show or hide the Node-RED output pin. When no KNX gateway is configured the output is kept enabled so Hue events still reach the flow.
976
+
977
+ ##### General settings
978
+
979
+ |Property|Description|
980
+ |--|--|
981
+ | KNX GW | KNX gateway used for GA autocomplete. |
982
+ | Hue Bridge | Hue Bridge hosting the Tap Dial. |
983
+ | Hue Tap Dial | Rotary device to control (autocomplete; refresh button reloads the list). |
984
+
985
+ ##### Mapping tab
986
+
987
+ |Property|Description|
988
+ |--|--|
989
+ | Rotate GA | KNX GA receiving rotation events (supports DPT 3.007, 5.001, 232.600). |
990
+ | Name | Friendly label for the GA. |
991
+
992
+ ##### Outputs
993
+
994
+ |#|Port|Payload|
995
+ |--|--|--|
996
+ |1|Standard output|`msg.payload` (object) Raw Hue event emitted by the Tap Dial.|
997
+
998
+ > ℹ️ KNX-specific widgets appear only after selecting a KNX gateway; the Mapping tab stays hidden until both the bridge and the gateway are configured.
999
+
1000
+ ---
1001
+
1002
+ <span id="hue-controller-help-motion" data-hue-controller-type="motion"></span>
1003
+
1004
+ ## Motion (`motion`)
1005
+
1006
+ <p>This node listens to a Hue motion sensor and mirrors the events to KNX and/or your Node-RED flow.</p>
1007
+
1008
+ Start typing the KNX device name or Group Address in the GA field; suggestions appear while you type. Hit the refresh button next to "Hue sensor” to reload the device list from the bridge if you add new sensors.
1009
+
1010
+ **General**
1011
+
1012
+ |Property|Description|
1013
+ |--|--|
1014
+ | KNX GW | KNX gateway that receives the motion updates (required before KNX mapping fields appear). |
1015
+ | Hue Bridge | Hue Bridge to query. |
1016
+ | Hue motion sensor | Hue motion sensor (supports autocomplete and refresh). |
1017
+
1018
+ **Mapping**
1019
+
1020
+ |Property|Description|
1021
+ |--|--|
1022
+ | Motion | KNX GA that receives `true` when motion is detected and `false` when the area is clear. Recommended DPT: <b>1.001</b>. |
1023
+
1024
+ **Behaviour**
1025
+
1026
+ |Property|Description|
1027
+ |--|--|
1028
+ | Node output pin | Show or hide the Node-RED output. When no KNX gateway is selected the output pin stays enabled so Hue motion events still reach your flow. |
1029
+
1030
+ > ℹ️ KNX widgets remain hidden until you select a KNX gateway, making it easy to use the node purely as a Hue → Node-RED listener.
1031
+
1032
+ ##### Output
1033
+
1034
+ 1. Standard output — `msg.payload` (boolean)
1035
+ : `true` on motion, `false` when motion ends.
1036
+
1037
+ ---
1038
+
1039
+ <span id="hue-controller-help-area_motion" data-hue-controller-type="area_motion"></span>
1040
+
1041
+ ## Area motion (`area_motion`)
1042
+
1043
+ <p>The Hue Motion Area node listens to MotionAware area motion events (Hue Bridge Pro) and mirrors the aggregated detected/not-detected state to KNX or your Node-RED flow.</p>
1044
+
1045
+ Start typing in the GA field (name or Group Address) to link the KNX GA; devices appear while you type.
1046
+
1047
+ **General**
1048
+
1049
+ |Property|Description|
1050
+ |--|--|
1051
+ | KNX GW | Select the KNX gateway to be used. |
1052
+ | HUE Bridge | Select the Hue Bridge Pro to be used. |
1053
+ | HUE Area | MotionAware area (convenience or security) to monitor (autocomplete while typing). |
1054
+ | Read status at startup | On startup/reconnect, read the current value and send it to KNX (default: yes). |
1055
+
1056
+ **Mapping**
1057
+
1058
+ |Property|Description|
1059
+ |--|--|
1060
+ | Motion | KNX GA for the area motion state (boolean). Recommended DPT: <b>1.001</b>. |
1061
+
1062
+ **Behaviour**
1063
+
1064
+ |Property|Description|
1065
+ |--|--|
1066
+ | Node output pin | Show or hide the Node-RED output. When no KNX gateway is selected the output pin stays enabled so MotionAware events still reach your flow. |
1067
+
1068
+ ##### Outputs
1069
+
1070
+ 1. Standard output
1071
+ : `msg.payload` (boolean): `true` when motion is detected in the area, otherwise `false`.
1072
+
1073
+ ##### Details
1074
+
1075
+ `msg.payload` carries the latest MotionAware area motion status (aggregated from the underlying sensors).
1076
+
1077
+ ---
1078
+
1079
+ <span id="hue-controller-help-camera_motion" data-hue-controller-type="camera_motion"></span>
1080
+
1081
+ ## Camera motion (`camera_motion`)
1082
+
1083
+ <p>The Hue Camera Motion node listens to Philips Hue camera motion services and mirrors the detected/not detected state to KNX.</p>
1084
+
1085
+ Start typing in the GA field (name or Group Address) to link the KNX GA; devices appear while you type.
1086
+
1087
+ **General**
1088
+
1089
+ |Property|Description|
1090
+ |--|--|
1091
+ | KNX GW | Select the KNX gateway to be used |
1092
+ | Hue Bridge | Select the Hue Bridge to be used |
1093
+ | Hue Camera Motion | Hue camera motion sensor (autocomplete while typing) |
1094
+ | Read status at startup | On startup/reconnect, read the current value and send it to KNX (default: no) |
1095
+
1096
+ **Mapping**
1097
+
1098
+ |Property|Description|
1099
+ |--|--|
1100
+ | Motion | KNX GA for camera motion (boolean). Recommended DPT: <b>1.001</b> |
1101
+
1102
+ ##### Outputs
1103
+
1104
+ 1. Standard output
1105
+ : `msg.payload` (boolean): `true` when motion is detected; otherwise `false`
1106
+
1107
+ ##### Details
1108
+
1109
+ `msg.payload` carries the latest motion status reported by the Hue camera service.
1110
+
1111
+ ---
1112
+
1113
+ <span id="hue-controller-help-contact" data-hue-controller-type="contact"></span>
1114
+
1115
+ ## Contact (`contact`)
1116
+
1117
+ <p>This node forwards events from a Hue contact sensor and maps them to KNX group addresses.</p>
1118
+
1119
+ Start typing in the GA field, the name or group address of your KNX device, the avaiable devices start showing up while you're typing.
1120
+
1121
+ **General**
1122
+
1123
+ |Property|Description|
1124
+ |--|--|
1125
+ | KNX GW | Select the KNX gateway to be used |
1126
+ | Hue Bridge | Select the Hue Bridge to be used |
1127
+ | Hue Contact Sensor | Hue contact sensor to be used (autocomplete while typing).|
1128
+
1129
+ | Property | Description |
1130
+ |--|--|
1131
+ | Contact | When the contact opens/closes, send KNX value: _true_ on active/open, otherwise _false_. |
1132
+
1133
+ ##### Outputs
1134
+
1135
+ 1. Standard output
1136
+ : payload (boolean) : the standard output of the command.
1137
+
1138
+ ##### Details
1139
+
1140
+ `msg.payload` carries the raw Hue event (boolean/object). Use it for custom logic if needed.
1141
+
1142
+ ---
1143
+
1144
+ <span id="hue-controller-help-light_level" data-hue-controller-type="light_level"></span>
1145
+
1146
+ ## Light level (`light_level`)
1147
+
1148
+ <p>This node reads lux events from a Hue Light Sensor and maps them to KNX.</p>
1149
+
1150
+ It emits the ambient illuminance (lux) each time it changes. Start typing in the GA field (name or Group Address) to link the KNX GA; devices appear while you type.
1151
+
1152
+ **General**
1153
+
1154
+ |Property|Description|
1155
+ |--|--|
1156
+ | KNX GW | Select the KNX gateway to be used |
1157
+ | Hue Bridge | Select the Hue Bridge to be used |
1158
+ | Hue Light Sensor | Hue Light Sensor to use (autocomplete while typing).|
1159
+ | Read status at startup | Read the status at startup and emit the event to the KNX bus at startup/reconnection. (Default "no")|
1160
+
1161
+ **Mapping**
1162
+
1163
+ | Property | Description |
1164
+ |--|--|
1165
+ | Lux | KNX GA that receives the lux value. |
1166
+
1167
+ ##### Outputs
1168
+
1169
+ 1. Standard output
1170
+ : payload (number): current lux value.
1171
+
1172
+ ##### Details
1173
+
1174
+ `msg.payload` carries the numeric lux value. Use it for custom logic if needed.
1175
+
1176
+ ---
1177
+
1178
+ <span id="hue-controller-help-temperature" data-hue-controller-type="temperature"></span>
1179
+
1180
+ ## Temperature (`temperature`)
1181
+
1182
+ <p>This node reads temperature (°C) from a Hue temperature sensor and maps it to KNX.</p>
1183
+
1184
+ Start typing in the GA field (name or Group Address) to link the KNX GA; devices appear while you type.
1185
+
1186
+ **General**
1187
+
1188
+ |Property|Description|
1189
+ |--|--|
1190
+ | KNX GW | Select the KNX gateway to be used |
1191
+ | Hue Bridge | Select the Hue Bridge to be used |
1192
+ | Hue temperature sensor | Hue temperature sensor (autocomplete while typing) |
1193
+ | Read status at startup | On startup/reconnect, read current value and send it to KNX (default: no) |
1194
+
1195
+ **Mapping**
1196
+
1197
+ |Property|Description|
1198
+ |--|--|
1199
+ | Temp | KNX GA for temperature in Celsius. Recommended DPT: <b>9.001</b> |
1200
+
1201
+ ##### Outputs
1202
+
1203
+ 1. Standard output
1204
+ : `msg.payload` (number): current temperature in °C
1205
+
1206
+ ##### Details
1207
+
1208
+ `msg.payload` carries the numeric temperature value.
1209
+
1210
+ ---
1211
+
1212
+ <span id="hue-controller-help-humidity" data-hue-controller-type="humidity"></span>
1213
+
1214
+ ## Humidity (`humidity`)
1215
+
1216
+ <p>This node reads relative humidity (%) from a Hue humidity sensor and maps it to KNX.</p>
1217
+
1218
+ Start typing in the GA field (name or Group Address) to link the KNX GA; devices appear while you type.
1219
+
1220
+ **General**
1221
+
1222
+ |Property|Description|
1223
+ |--|--|
1224
+ | KNX GW | Select the KNX gateway to be used |
1225
+ | Hue Bridge | Select the Hue Bridge to be used |
1226
+ | Hue Sensor | Hue humidity sensor (autocomplete while typing) |
1227
+ | Read status at startup | On startup/reconnect, read current value and send it to KNX (default: no) |
1228
+
1229
+ **Mapping**
1230
+
1231
+ |Property|Description|
1232
+ |--|--|
1233
+ | Humidity | KNX GA for relative humidity %. Recommended DPT: <b>9.007</b> |
1234
+
1235
+ ##### Outputs
1236
+
1237
+ 1. Standard output
1238
+ : `msg.payload` (number): current relative humidity in %
1239
+
1240
+ ##### Details
1241
+
1242
+ `msg.payload` carries the numeric humidity value (percentage).
1243
+
1244
+ ---
1245
+
1246
+ <span id="hue-controller-help-scene" data-hue-controller-type="scene"></span>
1247
+
1248
+ ## Scene (`scene`)
1249
+
1250
+ The **Hue Scene** node exposes Hue scenes to KNX and can forward the raw Hue events to a Node-RED flow. The scene field supports autocomplete; use the refresh icon after adding scenes on the bridge so the list stays up to date.
1251
+
1252
+ ##### Tabs at a glance
1253
+
1254
+ - **Mapping** - link KNX group addresses to the selected Hue scene. DPT 1.xxx performs boolean recall, while DPT 18.xxx sends a KNX scene number.
1255
+ - **Multi scene** - build a rule list that associates KNX scene numbers with different Hue scenes and chooses whether each scene is recalled as _active_, _dynamic\_palette_ or _static_.
1256
+ - **Behaviour** - toggle the Node-RED output pin. When no KNX gateway is configured the pin remains enabled so bridge events still reach the flow.
1257
+
1258
+ ##### General settings
1259
+
1260
+ |Property|Description|
1261
+ |--|--|
1262
+ | KNX GW | KNX gateway supplying the address catalogue used for autocomplete. |
1263
+ | Hue Bridge | Hue Bridge that hosts the scenes. |
1264
+ | Hue Scene | Scene to recall (autocomplete; refresh button reloads the bridge catalogue). |
1265
+
1266
+ ##### Mapping tab
1267
+
1268
+ |Property|Description|
1269
+ |--|--|
1270
+ | Recall | KNX group address that recalls the scene. Use DPT 1.xxx for boolean control or DPT 18.xxx to transmit a KNX scene number. |
1271
+ | DPT | Datapoint used with the recall GA (1.xxx or 18.001). |
1272
+ | Name | Friendly label for the recall GA. |
1273
+ | # | Appears when a KNX scene DPT is chosen; select the KNX scene number to send. |
1274
+ | Status GA | Optional boolean GA that mirrors whether the scene is currently active. |
1275
+
1276
+ ##### Multi scene tab
1277
+
1278
+ |Property|Description|
1279
+ |--|--|
1280
+ | Recall | KNX GA (DPT 18.001) that selects scenes by number. |
1281
+ | Scene selector | Editable list that maps KNX scene numbers to Hue scenes with the desired recall mode. Drag handles reorder entries. |
1282
+
1283
+ > ℹ️ KNX-specific widgets only appear after a KNX gateway is selected. The Mapping tabs remain hidden until both the bridge and the gateway are configured.
1284
+
1285
+ ---
1286
+
1287
+ <span id="hue-controller-help-device_power" data-hue-controller-type="device_power"></span>
1288
+
1289
+ ## Battery (`device_power`)
1290
+
1291
+ <p>This node exposes the battery level of a Hue device to KNX and raises an event whenever the value changes.</p>
1292
+
1293
+ Start typing the KNX device name or Group Address in the GA field; matching entries appear while you type. Use the refresh icon next to <q>Hue sensor</q> to reload the list from the Hue bridge after adding new devices.
1294
+
1295
+ **General**
1296
+
1297
+ |Property|Description|
1298
+ |--|--|
1299
+ | KNX GW | KNX gateway used to publish the battery level (required before KNX mapping fields appear). |
1300
+ | Hue Bridge | Hue Bridge that hosts the device. |
1301
+ | Hue battery sensor | Hue device/sensor providing the battery level (supports autocomplete and refresh). |
1302
+
1303
+ **Mapping**
1304
+
1305
+ |Property|Description|
1306
+ |--|--|
1307
+ | Level | KNX GA for the battery percentage (0-100%). Recommended DPT: <b>5.001</b>. |
1308
+
1309
+ **Behaviour**
1310
+
1311
+ |Property|Description|
1312
+ |--|--|
1313
+ | Read status at startup | On deploy/reconnect read the current battery value and publish it to KNX. Default: "yes”. |
1314
+ | Node output pin | Show or hide the Node-RED output. When no KNX gateway is selected the output stays enabled so Hue events continue to reach the flow. |
1315
+
1316
+ > ℹ️ KNX mapping widgets remain hidden until a KNX gateway is selected. This keeps the editor tidy when the node is used only to forward Hue events into Node-RED.
1317
+
1318
+ ---
1319
+
1320
+ <span id="hue-controller-help-zigbee_connectivity" data-hue-controller-type="zigbee_connectivity"></span>
1321
+
1322
+ ## Zigbee connectivity (`zigbee_connectivity`)
1323
+
1324
+ <p>This node retrieves the Zigbee connectivity status from a Hue device and exposes it to KNX.</p>
1325
+
1326
+ Start typing the KNX device name or Group Address in the GA field; suggestions appear while you type.
1327
+
1328
+ **General**
1329
+
1330
+ |Property|Description|
1331
+ |--|--|
1332
+ | KNX GW | KNX gateway used to publish the status. |
1333
+ | Hue Bridge | Hue Bridge to query. |
1334
+ | Hue zigbee connectivity | Hue sensor/device providing the Zigbee connectivity info. Autocomplete while typing. |
1335
+
1336
+ **Mapping**
1337
+
1338
+ |Property|Description|
1339
+ |--|--|
1340
+ | Status | KNX Group Address that reflects Zigbee connectivity. Becomes _true_ when connected, otherwise _false_. |
1341
+ | Read status at startup | Reads current status at editor start/reconnection and emits to KNX. Default: "yes”. |
1342
+
1343
+ ##### Outputs
1344
+
1345
+ 1. Standard output
1346
+ : payload (boolean): connectivity state.
1347
+
1348
+ ##### Details
1349
+
1350
+ `msg.payload` carries the boolean state (true/false).\
1351
+ `msg.status` contains a textual status: one of **connected, disconnected, connectivity\_issue, unidirectional\_incoming** .
1352
+
1353
+ ---
1354
+
1355
+ <span id="hue-controller-help-device_software_update" data-hue-controller-type="device_software_update"></span>
1356
+
1357
+ ## Device software update (`device_software_update`)
1358
+
1359
+ <p>This node monitors whether a selected Hue device has a software update available and publishes the status to KNX.</p>
1360
+
1361
+ Start typing the name or group address of your KNX device in the GA field, the avaiable devices start showing up while
1362
+ you're typing.
1363
+
1364
+ **General**
1365
+
1366
+ |Property|Description|
1367
+ |--|--|
1368
+ | KNX GW | Select the KNX gateway to be used |
1369
+ | Hue Bridge | Select the Hue Bridge to be used |
1370
+ | Hue device | Hue device to monitor for software updates (autocomplete while typing).|
1371
+
1372
+ **Mapping**
1373
+
1374
+ | Property | Description |
1375
+ |--|--|
1376
+ | Status | KNX GA reflecting update status. _true_ if an update is available/ready/being installed, otherwise _false_. |
1377
+ | Read status at startup | Read current status at startup/reconnection and emit to KNX (default "yes”). |
1378
+
1379
+ ##### Outputs
1380
+
1381
+ 1. Standard output
1382
+ : payload (boolean): update flag.
1383
+ : status (string): one of **no\_update, update\_pending, ready\_to\_install, installing** .
1384
+
1385
+ <!-- HUE_CONTROLLER_PROFILE_HELP_END -->
1386
+ </script>